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