001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.context.support; 018 019import java.io.Serializable; 020import java.util.Collections; 021import java.util.Enumeration; 022import java.util.HashMap; 023import java.util.Map; 024 025import javax.faces.context.ExternalContext; 026import javax.faces.context.FacesContext; 027import javax.servlet.ServletConfig; 028import javax.servlet.ServletContext; 029import javax.servlet.ServletRequest; 030import javax.servlet.ServletResponse; 031import javax.servlet.http.HttpSession; 032 033import org.springframework.beans.factory.ObjectFactory; 034import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 035import org.springframework.core.env.MutablePropertySources; 036import org.springframework.core.env.PropertySource.StubPropertySource; 037import org.springframework.lang.Nullable; 038import org.springframework.util.Assert; 039import org.springframework.util.ClassUtils; 040import org.springframework.web.context.ConfigurableWebApplicationContext; 041import org.springframework.web.context.WebApplicationContext; 042import org.springframework.web.context.request.RequestAttributes; 043import org.springframework.web.context.request.RequestContextHolder; 044import org.springframework.web.context.request.RequestScope; 045import org.springframework.web.context.request.ServletRequestAttributes; 046import org.springframework.web.context.request.ServletWebRequest; 047import org.springframework.web.context.request.SessionScope; 048import org.springframework.web.context.request.WebRequest; 049 050/** 051 * Convenience methods for retrieving the root {@link WebApplicationContext} for 052 * a given {@link ServletContext}. This is useful for programmatically accessing 053 * a Spring application context from within custom web views or MVC actions. 054 * 055 * <p>Note that there are more convenient ways of accessing the root context for 056 * many web frameworks, either part of Spring or available as an external library. 057 * This helper class is just the most generic way to access the root context. 058 * 059 * @author Juergen Hoeller 060 * @see org.springframework.web.context.ContextLoader 061 * @see org.springframework.web.servlet.FrameworkServlet 062 * @see org.springframework.web.servlet.DispatcherServlet 063 * @see org.springframework.web.jsf.FacesContextUtils 064 * @see org.springframework.web.jsf.el.SpringBeanFacesELResolver 065 */ 066public abstract class WebApplicationContextUtils { 067 068 private static final boolean jsfPresent = 069 ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader()); 070 071 072 /** 073 * Find the root {@code WebApplicationContext} for this web app, typically 074 * loaded via {@link org.springframework.web.context.ContextLoaderListener}. 075 * <p>Will rethrow an exception that happened on root context startup, 076 * to differentiate between a failed context startup and no context at all. 077 * @param sc the ServletContext to find the web application context for 078 * @return the root WebApplicationContext for this web app 079 * @throws IllegalStateException if the root WebApplicationContext could not be found 080 * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 081 */ 082 public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc) throws IllegalStateException { 083 WebApplicationContext wac = getWebApplicationContext(sc); 084 if (wac == null) { 085 throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?"); 086 } 087 return wac; 088 } 089 090 /** 091 * Find the root {@code WebApplicationContext} for this web app, typically 092 * loaded via {@link org.springframework.web.context.ContextLoaderListener}. 093 * <p>Will rethrow an exception that happened on root context startup, 094 * to differentiate between a failed context startup and no context at all. 095 * @param sc the ServletContext to find the web application context for 096 * @return the root WebApplicationContext for this web app, or {@code null} if none 097 * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 098 */ 099 @Nullable 100 public static WebApplicationContext getWebApplicationContext(ServletContext sc) { 101 return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 102 } 103 104 /** 105 * Find a custom {@code WebApplicationContext} for this web app. 106 * @param sc the ServletContext to find the web application context for 107 * @param attrName the name of the ServletContext attribute to look for 108 * @return the desired WebApplicationContext for this web app, or {@code null} if none 109 */ 110 @Nullable 111 public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) { 112 Assert.notNull(sc, "ServletContext must not be null"); 113 Object attr = sc.getAttribute(attrName); 114 if (attr == null) { 115 return null; 116 } 117 if (attr instanceof RuntimeException) { 118 throw (RuntimeException) attr; 119 } 120 if (attr instanceof Error) { 121 throw (Error) attr; 122 } 123 if (attr instanceof Exception) { 124 throw new IllegalStateException((Exception) attr); 125 } 126 if (!(attr instanceof WebApplicationContext)) { 127 throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr); 128 } 129 return (WebApplicationContext) attr; 130 } 131 132 /** 133 * Find a unique {@code WebApplicationContext} for this web app: either the 134 * root web app context (preferred) or a unique {@code WebApplicationContext} 135 * among the registered {@code ServletContext} attributes (typically coming 136 * from a single {@code DispatcherServlet} in the current web application). 137 * <p>Note that {@code DispatcherServlet}'s exposure of its context can be 138 * controlled through its {@code publishContext} property, which is {@code true} 139 * by default but can be selectively switched to only publish a single context 140 * despite multiple {@code DispatcherServlet} registrations in the web app. 141 * @param sc the ServletContext to find the web application context for 142 * @return the desired WebApplicationContext for this web app, or {@code null} if none 143 * @since 4.2 144 * @see #getWebApplicationContext(ServletContext) 145 * @see ServletContext#getAttributeNames() 146 */ 147 @Nullable 148 public static WebApplicationContext findWebApplicationContext(ServletContext sc) { 149 WebApplicationContext wac = getWebApplicationContext(sc); 150 if (wac == null) { 151 Enumeration<String> attrNames = sc.getAttributeNames(); 152 while (attrNames.hasMoreElements()) { 153 String attrName = attrNames.nextElement(); 154 Object attrValue = sc.getAttribute(attrName); 155 if (attrValue instanceof WebApplicationContext) { 156 if (wac != null) { 157 throw new IllegalStateException("No unique WebApplicationContext found: more than one " + 158 "DispatcherServlet registered with publishContext=true?"); 159 } 160 wac = (WebApplicationContext) attrValue; 161 } 162 } 163 } 164 return wac; 165 } 166 167 168 /** 169 * Register web-specific scopes ("request", "session", "globalSession") 170 * with the given BeanFactory, as used by the WebApplicationContext. 171 * @param beanFactory the BeanFactory to configure 172 */ 173 public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) { 174 registerWebApplicationScopes(beanFactory, null); 175 } 176 177 /** 178 * Register web-specific scopes ("request", "session", "globalSession", "application") 179 * with the given BeanFactory, as used by the WebApplicationContext. 180 * @param beanFactory the BeanFactory to configure 181 * @param sc the ServletContext that we're running within 182 */ 183 public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, 184 @Nullable ServletContext sc) { 185 186 beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()); 187 beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope()); 188 if (sc != null) { 189 ServletContextScope appScope = new ServletContextScope(sc); 190 beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); 191 // Register as ServletContext attribute, for ContextCleanupListener to detect it. 192 sc.setAttribute(ServletContextScope.class.getName(), appScope); 193 } 194 195 beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory()); 196 beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory()); 197 beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory()); 198 beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory()); 199 if (jsfPresent) { 200 FacesDependencyRegistrar.registerFacesDependencies(beanFactory); 201 } 202 } 203 204 /** 205 * Register web-specific environment beans ("contextParameters", "contextAttributes") 206 * with the given BeanFactory, as used by the WebApplicationContext. 207 * @param bf the BeanFactory to configure 208 * @param sc the ServletContext that we're running within 209 */ 210 public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, @Nullable ServletContext sc) { 211 registerEnvironmentBeans(bf, sc, null); 212 } 213 214 /** 215 * Register web-specific environment beans ("contextParameters", "contextAttributes") 216 * with the given BeanFactory, as used by the WebApplicationContext. 217 * @param bf the BeanFactory to configure 218 * @param servletContext the ServletContext that we're running within 219 * @param servletConfig the ServletConfig 220 */ 221 public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, 222 @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) { 223 224 if (servletContext != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) { 225 bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, servletContext); 226 } 227 228 if (servletConfig != null && !bf.containsBean(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME)) { 229 bf.registerSingleton(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME, servletConfig); 230 } 231 232 if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) { 233 Map<String, String> parameterMap = new HashMap<>(); 234 if (servletContext != null) { 235 Enumeration<?> paramNameEnum = servletContext.getInitParameterNames(); 236 while (paramNameEnum.hasMoreElements()) { 237 String paramName = (String) paramNameEnum.nextElement(); 238 parameterMap.put(paramName, servletContext.getInitParameter(paramName)); 239 } 240 } 241 if (servletConfig != null) { 242 Enumeration<?> paramNameEnum = servletConfig.getInitParameterNames(); 243 while (paramNameEnum.hasMoreElements()) { 244 String paramName = (String) paramNameEnum.nextElement(); 245 parameterMap.put(paramName, servletConfig.getInitParameter(paramName)); 246 } 247 } 248 bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME, 249 Collections.unmodifiableMap(parameterMap)); 250 } 251 252 if (!bf.containsBean(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME)) { 253 Map<String, Object> attributeMap = new HashMap<>(); 254 if (servletContext != null) { 255 Enumeration<?> attrNameEnum = servletContext.getAttributeNames(); 256 while (attrNameEnum.hasMoreElements()) { 257 String attrName = (String) attrNameEnum.nextElement(); 258 attributeMap.put(attrName, servletContext.getAttribute(attrName)); 259 } 260 } 261 bf.registerSingleton(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME, 262 Collections.unmodifiableMap(attributeMap)); 263 } 264 } 265 266 /** 267 * Convenient variant of {@link #initServletPropertySources(MutablePropertySources, 268 * ServletContext, ServletConfig)} that always provides {@code null} for the 269 * {@link ServletConfig} parameter. 270 * @see #initServletPropertySources(MutablePropertySources, ServletContext, ServletConfig) 271 */ 272 public static void initServletPropertySources(MutablePropertySources propertySources, ServletContext servletContext) { 273 initServletPropertySources(propertySources, servletContext, null); 274 } 275 276 /** 277 * Replace {@code Servlet}-based {@link StubPropertySource stub property sources} with 278 * actual instances populated with the given {@code servletContext} and 279 * {@code servletConfig} objects. 280 * <p>This method is idempotent with respect to the fact it may be called any number 281 * of times but will perform replacement of stub property sources with their 282 * corresponding actual property sources once and only once. 283 * @param sources the {@link MutablePropertySources} to initialize (must not 284 * be {@code null}) 285 * @param servletContext the current {@link ServletContext} (ignored if {@code null} 286 * or if the {@link StandardServletEnvironment#SERVLET_CONTEXT_PROPERTY_SOURCE_NAME 287 * servlet context property source} has already been initialized) 288 * @param servletConfig the current {@link ServletConfig} (ignored if {@code null} 289 * or if the {@link StandardServletEnvironment#SERVLET_CONFIG_PROPERTY_SOURCE_NAME 290 * servlet config property source} has already been initialized) 291 * @see org.springframework.core.env.PropertySource.StubPropertySource 292 * @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources() 293 */ 294 public static void initServletPropertySources(MutablePropertySources sources, 295 @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) { 296 297 Assert.notNull(sources, "'propertySources' must not be null"); 298 String name = StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME; 299 if (servletContext != null && sources.get(name) instanceof StubPropertySource) { 300 sources.replace(name, new ServletContextPropertySource(name, servletContext)); 301 } 302 name = StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME; 303 if (servletConfig != null && sources.get(name) instanceof StubPropertySource) { 304 sources.replace(name, new ServletConfigPropertySource(name, servletConfig)); 305 } 306 } 307 308 /** 309 * Return the current RequestAttributes instance as ServletRequestAttributes. 310 * @see RequestContextHolder#currentRequestAttributes() 311 */ 312 private static ServletRequestAttributes currentRequestAttributes() { 313 RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes(); 314 if (!(requestAttr instanceof ServletRequestAttributes)) { 315 throw new IllegalStateException("Current request is not a servlet request"); 316 } 317 return (ServletRequestAttributes) requestAttr; 318 } 319 320 321 /** 322 * Factory that exposes the current request object on demand. 323 */ 324 @SuppressWarnings("serial") 325 private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable { 326 327 @Override 328 public ServletRequest getObject() { 329 return currentRequestAttributes().getRequest(); 330 } 331 332 @Override 333 public String toString() { 334 return "Current HttpServletRequest"; 335 } 336 } 337 338 339 /** 340 * Factory that exposes the current response object on demand. 341 */ 342 @SuppressWarnings("serial") 343 private static class ResponseObjectFactory implements ObjectFactory<ServletResponse>, Serializable { 344 345 @Override 346 public ServletResponse getObject() { 347 ServletResponse response = currentRequestAttributes().getResponse(); 348 if (response == null) { 349 throw new IllegalStateException("Current servlet response not available - " + 350 "consider using RequestContextFilter instead of RequestContextListener"); 351 } 352 return response; 353 } 354 355 @Override 356 public String toString() { 357 return "Current HttpServletResponse"; 358 } 359 } 360 361 362 /** 363 * Factory that exposes the current session object on demand. 364 */ 365 @SuppressWarnings("serial") 366 private static class SessionObjectFactory implements ObjectFactory<HttpSession>, Serializable { 367 368 @Override 369 public HttpSession getObject() { 370 return currentRequestAttributes().getRequest().getSession(); 371 } 372 373 @Override 374 public String toString() { 375 return "Current HttpSession"; 376 } 377 } 378 379 380 /** 381 * Factory that exposes the current WebRequest object on demand. 382 */ 383 @SuppressWarnings("serial") 384 private static class WebRequestObjectFactory implements ObjectFactory<WebRequest>, Serializable { 385 386 @Override 387 public WebRequest getObject() { 388 ServletRequestAttributes requestAttr = currentRequestAttributes(); 389 return new ServletWebRequest(requestAttr.getRequest(), requestAttr.getResponse()); 390 } 391 392 @Override 393 public String toString() { 394 return "Current ServletWebRequest"; 395 } 396 } 397 398 399 /** 400 * Inner class to avoid hard-coded JSF dependency. 401 */ 402 private static class FacesDependencyRegistrar { 403 404 public static void registerFacesDependencies(ConfigurableListableBeanFactory beanFactory) { 405 beanFactory.registerResolvableDependency(FacesContext.class, new ObjectFactory<FacesContext>() { 406 @Override 407 public FacesContext getObject() { 408 return FacesContext.getCurrentInstance(); 409 } 410 @Override 411 public String toString() { 412 return "Current JSF FacesContext"; 413 } 414 }); 415 beanFactory.registerResolvableDependency(ExternalContext.class, new ObjectFactory<ExternalContext>() { 416 @Override 417 public ExternalContext getObject() { 418 return FacesContext.getCurrentInstance().getExternalContext(); 419 } 420 @Override 421 public String toString() { 422 return "Current JSF ExternalContext"; 423 } 424 }); 425 } 426 } 427 428}