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