View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2015 ForgeRock AS.
15   */
16  
17  package org.forgerock.http.servlet;
18  
19  import jakarta.servlet.ServletConfig;
20  import jakarta.servlet.ServletContext;
21  import jakarta.servlet.ServletException;
22  import java.util.ArrayList;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.ServiceLoader;
26  
27  import com.google.inject.ConfigurationException;
28  import com.google.inject.ProvisionException;
29  import org.forgerock.guice.core.InjectorHolder;
30  import org.forgerock.http.HttpApplication;
31  
32  /**
33   * An enum containing the possible methods of loading the {@code HttpApplication}.
34   */
35  enum HttpApplicationLoader {
36  
37      /**
38       * Uses the {@link ServiceLoader} framework to locate all instances of
39       * {@code HttpApplication}s on the classpath.
40       *
41       * <p>If no {@code HttpApplication} implementation is found or multiple are
42       * found then a {@link ServletException} will be thrown.</p>
43       *
44       * <p>The following is an example Servlet declaration:
45       * <pre>
46       * &lt;servlet&gt;
47       *     &lt;servlet-name&gt;ServletPathHttpServlet&lt;/servlet-name&gt;
48       *     &lt;servlet-class&gt;org.forgerock.http.servlet.HttpFrameworkServlet&lt;/servlet-class&gt;
49       *     &lt;async-supported&gt;true&lt;/async-supported&gt;
50       *     &lt;init-param&gt;
51       *         &lt;param-name&gt;application-loader&lt;/param-name&gt;
52       *         &lt;param-value&gt;service_loader&lt;/param-value&gt; &lt;!--This is the default so can be omitted--&gt;
53       *     &lt;/init-param&gt;
54       *     &lt;init-param&gt;
55       *         &lt;param-name&gt;routing-base&lt;/param-name&gt;
56       *         &lt;param-value&gt;servlet_path&lt;/param-value&gt; &lt;!--This is the default so can be omitted--&gt;
57       *     &lt;/init-param&gt;
58       * &lt;/servlet&gt;
59       * </pre></p>
60       *
61       * @see ServletRoutingBase
62       */
63      SERVICE_LOADER {
64          @Override
65          HttpApplication load(ServletConfig config) throws ServletException {
66              ServiceLoader<HttpApplication> configurations = ServiceLoader.load(HttpApplication.class);
67              Iterator<HttpApplication> iterator = configurations.iterator();
68  
69              if (!iterator.hasNext()) {
70                  throw new ServletException("No HttpApplication implementation registered.");
71              }
72  
73              HttpApplication configuration = iterator.next();
74  
75              if (iterator.hasNext()) {
76                  // Multiple ServletConfigurations registered!
77                  List<Object> messageParams = new ArrayList<Object>();
78                  messageParams.add(iterator.next().getClass().getName());
79  
80                  String message = "Multiple HttpApplication implementations registered.\n"
81                          + "%d configurations found: %s";
82  
83                  while (iterator.hasNext()) {
84                      messageParams.add(iterator.next().getClass().getName());
85                      message += ", %s";
86                  }
87                  messageParams.add(0, messageParams.size());
88  
89                  throw new ServletException(String.format(message, messageParams.toArray()));
90              }
91              return configuration;
92          }
93      },
94  
95      /**
96       * Uses the attributes of the {@link ServletContext} to find the
97       * {@code HttpApplication} using a key provided by an
98       * {@literal application-key} init-param. The attributes on the
99       * {@code ServletContext} must be set before the
100      * {@code HttpFrameworkServlet} is initialised.
101      *
102      * <p>If no {@literal application-key} init-param is set, no
103      * {@code HttpApplication} defined for the key or the object defined for
104      * the key is not a {@code HttpApplication} then a {@link ServletException}
105      * will be thrown.</p>
106      *
107      * <p>The following is an example Servlet declaration:
108      * <pre>
109      * &lt;listener&gt;
110      *     &lt;listener-class&gt;
111      *         org.forgerock.http.servlet.example.multiple.ExampleHttpFrameworkServletContextListener
112      *     &lt;/listener-class&gt;
113      * &lt;/listener&gt;
114      *
115      * &lt;servlet&gt;
116      *     &lt;servlet-name&gt;AdminHttpApplicationServlet&lt;/servlet-name&gt;
117      *     &lt;servlet-class&gt;org.forgerock.http.servlet.HttpFrameworkServlet&lt;/servlet-class&gt;
118      *     &lt;async-supported&gt;true&lt;/async-supported&gt;
119      *     &lt;init-param&gt;
120      *         &lt;param-name&gt;application-loader&lt;/param-name&gt;
121      *         &lt;param-value&gt;servlet_context&lt;/param-value&gt;
122      *     &lt;/init-param&gt;
123      *     &lt;init-param&gt;
124      *         &lt;param-name&gt;application-key&lt;/param-name&gt;
125      *         &lt;param-value&gt;adminApp&lt;/param-value&gt;
126      *     &lt;/init-param&gt;
127      * &lt;/servlet&gt;
128      * </pre></p>
129      *
130      * @see HttpFrameworkServletContextListener
131      */
132     SERVLET_CONTEXT {
133         private final static String APPLICATION_KEY_INIT_PARAM_NAME = "application-key";
134 
135         @Override
136         HttpApplication load(ServletConfig config) throws ServletException {
137             String applicationKey = config.getInitParameter(APPLICATION_KEY_INIT_PARAM_NAME);
138             if (applicationKey == null) {
139                 throw new ServletException("No " + APPLICATION_KEY_INIT_PARAM_NAME + " init-param set.");
140             }
141             Object application = config.getServletContext().getAttribute(applicationKey);
142             if (application == null) {
143                 throw new ServletException("No HttpApplication found for key: " + applicationKey);
144             } else if (!(application instanceof HttpApplication)) {
145                 throw new ServletException("Invalid type: " + applicationKey.getClass().getName()
146                         + ". Application MUST BE an instance of " + HttpApplication.class.getName());
147             }
148             return (HttpApplication) application;
149         }
150     },
151 
152     /**
153      * Uses {@literal Guice} to locate the {@code HttpApplication} instance.
154      * This application loader can only be used if
155      * {@literal org.forgerock.commons:forgerock-guice-core} is on the
156      * classpath.
157      *
158      * <p>If no {@literal application-class} init-param is set the
159      * {@literal Guice} will be asked to get an instance of the
160      * {@code HttpApplication} interface.</p>
161      *
162      * <p>The following is an example Servlet declaration:
163      * <pre>
164      * &lt;servlet&gt;
165      *     &lt;servlet-name&gt;ServletPathHttpServlet&lt;/servlet-name&gt;
166      *     &lt;servlet-class&gt;org.forgerock.http.servlet.HttpFrameworkServlet&lt;/servlet-class&gt;
167      *     &lt;async-supported&gt;true&lt;/async-supported&gt;
168      *     &lt;init-param&gt;
169      *         &lt;param-name&gt;application-loader&lt;/param-name&gt;
170      *         &lt;param-value&gt;guice&lt;/param-value&gt;
171      *     &lt;/init-param&gt;
172      *     &lt;init-param&gt;
173      *         &lt;!--Defaults to HttpApplication if omitted--&gt;
174      *         &lt;param-name&gt;application-class&lt;/param-name&gt;
175      *         &lt;param-value&gt;org.forgerock.http.servlet.example.ExampleHttpApplication&lt;/param-value&gt;
176      *     &lt;/init-param&gt;
177      *     &lt;init-param&gt;
178      *         &lt;param-name&gt;routing-base&lt;/param-name&gt;
179      *         &lt;param-value&gt;servlet_path&lt;/param-value&gt; &lt;!--This is the default so can be omitted--&gt;
180      *     &lt;/init-param&gt;
181      * &lt;/servlet&gt;
182      * </pre></p>
183      *
184      * @see InjectorHolder
185      */
186     GUICE {
187         @Override
188         HttpApplication load(ServletConfig config) throws ServletException {
189             return LazilyLinkGuice.load(config);
190         }
191     };
192 
193     // Force lazy linkage to Guice because the Guice dependency is optional.
194     private static class LazilyLinkGuice {
195         private static HttpApplication load(ServletConfig config) throws ServletException {
196             String applicationClassName = config.getInitParameter("application-class");
197             try {
198                 if (applicationClassName == null) {
199                     return InjectorHolder.getInstance(HttpApplication.class);
200                 } else {
201                     return InjectorHolder.getInstance(Class.forName(applicationClassName)
202                             .asSubclass(HttpApplication.class));
203                 }
204             } catch (ClassNotFoundException e) {
205                 throw new ServletException("Failed to find the Http Application class: " + applicationClassName, e);
206             } catch (ConfigurationException | ProvisionException e) {
207                 throw new ServletException("Failed to load the Http Application class: " + applicationClassName, e);
208             }
209         }
210     }
211 
212     /**
213      * Loads the {@code HttpApplication}.
214      *
215      * @param config The {@code ServletConfig}.
216      * @return The {@code HttpApplication} instance.
217      * @throws ServletException If the {@code HttpApplication} could not be
218      * loaded.
219      */
220     abstract HttpApplication load(ServletConfig config) throws ServletException;
221 }