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.context.i18n;
018
019import java.util.Locale;
020import java.util.TimeZone;
021
022import org.springframework.core.NamedInheritableThreadLocal;
023import org.springframework.core.NamedThreadLocal;
024
025/**
026 * Simple holder class that associates a LocaleContext instance
027 * with the current thread. The LocaleContext will be inherited
028 * by any child threads spawned by the current thread if the
029 * {@code inheritable} flag is set to {@code true}.
030 *
031 * <p>Used as a central holder for the current Locale in Spring,
032 * wherever necessary: for example, in MessageSourceAccessor.
033 * DispatcherServlet automatically exposes its current Locale here.
034 * Other applications can expose theirs too, to make classes like
035 * MessageSourceAccessor automatically use that Locale.
036 *
037 * @author Juergen Hoeller
038 * @author Nicholas Williams
039 * @since 1.2
040 * @see LocaleContext
041 * @see org.springframework.context.support.MessageSourceAccessor
042 * @see org.springframework.web.servlet.DispatcherServlet
043 */
044public abstract class LocaleContextHolder {
045
046        private static final ThreadLocal<LocaleContext> localeContextHolder =
047                        new NamedThreadLocal<LocaleContext>("LocaleContext");
048
049        private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
050                        new NamedInheritableThreadLocal<LocaleContext>("LocaleContext");
051
052        // Shared default locale at the framework level
053        private static Locale defaultLocale;
054
055        // Shared default time zone at the framework level
056        private static TimeZone defaultTimeZone;
057
058
059        /**
060         * Reset the LocaleContext for the current thread.
061         */
062        public static void resetLocaleContext() {
063                localeContextHolder.remove();
064                inheritableLocaleContextHolder.remove();
065        }
066
067        /**
068         * Associate the given LocaleContext with the current thread,
069         * <i>not</i> exposing it as inheritable for child threads.
070         * <p>The given LocaleContext may be a {@link TimeZoneAwareLocaleContext},
071         * containing a locale with associated time zone information.
072         * @param localeContext the current LocaleContext,
073         * or {@code null} to reset the thread-bound context
074         * @see SimpleLocaleContext
075         * @see SimpleTimeZoneAwareLocaleContext
076         */
077        public static void setLocaleContext(LocaleContext localeContext) {
078                setLocaleContext(localeContext, false);
079        }
080
081        /**
082         * Associate the given LocaleContext with the current thread.
083         * <p>The given LocaleContext may be a {@link TimeZoneAwareLocaleContext},
084         * containing a locale with associated time zone information.
085         * @param localeContext the current LocaleContext,
086         * or {@code null} to reset the thread-bound context
087         * @param inheritable whether to expose the LocaleContext as inheritable
088         * for child threads (using an {@link InheritableThreadLocal})
089         * @see SimpleLocaleContext
090         * @see SimpleTimeZoneAwareLocaleContext
091         */
092        public static void setLocaleContext(LocaleContext localeContext, boolean inheritable) {
093                if (localeContext == null) {
094                        resetLocaleContext();
095                }
096                else {
097                        if (inheritable) {
098                                inheritableLocaleContextHolder.set(localeContext);
099                                localeContextHolder.remove();
100                        }
101                        else {
102                                localeContextHolder.set(localeContext);
103                                inheritableLocaleContextHolder.remove();
104                        }
105                }
106        }
107
108        /**
109         * Return the LocaleContext associated with the current thread, if any.
110         * @return the current LocaleContext, or {@code null} if none
111         */
112        public static LocaleContext getLocaleContext() {
113                LocaleContext localeContext = localeContextHolder.get();
114                if (localeContext == null) {
115                        localeContext = inheritableLocaleContextHolder.get();
116                }
117                return localeContext;
118        }
119
120        /**
121         * Associate the given Locale with the current thread,
122         * preserving any TimeZone that may have been set already.
123         * <p>Will implicitly create a LocaleContext for the given Locale,
124         * <i>not</i> exposing it as inheritable for child threads.
125         * @param locale the current Locale, or {@code null} to reset
126         * the locale part of thread-bound context
127         * @see #setTimeZone(TimeZone)
128         * @see SimpleLocaleContext#SimpleLocaleContext(Locale)
129         */
130        public static void setLocale(Locale locale) {
131                setLocale(locale, false);
132        }
133
134        /**
135         * Associate the given Locale with the current thread,
136         * preserving any TimeZone that may have been set already.
137         * <p>Will implicitly create a LocaleContext for the given Locale.
138         * @param locale the current Locale, or {@code null} to reset
139         * the locale part of thread-bound context
140         * @param inheritable whether to expose the LocaleContext as inheritable
141         * for child threads (using an {@link InheritableThreadLocal})
142         * @see #setTimeZone(TimeZone, boolean)
143         * @see SimpleLocaleContext#SimpleLocaleContext(Locale)
144         */
145        public static void setLocale(Locale locale, boolean inheritable) {
146                LocaleContext localeContext = getLocaleContext();
147                TimeZone timeZone = (localeContext instanceof TimeZoneAwareLocaleContext ?
148                                ((TimeZoneAwareLocaleContext) localeContext).getTimeZone() : null);
149                if (timeZone != null) {
150                        localeContext = new SimpleTimeZoneAwareLocaleContext(locale, timeZone);
151                }
152                else if (locale != null) {
153                        localeContext = new SimpleLocaleContext(locale);
154                }
155                else {
156                        localeContext = null;
157                }
158                setLocaleContext(localeContext, inheritable);
159        }
160
161        /**
162         * Set a shared default locale at the framework level,
163         * as an alternative to the JVM-wide default locale.
164         * <p><b>NOTE:</b> This can be useful to set an application-level
165         * default locale which differs from the JVM-wide default locale.
166         * However, this requires each such application to operate against
167         * locally deployed Spring Framework jars. Do not deploy Spring
168         * as a shared library at the server level in such a scenario!
169         * @param locale the default locale (or {@code null} for none,
170         * letting lookups fall back to {@link Locale#getDefault()})
171         * @since 4.3.5
172         * @see #getLocale()
173         * @see Locale#getDefault()
174         */
175        public static void setDefaultLocale(Locale locale) {
176                LocaleContextHolder.defaultLocale = locale;
177        }
178
179        /**
180         * Return the Locale associated with the current thread, if any,
181         * or the system default Locale otherwise. This is effectively a
182         * replacement for {@link java.util.Locale#getDefault()},
183         * able to optionally respect a user-level Locale setting.
184         * <p>Note: This method has a fallback to the shared default Locale,
185         * either at the framework level or at the JVM-wide system level.
186         * If you'd like to check for the raw LocaleContext content
187         * (which may indicate no specific locale through {@code null}, use
188         * {@link #getLocaleContext()} and call {@link LocaleContext#getLocale()}
189         * @return the current Locale, or the system default Locale if no
190         * specific Locale has been associated with the current thread
191         * @see LocaleContext#getLocale()
192         * @see #setDefaultLocale(Locale)
193         * @see java.util.Locale#getDefault()
194         */
195        public static Locale getLocale() {
196                LocaleContext localeContext = getLocaleContext();
197                if (localeContext != null) {
198                        Locale locale = localeContext.getLocale();
199                        if (locale != null) {
200                                return locale;
201                        }
202                }
203                return (defaultLocale != null ? defaultLocale : Locale.getDefault());
204        }
205
206        /**
207         * Associate the given TimeZone with the current thread,
208         * preserving any Locale that may have been set already.
209         * <p>Will implicitly create a LocaleContext for the given Locale,
210         * <i>not</i> exposing it as inheritable for child threads.
211         * @param timeZone the current TimeZone, or {@code null} to reset
212         * the time zone part of the thread-bound context
213         * @see #setLocale(Locale)
214         * @see SimpleTimeZoneAwareLocaleContext#SimpleTimeZoneAwareLocaleContext(Locale, TimeZone)
215         */
216        public static void setTimeZone(TimeZone timeZone) {
217                setTimeZone(timeZone, false);
218        }
219
220        /**
221         * Associate the given TimeZone with the current thread,
222         * preserving any Locale that may have been set already.
223         * <p>Will implicitly create a LocaleContext for the given Locale.
224         * @param timeZone the current TimeZone, or {@code null} to reset
225         * the time zone part of the thread-bound context
226         * @param inheritable whether to expose the LocaleContext as inheritable
227         * for child threads (using an {@link InheritableThreadLocal})
228         * @see #setLocale(Locale, boolean)
229         * @see SimpleTimeZoneAwareLocaleContext#SimpleTimeZoneAwareLocaleContext(Locale, TimeZone)
230         */
231        public static void setTimeZone(TimeZone timeZone, boolean inheritable) {
232                LocaleContext localeContext = getLocaleContext();
233                Locale locale = (localeContext != null ? localeContext.getLocale() : null);
234                if (timeZone != null) {
235                        localeContext = new SimpleTimeZoneAwareLocaleContext(locale, timeZone);
236                }
237                else if (locale != null) {
238                        localeContext = new SimpleLocaleContext(locale);
239                }
240                else {
241                        localeContext = null;
242                }
243                setLocaleContext(localeContext, inheritable);
244        }
245
246        /**
247         * Set a shared default time zone at the framework level,
248         * as an alternative to the JVM-wide default time zone.
249         * <p><b>NOTE:</b> This can be useful to set an application-level
250         * default time zone which differs from the JVM-wide default time zone.
251         * However, this requires each such application to operate against
252         * locally deployed Spring Framework jars. Do not deploy Spring
253         * as a shared library at the server level in such a scenario!
254         * @param timeZone the default time zone (or {@code null} for none,
255         * letting lookups fall back to {@link TimeZone#getDefault()})
256         * @since 4.3.5
257         * @see #getTimeZone()
258         * @see TimeZone#getDefault()
259         */
260        public static void setDefaultTimeZone(TimeZone timeZone) {
261                defaultTimeZone = timeZone;
262        }
263
264        /**
265         * Return the TimeZone associated with the current thread, if any,
266         * or the system default TimeZone otherwise. This is effectively a
267         * replacement for {@link java.util.TimeZone#getDefault()},
268         * able to optionally respect a user-level TimeZone setting.
269         * <p>Note: This method has a fallback to the shared default TimeZone,
270         * either at the framework level or at the JVM-wide system level.
271         * If you'd like to check for the raw LocaleContext content
272         * (which may indicate no specific time zone through {@code null}, use
273         * {@link #getLocaleContext()} and call {@link TimeZoneAwareLocaleContext#getTimeZone()}
274         * after downcasting to {@link TimeZoneAwareLocaleContext}.
275         * @return the current TimeZone, or the system default TimeZone if no
276         * specific TimeZone has been associated with the current thread
277         * @see TimeZoneAwareLocaleContext#getTimeZone()
278         * @see #setDefaultTimeZone(TimeZone)
279         * @see java.util.TimeZone#getDefault()
280         */
281        public static TimeZone getTimeZone() {
282                LocaleContext localeContext = getLocaleContext();
283                if (localeContext instanceof TimeZoneAwareLocaleContext) {
284                        TimeZone timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
285                        if (timeZone != null) {
286                                return timeZone;
287                        }
288                }
289                return (defaultTimeZone != null ? defaultTimeZone : TimeZone.getDefault());
290        }
291
292}