001/*
002 * Copyright 2002-2018 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.servlet.support;
018
019import java.util.Locale;
020import java.util.Map;
021import java.util.TimeZone;
022
023import javax.servlet.ServletContext;
024import javax.servlet.ServletRequest;
025import javax.servlet.http.HttpServletRequest;
026import javax.servlet.http.HttpServletResponse;
027
028import org.springframework.context.i18n.LocaleContext;
029import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
030import org.springframework.lang.Nullable;
031import org.springframework.ui.context.Theme;
032import org.springframework.ui.context.ThemeSource;
033import org.springframework.util.Assert;
034import org.springframework.util.CollectionUtils;
035import org.springframework.web.context.ContextLoader;
036import org.springframework.web.context.WebApplicationContext;
037import org.springframework.web.context.support.WebApplicationContextUtils;
038import org.springframework.web.servlet.DispatcherServlet;
039import org.springframework.web.servlet.FlashMap;
040import org.springframework.web.servlet.FlashMapManager;
041import org.springframework.web.servlet.LocaleContextResolver;
042import org.springframework.web.servlet.LocaleResolver;
043import org.springframework.web.servlet.ThemeResolver;
044import org.springframework.web.util.UriComponents;
045import org.springframework.web.util.UriComponentsBuilder;
046
047/**
048 * Utility class for easy access to request-specific state which has been
049 * set by the {@link org.springframework.web.servlet.DispatcherServlet}.
050 *
051 * <p>Supports lookup of current WebApplicationContext, LocaleResolver,
052 * Locale, ThemeResolver, Theme, and MultipartResolver.
053 *
054 * @author Juergen Hoeller
055 * @author Rossen Stoyanchev
056 * @since 03.03.2003
057 * @see RequestContext
058 * @see org.springframework.web.servlet.DispatcherServlet
059 */
060public abstract class RequestContextUtils {
061
062        /**
063         * The name of the bean to use to look up in an implementation of
064         * {@link RequestDataValueProcessor} has been configured.
065         * @since 4.2.1
066         */
067        public static final String REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME = "requestDataValueProcessor";
068
069
070        /**
071         * Look for the WebApplicationContext associated with the DispatcherServlet
072         * that has initiated request processing, and for the global context if none
073         * was found associated with the current request. The global context will
074         * be found via the ServletContext or via ContextLoader's current context.
075         * <p>NOTE: This variant remains compatible with Servlet 2.5, explicitly
076         * checking a given ServletContext instead of deriving it from the request.
077         * @param request current HTTP request
078         * @param servletContext current servlet context
079         * @return the request-specific WebApplicationContext, or the global one
080         * if no request-specific context has been found, or {@code null} if none
081         * @since 4.2.1
082         * @see DispatcherServlet#WEB_APPLICATION_CONTEXT_ATTRIBUTE
083         * @see WebApplicationContextUtils#getWebApplicationContext(ServletContext)
084         * @see ContextLoader#getCurrentWebApplicationContext()
085         */
086        @Nullable
087        public static WebApplicationContext findWebApplicationContext(
088                        HttpServletRequest request, @Nullable ServletContext servletContext) {
089
090                WebApplicationContext webApplicationContext = (WebApplicationContext) request.getAttribute(
091                                DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
092                if (webApplicationContext == null) {
093                        if (servletContext != null) {
094                                webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
095                        }
096                        if (webApplicationContext == null) {
097                                webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
098                        }
099                }
100                return webApplicationContext;
101        }
102
103        /**
104         * Look for the WebApplicationContext associated with the DispatcherServlet
105         * that has initiated request processing, and for the global context if none
106         * was found associated with the current request. The global context will
107         * be found via the ServletContext or via ContextLoader's current context.
108         * <p>NOTE: This variant requires Servlet 3.0+ and is generally recommended
109         * for forward-looking custom user code.
110         * @param request current HTTP request
111         * @return the request-specific WebApplicationContext, or the global one
112         * if no request-specific context has been found, or {@code null} if none
113         * @since 4.2.1
114         * @see #findWebApplicationContext(HttpServletRequest, ServletContext)
115         * @see ServletRequest#getServletContext()
116         * @see ContextLoader#getCurrentWebApplicationContext()
117         */
118        @Nullable
119        public static WebApplicationContext findWebApplicationContext(HttpServletRequest request) {
120                return findWebApplicationContext(request, request.getServletContext());
121        }
122
123        /**
124         * Return the LocaleResolver that has been bound to the request by the
125         * DispatcherServlet.
126         * @param request current HTTP request
127         * @return the current LocaleResolver, or {@code null} if not found
128         */
129        @Nullable
130        public static LocaleResolver getLocaleResolver(HttpServletRequest request) {
131                return (LocaleResolver) request.getAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE);
132        }
133
134        /**
135         * Retrieve the current locale from the given request, using the
136         * LocaleResolver bound to the request by the DispatcherServlet
137         * (if available), falling back to the request's accept-header Locale.
138         * <p>This method serves as a straightforward alternative to the standard
139         * Servlet {@link javax.servlet.http.HttpServletRequest#getLocale()} method,
140         * falling back to the latter if no more specific locale has been found.
141         * <p>Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getLocale()}
142         * which will normally be populated with the same Locale.
143         * @param request current HTTP request
144         * @return the current locale for the given request, either from the
145         * LocaleResolver or from the plain request itself
146         * @see #getLocaleResolver
147         * @see org.springframework.context.i18n.LocaleContextHolder#getLocale()
148         */
149        public static Locale getLocale(HttpServletRequest request) {
150                LocaleResolver localeResolver = getLocaleResolver(request);
151                return (localeResolver != null ? localeResolver.resolveLocale(request) : request.getLocale());
152        }
153
154        /**
155         * Retrieve the current time zone from the given request, using the
156         * TimeZoneAwareLocaleResolver bound to the request by the DispatcherServlet
157         * (if available), falling back to the system's default time zone.
158         * <p>Note: This method returns {@code null} if no specific time zone can be
159         * resolved for the given request. This is in contrast to {@link #getLocale}
160         * where there is always the request's accept-header locale to fall back to.
161         * <p>Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getTimeZone()}
162         * which will normally be populated with the same TimeZone: That method only
163         * differs in terms of its fallback to the system time zone if the LocaleResolver
164         * hasn't provided a specific time zone (instead of this method's {@code null}).
165         * @param request current HTTP request
166         * @return the current time zone for the given request, either from the
167         * TimeZoneAwareLocaleResolver or {@code null} if none associated
168         * @see #getLocaleResolver
169         * @see org.springframework.context.i18n.LocaleContextHolder#getTimeZone()
170         */
171        @Nullable
172        public static TimeZone getTimeZone(HttpServletRequest request) {
173                LocaleResolver localeResolver = getLocaleResolver(request);
174                if (localeResolver instanceof LocaleContextResolver) {
175                        LocaleContext localeContext = ((LocaleContextResolver) localeResolver).resolveLocaleContext(request);
176                        if (localeContext instanceof TimeZoneAwareLocaleContext) {
177                                return ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
178                        }
179                }
180                return null;
181        }
182
183        /**
184         * Return the ThemeResolver that has been bound to the request by the
185         * DispatcherServlet.
186         * @param request current HTTP request
187         * @return the current ThemeResolver, or {@code null} if not found
188         */
189        @Nullable
190        public static ThemeResolver getThemeResolver(HttpServletRequest request) {
191                return (ThemeResolver) request.getAttribute(DispatcherServlet.THEME_RESOLVER_ATTRIBUTE);
192        }
193
194        /**
195         * Return the ThemeSource that has been bound to the request by the
196         * DispatcherServlet.
197         * @param request current HTTP request
198         * @return the current ThemeSource
199         */
200        @Nullable
201        public static ThemeSource getThemeSource(HttpServletRequest request) {
202                return (ThemeSource) request.getAttribute(DispatcherServlet.THEME_SOURCE_ATTRIBUTE);
203        }
204
205        /**
206         * Retrieves the current theme from the given request, using the ThemeResolver
207         * and ThemeSource bound to the request by the DispatcherServlet.
208         * @param request current HTTP request
209         * @return the current theme, or {@code null} if not found
210         * @see #getThemeResolver
211         */
212        @Nullable
213        public static Theme getTheme(HttpServletRequest request) {
214                ThemeResolver themeResolver = getThemeResolver(request);
215                ThemeSource themeSource = getThemeSource(request);
216                if (themeResolver != null && themeSource != null) {
217                        String themeName = themeResolver.resolveThemeName(request);
218                        return themeSource.getTheme(themeName);
219                }
220                else {
221                        return null;
222                }
223        }
224
225        /**
226         * Return read-only "input" flash attributes from request before redirect.
227         * @param request current request
228         * @return a read-only Map, or {@code null} if not found
229         * @see FlashMap
230         */
231        @SuppressWarnings("unchecked")
232        @Nullable
233        public static Map<String, ?> getInputFlashMap(HttpServletRequest request) {
234                return (Map<String, ?>) request.getAttribute(DispatcherServlet.INPUT_FLASH_MAP_ATTRIBUTE);
235        }
236
237        /**
238         * Return "output" FlashMap to save attributes for request after redirect.
239         * @param request current request
240         * @return a {@link FlashMap} instance, never {@code null} within a
241         * {@code DispatcherServlet}-handled request
242         */
243        public static FlashMap getOutputFlashMap(HttpServletRequest request) {
244                return (FlashMap) request.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
245        }
246
247        /**
248         * Return the {@code FlashMapManager} instance to save flash attributes.
249         * <p>As of 5.0 the convenience method {@link #saveOutputFlashMap} may be
250         * used to save the "output" FlashMap.
251         * @param request the current request
252         * @return a {@link FlashMapManager} instance, never {@code null} within a
253         * {@code DispatcherServlet}-handled request
254         */
255        @Nullable
256        public static FlashMapManager getFlashMapManager(HttpServletRequest request) {
257                return (FlashMapManager) request.getAttribute(DispatcherServlet.FLASH_MAP_MANAGER_ATTRIBUTE);
258        }
259
260        /**
261         * Convenience method that retrieves the {@link #getOutputFlashMap "output"
262         * FlashMap}, updates it with the path and query params of the target URL,
263         * and then saves it using the {@link #getFlashMapManager FlashMapManager}.
264         * @param location the target URL for the redirect
265         * @param request the current request
266         * @param response the current response
267         * @since 5.0
268         */
269        public static void saveOutputFlashMap(String location, HttpServletRequest request, HttpServletResponse response) {
270                FlashMap flashMap = getOutputFlashMap(request);
271                if (CollectionUtils.isEmpty(flashMap)) {
272                        return;
273                }
274
275                UriComponents uriComponents = UriComponentsBuilder.fromUriString(location).build();
276                flashMap.setTargetRequestPath(uriComponents.getPath());
277                flashMap.addTargetRequestParams(uriComponents.getQueryParams());
278
279                FlashMapManager manager = getFlashMapManager(request);
280                Assert.state(manager != null, "No FlashMapManager. Is this a DispatcherServlet handled request?");
281                manager.saveOutputFlashMap(flashMap, request, response);
282        }
283
284}