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}