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}