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 }