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 * Portions Copyright 2013-2024 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 > 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 > 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}