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