001/*
002 * Copyright 2002-2017 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.i18n;
018
019import java.util.Locale;
020import java.util.TimeZone;
021import javax.servlet.http.HttpServletRequest;
022import javax.servlet.http.HttpServletResponse;
023
024import org.springframework.context.i18n.LocaleContext;
025import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
026import org.springframework.web.util.WebUtils;
027
028/**
029 * {@link org.springframework.web.servlet.LocaleResolver} implementation that
030 * uses a locale attribute in the user's session in case of a custom setting,
031 * with a fallback to the specified default locale or the request's
032 * accept-header locale.
033 *
034 * <p>This is most appropriate if the application needs user sessions anyway,
035 * i.e. when the {@code HttpSession} does not have to be created just for storing
036 * the user's locale. The session may optionally contain an associated time zone
037 * attribute as well; alternatively, you may specify a default time zone.
038 *
039 * <p>Custom controllers can override the user's locale and time zone by calling
040 * {@code #setLocale(Context)} on the resolver, e.g. responding to a locale change
041 * request. As a more convenient alternative, consider using
042 * {@link org.springframework.web.servlet.support.RequestContext#changeLocale}.
043 *
044 * <p>In contrast to {@link CookieLocaleResolver}, this strategy stores locally
045 * chosen locale settings in the Servlet container's {@code HttpSession}. As a
046 * consequence, those settings are just temporary for each session and therefore
047 * lost when each session terminates.
048 *
049 * <p>Note that there is no direct relationship with external session management
050 * mechanisms such as the "Spring Session" project. This {@code LocaleResolver}
051 * will simply evaluate and modify corresponding {@code HttpSession} attributes
052 * against the current {@code HttpServletRequest}.
053 *
054 * @author Juergen Hoeller
055 * @since 27.02.2003
056 * @see #setDefaultLocale
057 * @see #setDefaultTimeZone
058 */
059public class SessionLocaleResolver extends AbstractLocaleContextResolver {
060
061        /**
062         * Name of the session attribute that holds the Locale.
063         * Only used internally by this implementation.
064         * <p>Use {@code RequestContext(Utils).getLocale()}
065         * to retrieve the current locale in controllers or views.
066         * @see org.springframework.web.servlet.support.RequestContext#getLocale
067         * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
068         */
069        public static final String LOCALE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".LOCALE";
070
071        /**
072         * Name of the session attribute that holds the TimeZone.
073         * Only used internally by this implementation.
074         * <p>Use {@code RequestContext(Utils).getTimeZone()}
075         * to retrieve the current time zone in controllers or views.
076         * @see org.springframework.web.servlet.support.RequestContext#getTimeZone
077         * @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
078         */
079        public static final String TIME_ZONE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".TIME_ZONE";
080
081
082        private String localeAttributeName = LOCALE_SESSION_ATTRIBUTE_NAME;
083
084        private String timeZoneAttributeName = TIME_ZONE_SESSION_ATTRIBUTE_NAME;
085
086
087        /**
088         * Specify the name of the corresponding attribute in the {@code HttpSession},
089         * holding the current {@link Locale} value.
090         * <p>The default is an internal {@link #LOCALE_SESSION_ATTRIBUTE_NAME}.
091         * @since 4.3.8
092         */
093        public void setLocaleAttributeName(String localeAttributeName) {
094                this.localeAttributeName = localeAttributeName;
095        }
096
097        /**
098         * Specify the name of the corresponding attribute in the {@code HttpSession},
099         * holding the current {@link TimeZone} value.
100         * <p>The default is an internal {@link #TIME_ZONE_SESSION_ATTRIBUTE_NAME}.
101         * @since 4.3.8
102         */
103        public void setTimeZoneAttributeName(String timeZoneAttributeName) {
104                this.timeZoneAttributeName = timeZoneAttributeName;
105        }
106
107
108        @Override
109        public Locale resolveLocale(HttpServletRequest request) {
110                Locale locale = (Locale) WebUtils.getSessionAttribute(request, this.localeAttributeName);
111                if (locale == null) {
112                        locale = determineDefaultLocale(request);
113                }
114                return locale;
115        }
116
117        @Override
118        public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
119                return new TimeZoneAwareLocaleContext() {
120                        @Override
121                        public Locale getLocale() {
122                                Locale locale = (Locale) WebUtils.getSessionAttribute(request, localeAttributeName);
123                                if (locale == null) {
124                                        locale = determineDefaultLocale(request);
125                                }
126                                return locale;
127                        }
128                        @Override
129                        public TimeZone getTimeZone() {
130                                TimeZone timeZone = (TimeZone) WebUtils.getSessionAttribute(request, timeZoneAttributeName);
131                                if (timeZone == null) {
132                                        timeZone = determineDefaultTimeZone(request);
133                                }
134                                return timeZone;
135                        }
136                };
137        }
138
139        @Override
140        public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
141                Locale locale = null;
142                TimeZone timeZone = null;
143                if (localeContext != null) {
144                        locale = localeContext.getLocale();
145                        if (localeContext instanceof TimeZoneAwareLocaleContext) {
146                                timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
147                        }
148                }
149                WebUtils.setSessionAttribute(request, this.localeAttributeName, locale);
150                WebUtils.setSessionAttribute(request, this.timeZoneAttributeName, timeZone);
151        }
152
153
154        /**
155         * Determine the default locale for the given request,
156         * Called if no Locale session attribute has been found.
157         * <p>The default implementation returns the specified default locale,
158         * if any, else falls back to the request's accept-header locale.
159         * @param request the request to resolve the locale for
160         * @return the default locale (never {@code null})
161         * @see #setDefaultLocale
162         * @see javax.servlet.http.HttpServletRequest#getLocale()
163         */
164        protected Locale determineDefaultLocale(HttpServletRequest request) {
165                Locale defaultLocale = getDefaultLocale();
166                if (defaultLocale == null) {
167                        defaultLocale = request.getLocale();
168                }
169                return defaultLocale;
170        }
171
172        /**
173         * Determine the default time zone for the given request,
174         * Called if no TimeZone session attribute has been found.
175         * <p>The default implementation returns the specified default time zone,
176         * if any, or {@code null} otherwise.
177         * @param request the request to resolve the time zone for
178         * @return the default time zone (or {@code null} if none defined)
179         * @see #setDefaultTimeZone
180         */
181        protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {
182                return getDefaultTimeZone();
183        }
184
185}