/*
* 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
*
*
* Copyright 2011-2016 UnboundID Corp.
*/
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();
}
}
|