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