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 }