diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/TimeZoneEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/TimeZoneEditor.java index 6ee9089039..4c287a0ee8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/TimeZoneEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/TimeZoneEditor.java @@ -19,6 +19,8 @@ package org.springframework.beans.propertyeditors; import java.beans.PropertyEditorSupport; import java.util.TimeZone; +import org.springframework.util.StringUtils; + /** * Editor for {@code java.util.TimeZone}, translating timezone IDs into * {@code TimeZone} objects. Exposes the {@code TimeZone} ID as a text @@ -33,7 +35,7 @@ public class TimeZoneEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { - setValue(TimeZone.getTimeZone(text)); + setValue(StringUtils.parseTimeZoneString(text)); } @Override diff --git a/spring-beans/src/test/java/org/springframework/beans/propertyeditors/ZoneIdEditorTests.java b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/ZoneIdEditorTests.java new file mode 100644 index 0000000000..b819fb9679 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/propertyeditors/ZoneIdEditorTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.propertyeditors; + +import java.time.ZoneId; + +import junit.framework.TestCase; + +/** + * @author Nicholas Williams + */ +public class ZoneIdEditorTests extends TestCase { + + public void testAmericaChicago() { + ZoneIdEditor editor = new ZoneIdEditor(); + editor.setAsText("America/Chicago"); + + ZoneId zoneId = (ZoneId) editor.getValue(); + assertNotNull("The zone ID should not be null.", zoneId); + assertEquals("The zone ID is not correct.", ZoneId.of("America/Chicago"), zoneId); + + assertEquals("The text version is not correct.", "America/Chicago", editor.getAsText()); + } + + public void testAmericaLosAngeles() { + ZoneIdEditor editor = new ZoneIdEditor(); + editor.setAsText("America/Los_Angeles"); + + ZoneId zoneId = (ZoneId) editor.getValue(); + assertNotNull("The zone ID should not be null.", zoneId); + assertEquals("The zone ID is not correct.", ZoneId.of("America/Los_Angeles"), zoneId); + + assertEquals("The text version is not correct.", "America/Los_Angeles", editor.getAsText()); + } + + public void testGetNullAsText() { + ZoneIdEditor editor = new ZoneIdEditor(); + + assertEquals("The returned value is not correct.", "", editor.getAsText()); + } + + public void testGetValueAsText() { + ZoneIdEditor editor = new ZoneIdEditor(); + editor.setValue(ZoneId.of("America/New_York")); + + assertEquals("The text version is not correct.", "America/New_York", editor.getAsText()); + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/i18n/LocaleContext.java b/spring-context/src/main/java/org/springframework/context/i18n/LocaleContext.java index c746151b28..6ac813b527 100644 --- a/spring-context/src/main/java/org/springframework/context/i18n/LocaleContext.java +++ b/spring-context/src/main/java/org/springframework/context/i18n/LocaleContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2005 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,14 +26,15 @@ import java.util.Locale; * * @author Juergen Hoeller * @since 1.2 - * @see LocaleContextHolder - * @see java.util.Locale + * @see LocaleContextHolder#getLocale() + * @see TimeZoneAwareLocaleContext */ public interface LocaleContext { /** * Return the current Locale, which can be fixed or determined dynamically, * depending on the implementation strategy. + * @return the current Locale, or {@code null} if no specific Locale associated */ Locale getLocale(); diff --git a/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java b/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java index 2ca6ea2f85..7c41f82a00 100644 --- a/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java +++ b/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.context.i18n; import java.util.Locale; +import java.util.TimeZone; import org.springframework.core.NamedInheritableThreadLocal; import org.springframework.core.NamedThreadLocal; @@ -59,7 +60,11 @@ public abstract class LocaleContextHolder { /** * Associate the given LocaleContext with the current thread, * not exposing it as inheritable for child threads. + *
The given LocaleContext may be a {@link TimeZoneAwareLocaleContext}, + * containing a locale with associated time zone information. * @param localeContext the current LocaleContext + * @see SimpleLocaleContext + * @see SimpleTimeZoneAwareLocaleContext */ public static void setLocaleContext(LocaleContext localeContext) { setLocaleContext(localeContext, false); @@ -67,10 +72,14 @@ public abstract class LocaleContextHolder { /** * Associate the given LocaleContext with the current thread. + *
The given LocaleContext may be a {@link TimeZoneAwareLocaleContext}, + * containing a locale with associated time zone information. * @param localeContext the current LocaleContext, * or {@code null} to reset the thread-bound context * @param inheritable whether to expose the LocaleContext as inheritable * for child threads (using an {@link InheritableThreadLocal}) + * @see SimpleLocaleContext + * @see SimpleTimeZoneAwareLocaleContext */ public static void setLocaleContext(LocaleContext localeContext, boolean inheritable) { if (localeContext == null) { @@ -128,7 +137,13 @@ public abstract class LocaleContextHolder { /** * Return the Locale associated with the current thread, if any, - * or the system default Locale else. + * or the system default Locale else. This is effectively a + * replacement for {@link java.util.Locale#getDefault()}, + * able to optionally respect a user-level Locale setting. + *
Note: This method has a fallback to the system default Locale. + * If you'd like to check for the raw LocaleContext content + * (which may indicate no specific locale through {@code null}, use + * {@link #getLocaleContext()} and call {@link LocaleContext#getLocale()} * @return the current Locale, or the system default Locale if no * specific Locale has been associated with the current thread * @see LocaleContext#getLocale() @@ -136,7 +151,39 @@ public abstract class LocaleContextHolder { */ public static Locale getLocale() { LocaleContext localeContext = getLocaleContext(); - return (localeContext != null ? localeContext.getLocale() : Locale.getDefault()); + if (localeContext != null) { + Locale locale = localeContext.getLocale(); + if (locale != null) { + return locale; + } + } + return Locale.getDefault(); + } + + /** + * Return the TimeZone associated with the current thread, if any, + * or the system default TimeZone else. This is effectively a + * replacement for {@link java.util.TimeZone#getDefault()}, + * able to optionally respect a user-level TimeZone setting. + *
Note: This method has a fallback to the system default Locale. + * If you'd like to check for the raw LocaleContext content + * (which may indicate no specific time zone through {@code null}, use + * {@link #getLocaleContext()} and call {@link TimeZoneAwareLocaleContext#getTimeZone()} + * after downcasting to {@link TimeZoneAwareLocaleContext}. + * @return the current TimeZone, or the system default TimeZone if no + * specific TimeZone has been associated with the current thread + * @see TimeZoneAwareLocaleContext#getTimeZone() + * @see java.util.TimeZone#getDefault() + */ + public static TimeZone getTimeZone() { + LocaleContext localeContext = getLocaleContext(); + if (localeContext instanceof TimeZoneAwareLocaleContext) { + TimeZone timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone(); + if (timeZone != null) { + return timeZone; + } + } + return TimeZone.getDefault(); } } diff --git a/spring-context/src/main/java/org/springframework/context/i18n/SimpleLocaleContext.java b/spring-context/src/main/java/org/springframework/context/i18n/SimpleLocaleContext.java index de1246bf0b..e189d9e5d0 100644 --- a/spring-context/src/main/java/org/springframework/context/i18n/SimpleLocaleContext.java +++ b/spring-context/src/main/java/org/springframework/context/i18n/SimpleLocaleContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,14 +18,15 @@ package org.springframework.context.i18n; import java.util.Locale; -import org.springframework.util.Assert; - /** * Simple implementation of the {@link LocaleContext} interface, * always returning a specified {@code Locale}. * * @author Juergen Hoeller * @since 1.2 + * @see LocaleContextHolder#setLocaleContext + * @see LocaleContextHolder#getLocale() + * @see SimpleTimeZoneAwareLocaleContext */ public class SimpleLocaleContext implements LocaleContext { @@ -34,11 +35,10 @@ public class SimpleLocaleContext implements LocaleContext { /** * Create a new SimpleLocaleContext that exposes the specified Locale. - * Every {@code getLocale()} will return this Locale. + * Every {@link #getLocale()} call will return this Locale. * @param locale the Locale to expose */ public SimpleLocaleContext(Locale locale) { - Assert.notNull(locale, "Locale must not be null"); this.locale = locale; } diff --git a/spring-context/src/main/java/org/springframework/context/i18n/SimpleTimeZoneAwareLocaleContext.java b/spring-context/src/main/java/org/springframework/context/i18n/SimpleTimeZoneAwareLocaleContext.java new file mode 100644 index 0000000000..a14db33b0d --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/i18n/SimpleTimeZoneAwareLocaleContext.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.i18n; + +import java.util.Locale; +import java.util.TimeZone; + +/** + * Simple implementation of the {@link TimeZoneAwareLocaleContext} interface, + * always returning a specified {@code Locale} and {@code TimeZone}. + * + *
Note: Prefer the use of {@link SimpleLocaleContext} when only setting + * a Locale but no TimeZone. + * + * @author Juergen Hoeller + * @since 4.0 + * @see LocaleContextHolder#setLocaleContext + * @see LocaleContextHolder#getTimeZone() + */ +public class SimpleTimeZoneAwareLocaleContext extends SimpleLocaleContext implements TimeZoneAwareLocaleContext { + + private final TimeZone timeZone; + + + /** + * Create a new SimpleTimeZoneAwareLocaleContext that exposes the specified + * Locale and TimeZone. Every {@link #getLocale()} call will return the given + * Locale, and every {@link #getTimeZone()} call will return the given TimeZone. + * @param locale the Locale to expose + * @param timeZone the TimeZone to expose + */ + public SimpleTimeZoneAwareLocaleContext(Locale locale, TimeZone timeZone) { + super(locale); + this.timeZone = timeZone; + } + + + public TimeZone getTimeZone() { + return this.timeZone; + } + + @Override + public String toString() { + return super.toString() + " " + this.timeZone.toString(); + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/i18n/TimeZoneAwareLocaleContext.java b/spring-context/src/main/java/org/springframework/context/i18n/TimeZoneAwareLocaleContext.java new file mode 100644 index 0000000000..a25a77c035 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/i18n/TimeZoneAwareLocaleContext.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.i18n; + +import java.util.TimeZone; + +/** + * Extension of {@link LocaleContext}, adding awareness of the current time zone. + * + *
Having this variant of LocaleContext set to {@link LocaleContextHolder} means + * that some TimeZone-aware infrastructure has been configured, even if it may not + * be able to produce a non-null TimeZone at the moment. + * + * @author Juergen Hoeller + * @since 4.0 + * @see LocaleContextHolder#getTimeZone() + */ +public interface TimeZoneAwareLocaleContext extends LocaleContext { + + /** + * Return the current TimeZone, which can be fixed or determined dynamically, + * depending on the implementation strategy. + * @return the current TimeZone, or {@code null} if no specific TimeZone associated + */ + TimeZone getTimeZone(); + +} diff --git a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeContext.java b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeContext.java index e4db4a2557..b5c2766c7a 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeContext.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeContext.java @@ -16,10 +16,16 @@ package org.springframework.format.datetime.joda; +import java.util.TimeZone; + import org.joda.time.Chronology; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormatter; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.context.i18n.TimeZoneAwareLocaleContext; + /** * A context that holds user-specific Joda-Time settings such as the user's * Chronology (calendar system) and time zone. @@ -53,6 +59,11 @@ public class JodaTimeContext { /** * Set the user's time zone. + *
Alternatively, set a {@link TimeZoneAwareLocaleContext} on
+ * {@link LocaleContextHolder}. This context class will fall back to
+ * checking the locale context if no setting has been provided here.
+ * @see org.springframework.context.i18n.LocaleContextHolder#getTimeZone()
+ * @see org.springframework.context.i18n.LocaleContextHolder#setLocaleContext
*/
public void setTimeZone(DateTimeZone timeZone) {
this.timeZone = timeZone;
@@ -80,6 +91,15 @@ public class JodaTimeContext {
if (this.timeZone != null) {
formatter = formatter.withZone(this.timeZone);
}
+ else {
+ LocaleContext localeContext = LocaleContextHolder.getLocaleContext();
+ if (localeContext instanceof TimeZoneAwareLocaleContext) {
+ TimeZone timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
+ if (timeZone != null) {
+ formatter = formatter.withZone(DateTimeZone.forTimeZone(timeZone));
+ }
+ }
+ }
return formatter;
}
diff --git a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContext.java b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContext.java
index ec8db53ecc..1d165ce923 100644
--- a/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContext.java
+++ b/spring-context/src/main/java/org/springframework/format/datetime/standard/DateTimeContext.java
@@ -19,6 +19,11 @@ package org.springframework.format.datetime.standard;
import java.time.ZoneId;
import java.time.chrono.Chronology;
import java.time.format.DateTimeFormatter;
+import java.util.TimeZone;
+
+import org.springframework.context.i18n.LocaleContext;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
/**
* A context that holds user-specific java.time (JSR-310) settings
@@ -52,6 +57,11 @@ public class DateTimeContext {
/**
* Set the user's time zone.
+ *
Alternatively, set a {@link TimeZoneAwareLocaleContext} on + * {@link LocaleContextHolder}. This context class will fall back to + * checking the locale context if no setting has been provided here. + * @see org.springframework.context.i18n.LocaleContextHolder#getTimeZone() + * @see org.springframework.context.i18n.LocaleContextHolder#setLocaleContext */ public void setTimeZone(ZoneId timeZone) { this.timeZone = timeZone; @@ -79,6 +89,15 @@ public class DateTimeContext { if (this.timeZone != null) { formatter = formatter.withZone(this.timeZone); } + else { + LocaleContext localeContext = LocaleContextHolder.getLocaleContext(); + if (localeContext instanceof TimeZoneAwareLocaleContext) { + TimeZone timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone(); + if (timeZone != null) { + formatter = formatter.withZone(timeZone.toZoneId()); + } + } + } return formatter; } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java index feba52848b..26d8150b79 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java @@ -42,6 +42,7 @@ import org.springframework.scheduling.support.ScheduledMethodRunnable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; +import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; /** @@ -192,11 +193,7 @@ public class ScheduledAnnotationBeanPostProcessor } TimeZone timeZone; if (!"".equals(zone)) { - timeZone = TimeZone.getTimeZone(zone); - // Check for that silly TimeZone fallback... - if ("GMT".equals(timeZone.getID()) && !zone.startsWith("GMT")) { - throw new IllegalArgumentException("Invalid time zone id '" + zone + "'"); - } + timeZone = StringUtils.parseTimeZoneString(zone); } else { timeZone = TimeZone.getDefault(); diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java index 8ef74e18fc..3daea6307e 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.util.UUID; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.ConverterRegistry; +import org.springframework.util.ClassUtils; /** * A specialization of {@link GenericConversionService} configured by default with @@ -31,10 +32,16 @@ import org.springframework.core.convert.converter.ConverterRegistry; * {@code ConverterRegistry} instance. * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 */ public class DefaultConversionService extends GenericConversionService { + /** Java 8's java.time package available? */ + private static final boolean zoneIdAvailable = + ClassUtils.isPresent("java.time.ZoneId", DefaultConversionService.class.getClassLoader()); + + /** * Create a new {@code DefaultConversionService} with the set of * {@linkplain DefaultConversionService#addDefaultConverters(ConverterRegistry) default converters}. @@ -43,46 +50,55 @@ public class DefaultConversionService extends GenericConversionService { addDefaultConverters(this); } + // static utility methods /** * Add converters appropriate for most environments. - * @param converterRegistry the registry of converters to add to (must also be castable to ConversionService) - * @throws ClassCastException if the converterRegistry could not be cast to a ConversionService + * @param converterRegistry the registry of converters to add to (must also be castable to ConversionService, + * e.g. being a {@link ConfigurableConversionService}) + * @throws ClassCastException if the given ConverterRegistry could not be cast to a ConversionService */ public static void addDefaultConverters(ConverterRegistry converterRegistry) { addScalarConverters(converterRegistry); addCollectionConverters(converterRegistry); - addBinaryConverters(converterRegistry); - addFallbackConverters(converterRegistry); + + converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry)); + if (zoneIdAvailable) { + ZoneIdConverterRegistrar.registerZoneIdConverters(converterRegistry); + } + + converterRegistry.addConverter(new ObjectToObjectConverter()); + converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry)); + converterRegistry.addConverter(new FallbackObjectToStringConverter()); } // internal helpers private static void addScalarConverters(ConverterRegistry converterRegistry) { - ConversionService conversionService = (ConversionService) converterRegistry; - converterRegistry.addConverter(new StringToBooleanConverter()); - converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter()); + converterRegistry.addConverterFactory(new NumberToNumberConverterFactory()); converterRegistry.addConverterFactory(new StringToNumberConverterFactory()); converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter()); - converterRegistry.addConverterFactory(new NumberToNumberConverterFactory()); - converterRegistry.addConverter(new StringToCharacterConverter()); converterRegistry.addConverter(Character.class, String.class, new ObjectToStringConverter()); converterRegistry.addConverter(new NumberToCharacterConverter()); converterRegistry.addConverterFactory(new CharacterToNumberFactory()); + converterRegistry.addConverter(new StringToBooleanConverter()); + converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter()); + converterRegistry.addConverterFactory(new StringToEnumConverterFactory()); - converterRegistry.addConverter(Enum.class, String.class, new EnumToStringConverter(conversionService)); + converterRegistry.addConverter(Enum.class, String.class, + new EnumToStringConverter((ConversionService) converterRegistry)); converterRegistry.addConverter(new StringToLocaleConverter()); converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter()); - converterRegistry.addConverter(new PropertiesToStringConverter()); converterRegistry.addConverter(new StringToPropertiesConverter()); + converterRegistry.addConverter(new PropertiesToStringConverter()); converterRegistry.addConverter(new StringToUUIDConverter()); converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter()); @@ -90,6 +106,7 @@ public class DefaultConversionService extends GenericConversionService { private static void addCollectionConverters(ConverterRegistry converterRegistry) { ConversionService conversionService = (ConversionService) converterRegistry; + converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService)); converterRegistry.addConverter(new CollectionToArrayConverter(conversionService)); @@ -110,16 +127,16 @@ public class DefaultConversionService extends GenericConversionService { converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService)); } - private static void addBinaryConverters(ConverterRegistry converterRegistry) { - ConversionService conversionService = (ConversionService) converterRegistry; - converterRegistry.addConverter(new ByteBufferConverter(conversionService)); - } - private static void addFallbackConverters(ConverterRegistry converterRegistry) { - ConversionService conversionService = (ConversionService) converterRegistry; - converterRegistry.addConverter(new ObjectToObjectConverter()); - converterRegistry.addConverter(new IdToEntityConverter(conversionService)); - converterRegistry.addConverter(new FallbackObjectToStringConverter()); + /** + * Inner class to avoid a hard-coded dependency on Java 8's {@link java.time.ZoneId}. + */ + private static final class ZoneIdConverterRegistrar { + + public static void registerZoneIdConverters(ConverterRegistry converterRegistry) { + converterRegistry.addConverter(new TimeZoneToZoneIdConverter()); + converterRegistry.addConverter(new ZoneIdToTimeZoneConverter()); + } } } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/TimeZoneToZoneIdConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/TimeZoneToZoneIdConverter.java new file mode 100644 index 0000000000..991ee4a490 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/convert/support/TimeZoneToZoneIdConverter.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.convert.support; + +import java.time.ZoneId; +import java.util.TimeZone; + +import org.springframework.core.convert.converter.Converter; + +/** + * Simple Converter from {@link java.util.TimeZone} to Java 8's {@link java.time.ZoneId}. + * + *
Note that Spring's default ConversionService setup understands the 'of' convention that
+ * the JSR-310 {@code java.time} package consistently uses. That convention is implemented
+ * reflectively in {@link ObjectToObjectConverter}, not in specific JSR-310 converters.
+ *
+ * @author Juergen Hoeller
+ * @since 4.0
+ * @see ZoneIdToTimeZoneConverter
+ */
+class TimeZoneToZoneIdConverter implements Converter Note that Spring's default ConversionService setup understands the 'of' convention that
+ * the JSR-310 {@code java.time} package consistently uses. That convention is implemented
+ * reflectively in {@link ObjectToObjectConverter}, not in specific JSR-310 converters.
+ *
+ * @author Juergen Hoeller
+ * @since 4.0
+ * @see TimeZoneToZoneIdConverter
+ */
+class ZoneIdToTimeZoneConverter implements Converter This is the inverse operation of {@link Locale#toString Locale's toString}.
- * @param localeString the locale string, following {@code Locale's}
+ * @param localeString the locale String, following {@code Locale's}
* {@code toString()} format ("en", "en_UK", etc);
* also accepts spaces as separators, as an alternative to underscores
* @return a corresponding {@code Locale} instance
+ * @throws IllegalArgumentException in case of an invalid locale specification
*/
public static Locale parseLocaleString(String localeString) {
String[] parts = tokenizeToStringArray(localeString, "_ ", false, false);
@@ -719,6 +721,22 @@ public abstract class StringUtils {
return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : "");
}
+ /**
+ * Parse the given {@code timeZoneString} value into a {@link TimeZone}.
+ * @param timeZoneString the time zone String, following {@link TimeZone#getTimeZone(String)}
+ * but throwing {@link IllegalArgumentException} in case of an invalid time zone specification
+ * @return a corresponding {@link TimeZone} instance
+ * @throws IllegalArgumentException in case of an invalid time zone specification
+ */
+ public static TimeZone parseTimeZoneString(String timeZoneString) {
+ TimeZone timeZone = TimeZone.getTimeZone(timeZoneString);
+ if ("GMT".equals(timeZone.getID()) && !timeZoneString.startsWith("GMT")) {
+ // We don't want that GMT fallback...
+ throw new IllegalArgumentException("Invalid time zone specification '" + timeZoneString + "'");
+ }
+ return timeZone;
+ }
+
//---------------------------------------------------------------------
// Convenience methods for working with String arrays
diff --git a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java
index 5387b8d830..222d63c989 100644
--- a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java
+++ b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/HttpComponentsHttpInvokerRequestExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.remoting.httpinvoker;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Locale;
import java.util.zip.GZIPInputStream;
import org.apache.http.Header;
@@ -170,9 +171,12 @@ public class HttpComponentsHttpInvokerRequestExecutor extends AbstractHttpInvoke
*/
protected HttpPost createHttpPost(HttpInvokerClientConfiguration config) throws IOException {
HttpPost httpPost = new HttpPost(config.getServiceUrl());
- LocaleContext locale = LocaleContextHolder.getLocaleContext();
- if (locale != null) {
- httpPost.addHeader(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale()));
+ LocaleContext localeContext = LocaleContextHolder.getLocaleContext();
+ if (localeContext != null) {
+ Locale locale = localeContext.getLocale();
+ if (locale != null) {
+ httpPost.addHeader(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale));
+ }
}
if (isAcceptGzipEncoding()) {
httpPost.addHeader(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
diff --git a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java
index 216b6d8112..c65f7ee02b 100644
--- a/spring-web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java
+++ b/spring-web/src/main/java/org/springframework/remoting/httpinvoker/SimpleHttpInvokerRequestExecutor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
+import java.util.Locale;
import java.util.zip.GZIPInputStream;
import org.springframework.context.i18n.LocaleContext;
@@ -40,7 +41,6 @@ import org.springframework.util.StringUtils;
*
* @author Juergen Hoeller
* @since 1.1
- * @see CommonsHttpInvokerRequestExecutor
* @see java.net.HttpURLConnection
*/
public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequestExecutor {
@@ -133,9 +133,13 @@ public class SimpleHttpInvokerRequestExecutor extends AbstractHttpInvokerRequest
connection.setRequestMethod(HTTP_METHOD_POST);
connection.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, getContentType());
connection.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength));
- LocaleContext locale = LocaleContextHolder.getLocaleContext();
- if (locale != null) {
- connection.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale.getLocale()));
+
+ LocaleContext localeContext = LocaleContextHolder.getLocaleContext();
+ if (localeContext != null) {
+ Locale locale = localeContext.getLocale();
+ if (locale != null) {
+ connection.setRequestProperty(HTTP_HEADER_ACCEPT_LANGUAGE, StringUtils.toLanguageTag(locale));
+ }
}
if (isAcceptGzipEncoding()) {
connection.setRequestProperty(HTTP_HEADER_ACCEPT_ENCODING, ENCODING_GZIP);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
index 4baff7978b..665b2a2a2e 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
@@ -28,7 +28,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
-
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -36,6 +35,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -1042,16 +1042,17 @@ public class DispatcherServlet extends FrameworkServlet {
*/
@Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
- return new LocaleContext() {
- @Override
- public Locale getLocale() {
- return localeResolver.resolveLocale(request);
- }
- @Override
- public String toString() {
- return getLocale().toString();
- }
- };
+ if (this.localeResolver instanceof LocaleContextResolver) {
+ return ((LocaleContextResolver) this.localeResolver).resolveLocaleContext(request);
+ }
+ else {
+ return new LocaleContext() {
+ @Override
+ public Locale getLocale() {
+ return localeResolver.resolveLocale(request);
+ }
+ };
+ }
}
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleContextResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleContextResolver.java
new file mode 100644
index 0000000000..ffa7b24f7f
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleContextResolver.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet;
+
+import java.util.Locale;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.context.i18n.LocaleContext;
+
+/**
+ * Extension of {@link LocaleResolver}, adding support for a rich locale context
+ * (potentially including locale and time zone information).
+ *
+ * @author Juergen Hoeller
+ * @since 4.0
+ * @see org.springframework.context.i18n.LocaleContext
+ * @see org.springframework.context.i18n.TimeZoneAwareLocaleContext
+ * @see org.springframework.context.i18n.LocaleContextHolder
+ * @see org.springframework.web.servlet.support.RequestContext#getTimeZone
+ * @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
+ */
+public interface LocaleContextResolver extends LocaleResolver {
+
+ /**
+ * Resolve the current locale context via the given request.
+ * This is primarily intended for framework-level processing; consider using
+ * {@link org.springframework.web.servlet.support.RequestContextUtils} or
+ * {@link org.springframework.web.servlet.support.RequestContext} for
+ * application-level access to the current locale and/or time zone.
+ * The returned context may be a
+ * {@link org.springframework.context.i18n.TimeZoneAwareLocaleContext},
+ * containing a locale with associated time zone information.
+ * Simply apply an {@code instanceof} check and downcast accordingly.
+ * Custom resolver implementations may also return extra settings in
+ * the returned context, which again can be accessed through downcasting.
+ * @param request the request to resolve the locale context for
+ * @return the current locale context (never {@code null}
+ * @see #resolveLocale(HttpServletRequest)
+ * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
+ * @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
+ */
+ LocaleContext resolveLocaleContext(HttpServletRequest request);
+
+ /**
+ * Set the current locale context to the given one,
+ * potentially including a locale with associated time zone information.
+ * @param request the request to be used for locale modification
+ * @param response the response to be used for locale modification
+ * @param localeContext the new locale context, or {@code null} to clear the locale
+ * @throws UnsupportedOperationException if the LocaleResolver implementation
+ * does not support dynamic changing of the locale or time zone
+ * @see #setLocale(HttpServletRequest, HttpServletResponse, Locale)
+ * @see org.springframework.context.i18n.SimpleLocaleContext
+ * @see org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext
+ */
+ void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext);
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java
index 380bdfd397..3d8258841d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/LocaleResolver.java
@@ -34,8 +34,19 @@ import javax.servlet.http.HttpServletResponse;
* to retrieve the current locale in controllers or views, independent
* of the actual resolution strategy.
*
+ * Note: As of Spring 4.0, there is an extended strategy interface
+ * called {@link LocaleContextResolver}, allowing for resolution of
+ * a {@link org.springframework.context.i18n.LocaleContext} object,
+ * potentially including associated time zone information. Spring's
+ * provided resolver implementations implement the extended
+ * {@link LocaleContextResolver} interface wherever appropriate.
+ *
* @author Juergen Hoeller
* @since 27.02.2003
+ * @see LocaleContextResolver
+ * @see org.springframework.context.i18n.LocaleContextHolder
+ * @see org.springframework.web.servlet.support.RequestContext#getLocale
+ * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
*/
public interface LocaleResolver {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleContextResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleContextResolver.java
new file mode 100644
index 0000000000..f3ab7a1f4b
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleContextResolver.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.i18n;
+
+import java.util.Locale;
+import java.util.TimeZone;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.context.i18n.SimpleLocaleContext;
+import org.springframework.web.servlet.LocaleContextResolver;
+
+/**
+ * Abstract base class for {@link LocaleContextResolver} implementations.
+ * Provides support for a default locale and a default time zone.
+ *
+ * Also provides pre-implemented versions of {@link #resolveLocale} and {@link #setLocale},
+ * delegating to {@link #resolveLocaleContext} and {@link #setLocaleContext}.
+ *
+ * @author Juergen Hoeller
+ * @since 4.0
+ * @see #setDefaultLocale
+ * @see #setDefaultTimeZone
+ */
+public abstract class AbstractLocaleContextResolver extends AbstractLocaleResolver implements LocaleContextResolver {
+
+ private TimeZone defaultTimeZone;
+
+
+ /**
+ * Set a default TimeZone that this resolver will return if no other time zone found.
+ */
+ public void setDefaultTimeZone(TimeZone defaultTimeZone) {
+ this.defaultTimeZone = defaultTimeZone;
+ }
+
+ /**
+ * Return the default TimeZone that this resolver is supposed to fall back to, if any.
+ */
+ public TimeZone getDefaultTimeZone() {
+ return this.defaultTimeZone;
+ }
+
+
+ @Override
+ public Locale resolveLocale(HttpServletRequest request) {
+ return resolveLocaleContext(request).getLocale();
+ }
+
+ @Override
+ public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
+ setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null));
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleResolver.java
index 885688036f..c5fa0d7ba4 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AbstractLocaleResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2007 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import org.springframework.web.servlet.LocaleResolver;
*
* @author Juergen Hoeller
* @since 1.2.9
+ * @see #setDefaultLocale
*/
public abstract class AbstractLocaleResolver implements LocaleResolver {
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
index 007090ad17..4bf43e2845 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,16 @@
package org.springframework.web.servlet.i18n;
import java.util.Locale;
-
+import java.util.TimeZone;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.springframework.context.i18n.LocaleContext;
+import org.springframework.context.i18n.SimpleLocaleContext;
+import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.LocaleContextResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.util.CookieGenerator;
import org.springframework.web.util.WebUtils;
@@ -33,29 +37,44 @@ import org.springframework.web.util.WebUtils;
* or the request's accept-header locale.
*
* This is particularly useful for stateless applications without user sessions.
+ * The cookie may optionally contain an associated time zone value as well;
+ * alternatively, you may specify a default time zone.
*
- * Custom controllers can thus override the user's locale by calling
- * {@link #setLocale(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.util.Locale)},
- * for example responding to a certain locale change request.
+ * Custom controllers can override the user's locale and time zone by calling
+ * {@code #setLocale(Context)} on the resolver, e.g. responding to a locale change
+ * request. As a more convenient alternative, consider using
+ * {@link org.springframework.web.servlet.support.RequestContext#changeLocale}.
*
* @author Juergen Hoeller
* @author Jean-Pierre Pawlak
* @since 27.02.2003
* @see #setDefaultLocale
- * @see #setLocale
+ * @see #setDefaultTimeZone
*/
-public class CookieLocaleResolver extends CookieGenerator implements LocaleResolver {
+public class CookieLocaleResolver extends CookieGenerator implements LocaleContextResolver {
/**
- * The name of the request attribute that holds the locale.
+ * The name of the request attribute that holds the Locale.
* Only used for overriding a cookie value if the locale has been
- * changed in the course of the current request! Use
- * {@link org.springframework.web.servlet.support.RequestContext#getLocale}
+ * changed in the course of the current request!
+ * Use {@code RequestContext(Utils).getLocale()}
* to retrieve the current locale in controllers or views.
* @see org.springframework.web.servlet.support.RequestContext#getLocale
+ * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
*/
public static final String LOCALE_REQUEST_ATTRIBUTE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE";
+ /**
+ * The name of the request attribute that holds the TimeZone.
+ * Only used for overriding a cookie value if the locale has been
+ * changed in the course of the current request!
+ * Use {@code RequestContext(Utils).getTimeZone()}
+ * to retrieve the current time zone in controllers or views.
+ * @see org.springframework.web.servlet.support.RequestContext#getTimeZone
+ * @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
+ */
+ public static final String TIME_ZONE_REQUEST_ATTRIBUTE_NAME = CookieLocaleResolver.class.getName() + ".TIME_ZONE";
+
/**
* The default cookie name used if none is explicitly set.
*/
@@ -64,9 +83,11 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleResol
private Locale defaultLocale;
+ private TimeZone defaultTimeZone;
+
/**
- * Creates a new instance of the {@link CookieLocaleResolver} class
+ * Create a new instance of the {@link CookieLocaleResolver} class
* using the {@link #DEFAULT_COOKIE_NAME default cookie name}.
*/
public CookieLocaleResolver() {
@@ -88,45 +109,100 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleResol
return this.defaultLocale;
}
+ /**
+ * Set a fixed TimeZone that this resolver will return if no cookie found.
+ */
+ public void setDefaultTimeZone(TimeZone defaultTimeZone) {
+ this.defaultTimeZone = defaultTimeZone;
+ }
+
+ /**
+ * Return the fixed TimeZone that this resolver will return if no cookie found,
+ * if any.
+ */
+ protected TimeZone getDefaultTimeZone() {
+ return this.defaultTimeZone;
+ }
+
@Override
public Locale resolveLocale(HttpServletRequest request) {
- // Check request for pre-parsed or preset locale.
- Locale locale = (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
- if (locale != null) {
- return locale;
- }
+ parseLocaleCookieIfNecessary(request);
+ return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
+ }
- // Retrieve and parse cookie value.
- Cookie cookie = WebUtils.getCookie(request, getCookieName());
- if (cookie != null) {
- locale = StringUtils.parseLocaleString(cookie.getValue());
- if (logger.isDebugEnabled()) {
- logger.debug("Parsed cookie value [" + cookie.getValue() + "] into locale '" + locale + "'");
+ @Override
+ public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
+ parseLocaleCookieIfNecessary(request);
+ return new TimeZoneAwareLocaleContext() {
+ @Override
+ public Locale getLocale() {
+ return (Locale) request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME);
}
- if (locale != null) {
- request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
- return locale;
+ @Override
+ public TimeZone getTimeZone() {
+ return (TimeZone) request.getAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME);
}
- }
+ };
+ }
- return determineDefaultLocale(request);
+ private void parseLocaleCookieIfNecessary(HttpServletRequest request) {
+ if (request.getAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME) == null) {
+ // Retrieve and parse cookie value.
+ Cookie cookie = WebUtils.getCookie(request, getCookieName());
+ Locale locale = null;
+ TimeZone timeZone = null;
+ if (cookie != null) {
+ String value = cookie.getValue();
+ String localePart = value;
+ String timeZonePart = null;
+ int spaceIndex = localePart.indexOf(' ');
+ if (spaceIndex != -1) {
+ localePart = value.substring(0, spaceIndex);
+ timeZonePart = value.substring(spaceIndex + 1);
+ }
+ locale = (!"-".equals(localePart) ? StringUtils.parseLocaleString(localePart) : null);
+ if (timeZonePart != null) {
+ timeZone = StringUtils.parseTimeZoneString(timeZonePart);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Parsed cookie value [" + cookie.getValue() + "] into locale '" + locale +
+ "'" + (timeZone != null ? " and time zone '" + timeZone.getID() + "'" : ""));
+ }
+ }
+ request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
+ (locale != null ? locale: determineDefaultLocale(request)));
+ request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
+ (timeZone != null ? timeZone : determineDefaultTimeZone(request)));
+ }
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
- if (locale != null) {
- // Set request attribute and add cookie.
- request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, locale);
- addCookie(response, locale.toString());
+ setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null));
+ }
+
+ @Override
+ public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
+ Locale locale = null;
+ TimeZone timeZone = null;
+ if (localeContext != null) {
+ locale = localeContext.getLocale();
+ if (localeContext instanceof TimeZoneAwareLocaleContext) {
+ timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
+ }
+ addCookie(response, (locale != null ? locale : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
}
else {
- // Set request attribute to fallback locale and remove cookie.
- request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, determineDefaultLocale(request));
removeCookie(response);
}
+ request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
+ (locale != null ? locale: determineDefaultLocale(request)));
+ request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
+ (timeZone != null ? timeZone : determineDefaultTimeZone(request)));
}
+
/**
* Determine the default locale for the given request,
* Called if no locale cookie has been found.
@@ -145,4 +221,17 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleResol
return defaultLocale;
}
+ /**
+ * Determine the default time zone for the given request,
+ * Called if no TimeZone cookie has been found.
+ * The default implementation returns the specified default time zone,
+ * if any, or {@code null} otherwise.
+ * @param request the request to resolve the time zone for
+ * @return the default time zone (or {@code null} if none defined)
+ * @see #setDefaultTimeZone
+ */
+ protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {
+ return getDefaultTimeZone();
+ }
+
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/FixedLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/FixedLocaleResolver.java
index e7397c0a1d..618438327d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/FixedLocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/FixedLocaleResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,30 +17,36 @@
package org.springframework.web.servlet.i18n;
import java.util.Locale;
-
+import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.springframework.context.i18n.LocaleContext;
+import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
+
/**
* {@link org.springframework.web.servlet.LocaleResolver} implementation
- * that always returns a fixed default locale. Default is the current
- * JVM's default locale.
+ * that always returns a fixed default locale and optionally time zone.
+ * Default is the current JVM's default locale.
*
- * Note: Does not support {@code setLocale}, as the fixed locale
- * cannot be changed.
+ * Note: Does not support {@code setLocale(Context)}, as the fixed
+ * locale and time zone cannot be changed.
*
* @author Juergen Hoeller
* @since 1.1
* @see #setDefaultLocale
+ * @see #setDefaultTimeZone
*/
-public class FixedLocaleResolver extends AbstractLocaleResolver {
+public class FixedLocaleResolver extends AbstractLocaleContextResolver {
/**
* Create a default FixedLocaleResolver, exposing a configured default
* locale (or the JVM's default locale as fallback).
* @see #setDefaultLocale
+ * @see #setDefaultTimeZone
*/
public FixedLocaleResolver() {
+ setDefaultLocale(Locale.getDefault());
}
/**
@@ -51,6 +57,16 @@ public class FixedLocaleResolver extends AbstractLocaleResolver {
setDefaultLocale(locale);
}
+ /**
+ * Create a FixedLocaleResolver that exposes the given locale and time zone.
+ * @param locale the locale to expose
+ * @param timeZone the time zone to expose
+ */
+ public FixedLocaleResolver(Locale locale, TimeZone timeZone) {
+ setDefaultLocale(locale);
+ setDefaultTimeZone(timeZone);
+ }
+
@Override
public Locale resolveLocale(HttpServletRequest request) {
@@ -62,9 +78,22 @@ public class FixedLocaleResolver extends AbstractLocaleResolver {
}
@Override
- public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
- throw new UnsupportedOperationException(
- "Cannot change fixed locale - use a different locale resolution strategy");
+ public LocaleContext resolveLocaleContext(HttpServletRequest request) {
+ return new TimeZoneAwareLocaleContext() {
+ @Override
+ public Locale getLocale() {
+ return getDefaultLocale();
+ }
+ @Override
+ public TimeZone getTimeZone() {
+ return getDefaultTimeZone();
+ }
+ };
+ }
+
+ @Override
+ public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
+ throw new UnsupportedOperationException("Cannot change fixed locale - use a different locale resolution strategy");
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java
index 07ba5fc970..8b3a602f1d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/SessionLocaleResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,12 @@
package org.springframework.web.servlet.i18n;
import java.util.Locale;
-
+import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.springframework.context.i18n.LocaleContext;
+import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.web.util.WebUtils;
/**
@@ -30,27 +32,41 @@ import org.springframework.web.util.WebUtils;
*
* This is most appropriate if the application needs user sessions anyway,
* that is, when the HttpSession does not have to be created for the locale.
+ * The session may optionally contain an associated time zone attribute as well;
+ * alternatively, you may specify a default time zone.
*
- * Custom controllers can override the user's locale by calling
- * {@code setLocale}, e.g. responding to a locale change request.
+ * Custom controllers can override the user's locale and time zone by calling
+ * {@code #setLocale(Context)} on the resolver, e.g. responding to a locale change
+ * request. As a more convenient alternative, consider using
+ * {@link org.springframework.web.servlet.support.RequestContext#changeLocale}.
*
* @author Juergen Hoeller
* @since 27.02.2003
* @see #setDefaultLocale
- * @see #setLocale
+ * @see #setDefaultTimeZone
*/
-public class SessionLocaleResolver extends AbstractLocaleResolver {
+public class SessionLocaleResolver extends AbstractLocaleContextResolver {
/**
- * Name of the session attribute that holds the locale.
+ * Name of the session attribute that holds the Locale.
* Only used internally by this implementation.
- * Use {@code RequestContext(Utils).getLocale()}
+ * Use {@code RequestContext(Utils).getLocale()}
* to retrieve the current locale in controllers or views.
* @see org.springframework.web.servlet.support.RequestContext#getLocale
* @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
*/
public static final String LOCALE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".LOCALE";
+ /**
+ * Name of the session attribute that holds the TimeZone.
+ * Only used internally by this implementation.
+ * Use {@code RequestContext(Utils).getTimeZone()}
+ * to retrieve the current time zone in controllers or views.
+ * @see org.springframework.web.servlet.support.RequestContext#getTimeZone
+ * @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone
+ */
+ public static final String TIME_ZONE_SESSION_ATTRIBUTE_NAME = SessionLocaleResolver.class.getName() + ".TIME_ZONE";
+
@Override
public Locale resolveLocale(HttpServletRequest request) {
@@ -61,9 +77,46 @@ public class SessionLocaleResolver extends AbstractLocaleResolver {
return locale;
}
+ @Override
+ public LocaleContext resolveLocaleContext(final HttpServletRequest request) {
+ return new TimeZoneAwareLocaleContext() {
+ @Override
+ public Locale getLocale() {
+ Locale locale = (Locale) WebUtils.getSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME);
+ if (locale == null) {
+ locale = determineDefaultLocale(request);
+ }
+ return locale;
+ }
+ @Override
+ public TimeZone getTimeZone() {
+ TimeZone timeZone = (TimeZone) WebUtils.getSessionAttribute(request, TIME_ZONE_SESSION_ATTRIBUTE_NAME);
+ if (timeZone == null) {
+ timeZone = determineDefaultTimeZone(request);
+ }
+ return timeZone;
+ }
+ };
+ }
+
+ @Override
+ public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) {
+ Locale locale = null;
+ TimeZone timeZone = null;
+ if (localeContext != null) {
+ locale = localeContext.getLocale();
+ if (localeContext instanceof TimeZoneAwareLocaleContext) {
+ timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
+ }
+ }
+ WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale);
+ WebUtils.setSessionAttribute(request, TIME_ZONE_SESSION_ATTRIBUTE_NAME, timeZone);
+ }
+
+
/**
* Determine the default locale for the given request,
- * Called if no locale session attribute has been found.
+ * Called if no Locale session attribute has been found.
* The default implementation returns the specified default locale,
* if any, else falls back to the request's accept-header locale.
* @param request the request to resolve the locale for
@@ -79,9 +132,17 @@ public class SessionLocaleResolver extends AbstractLocaleResolver {
return defaultLocale;
}
- @Override
- public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
- WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale);
+ /**
+ * Determine the default time zone for the given request,
+ * Called if no TimeZone session attribute has been found.
+ * The default implementation returns the specified default time zone,
+ * if any, or {@code null} otherwise.
+ * @param request the request to resolve the time zone for
+ * @return the default time zone (or {@code null} if none defined)
+ * @see #setDefaultTimeZone
+ */
+ protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {
+ return getDefaultTimeZone();
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java
index 3a6770c793..7be1a8a4f5 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,8 +21,9 @@ import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.security.Principal;
+import java.time.ZoneId;
import java.util.Locale;
-
+import java.util.TimeZone;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@@ -45,12 +46,15 @@ import org.springframework.web.servlet.support.RequestContextUtils;
* Suitable for exposition to views, and usage within JSP's "useBean" tag, JSP scriptlets, JSTL EL, Velocity
* templates, etc. Necessary for views that do not have access to the servlet request, like Velocity templates.
@@ -56,8 +62,8 @@ import org.springframework.web.util.WebUtils;
* Can be instantiated manually, or automatically exposed to views as model attribute via AbstractView's
* "requestContextAttribute" property.
*
- * Will also work outside of DispatcherServlet requests, accessing the root WebApplicationContext and using an
- * appropriate fallback for the locale (the HttpServletRequest's primary locale).
+ * Will also work outside of DispatcherServlet requests, accessing the root WebApplicationContext and using
+ * an appropriate fallback for the locale (the HttpServletRequest's primary locale).
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
@@ -70,15 +76,16 @@ import org.springframework.web.util.WebUtils;
public class RequestContext {
/**
- * Default theme name used if the RequestContext cannot find a ThemeResolver. Only applies to non-DispatcherServlet
- * requests. Same as AbstractThemeResolver's default, but not linked in here to avoid package interdependencies.
+ * Default theme name used if the RequestContext cannot find a ThemeResolver.
+ * Only applies to non-DispatcherServlet requests.
+ * Same as AbstractThemeResolver's default, but not linked in here to avoid package interdependencies.
* @see org.springframework.web.servlet.theme.AbstractThemeResolver#ORIGINAL_DEFAULT_THEME_NAME
*/
public static final String DEFAULT_THEME_NAME = "theme";
/**
- * Request attribute to hold the current web application context for RequestContext usage. By default, the
- * DispatcherServlet's context (or the root context as fallback) is exposed.
+ * Request attribute to hold the current web application context for RequestContext usage.
+ * By default, the DispatcherServlet's context (or the root context as fallback) is exposed.
*/
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = RequestContext.class.getName() + ".CONTEXT";
@@ -102,6 +109,8 @@ public class RequestContext {
private Locale locale;
+ private TimeZone timeZone;
+
private Theme theme;
private Boolean defaultHtmlEscape;
@@ -114,10 +123,11 @@ public class RequestContext {
/**
- * Create a new RequestContext for the given request, using the request attributes for Errors retrieval. This
- * only works with InternalResourceViews, as Errors instances are part of the model and not normally exposed as
- * request attributes. It will typically be used within JSPs or custom tags. Will only work within a
- * DispatcherServlet request. Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
+ * Create a new RequestContext for the given request, using the request attributes for Errors retrieval.
+ * This only works with InternalResourceViews, as Errors instances are part of the model and not
+ * normally exposed as request attributes. It will typically be used within JSPs or custom tags.
+ * Will only work within a DispatcherServlet request.
+ * Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
* @param request current HTTP request
* @see org.springframework.web.servlet.DispatcherServlet
* @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.ServletContext)
@@ -127,13 +137,29 @@ public class RequestContext {
}
/**
- * Create a new RequestContext for the given request, using the request attributes for Errors retrieval. This
- * only works with InternalResourceViews, as Errors instances are part of the model and not normally exposed as
- * request attributes. It will typically be used within JSPs or custom tags. If a ServletContext is specified,
- * the RequestContext will also work with the root WebApplicationContext (outside a DispatcherServlet).
+ * Create a new RequestContext for the given request, using the request attributes for Errors retrieval.
+ * This only works with InternalResourceViews, as Errors instances are part of the model and not
+ * normally exposed as request attributes. It will typically be used within JSPs or custom tags.
+ * Will only work within a DispatcherServlet request.
+ * Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
* @param request current HTTP request
- * @param servletContext the servlet context of the web application (can be {@code null}; necessary for
- * fallback to root WebApplicationContext)
+ * @param response current HTTP response
+ * @see org.springframework.web.servlet.DispatcherServlet
+ * @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.ServletContext, Map)
+ */
+ public RequestContext(HttpServletRequest request, HttpServletResponse response) {
+ initContext(request, response, null, null);
+ }
+
+ /**
+ * Create a new RequestContext for the given request, using the request attributes for Errors retrieval.
+ * This only works with InternalResourceViews, as Errors instances are part of the model and not
+ * normally exposed as request attributes. It will typically be used within JSPs or custom tags.
+ * If a ServletContext is specified, the RequestContext will also work with the root
+ * WebApplicationContext (outside a DispatcherServlet).
+ * @param request current HTTP request
+ * @param servletContext the servlet context of the web application (can be {@code null};
+ * necessary for fallback to root WebApplicationContext)
* @see org.springframework.web.context.WebApplicationContext
* @see org.springframework.web.servlet.DispatcherServlet
*/
@@ -142,13 +168,13 @@ public class RequestContext {
}
/**
- * Create a new RequestContext for the given request, using the given model attributes for Errors retrieval. This
- * works with all View implementations. It will typically be used by View implementations. Will only work
- * within a DispatcherServlet request. Pass in a ServletContext to be able to fallback to the root
- * WebApplicationContext.
+ * Create a new RequestContext for the given request, using the given model attributes for Errors retrieval.
+ * This works with all View implementations. It will typically be used by View implementations.
+ * Will only work within a DispatcherServlet request.
+ * Pass in a ServletContext to be able to fallback to the root WebApplicationContext.
* @param request current HTTP request
- * @param model the model attributes for the current view (can be {@code null}, using the request attributes
- * for Errors retrieval)
+ * @param model the model attributes for the current view (can be {@code null},
+ * using the request attributes for Errors retrieval)
* @see org.springframework.web.servlet.DispatcherServlet
* @see #RequestContext(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.ServletContext, Map)
*/
@@ -157,9 +183,10 @@ public class RequestContext {
}
/**
- * Create a new RequestContext for the given request, using the given model attributes for Errors retrieval. This
- * works with all View implementations. It will typically be used by View implementations. If a ServletContext is
- * specified, the RequestContext will also work with a root WebApplicationContext (outside a DispatcherServlet).
+ * Create a new RequestContext for the given request, using the given model attributes for Errors retrieval.
+ * This works with all View implementations. It will typically be used by View implementations.
+ * If a ServletContext is specified, the RequestContext will also work with a root
+ * WebApplicationContext (outside a DispatcherServlet).
* @param request current HTTP request
* @param response current HTTP response
* @param servletContext the servlet context of the web application (can be {@code null}; necessary for
@@ -212,14 +239,25 @@ public class RequestContext {
// Determine locale to use for this RequestContext.
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
- if (localeResolver != null) {
+ if (localeResolver instanceof LocaleContextResolver) {
+ LocaleContext localeContext = ((LocaleContextResolver) localeResolver).resolveLocaleContext(request);
+ this.locale = localeContext.getLocale();
+ if (localeContext instanceof TimeZoneAwareLocaleContext) {
+ this.timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
+ }
+ }
+ else if (localeResolver != null) {
// Try LocaleResolver (we're within a DispatcherServlet request).
this.locale = localeResolver.resolveLocale(request);
}
- else {
- // No LocaleResolver available -> try fallback.
+
+ // Try JSTL fallbacks if necessary.
+ if (this.locale == null) {
this.locale = getFallbackLocale();
}
+ if (this.timeZone == null) {
+ this.timeZone = getFallbackTimeZone();
+ }
// Determine default HTML escape setting from the "defaultHtmlEscape"
// context-param in web.xml, if any.
@@ -235,9 +273,8 @@ public class RequestContext {
/**
* Determine the fallback locale for this context.
- * The default implementation checks for a JSTL locale attribute in request,
- * session or application scope; if not found, returns the
- * {@code HttpServletRequest.getLocale()}.
+ * The default implementation checks for a JSTL locale attribute in request, session
+ * or application scope; if not found, returns the {@code HttpServletRequest.getLocale()}.
* @return the fallback locale (never {@code null})
* @see javax.servlet.http.HttpServletRequest#getLocale()
*/
@@ -251,6 +288,22 @@ public class RequestContext {
return getRequest().getLocale();
}
+ /**
+ * Determine the fallback time zone for this context.
+ * The default implementation checks for a JSTL time zone attribute in request,
+ * session or application scope; returns {@code null} if not found.
+ * @return the fallback time zone (or {@code null} if none derivable from the request)
+ */
+ protected TimeZone getFallbackTimeZone() {
+ if (jstlPresent) {
+ TimeZone timeZone = JstlLocaleResolver.getJstlTimeZone(getRequest(), getServletContext());
+ if (timeZone != null) {
+ return timeZone;
+ }
+ }
+ return null;
+ }
+
/**
* Determine the fallback theme for this context.
* The default implementation returns the default theme (with name "theme").
@@ -306,17 +359,65 @@ public class RequestContext {
}
/**
- * Return the current Locale (never {@code null}).
+ * Return the current Locale (falling back to the request locale; never {@code null}).
+ * Typically coming from a DispatcherServlet's {@link LocaleResolver}.
+ * Also includes a fallback check for JSTL's Locale attribute.
+ * @see RequestContextUtils#getLocale
*/
public final Locale getLocale() {
return this.locale;
}
+ /**
+ * Return the current TimeZone (or {@code null} if none derivable from the request).
+ * Typically coming from a DispatcherServlet's {@link LocaleContextResolver}.
+ * Also includes a fallback check for JSTL's TimeZone attribute.
+ * @see RequestContextUtils#getTimeZone
+ */
+ public TimeZone getTimeZone() {
+ return this.timeZone;
+ }
+
+ /**
+ * Change the current locale to the specified one,
+ * storing the new locale through the configured {@link LocaleResolver}.
+ * @param locale the new locale
+ * @see LocaleResolver#setLocale
+ * @see #changeLocale(java.util.Locale, java.util.TimeZone)
+ */
+ public void changeLocale(Locale locale) {
+ LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(this.request);
+ if (localeResolver == null) {
+ throw new IllegalStateException("Cannot change locale if no LocaleResolver configured");
+ }
+ localeResolver.setLocale(this.request, this.response, locale);
+ this.locale = locale;
+ }
+
+ /**
+ * Change the current locale to the specified locale and time zone context,
+ * storing the new locale context through the configured {@link LocaleResolver}.
+ * @param locale the new locale
+ * @param timeZone the new time zone
+ * @see LocaleContextResolver#setLocaleContext
+ * @see org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext
+ */
+ public void changeLocale(Locale locale, TimeZone timeZone) {
+ LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(this.request);
+ if (!(localeResolver instanceof LocaleContextResolver)) {
+ throw new IllegalStateException("Cannot change locale context if no LocaleContextResolver configured");
+ }
+ ((LocaleContextResolver) localeResolver).setLocaleContext(this.request, this.response,
+ new SimpleTimeZoneAwareLocaleContext(locale, timeZone));
+ this.locale = locale;
+ this.timeZone = timeZone;
+ }
+
/**
* Return the current theme (never {@code null}).
* Resolved lazily for more efficiency when theme support is not being used.
*/
- public final Theme getTheme() {
+ public Theme getTheme() {
if (this.theme == null) {
// Lazily determine theme to use for this RequestContext.
this.theme = RequestContextUtils.getTheme(this.request);
@@ -329,8 +430,39 @@ public class RequestContext {
}
/**
- * (De)activate default HTML escaping for messages and errors, for the scope of this RequestContext. The default is
- * the application-wide setting (the "defaultHtmlEscape" context-param in web.xml).
+ * Change the current theme to the specified one,
+ * storing the new theme name through the configured {@link ThemeResolver}.
+ * @param theme the new theme
+ * @see ThemeResolver#setThemeName
+ */
+ public void changeTheme(Theme theme) {
+ ThemeResolver themeResolver = RequestContextUtils.getThemeResolver(this.request);
+ if (themeResolver == null) {
+ throw new IllegalStateException("Cannot change theme if no ThemeResolver configured");
+ }
+ themeResolver.setThemeName(this.request, this.response, (theme != null ? theme.getName() : null));
+ this.theme = theme;
+ }
+
+ /**
+ * Change the current theme to the specified theme by name,
+ * storing the new theme name through the configured {@link ThemeResolver}.
+ * @param themeName the name of the new theme
+ * @see ThemeResolver#setThemeName
+ */
+ public void changeTheme(String themeName) {
+ ThemeResolver themeResolver = RequestContextUtils.getThemeResolver(this.request);
+ if (themeResolver == null) {
+ throw new IllegalStateException("Cannot change theme if no ThemeResolver configured");
+ }
+ themeResolver.setThemeName(this.request, this.response, themeName);
+ // Ask for re-resolution on next getTheme call.
+ this.theme = null;
+ }
+
+ /**
+ * (De)activate default HTML escaping for messages and errors, for the scope of this RequestContext.
+ * The default is the application-wide setting (the "defaultHtmlEscape" context-param in web.xml).
* @see org.springframework.web.util.WebUtils#isDefaultHtmlEscape
*/
public void setDefaultHtmlEscape(boolean defaultHtmlEscape) {
@@ -353,8 +485,8 @@ public class RequestContext {
}
/**
- * Set the UrlPathHelper to use for context path and request URI decoding. Can be used to pass a shared
- * UrlPathHelper instance in.
+ * Set the UrlPathHelper to use for context path and request URI decoding.
+ * Can be used to pass a shared UrlPathHelper instance in.
* A default UrlPathHelper is always available.
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
@@ -363,8 +495,8 @@ public class RequestContext {
}
/**
- * Return the UrlPathHelper used for context path and request URI decoding. Can be used to configure the current
- * UrlPathHelper.
+ * Return the UrlPathHelper used for context path and request URI decoding.
+ * Can be used to configure the current UrlPathHelper.
* A default UrlPathHelper is always available.
*/
public UrlPathHelper getUrlPathHelper() {
@@ -381,8 +513,9 @@ public class RequestContext {
}
/**
- * Return the context path of the original request, that is, the path that indicates the current web application.
- * This is useful for building links to other resources within the application.
+ * Return the context path of the original request, that is, the path that
+ * indicates the current web application. This is useful for building links
+ * to other resources within the application.
* Delegates to the UrlPathHelper for decoding.
* @see javax.servlet.http.HttpServletRequest#getContextPath
* @see #getUrlPathHelper
@@ -408,7 +541,6 @@ public class RequestContext {
* Return a context-aware URl for the given relative URL with placeholders (named keys with braces {@code {}}).
* For example, send in a relative URL {@code foo/{bar}?spam={spam}} and a parameter map
* {@code {bar=baz,spam=nuts}} and the result will be {@code [contextpath]/foo/baz?spam=nuts}.
- *
* @param relativeUrl the relative URL part
* @param params a map of parameters to insert as placeholders in the url
* @return a URL that points back to the server with an absolute path (also URL-encoded accordingly)
@@ -439,10 +571,9 @@ public class RequestContext {
}
/**
- * Return the request URI of the original request, that is, the invoked URL without parameters. This is particularly
- * useful as HTML form action target, possibly in combination with the original query string. Note this
- * implementation will correctly resolve to the URI of any originating root request in the presence of a forwarded
- * request. However, this can only work when the Servlet 2.4 'forward' request attributes are present.
+ * Return the request URI of the original request, that is, the invoked URL
+ * without parameters. This is particularly useful as HTML form action target,
+ * possibly in combination with the original query string.
* Delegates to the UrlPathHelper for decoding.
* @see #getQueryString
* @see org.springframework.web.util.UrlPathHelper#getOriginatingRequestUri
@@ -453,10 +584,9 @@ public class RequestContext {
}
/**
- * Return the query string of the current request, that is, the part after the request path. This is particularly
- * useful for building an HTML form action target in combination with the original request URI. Note this
- * implementation will correctly resolve to the query string of any originating root request in the presence of a
- * forwarded request. However, this can only work when the Servlet 2.4 'forward' request attributes are present.
+ * Return the query string of the current request, that is, the part after
+ * the request path. This is particularly useful for building an HTML form
+ * action target in combination with the original request URI.
* Delegates to the UrlPathHelper for decoding.
* @see #getRequestUri
* @see org.springframework.web.util.UrlPathHelper#getOriginatingQueryString
@@ -768,6 +898,20 @@ public class RequestContext {
}
return (localeObject instanceof Locale ? (Locale) localeObject : null);
}
+
+ public static TimeZone getJstlTimeZone(HttpServletRequest request, ServletContext servletContext) {
+ Object timeZoneObject = Config.get(request, Config.FMT_TIME_ZONE);
+ if (timeZoneObject == null) {
+ HttpSession session = request.getSession(false);
+ if (session != null) {
+ timeZoneObject = Config.get(session, Config.FMT_TIME_ZONE);
+ }
+ if (timeZoneObject == null && servletContext != null) {
+ timeZoneObject = Config.get(servletContext, Config.FMT_TIME_ZONE);
+ }
+ }
+ return (timeZoneObject instanceof TimeZone ? (TimeZone) timeZoneObject : null);
+ }
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java
index f992839046..f56a615ec9 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContextUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,11 +18,13 @@ package org.springframework.web.servlet.support;
import java.util.Locale;
import java.util.Map;
-
+import java.util.TimeZone;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.context.i18n.LocaleContext;
+import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.ui.context.Theme;
import org.springframework.ui.context.ThemeSource;
import org.springframework.web.context.WebApplicationContext;
@@ -30,6 +32,7 @@ import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.FlashMapManager;
+import org.springframework.web.servlet.LocaleContextResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ThemeResolver;
@@ -98,23 +101,51 @@ public abstract class RequestContextUtils {
}
/**
- * Retrieves the current locale from the given request,
- * using the LocaleResolver bound to the request by the DispatcherServlet
+ * Retrieve the current locale from the given request, using the
+ * LocaleResolver bound to the request by the DispatcherServlet
* (if available), falling back to the request's accept-header Locale.
+ * This method serves as a straightforward alternative to the standard
+ * Servlet {@link javax.servlet.http.HttpServletRequest#getLocale()} method,
+ * falling back to the latter if no more specific locale has been found.
+ * Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getLocale()}
+ * which will normally be populated with the same Locale.
* @param request current HTTP request
- * @return the current locale, either from the LocaleResolver or from
- * the plain request
+ * @return the current locale for the given request, either from the
+ * LocaleResolver or from the plain request itself
* @see #getLocaleResolver
- * @see javax.servlet.http.HttpServletRequest#getLocale()
+ * @see org.springframework.context.i18n.LocaleContextHolder#getLocale()
*/
public static Locale getLocale(HttpServletRequest request) {
LocaleResolver localeResolver = getLocaleResolver(request);
- if (localeResolver != null) {
- return localeResolver.resolveLocale(request);
- }
- else {
- return request.getLocale();
+ return (localeResolver != null ? localeResolver.resolveLocale(request) : request.getLocale());
+ }
+
+ /**
+ * Retrieve the current time zone from the given request, using the
+ * TimeZoneAwareLocaleResolver bound to the request by the DispatcherServlet
+ * (if available), falling back to the system's default time zone.
+ * Note: This method returns {@code null} if no specific time zone can be
+ * resolved for the given request. This is in contrast to {@link #getLocale}
+ * where there is always the request's accept-header locale to fall back to.
+ * Consider using {@link org.springframework.context.i18n.LocaleContextHolder#getTimeZone()}
+ * which will normally be populated with the same TimeZone: That method only
+ * differs in terms of its fallback to the system time zone if the LocaleResolver
+ * hasn't provided provided a specific time zone (instead of this method's {@code null}).
+ * @param request current HTTP request
+ * @return the current time zone for the given request, either from the
+ * TimeZoneAwareLocaleResolver or {@code null} if none associated
+ * @see #getLocaleResolver
+ * @see org.springframework.context.i18n.LocaleContextHolder#getTimeZone()
+ */
+ public static TimeZone getTimeZone(HttpServletRequest request) {
+ LocaleResolver localeResolver = getLocaleResolver(request);
+ if (localeResolver instanceof LocaleContextResolver) {
+ LocaleContext localeContext = ((LocaleContextResolver) localeResolver).resolveLocaleContext(request);
+ if (localeContext instanceof TimeZoneAwareLocaleContext) {
+ return ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
+ }
}
+ return null;
}
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/AbstractJasperReportsView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/AbstractJasperReportsView.java
index 93c62e5159..28b4c4348f 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/AbstractJasperReportsView.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/jasperreports/AbstractJasperReportsView.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,10 @@ import java.sql.SQLException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.Properties;
+import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
@@ -580,14 +582,19 @@ public abstract class AbstractJasperReportsView extends AbstractUrlBasedView {
*/
protected void exposeLocalizationContext(Map