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 * <servlet>
47 * <servlet-name>ServletPathHttpServlet</servlet-name>
48 * <servlet-class>org.forgerock.http.servlet.HttpFrameworkServlet</servlet-class>
49 * <async-supported>true</async-supported>
50 * <init-param>
51 * <param-name>application-loader</param-name>
52 * <param-value>service_loader</param-value> <!--This is the default so can be omitted-->
53 * </init-param>
54 * <init-param>
55 * <param-name>routing-base</param-name>
56 * <param-value>servlet_path</param-value> <!--This is the default so can be omitted-->
57 * </init-param>
58 * </servlet>
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 * <listener>
110 * <listener-class>
111 * org.forgerock.http.servlet.example.multiple.ExampleHttpFrameworkServletContextListener
112 * </listener-class>
113 * </listener>
114 *
115 * <servlet>
116 * <servlet-name>AdminHttpApplicationServlet</servlet-name>
117 * <servlet-class>org.forgerock.http.servlet.HttpFrameworkServlet</servlet-class>
118 * <async-supported>true</async-supported>
119 * <init-param>
120 * <param-name>application-loader</param-name>
121 * <param-value>servlet_context</param-value>
122 * </init-param>
123 * <init-param>
124 * <param-name>application-key</param-name>
125 * <param-value>adminApp</param-value>
126 * </init-param>
127 * </servlet>
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 * <servlet>
165 * <servlet-name>ServletPathHttpServlet</servlet-name>
166 * <servlet-class>org.forgerock.http.servlet.HttpFrameworkServlet</servlet-class>
167 * <async-supported>true</async-supported>
168 * <init-param>
169 * <param-name>application-loader</param-name>
170 * <param-value>guice</param-value>
171 * </init-param>
172 * <init-param>
173 * <!--Defaults to HttpApplication if omitted-->
174 * <param-name>application-class</param-name>
175 * <param-value>org.forgerock.http.servlet.example.ExampleHttpApplication</param-value>
176 * </init-param>
177 * <init-param>
178 * <param-name>routing-base</param-name>
179 * <param-value>servlet_path</param-value> <!--This is the default so can be omitted-->
180 * </init-param>
181 * </servlet>
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 }