/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * docs/licenses/cddl.txt * or http://www.opensource.org/licenses/cddl1.php. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * docs/licenses/cddl.txt. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2011-2023 Ping Identity Corporation */ package com.unboundid.directory.sdk.examples; import java.util.Arrays; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.servlet.Filter; import com.unboundid.directory.sdk.http.api.HTTPServletExtension; import com.unboundid.directory.sdk.http.config.HTTPServletExtensionConfig; import com.unboundid.directory.sdk.http.types.HTTPServerContext; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.util.args.ArgumentException; import com.unboundid.util.args.ArgumentParser; import com.unboundid.util.args.StringArgument; /** * This class provides a simple example of an HTTP servlet extension which will * create a simple "Hello, World!" servlet. It has two configuration arguments: * <UL> * <LI>name -- The name to display in the servlet's hello message. If this is * not provided, then a default value of "World" will be used.</LI> * <LI>path -- The path that should be used to invoke the servlet. If this is * not provided, then a default value of "/hello" will be used.</LI> * </UL> */ public final class ExampleHTTPServletExtension extends HTTPServletExtension { /** * The name of the argument that will be used to specify the name that should * be used in the greeting. */ private static final String ARG_NAME_NAME = "name"; /** * The name of the argument that will be used to specify the path that will be * used to access the servlet. */ private static final String ARG_NAME_PATH = "path"; /** * The servlet filter that will be used with the servlet. */ private static final ExampleServletFilter FILTER = new ExampleServletFilter(); // Indicates whether one or more init parameters were provided. private static volatile boolean initParametersProvided = false; // Indicates whether the post-registration method has been called. private static volatile boolean postRegistrationMethodCalled = false; // Indicates whether the post-shutdown method has been called. private static volatile boolean postShutdownMethodCalled = false; // Indicates whether the servlet context was available in the // post-registration method. private static volatile boolean servletContextAvailable = false; // The servlet that has been created. private volatile ExampleHelloServlet servlet; // The path that will be used for the servlet. private volatile String path; /** * Creates a new instance of this HTTP servlet extension. All HTTP servlet * extension implementations must include a default constructor, but any * initialization should generally be done in the {@code createServlet} * method. */ public ExampleHTTPServletExtension() { servlet = null; path = null; } /** * Retrieves a human-readable name for this extension. * * @return A human-readable name for this extension. */ @Override() public String getExtensionName() { return "Example HTTP Servlet Extension"; } /** * Retrieves a human-readable description for this extension. Each element * of the array that is returned will be considered a separate paragraph in * generated documentation. * * @return A human-readable description for this extension, or {@code null} * or an empty array if no description should be available. */ @Override() public String[] getExtensionDescription() { return new String[] { "This HTTP servlet extension serves an example that may be used to " + "demonstrate the process for creating a third-party HTTP servlet " + "extension. It will create a simple servlet which displays a " + "hello message." }; } /** * Updates the provided argument parser to define any configuration arguments * which may be used by this HTTP servlet extension. The argument parser may * also be updated to define relationships between arguments (e.g., to specify * required, exclusive, or dependent argument sets). * * @param parser The argument parser to be updated with the configuration * arguments which may be used by this HTTP servlet extension. * * @throws ArgumentException If a problem is encountered while updating the * provided argument parser. */ @Override() public void defineConfigArguments(final ArgumentParser parser) throws ArgumentException { // Add an argument that allows you to specify the name to use in the // greeting. Character shortIdentifier = null; String longIdentifier = ARG_NAME_NAME; boolean required = false; int maxOccurrences = 1; String placeholder = "{name}"; String description = "The name to use in the greeting."; String defaultValue = "World"; parser.addArgument(new StringArgument(shortIdentifier, longIdentifier, required, maxOccurrences, placeholder, description, defaultValue)); // Add an argument that allows you to specify the request path. shortIdentifier = null; longIdentifier = ARG_NAME_PATH; required = false; maxOccurrences = 1; placeholder = "{path}"; description = "The path to use to access the servlet. Note that " + "changes to this argument will only take effect if the associated " + "HTTP connection handler (or the entire server) is stopped and " + "re-started."; defaultValue = "/hello"; parser.addArgument(new StringArgument(shortIdentifier, longIdentifier, required, maxOccurrences, placeholder, description, defaultValue)); } /** * Creates an HTTP servlet extension using the provided information. * * @param serverContext A handle to the server context for the server in * which this extension is running. * @param config The general configuration for this HTTP servlet * extension. * @param parser The argument parser which has been initialized from * the configuration for this HTTP servlet extension. * * @return The HTTP servlet that has been created. * * @throws LDAPException If a problem is encountered while attempting to * create the HTTP servlet. */ @Override() public ExampleHelloServlet createServlet( final HTTPServerContext serverContext, final HTTPServletExtensionConfig config, final ArgumentParser parser) throws LDAPException { // Get the new greeting name. final StringArgument nameArg = (StringArgument) parser.getNamedArgument(ARG_NAME_NAME); // Get the servlet path. final StringArgument pathArg = (StringArgument) parser.getNamedArgument(ARG_NAME_PATH); path = pathArg.getValue(); servlet = new ExampleHelloServlet(nameArg.getValue()); return servlet; } /** * Retrieves a list of the request paths for which the associated servlet * should be invoked. This method will be called after the * {@link #createServlet} method has been used to create the servlet instance. * * @return A list of the request paths for which the associated servlet * should be invoked. */ @Override() public List<String> getServletPaths() { return Arrays.asList(path); } /** * Retrieves a map of initialization parameters that should be provided to the * servlet when it is initialized. * * @return A map of initialization parameters that should be provided to the * servlet when it is initialized, or an empty map if no * initialization parameters are needed. */ @Override() public Map<String,String> getServletInitParameters() { final LinkedHashMap<String,String> initParameters = new LinkedHashMap<String,String>(1); initParameters.put("foo", "bar"); return initParameters; } /** * Retrieves the order in which the servlet should be started. A value * greater than or equal to zero guarantees that the servlet will be started * as soon as the servlet engine has been started, in order of ascending * servlet init order values, before the {@code doPostRegistrationProcessing} * method has been called. If the value is less than zero, the servlet may * not be started until a request is received for one of its registered paths. * * @return The order in which the servlet should be started, or a negative * value if startup order does not matter. */ @Override() public int getServletInitOrder() { return 0; } /** * Retrieves a list of servlet filter instances that should be installed with * the created servlet instance, in the order they should be invoked. If the * servlet is to be registered with multiple paths, then these filters will be * installed for all of those paths. * * @return A list of servlet filter instances that should be installed with * the created servlet instance, in the order that they should be * invoked. It may be {@code null} or empty if no servlet filters * should be installed. */ @Override() public List<Filter> getServletFilters() { return Arrays.<Filter>asList(FILTER); } /** * Performs any processing that may be needed after the servlet has been * registered with the servlet engine. If the value returned from * {@link #getServletInitOrder()} is greater than or equal to zero, then the * servlet will have been started before this method is called. If the value * returned from {@code getServletInitOrder()} is negative, then the servlet * may or may not have been started by the time this method is called. * <BR><BR> * Note that the associated servlet can also perform any necessary * initialization processing in the {@code init} method provided by the * servlet API. */ @Override() public void doPostRegistrationProcessing() { final Enumeration<String> nameEnum = servlet.getInitParameterNames(); initParametersProvided = ((nameEnum != null) && nameEnum.hasMoreElements()); servletContextAvailable = (servlet.getServletContext() != null); postRegistrationMethodCalled = true; } /** * Performs any processing that may be needed after the servlet has been * taken out of service and the associated servlet engine has been shut down. * <BR><BR> * Note that the associated servlet can also perform any necessary * finalization processing in the {@code destroy} method provided by the * servlet API. That method will be called after the servlet has been taken * out of service, but before the servlet engine has been shut down. */ @Override() public void doPostShutdownProcessing() { postShutdownMethodCalled = true; } /** * Indicates whether the configuration contained in the provided argument * parser represents a valid configuration for this extension. * * @param config The general configuration for this HTTP * servlet extension. * @param parser The argument parser which has been initialized * with the proposed configuration. * @param unacceptableReasons A list that can be updated with reasons that * the proposed configuration is not acceptable. * * @return {@code true} if the proposed configuration is acceptable, or * {@code false} if not. */ @Override() public boolean isConfigurationAcceptable( final HTTPServletExtensionConfig config, final ArgumentParser parser, final List<String> unacceptableReasons) { // No special validation is required. return true; } /** * Attempts to apply the configuration contained in the provided argument * parser. * * @param config The general configuration for this HTTP * servlet extension. * @param parser The argument parser which has been * initialized with the new configuration. * @param adminActionsRequired A list that can be updated with information * about any administrative actions that may be * required before one or more of the * configuration changes will be applied. * @param messages A list that can be updated with information * about the result of applying the new * configuration. * * @return A result code that provides information about the result of * attempting to apply the configuration change. */ @Override() public ResultCode applyConfiguration(final HTTPServletExtensionConfig config, final ArgumentParser parser, final List<String> adminActionsRequired, final List<String> messages) { // We only need to validate changes made after the servlet has been created. // If the servlet hasn't yet been created, then we don't need to do // anything. if (servlet == null) { return ResultCode.SUCCESS; } // Get the new greeting name. final StringArgument nameArg = (StringArgument) parser.getNamedArgument(ARG_NAME_NAME); servlet.setName(nameArg.getValue()); // The path will not change dynamically. If a different path was given, // then report that as a required administrative action. final StringArgument pathArg = (StringArgument) parser.getNamedArgument(ARG_NAME_PATH); if (! path.equals(pathArg.getValue())) { adminActionsRequired.add("Changes to the servlet path will not take " + "effect until the HTTP connection handler (or entire server) is " + "restarted."); } return ResultCode.SUCCESS; } /** * Retrieves a map containing examples of configurations that may be used for * this extension. The map key should be a list of sample arguments, and the * corresponding value should be a description of the behavior that will be * exhibited by the extension when used with that configuration. * * @return A map containing examples of configurations that may be used for * this extension. It may be {@code null} or empty if there should * not be any example argument sets. */ @Override() public Map<List<String>,String> getExamplesArgumentSets() { final LinkedHashMap<List<String>,String> exampleMap = new LinkedHashMap<List<String>,String>(1); exampleMap.put( Arrays.asList( ARG_NAME_NAME + "=John", ARG_NAME_PATH + "=/hello"), "Create a simple servlet that will display 'Hello, John!' whenever " + "an HTTP request is received with a path of '/hello'."); return exampleMap; } /** * Indicates whether initialization parameters were provided to the servlet. * * @return {@code true} if initialization parameters were provided to the * servlet, or {@code false} if not. */ public static boolean initParametersProvided() { return initParametersProvided; } /** * Indicates whether the {@code doPostRegistrationProcessing} method was * called for this servlet. * * @return {@code true} if the {@code doPostRegistrationProcessing} method * was called for this servlet, or {@code false} if not. */ public static boolean postRegistrationMethodCalled() { return postRegistrationMethodCalled; } /** * Indicates whether the {@code doPostShutdownProcessing} method was called * for this servlet. * * @return {@code true} if the {@code doPostShutdownProcessing} method was * called for this servlet, or {@code false} if not. */ public static boolean postShutdownMethodCalled() { return postShutdownMethodCalled; } /** * Indicates whether the a servlet context instance was available to the * servlet when the {@code doPostRegistrationProcessing} method was called. * * @return {@code true} if a servlet context instance was available, or * {@code false} if not. */ public static boolean servletContextAvailable() { return servletContextAvailable; } /** * Indicates whether all filter processing has been invoked. * * @return {@code true} if all filter processing has been invoked, or * {@code false} if at least some element of filter processing has * not yet been invoked. */ public static boolean filterProcessingInvoked() { return FILTER.initInvoked() && FILTER.preChainInvoked() && FILTER.postChainInvoked() && FILTER.destroyInvoked(); } }