001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * docs/licenses/cddl.txt
011     * or http://www.opensource.org/licenses/cddl1.php.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * docs/licenses/cddl.txt.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2013-2014 UnboundID Corp.
026     */
027    package com.unboundid.directory.sdk.common.api;
028    
029    
030    import com.unboundid.directory.sdk.broker.internal.IdentityBrokerExtension;
031    import com.unboundid.directory.sdk.common.config.VelocityContextProviderConfig;
032    import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
033    import com.unboundid.directory.sdk.common.internal.Reconfigurable;
034    import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
035    import com.unboundid.directory.sdk.common.types.ServerContext;
036    import com.unboundid.directory.sdk.common.types.VelocityContext;
037    import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension;
038    import com.unboundid.directory.sdk.metrics.internal.MetricsEngineExtension;
039    import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
040    import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
041    import com.unboundid.ldap.sdk.LDAPException;
042    import com.unboundid.ldap.sdk.ResultCode;
043    import com.unboundid.util.Extensible;
044    import com.unboundid.util.ThreadSafety;
045    import com.unboundid.util.ThreadSafetyLevel;
046    import com.unboundid.util.args.ArgumentException;
047    import com.unboundid.util.args.ArgumentParser;
048    
049    import javax.servlet.http.HttpServletRequest;
050    import javax.servlet.http.HttpServletResponse;
051    import javax.servlet.http.HttpSession;
052    import java.io.IOException;
053    import java.util.List;
054    
055    import static com.unboundid.directory.sdk.common.config
056            .VelocityContextProviderConfig.ObjectScope;
057    import static com.unboundid.directory.sdk.common.config
058            .VelocityContextProviderConfig.ObjectScope.*;
059    
060    
061    /**
062     * This class defines an API that must be implemented by extensions which
063     * contribute content to server pages authored as Velocity templates.
064     * These pages are rendered by the Velocity HTTP Servlet Extension included
065     * with the server. During rendering of a Velocity page, the template is merged
066     * with a 'context' that provides values for variables, properties, and method
067     * references in the template.
068     * <p/>
069     * A context provider can be restricted to contributing content for certain
070     * pages by specifying one or more included or excluded views names.  A view
071     * name is the URL request path to to a template that does not include the
072     * HTTP servlet extension's base context path, nor a starting path separator.
073     * So for example if a template was accessible by the URL
074     * http://localhost:8080/view/path/to/template the view name would be
075     * 'path/to/template'.
076     * <p/>
077     * In addition to contributing content for views, a context provider can also
078     * be configured to add header fields to HTTP responses using the
079     * request-header configuration property.  Header field values specified the
080     * by a context provider override values for identical fields names for which
081     * the Velocity HTTP Servlet Extension is configured to add to responses.
082     * <p/>
083     * Context providers are restricted to servicing requests for the configured
084     * set of HTTP operations.  By default, only the HTTP GET method is enabled
085     * though this can be changed by updating the http-method configuration
086     * property.  Implementations should abide by the conventions mandated by the
087     * HTTP method specification.  For example, an implementation that handles the
088     * GET method should be restricted to retrieval only and not introduce any
089     * persistent side-effects that would change the state of the server.
090     * <H2>Configuring Velocity Context Providers</H2>
091     * In order to configure a Velocity context provider created using this API,
092     * use a command like:
093     * <PRE>
094     *      dsconfig create-velocity-context-provider \
095     *           --extension-name "<I>{extension}</I>" \
096     *           --provider-name "<I>{provider}</I>" \
097     *           --type third-party \
098     *           --set enabled:true \
099     *           --set "extension-class:<I>{class-name}</I>" \
100     *           --set "extension-argument:<I>{name=value}</I>"
101     * </PRE>
102     * where "<I>{extension}</I>" is the name of the Velocity HTTP servlet
103     * extension, ("Velocity" by default)
104     * "<I>{provider}</I>" is the name to use for the Velocity context provider
105     * instance, "<I>{class-name}</I>" is the fully-qualified name of the Java
106     * class that extends
107     * {@code com.unboundid.directory.sdk.common.api.VelocityContextProvider},
108     * and "<I>{name=value}</I>" represents name-value pairs for any arguments to
109     * provide to the virtual attribute provider.  If multiple arguments should be
110     * provided to the virtual attribute provider, then the
111     * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be
112     * provided multiple times.
113     */
114    @Extensible()
115    @DirectoryServerExtension()
116    @DirectoryProxyServerExtension(appliesToLocalContent = true,
117            appliesToRemoteContent = false)
118    @SynchronizationServerExtension(appliesToLocalContent = true,
119            appliesToSynchronizedContent = false)
120    @MetricsEngineExtension()
121    @IdentityBrokerExtension()
122    @ThreadSafety(level = ThreadSafetyLevel.INTERFACE_THREADSAFE)
123    public abstract class VelocityContextProvider
124            implements UnboundIDExtension,
125            Reconfigurable<VelocityContextProviderConfig>,
126            ExampleUsageProvider
127    {
128    
129    
130      // The current configuration.
131      private VelocityContextProviderConfig config;
132    
133    
134    
135      /**
136       * {@inheritDoc}
137       */
138      public abstract String getExtensionName();
139    
140    
141    
142      /**
143       * {@inheritDoc}
144       */
145      public abstract String[] getExtensionDescription();
146    
147    
148    
149      /**
150       * {@inheritDoc}
151       */
152      public void defineConfigArguments(final ArgumentParser parser)
153              throws ArgumentException
154      {
155        // No arguments will be allowed by default.
156      }
157    
158    
159    
160      /**
161       * Initializes this Velocity context provider.
162       *
163       * @param serverContext A handle to the server context for the server in
164       *                      which this extension is running.
165       * @param config        The general configuration for this Velocity context
166       *                      provider.
167       * @param parser        The argument parser which has been initialized from
168       *                      the configuration for this Velocity context
169       *                      provider.
170       * @throws LDAPException If a problem occurs while initializing this context
171       *                       provider.
172       */
173      public void initializeVelocityContextProvider(
174              final ServerContext serverContext,
175              final VelocityContextProviderConfig config,
176              final ArgumentParser parser)
177              throws LDAPException
178      {
179        this.config = config;
180      }
181    
182    
183    
184      /**
185       * {@inheritDoc}
186       */
187      public boolean isConfigurationAcceptable(
188              final VelocityContextProviderConfig config,
189              final ArgumentParser parser,
190              final List<String> unacceptableReasons)
191      {
192        // No extended validation will be performed by default.
193        return true;
194      }
195    
196    
197    
198      /**
199       * {@inheritDoc}
200       */
201      public ResultCode applyConfiguration(
202              final VelocityContextProviderConfig config,
203              final ArgumentParser parser,
204              final List<String> adminActionsRequired,
205              final List<String> messages)
206      {
207        // By default, no configuration changes will be applied.  If there are any
208        // arguments, then add an admin action message indicating that the extension
209        // needs to be restarted for any changes to take effect.
210        if (!parser.getNamedArguments().isEmpty())
211        {
212          adminActionsRequired.add(
213              "No configuration change has actually been applied.  The new " +
214                      "configuration will not take effect until this Velocity " +
215                      "context provider is disabled and re-enabled or until the " +
216                      "server is restarted.");
217        }
218    
219        return ResultCode.SUCCESS;
220      }
221    
222    
223    
224      /**
225       * Performs any cleanup which may be necessary when this virtual attribute
226       * provider is to be taken out of service.
227       */
228      public void finalizeVelocityContextProvider()
229      {
230        // No implementation is required.
231      }
232    
233    
234    
235      /**
236       * Handle an HTTP DELETE request.  Implementations should be idempotent in
237       * the sense that the side-effects of a single call to this method are the
238       * same as N > 0 identical requests.
239       *
240       * @param context   to update.
241       * @param request   for the the view implemented by a template.
242       * @param response  to the client.
243       *
244       * @throws IOException if the provider has a problem related to processing
245       *         the response such as sending an error to the client.
246       */
247      public void handleDelete(final VelocityContext context,
248                               final HttpServletRequest request,
249                               final HttpServletResponse response)
250        throws IOException
251      {
252        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
253      }
254    
255    
256    
257      /**
258       * Handle an HTTP GET request.  Implementations should be restricted to
259       * retrieval operations only, ensuring there are no persistent side-effects
260       * generated by the server as a result of this method being called.
261       *
262       * @param context   to update.
263       * @param request   for the the view implemented by a template.
264       * @param response  to the client.
265       *
266       * @throws IOException if the provider has a problem related to processing
267       *         the response such as sending an error to the client.
268       */
269      public void handleGet(final VelocityContext context,
270                            final HttpServletRequest request,
271                            final HttpServletResponse response)
272        throws IOException
273      {
274        // Do not respond with an error to avoid breaking existing implementations.
275      }
276    
277    
278    
279      /**
280       * Handle an HTTP HEAD request.  Implementations should be restricted to
281       * retrieval operations only, ensuring there are no persistent side-effects
282       * generated by the server as a result of this method being called.
283       *
284       * @param context   to update.
285       * @param request   for the the view implemented by a template.
286       * @param response  to the client.
287       *
288       * @throws IOException if the provider has a problem related to processing
289       *         the response such as sending an error to the client.
290       */
291      public void handleHead(final VelocityContext context,
292                             final HttpServletRequest request,
293                             final HttpServletResponse response)
294        throws IOException
295      {
296        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
297      }
298    
299    
300    
301      /**
302       * Handle an HTTP OPTIONS request.  Implementations should ensure there
303       * are no persistent side-effects generated by the server as a result of this
304       * method being called.
305       *
306       * @param context   to update.
307       * @param request   for the the view implemented by a template.
308       * @param response  to the client.
309       *
310       * @throws IOException if the provider has a problem related to processing
311       *         the response such as sending an error to the client.
312       */
313      public void handleOptions(final VelocityContext context,
314                                final HttpServletRequest request,
315                                final HttpServletResponse response)
316        throws IOException
317      {
318        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
319      }
320    
321    
322    
323      /**
324       * Handle an HTTP PATCH request.
325       *
326       * @param context   to update.
327       * @param request   for the the view implemented by a template.
328       * @param response  to the client.
329       *
330       * @throws IOException if the provider has a problem related to processing
331       *         the response such as sending an error to the client.
332       */
333      public void handlePatch(final VelocityContext context,
334                              final HttpServletRequest request,
335                              final HttpServletResponse response)
336        throws IOException
337      {
338        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
339      }
340    
341    
342    
343      /**
344       * Handle an HTTP POST request.
345       *
346       * @param context   to update.
347       * @param request   for the the view implemented by a template.
348       * @param response  to the client.
349       *
350       * @throws IOException if the provider has a problem related to processing
351       *         the response such as sending an error to the client.
352       */
353      public void handlePost(final VelocityContext context,
354                             final HttpServletRequest request,
355                             final HttpServletResponse response)
356        throws IOException
357      {
358        // Do not respond with an error to avoid breaking existing implementations.
359      }
360    
361    
362    
363      /**
364       * Handle an HTTP PUT request.  Implementations should be idempotent in
365       * the sense that the side-effects of a single call to this method are the
366       * same as N > 0 identical requests.
367       *
368       * @param context   to update.
369       * @param request   for the the view implemented by a template.
370       * @param response  to the client.
371       *
372       * @throws IOException if the provider has a problem related to processing
373       *         the response such as sending an error to the client.
374       */
375      public void handlePut(final VelocityContext context,
376                            final HttpServletRequest request,
377                            final HttpServletResponse response)
378        throws IOException
379      {
380        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
381      }
382    
383    
384    
385      /**
386       * Handle an HTTP TRACE request.  Implementations should ensure there
387       * are no persistent side-effects generated by the server as a result of this
388       * method being called.
389       *
390       * @param context   to update.
391       * @param request   for the the view implemented by a template.
392       * @param response  to the client.
393       *
394       * @throws IOException if the provider has a problem related to processing
395       *         the response such as sending an error to the client.
396       */
397      public void handleTrace(final VelocityContext context,
398                              final HttpServletRequest request,
399                              final HttpServletResponse response)
400        throws IOException
401      {
402        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
403      }
404    
405    
406    
407      /**
408       * Handle an HTTP method not already handled by one of the other
409       * {@code handleXXX} methods.  Implementations should check the value
410       * of the request's {@code getMethod()} method and take whatever action is
411       * necessary to fulfill the request before updating the context.  Unexpected
412       * HTTP methods should result in the client sending an HTTP 405 Method Not
413       * Allowed response.
414       *
415       * @param context   to update.
416       * @param request   for the the view implemented by a template.
417       * @param response  to the client.
418       *
419       * @throws IOException if the provider has a problem related to processing
420       *         the response such as sending an error to the client.
421       */
422      public void handleAdditionalMethod(final VelocityContext context,
423                                         final HttpServletRequest request,
424                                         final HttpServletResponse response)
425        throws IOException
426      {
427        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
428      }
429    
430    
431    
432      /**
433       * This method is called following a call to one of the {@code handleXXX}
434       * methods and may be used for logic that is independent of the HTTP operation
435       * requested.  HTTP method-specific code such as request attribute or form
436       * data processing should be restricted to the appropriate {@code handleXXX}
437       * method.
438       *
439       * @param context   to update.
440       * @param request   for the the view implemented by a template.
441       * @param response  to the client.
442       *
443       * @throws IOException if the provider has a problem related to processing
444       *         the response such as sending an error to the client.
445       */
446      public void updateContext(final VelocityContext context,
447                                final HttpServletRequest request,
448                                final HttpServletResponse response)
449        throws IOException
450      {
451        // No implementation.
452      }
453    
454    
455    
456      /**
457       * Gets an object from the current user session or servlet context
458       * depending on the object scope currently configured for this provider.
459       * This method can be used as a convenience for providers the maintain
460       * a set of context objects for a particular scope.
461       *
462       * @param <T>     the type of object to return.  If an object exists in
463       *                the current scope with the given name but is not of type
464       *                T this method returns {@code null}
465       *
466       * @param name    of the object.
467       * @param request current user request.
468       *
469       * @return the named object or {@code null} if no object exists by the
470       *         provided name or an input parameter is (@code null}.
471       */
472      protected <T> T getNamedObject(final String name,
473                                     final HttpServletRequest request)
474      {
475        T object = null;
476        if (this.config != null)
477        {
478          object = getNamedObject(name, request, config.getObjectScope());
479        }
480        return object;
481      }
482    
483    
484    
485      /**
486       * Stores an object in current user request, session or servlet context
487       * depending on the object scope currently configured for this provider.
488       * This method can be used as a convenience for providers the maintain
489       * a set of context objects for a particular scope.
490       *
491       * @param name    of the object.
492       * @param object  to store.
493       * @param request current user request.
494       */
495      protected void setNamedObject(final String name,
496                                    final Object object,
497                                    final HttpServletRequest request)
498      {
499        if (this.config != null)
500        {
501          setNamedObject(name, object, request, config.getObjectScope());
502        }
503      }
504    
505    
506    
507      /**
508       * Gets an object from the current user session or servlet context
509       * depending on the object scope currently configured for this provider.
510       * This method can be used as a convenience for providers the maintain
511       * a set of context objects for a particular scope.
512       *
513       * @param <T>     the type of object to return.  If an object exists in
514       *                the current scope with the given name but is not of type
515       *                T this method returns {@code null}
516       *
517       * @param name    of the object.
518       * @param request current user request.
519       * @param scope   from which to retrieve the object.
520       * @return the named object or {@code null} if no object exists by the
521       *         provided name or an input parameter is (@code null}.
522       */
523      @SuppressWarnings("unchecked")
524      public static <T> T getNamedObject(final String name,
525                                         final HttpServletRequest request,
526                                         final ObjectScope scope)
527      {
528        T object = null;
529        Object o = null;
530        if (name != null && request != null)
531        {
532          if (REQUEST.equals(scope))
533          {
534            o = request.getAttribute(name);
535          }
536          else if (SESSION.equals(scope))
537          {
538            HttpSession session = request.getSession(false);
539            if (session != null)
540            {
541              o = session.getAttribute(name);
542            }
543          }
544          else if (APPLICATION.equals(scope))
545          {
546            o = request.getServletContext().getAttribute(name);
547          }
548        }
549        try
550        {
551          object = (T) o;
552        }
553        catch (ClassCastException cce)
554        {
555          // ignore
556        }
557        return object;
558      }
559    
560    
561    
562      /**
563       * Stores an object in current user request, session or servlet context
564       * depending on the object scope currently configured for this provider.
565       * This method can be used as a convenience for providers the maintain
566       * a set of context objects for a particular scope.
567       *
568       * @param name    of the object.
569       * @param object  to store.
570       * @param request current user request.
571       * @param scope   in which to set the object.
572       */
573      public static void setNamedObject(final String name,
574                                        final Object object,
575                                        final HttpServletRequest request,
576                                        final ObjectScope scope)
577      {
578        if (scope != null && request != null && name != null)
579        {
580          if (REQUEST.equals(scope))
581          {
582            request.setAttribute(name, object);
583          }
584          else if (SESSION.equals(scope))
585          {
586            HttpSession session = request.getSession(true);
587            session.setAttribute(name, object);
588          }
589          else if (APPLICATION.equals(scope))
590          {
591            request.getServletContext().setAttribute(name, object);
592          }
593        }
594      }
595    
596    }