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 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.util.List;
053    
054    import static com.unboundid.directory.sdk.common.config
055            .VelocityContextProviderConfig.ObjectScope;
056    import static com.unboundid.directory.sdk.common.config
057            .VelocityContextProviderConfig.ObjectScope.*;
058    
059    
060    /**
061     * This class defines an API that must be implemented by extensions which
062     * contribute content to server pages authored as Velocity templates.
063     * These pages are rendered by the Velocity HTTP Servlet Extension included
064     * with the server. During rendering of a Velocity page, the template is merged
065     * with a 'context' that provides values for variables, properties, and method
066     * references in the template.
067     * <p/>
068     * A context provider can be restricted to contributing content for certain
069     * pages by specifying one or more included or excluded views names.  A view
070     * name is the URL request path to to a template that does not include the
071     * HTTP servlet extension's base context path, nor a starting path separator.
072     * So for example if a template was accessible by the URL
073     * http://localhost:8080/view/path/to/template the view name would be
074     * 'path/to/template'.
075     * <H2>Configuring Velocity Context Providers</H2>
076     * In order to configure a Velocity context provider created using this API,
077     * use a command like:
078     * <PRE>
079     *      dsconfig create-virtual-attribute \
080     *           --extension-name "<I>{extension}</I>" \
081     *           --provider-name "<I>{provider}</I>" \
082     *           --type third-party \
083     *           --set enabled:true \
084     *           --set "extension-class:<I>{class-name}</I>" \
085     *           --set "extension-argument:<I>{name=value}</I>"
086     * </PRE>
087     * where "<I>{extension}</I>" is the name of the Velocity HTTP servlet
088     * extension, ("Velocity" by default)
089     * "<I>{provider}</I>" is the name to use for the Velocity context provider
090     * instance, "<I>{class-name}</I>" is the fully-qualified name of the Java
091     * class that extends
092     * {@code com.unboundid.directory.sdk.common.api.VelocityContextProvider},
093     * and "<I>{name=value}</I>" represents name-value pairs for any arguments to
094     * provide to the virtual attribute provider.  If multiple arguments should be
095     * provided to the virtual attribute provider, then the
096     * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be
097     * provided multiple times.
098     */
099    @Extensible()
100    @DirectoryServerExtension()
101    @DirectoryProxyServerExtension(appliesToLocalContent = true,
102            appliesToRemoteContent = false)
103    @SynchronizationServerExtension(appliesToLocalContent = true,
104            appliesToSynchronizedContent = false)
105    @MetricsEngineExtension()
106    @IdentityBrokerExtension()
107    @ThreadSafety(level = ThreadSafetyLevel.INTERFACE_THREADSAFE)
108    public abstract class VelocityContextProvider
109            implements UnboundIDExtension,
110            Reconfigurable<VelocityContextProviderConfig>,
111            ExampleUsageProvider
112    {
113    
114    
115      // The current configuration.
116      private VelocityContextProviderConfig config;
117    
118    
119    
120      /**
121       * {@inheritDoc}
122       */
123      public abstract String getExtensionName();
124    
125    
126    
127      /**
128       * {@inheritDoc}
129       */
130      public abstract String[] getExtensionDescription();
131    
132    
133    
134      /**
135       * {@inheritDoc}
136       */
137      public void defineConfigArguments(final ArgumentParser parser)
138              throws ArgumentException
139      {
140        // No arguments will be allowed by default.
141      }
142    
143    
144    
145      /**
146       * Initializes this Velocity context provider.
147       *
148       * @param serverContext A handle to the server context for the server in
149       *                      which this extension is running.
150       * @param config        The general configuration for this Velocity context
151       *                      provider.
152       * @param parser        The argument parser which has been initialized from
153       *                      the configuration for this Velocity context
154       *                      provider.
155       * @throws LDAPException If a problem occurs while initializing this context
156       *                       provider.
157       */
158      public void initializeVelocityContextProvider(
159              final ServerContext serverContext,
160              final VelocityContextProviderConfig config,
161              final ArgumentParser parser)
162              throws LDAPException
163      {
164        this.config = config;
165      }
166    
167    
168    
169      /**
170       * {@inheritDoc}
171       */
172      public boolean isConfigurationAcceptable(
173              final VelocityContextProviderConfig config,
174              final ArgumentParser parser,
175              final List<String> unacceptableReasons)
176      {
177        // No extended validation will be performed by default.
178        return true;
179      }
180    
181    
182    
183      /**
184       * {@inheritDoc}
185       */
186      public ResultCode applyConfiguration(
187              final VelocityContextProviderConfig config,
188              final ArgumentParser parser,
189              final List<String> adminActionsRequired,
190              final List<String> messages)
191      {
192        // By default, no configuration changes will be applied.  If there are any
193        // arguments, then add an admin action message indicating that the extension
194        // needs to be restarted for any changes to take effect.
195        if (!parser.getNamedArguments().isEmpty())
196        {
197          adminActionsRequired.add(
198              "No configuration change has actually been applied.  The new " +
199                      "configuration will not take effect until this Velocity " +
200                      "context provider is disabled and re-enabled or until the " +
201                      "server is restarted.");
202        }
203    
204        return ResultCode.SUCCESS;
205      }
206    
207    
208    
209      /**
210       * Performs any cleanup which may be necessary when this virtual attribute
211       * provider is to be taken out of service.
212       */
213      public void finalizeVelocityContextProvider()
214      {
215        // No implementation is required.
216      }
217    
218    
219    
220      /**
221       * Return an object to be placed into a Velocity context for rending a
222       * template.
223       *
224       * @param context  to update.
225       * @param request  for the the view implemented by a template.
226       * @param response to the client.
227       */
228      public abstract void updateContext(VelocityContext context,
229                                         HttpServletRequest request,
230                                         HttpServletResponse response);
231    
232    
233    
234      /**
235       * Gets an object from the current user session or servlet context
236       * depending on the object scope currently configured for this provider.
237       * This method can be used as a convenience for providers the maintain
238       * a set of context objects for a particular scope.
239       *
240       * @param <T>     the type of object to return.  If an object exists in
241       *                the current scope with the given name but is not of type
242       *                T this method returns {@code null}
243       *
244       * @param name    of the object.
245       * @param request current user request.
246       *
247       * @return the named object or {@code null} if no object exists by the
248       *         provided name or an input parameter is (@code null}.
249       */
250      protected <T> T getNamedObject(final String name,
251                                     final HttpServletRequest request)
252      {
253        T object = null;
254        if (this.config != null)
255        {
256          object = getNamedObject(name, request, config.getObjectScope());
257        }
258        return object;
259      }
260    
261    
262    
263      /**
264       * Stores an object in current user request, session or servlet context
265       * depending on the object scope currently configured for this provider.
266       * This method can be used as a convenience for providers the maintain
267       * a set of context objects for a particular scope.
268       *
269       * @param name    of the object.
270       * @param object  to store.
271       * @param request current user request.
272       */
273      protected void setNamedObject(final String name,
274                                    final Object object,
275                                    final HttpServletRequest request)
276      {
277        if (this.config != null)
278        {
279          setNamedObject(name, object, request, config.getObjectScope());
280        }
281      }
282    
283    
284    
285      /**
286       * Gets an object from the current user session or servlet context
287       * depending on the object scope currently configured for this provider.
288       * This method can be used as a convenience for providers the maintain
289       * a set of context objects for a particular scope.
290       *
291       * @param <T>     the type of object to return.  If an object exists in
292       *                the current scope with the given name but is not of type
293       *                T this method returns {@code null}
294       *
295       * @param name    of the object.
296       * @param request current user request.
297       * @param scope   from which to retrieve the object.
298       * @return the named object or {@code null} if no object exists by the
299       *         provided name or an input parameter is (@code null}.
300       */
301      @SuppressWarnings("unchecked")
302      public static <T> T getNamedObject(final String name,
303                                         final HttpServletRequest request,
304                                         final ObjectScope scope)
305      {
306        T object = null;
307        Object o = null;
308        if (name != null && request != null)
309        {
310          if (REQUEST.equals(scope))
311          {
312            o = request.getAttribute(name);
313          }
314          else if (SESSION.equals(scope))
315          {
316            HttpSession session = request.getSession(false);
317            if (session != null)
318            {
319              o = session.getAttribute(name);
320            }
321          }
322          else if (APPLICATION.equals(scope))
323          {
324            o = request.getServletContext().getAttribute(name);
325          }
326        }
327        try
328        {
329          object = (T) o;
330        }
331        catch (ClassCastException cce)
332        {
333          // ignore
334        }
335        return object;
336      }
337    
338    
339    
340      /**
341       * Stores an object in current user request, session or servlet context
342       * depending on the object scope currently configured for this provider.
343       * This method can be used as a convenience for providers the maintain
344       * a set of context objects for a particular scope.
345       *
346       * @param name    of the object.
347       * @param object  to store.
348       * @param request current user request.
349       * @param scope   in which to set the object.
350       */
351      public static void setNamedObject(final String name,
352                                        final Object object,
353                                        final HttpServletRequest request,
354                                        final ObjectScope scope)
355      {
356        if (scope != null && request != null && name != null)
357        {
358          if (REQUEST.equals(scope))
359          {
360            request.setAttribute(name, object);
361          }
362          else if (SESSION.equals(scope))
363          {
364            HttpSession session = request.getSession(true);
365            session.setAttribute(name, object);
366          }
367          else if (APPLICATION.equals(scope))
368          {
369            request.getServletContext().setAttribute(name, object);
370          }
371        }
372      }
373    
374    }