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 2011-2024 Ping Identity Corporation 026 */ 027package com.unboundid.directory.sdk.http.scripting; 028 029 030 031import java.util.Collections; 032import java.util.List; 033import java.util.Map; 034 035import javax.servlet.Filter; 036import javax.servlet.http.HttpServlet; 037 038import com.unboundid.directory.sdk.broker.internal.BrokerExtension; 039import com.unboundid.directory.sdk.common.internal.Reconfigurable; 040import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension; 041import com.unboundid.directory.sdk.http.config.HTTPServletExtensionConfig; 042import com.unboundid.directory.sdk.http.types.HTTPServerContext; 043import com.unboundid.directory.sdk.metrics.internal.MetricsEngineExtension; 044import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension; 045import com.unboundid.ldap.sdk.LDAPException; 046import com.unboundid.ldap.sdk.ResultCode; 047import com.unboundid.util.Extensible; 048import com.unboundid.util.ThreadSafety; 049import com.unboundid.util.ThreadSafetyLevel; 050import com.unboundid.util.args.ArgumentException; 051import com.unboundid.util.args.ArgumentParser; 052 053 054 055/** 056 * This class defines an API that must be implemented by scripted extensions 057 * which create servlets for use with an HTTP connection handler. 058 * <BR> 059 * <H2>Configuring Groovy-Scripted HTTP Servlet Extensions</H2> 060 * In order to configure a scripted HTTP servlet extension based on this API and 061 * written in the Groovy scripting language, use a command like: 062 * <PRE> 063 * dsconfig create-http-servlet-extension \ 064 * --extension-name "<I>{extension-name}</I>" \ 065 * --type groovy-scripted \ 066 * --set enabled:true \ 067 * --set "script-class:<I>{class-name}</I>" \ 068 * --set "script-argument:<I>{name=value}</I>" 069 * </PRE> 070 * where "<I>{extension-name}</I>" is the name to use for the HTTP servlet 071 * extension instance, "<I>{class-name}</I>" is the fully-qualified name of the 072 * Groovy class written using this API, and "<I>{name=value}</I>" represents 073 * name-value pairs for any arguments to provide to the HTTP servlet extension. 074 * If multiple arguments should be provided to the HTTP servlet extension, then 075 * the "<CODE>--set script-argument:<I>{name=value}</I></CODE>" option should be 076 * provided multiple times. 077 * 078 * @see com.unboundid.directory.sdk.http.api.HTTPServletExtension 079 */ 080@Extensible() 081@DirectoryServerExtension() 082@DirectoryProxyServerExtension(appliesToLocalContent=true, 083 appliesToRemoteContent=true) 084@MetricsEngineExtension() 085@BrokerExtension() 086@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE) 087public abstract class ScriptedHTTPServletExtension 088 implements Reconfigurable<HTTPServletExtensionConfig> 089{ 090 /** 091 * Creates a new instance of this HTTP servlet extension. All HTTP servlet 092 * extension implementations must include a default constructor, but any 093 * initialization should generally be done in the {@code createServlet} 094 * method. 095 */ 096 public ScriptedHTTPServletExtension() 097 { 098 // No implementation is required. 099 } 100 101 102 103 /** 104 * {@inheritDoc} 105 */ 106 public void defineConfigArguments(final ArgumentParser parser) 107 throws ArgumentException 108 { 109 // No arguments will be allowed by default. 110 } 111 112 113 114 /** 115 * {@inheritDoc} 116 */ 117 public boolean isConfigurationAcceptable( 118 final HTTPServletExtensionConfig config, 119 final ArgumentParser parser, 120 final List<String> unacceptableReasons) 121 { 122 // No extended validation will be performed. 123 return true; 124 } 125 126 127 128 /** 129 * {@inheritDoc} 130 */ 131 public ResultCode applyConfiguration(final HTTPServletExtensionConfig config, 132 final ArgumentParser parser, 133 final List<String> adminActionsRequired, 134 final List<String> messages) 135 { 136 // By default, no configuration changes will be applied. If there are any 137 // arguments, then add an admin action message indicating that the extension 138 // needs to be restarted for any changes to take effect. 139 if (! parser.getNamedArguments().isEmpty()) 140 { 141 adminActionsRequired.add( 142 "No configuration change has actually been applied. The new " + 143 "configuration will not take effect until this HTTP servlet " + 144 "extension is disabled and re-enabled or until the server is " + 145 "restarted."); 146 } 147 148 return ResultCode.SUCCESS; 149 } 150 151 152 153 /** 154 * Creates an HTTP servlet using the provided information. 155 * 156 * @param serverContext A handle to the server context for the server in 157 * which this extension is running. 158 * @param config The general configuration for this HTTP servlet 159 * extension. 160 * @param parser The argument parser which has been initialized from 161 * the configuration for this HTTP servlet extension. 162 * 163 * @return The HTTP servlet that has been created. 164 * 165 * @throws LDAPException If a problem is encountered while attempting to 166 * create the HTTP servlet. 167 */ 168 public abstract HttpServlet createServlet( 169 final HTTPServerContext serverContext, 170 final HTTPServletExtensionConfig config, 171 final ArgumentParser parser) 172 throws LDAPException; 173 174 175 176 /** 177 * Retrieves a list of the request paths for which the associated servlet 178 * should be invoked. This method will be called after the 179 * {@link #createServlet} method has been used to create the servlet instance. 180 * 181 * @return A list of the request paths for which the associated servlet 182 * should be invoked. 183 */ 184 public abstract List<String> getServletPaths(); 185 186 187 188 /** 189 * Retrieves a map of initialization parameters that should be provided to the 190 * servlet when it is initialized. 191 * 192 * @return A map of initialization parameters that should be provided to the 193 * servlet when it is initialized, or an empty map if no 194 * initialization parameters are needed. 195 */ 196 public Map<String,String> getServletInitParameters() 197 { 198 return Collections.emptyMap(); 199 } 200 201 202 203 /** 204 * Retrieves the order in which the servlet should be started. A value 205 * greater than or equal to zero guarantees that the servlet will be started 206 * as soon as the servlet engine has been started, in order of ascending 207 * servlet init order values, before the {@code doPostRegistrationProcessing} 208 * method has been called. If the value is less than zero, the servlet may 209 * not be started until a request is received for one of its registered paths. 210 * 211 * @return The order in which the servlet should be started, or a negative 212 * value if startup order does not matter. 213 */ 214 public int getServletInitOrder() 215 { 216 return -1; 217 } 218 219 220 221 /** 222 * Retrieves a list of servlet filter instances that should be installed with 223 * the created servlet instance, in the order they should be invoked. If the 224 * servlet is to be registered with multiple paths, then these filters will be 225 * installed for all of those paths. 226 * 227 * @return A list of servlet filter instances that should be installed with 228 * the created servlet instance, in the order that they should be 229 * invoked. It may be {@code null} or empty if no servlet filters 230 * should be installed. 231 */ 232 public List<Filter> getServletFilters() 233 { 234 return Collections.emptyList(); 235 } 236 237 238 239 /** 240 * Performs any processing that may be needed after the servlet has been 241 * registered with the servlet engine. If the value returned from 242 * {@link #getServletInitOrder()} is greater than or equal to zero, then the 243 * servlet will have been started before this method is called. If the value 244 * returned from {@code getServletInitOrder()} is negative, then the servlet 245 * may or may not have been started by the time this method is called. 246 * <BR><BR> 247 * Note that the associated servlet can also perform any necessary 248 * initialization processing in the {@code init} method provided by the 249 * servlet API. 250 */ 251 public void doPostRegistrationProcessing() 252 { 253 // No implementation required by default. 254 } 255 256 257 258 /** 259 * Performs any processing that may be needed after the servlet has been 260 * taken out of service and the associated servlet engine has been shut down. 261 * <BR><BR> 262 * Note that the associated servlet can also perform any necessary 263 * finalization processing in the {@code destroy} method provided by the 264 * servlet API. That method will be called after the servlet has been taken 265 * out of service, but before the servlet engine has been shut down. 266 */ 267 public void doPostShutdownProcessing() 268 { 269 // No implementation required by default. 270 } 271}