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-2016 UnboundID Corp.
026 */
027package com.unboundid.directory.sdk.common.api;
028
029
030import com.unboundid.directory.sdk.broker.internal.BrokerExtension;
031import com.unboundid.directory.sdk.common.config.VelocityContextProviderConfig;
032import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
033import com.unboundid.directory.sdk.common.internal.Reconfigurable;
034import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
035import com.unboundid.directory.sdk.common.types.ServerContext;
036import com.unboundid.directory.sdk.common.types.VelocityContext;
037import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension;
038import com.unboundid.directory.sdk.metrics.internal.MetricsEngineExtension;
039import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
040import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
041import com.unboundid.ldap.sdk.LDAPException;
042import com.unboundid.ldap.sdk.ResultCode;
043import com.unboundid.util.Extensible;
044import com.unboundid.util.ThreadSafety;
045import com.unboundid.util.ThreadSafetyLevel;
046import com.unboundid.util.args.ArgumentException;
047import com.unboundid.util.args.ArgumentParser;
048
049import javax.servlet.http.HttpServletRequest;
050import javax.servlet.http.HttpServletResponse;
051import javax.servlet.http.HttpSession;
052import java.io.IOException;
053import java.util.List;
054
055import static com.unboundid.directory.sdk.common.config
056        .VelocityContextProviderConfig.ObjectScope;
057import 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@BrokerExtension()
122@ThreadSafety(level = ThreadSafetyLevel.INTERFACE_THREADSAFE)
123public 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 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 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 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 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 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 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 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 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 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 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}