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 }