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 { + + @Override + public ZoneId convert(TimeZone source) { + return source.toZoneId(); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java new file mode 100644 index 0000000000..261c4e3ffe --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.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 Java 8's {@link java.time.ZoneId} to {@link java.util.TimeZone}. + * + *

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 { + + @Override + public TimeZone convert(ZoneId source) { + return TimeZone.getTimeZone(source); + } + +} diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index 0b9a65d4dc..c1c9d7d245 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.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. @@ -28,6 +28,7 @@ import java.util.Locale; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; +import java.util.TimeZone; import java.util.TreeSet; /** @@ -674,10 +675,11 @@ public abstract class StringUtils { /** * Parse the given {@code localeString} value into a {@link Locale}. *

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; *

  • {@link HttpSession} *
  • {@link Principal} *
  • {@link Locale} + *
  • {@link TimeZone} (as of Spring 4.0) + *
  • {@link java.time.ZoneId} (as of Spring 4.0 and Java 8)
  • *
  • {@link InputStream} *
  • {@link Reader} * * * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 3.1 */ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { @@ -64,6 +68,8 @@ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgume HttpSession.class.isAssignableFrom(paramType) || Principal.class.isAssignableFrom(paramType) || Locale.class.equals(paramType) || + TimeZone.class.equals(paramType) || + "java.time.ZoneId".equals(paramType.getName()) || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType); } @@ -97,6 +103,13 @@ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgume else if (Locale.class.equals(paramType)) { return RequestContextUtils.getLocale(request); } + else if (TimeZone.class.equals(paramType)) { + TimeZone timeZone = RequestContextUtils.getTimeZone(request); + return (timeZone != null ? timeZone : TimeZone.getDefault()); + } + else if ("java.time.ZoneId".equals(paramType.getName())) { + return ZoneIdResolver.resolveZoneId(request); + } else if (InputStream.class.isAssignableFrom(paramType)) { return request.getInputStream(); } @@ -110,4 +123,16 @@ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgume } } + + /** + * Inner class to avoid a hard-coded dependency on Java 8's {@link java.time.ZoneId}. + */ + private static class ZoneIdResolver { + + public static Object resolveZoneId(HttpServletRequest request) { + TimeZone timeZone = RequestContextUtils.getTimeZone(request); + return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault()); + } + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/JstlUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/JstlUtils.java index dab8718382..959ba68d3a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/JstlUtils.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/JstlUtils.java @@ -18,7 +18,7 @@ package org.springframework.web.servlet.support; import java.util.Locale; import java.util.ResourceBundle; - +import java.util.TimeZone; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -80,6 +80,10 @@ public abstract class JstlUtils { public static void exposeLocalizationContext(HttpServletRequest request, MessageSource messageSource) { Locale jstlLocale = RequestContextUtils.getLocale(request); Config.set(request, Config.FMT_LOCALE, jstlLocale); + TimeZone timeZone = RequestContextUtils.getTimeZone(request); + if (timeZone != null) { + Config.set(request, Config.FMT_TIME_ZONE, timeZone); + } if (messageSource != null) { LocalizationContext jstlContext = new SpringLocalizationContext(messageSource, request); Config.set(request, Config.FMT_LOCALIZATION_CONTEXT, jstlContext); @@ -95,6 +99,10 @@ public abstract class JstlUtils { */ public static void exposeLocalizationContext(RequestContext requestContext) { Config.set(requestContext.getRequest(), Config.FMT_LOCALE, requestContext.getLocale()); + TimeZone timeZone = requestContext.getTimeZone(); + if (timeZone != null) { + Config.set(requestContext.getRequest(), Config.FMT_TIME_ZONE, timeZone); + } MessageSource messageSource = getJstlAwareMessageSource( requestContext.getServletContext(), requestContext.getMessageSource()); LocalizationContext jstlContext = new SpringLocalizationContext(messageSource, requestContext.getRequest()); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java index f2edbfedc3..10e0bbff3a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.TimeZone; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -29,6 +30,9 @@ import javax.servlet.jsp.jstl.core.Config; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceResolvable; import org.springframework.context.NoSuchMessageException; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext; +import org.springframework.context.i18n.TimeZoneAwareLocaleContext; import org.springframework.ui.context.Theme; import org.springframework.ui.context.ThemeSource; import org.springframework.ui.context.support.ResourceBundleThemeSource; @@ -40,15 +44,17 @@ import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; import org.springframework.web.bind.EscapedErrors; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.LocaleContextResolver; import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.ThemeResolver; import org.springframework.web.util.HtmlUtils; import org.springframework.web.util.UriTemplate; import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.WebUtils; /** - * Context holder for request-specific state, like current web application context, current locale, current theme, and - * potential binding errors. Provides easy access to localized messages and Errors instances. + * Context holder for request-specific state, like current web application context, current locale, current theme, + * and potential binding errors. Provides easy access to localized messages and Errors instances. * *

    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 model, HttpServletRequest request) { RequestContext rc = new RequestContext(request, getServletContext()); + Locale locale = rc.getLocale(); if (!model.containsKey(JRParameter.REPORT_LOCALE)) { - model.put(JRParameter.REPORT_LOCALE, rc.getLocale()); + model.put(JRParameter.REPORT_LOCALE, locale); + } + TimeZone timeZone = rc.getTimeZone(); + if (timeZone != null && !model.containsKey(JRParameter.REPORT_TIME_ZONE)) { + model.put(JRParameter.REPORT_TIME_ZONE, timeZone); } JasperReport report = getReport(); if ((report == null || report.getResourceBundle() == null) && !model.containsKey(JRParameter.REPORT_RESOURCE_BUNDLE)) { model.put(JRParameter.REPORT_RESOURCE_BUNDLE, - new MessageSourceResourceBundle(rc.getMessageSource(), rc.getLocale())); + new MessageSourceResourceBundle(rc.getMessageSource(), locale)); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java index d094d75e9f..69e074eb2f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/velocity/VelocityView.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,6 +18,7 @@ package org.springframework.web.servlet.view.velocity; import java.util.Locale; import java.util.Map; +import java.util.TimeZone; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -408,12 +409,11 @@ public class VelocityView extends AbstractTemplateView { // Expose locale-aware DateTool/NumberTool attributes. if (this.dateToolAttribute != null || this.numberToolAttribute != null) { - Locale locale = RequestContextUtils.getLocale(request); if (this.dateToolAttribute != null) { - velocityContext.put(this.dateToolAttribute, new LocaleAwareDateTool(locale)); + velocityContext.put(this.dateToolAttribute, new LocaleAwareDateTool(request)); } if (this.numberToolAttribute != null) { - velocityContext.put(this.numberToolAttribute, new LocaleAwareNumberTool(locale)); + velocityContext.put(this.numberToolAttribute, new LocaleAwareNumberTool(request)); } } } @@ -528,41 +528,48 @@ public class VelocityView extends AbstractTemplateView { /** - * Subclass of DateTool from Velocity Tools, using a passed-in Locale - * (usually the RequestContext Locale) instead of the default Locale.N + * Subclass of DateTool from Velocity Tools, using a Spring-resolved + * Locale and TimeZone instead of the default Locale. * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale + * @see org.springframework.web.servlet.support.RequestContextUtils#getTimeZone */ private static class LocaleAwareDateTool extends DateTool { - private final Locale locale; + private final HttpServletRequest request; - private LocaleAwareDateTool(Locale locale) { - this.locale = locale; + public LocaleAwareDateTool(HttpServletRequest request) { + this.request = request; } @Override public Locale getLocale() { - return this.locale; + return RequestContextUtils.getLocale(this.request); + } + + @Override + public TimeZone getTimeZone() { + TimeZone timeZone = RequestContextUtils.getTimeZone(this.request); + return (timeZone != null ? timeZone : super.getTimeZone()); } } /** - * Subclass of NumberTool from Velocity Tools, using a passed-in Locale - * (usually the RequestContext Locale) instead of the default Locale. + * Subclass of NumberTool from Velocity Tools, using a Spring-resolved + * Locale instead of the default Locale. * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale */ private static class LocaleAwareNumberTool extends NumberTool { - private final Locale locale; + private final HttpServletRequest request; - private LocaleAwareNumberTool(Locale locale) { - this.locale = locale; + public LocaleAwareNumberTool(HttpServletRequest request) { + this.request = request; } @Override public Locale getLocale() { - return this.locale; + return RequestContextUtils.getLocale(this.request); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/ComplexWebApplicationContext.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/ComplexWebApplicationContext.java index e9b5a1a9ad..6704c13a41 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/ComplexWebApplicationContext.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/ComplexWebApplicationContext.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.TimeZone; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -60,6 +61,7 @@ import org.springframework.web.servlet.i18n.SessionLocaleResolver; import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.mvc.ParameterizableViewController; import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; +import org.springframework.web.servlet.support.RequestContext; import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.servlet.theme.SessionThemeResolver; import org.springframework.web.servlet.theme.ThemeChangeInterceptor; @@ -402,21 +404,6 @@ public class ComplexWebApplicationContext extends StaticWebApplicationContext { if (!(request instanceof MultipartHttpServletRequest)) { throw new ServletException("Not in a MultipartHttpServletRequest"); } - if (!(RequestContextUtils.getLocaleResolver(request) instanceof SessionLocaleResolver)) { - throw new ServletException("Incorrect LocaleResolver"); - } - if (!Locale.CANADA.equals(RequestContextUtils.getLocale(request))) { - throw new ServletException("Incorrect Locale"); - } - if (!Locale.CANADA.equals(LocaleContextHolder.getLocale())) { - throw new ServletException("Incorrect Locale"); - } - if (!(RequestContextUtils.getThemeResolver(request) instanceof SessionThemeResolver)) { - throw new ServletException("Incorrect ThemeResolver"); - } - if (!"theme".equals(RequestContextUtils.getThemeResolver(request).resolveThemeName(request))) { - throw new ServletException("Incorrect theme name"); - } if (request.getParameter("fail") != null) { throw new ModelAndViewDefiningException(new ModelAndView("failed1")); } @@ -429,6 +416,45 @@ public class ComplexWebApplicationContext extends StaticWebApplicationContext { if (request.getParameter("exception") != null) { throw new RuntimeException("servlet"); } + if (!(RequestContextUtils.getLocaleResolver(request) instanceof SessionLocaleResolver)) { + throw new ServletException("Incorrect LocaleResolver"); + } + if (!Locale.CANADA.equals(RequestContextUtils.getLocale(request))) { + throw new ServletException("Incorrect Locale"); + } + if (!Locale.CANADA.equals(LocaleContextHolder.getLocale())) { + throw new ServletException("Incorrect Locale"); + } + if (RequestContextUtils.getTimeZone(request) != null) { + throw new ServletException("Incorrect TimeZone"); + } + if (!TimeZone.getDefault().equals(LocaleContextHolder.getTimeZone())) { + throw new ServletException("Incorrect TimeZone"); + } + if (!(RequestContextUtils.getThemeResolver(request) instanceof SessionThemeResolver)) { + throw new ServletException("Incorrect ThemeResolver"); + } + if (!"theme".equals(RequestContextUtils.getThemeResolver(request).resolveThemeName(request))) { + throw new ServletException("Incorrect theme name"); + } + RequestContext rc = new RequestContext(request); + rc.changeLocale(Locale.US, TimeZone.getTimeZone("GMT+1")); + rc.changeTheme("theme2"); + if (!Locale.US.equals(RequestContextUtils.getLocale(request))) { + throw new ServletException("Incorrect Locale"); + } + if (!Locale.US.equals(LocaleContextHolder.getLocale())) { + throw new ServletException("Incorrect Locale"); + } + if (!TimeZone.getTimeZone("GMT+1").equals(RequestContextUtils.getTimeZone(request))) { + throw new ServletException("Incorrect TimeZone"); + } + if (!TimeZone.getTimeZone("GMT+1").equals(LocaleContextHolder.getTimeZone())) { + throw new ServletException("Incorrect TimeZone"); + } + if (!"theme2".equals(RequestContextUtils.getThemeResolver(request).resolveThemeName(request))) { + throw new ServletException("Incorrect theme name"); + } } @Override diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java index cf606d3b8c..96b72c9cb4 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.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,25 +17,32 @@ package org.springframework.web.servlet.i18n; import java.util.Locale; - +import java.util.TimeZone; import javax.servlet.http.Cookie; -import junit.framework.TestCase; +import org.junit.Test; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.context.i18n.SimpleLocaleContext; +import org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext; +import org.springframework.context.i18n.TimeZoneAwareLocaleContext; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; +import static org.junit.Assert.*; + /** * @author Alef Arendsen * @author Juergen Hoeller * @author Rick Evans */ -public class CookieLocaleResolverTests extends TestCase { +public class CookieLocaleResolverTests { + @Test public void testResolveLocale() { MockHttpServletRequest request = new MockHttpServletRequest(); Cookie cookie = new Cookie("LanguageKoekje", "nl"); - request.setCookies(new Cookie[]{cookie}); + request.setCookies(cookie); CookieLocaleResolver resolver = new CookieLocaleResolver(); // yup, koekje is the Dutch name for Cookie ;-) @@ -44,6 +51,37 @@ public class CookieLocaleResolverTests extends TestCase { assertEquals(loc.getLanguage(), "nl"); } + @Test + public void testResolveLocaleContext() { + MockHttpServletRequest request = new MockHttpServletRequest(); + Cookie cookie = new Cookie("LanguageKoekje", "nl"); + request.setCookies(cookie); + + CookieLocaleResolver resolver = new CookieLocaleResolver(); + // yup, koekje is the Dutch name for Cookie ;-) + resolver.setCookieName("LanguageKoekje"); + LocaleContext loc = resolver.resolveLocaleContext(request); + assertEquals("nl", loc.getLocale().getLanguage()); + assertTrue(loc instanceof TimeZoneAwareLocaleContext); + assertNull(((TimeZoneAwareLocaleContext) loc).getTimeZone()); + } + + @Test + public void testResolveLocaleContextWithTimeZone() { + MockHttpServletRequest request = new MockHttpServletRequest(); + Cookie cookie = new Cookie("LanguageKoekje", "nl GMT+1"); + request.setCookies(cookie); + + CookieLocaleResolver resolver = new CookieLocaleResolver(); + // yup, koekje is the Dutch name for Cookie ;-) + resolver.setCookieName("LanguageKoekje"); + LocaleContext loc = resolver.resolveLocaleContext(request); + assertEquals("nl", loc.getLocale().getLanguage()); + assertTrue(loc instanceof TimeZoneAwareLocaleContext); + assertEquals(TimeZone.getTimeZone("GMT+1"), ((TimeZoneAwareLocaleContext) loc).getTimeZone()); + } + + @Test public void testSetAndResolveLocale() { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -59,13 +97,74 @@ public class CookieLocaleResolverTests extends TestCase { assertFalse(cookie.getSecure()); request = new MockHttpServletRequest(); - request.setCookies(new Cookie[]{cookie}); + request.setCookies(cookie); resolver = new CookieLocaleResolver(); Locale loc = resolver.resolveLocale(request); - assertEquals(loc.getLanguage(), "nl"); + assertEquals("nl", loc.getLanguage()); } + @Test + public void testSetAndResolveLocaleContext() { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + CookieLocaleResolver resolver = new CookieLocaleResolver(); + resolver.setLocaleContext(request, response, new SimpleLocaleContext(new Locale("nl", ""))); + + Cookie cookie = response.getCookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME); + request = new MockHttpServletRequest(); + request.setCookies(cookie); + + resolver = new CookieLocaleResolver(); + LocaleContext loc = resolver.resolveLocaleContext(request); + assertEquals("nl", loc.getLocale().getLanguage()); + assertTrue(loc instanceof TimeZoneAwareLocaleContext); + assertNull(((TimeZoneAwareLocaleContext) loc).getTimeZone()); + } + + @Test + public void testSetAndResolveLocaleContextWithTimeZone() { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + CookieLocaleResolver resolver = new CookieLocaleResolver(); + resolver.setLocaleContext(request, response, + new SimpleTimeZoneAwareLocaleContext(new Locale("nl", ""), TimeZone.getTimeZone("GMT+1"))); + + Cookie cookie = response.getCookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME); + request = new MockHttpServletRequest(); + request.setCookies(cookie); + + resolver = new CookieLocaleResolver(); + LocaleContext loc = resolver.resolveLocaleContext(request); + assertEquals("nl", loc.getLocale().getLanguage()); + assertTrue(loc instanceof TimeZoneAwareLocaleContext); + assertEquals(TimeZone.getTimeZone("GMT+1"), ((TimeZoneAwareLocaleContext) loc).getTimeZone()); + } + + @Test + public void testSetAndResolveLocaleContextWithTimeZoneOnly() { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + CookieLocaleResolver resolver = new CookieLocaleResolver(); + resolver.setLocaleContext(request, response, + new SimpleTimeZoneAwareLocaleContext(null, TimeZone.getTimeZone("GMT+1"))); + + Cookie cookie = response.getCookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME); + request = new MockHttpServletRequest(); + request.addPreferredLocale(Locale.GERMANY); + request.setCookies(cookie); + + resolver = new CookieLocaleResolver(); + LocaleContext loc = resolver.resolveLocaleContext(request); + assertEquals(Locale.GERMANY, loc.getLocale()); + assertTrue(loc instanceof TimeZoneAwareLocaleContext); + assertEquals(TimeZone.getTimeZone("GMT+1"), ((TimeZoneAwareLocaleContext) loc).getTimeZone()); + } + + @Test public void testCustomCookie() { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -87,24 +186,39 @@ public class CookieLocaleResolverTests extends TestCase { assertTrue(cookie.getSecure()); request = new MockHttpServletRequest(); - request.setCookies(new Cookie[]{cookie}); + request.setCookies(cookie); resolver = new CookieLocaleResolver(); resolver.setCookieName("LanguageKoek"); Locale loc = resolver.resolveLocale(request); - assertEquals(loc.getLanguage(), "nl"); + assertEquals("nl", loc.getLanguage()); } + @Test public void testResolveLocaleWithoutCookie() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(Locale.TAIWAN); CookieLocaleResolver resolver = new CookieLocaleResolver(); - Locale locale = resolver.resolveLocale(request); - assertEquals(request.getLocale(), locale); + Locale loc = resolver.resolveLocale(request); + assertEquals(request.getLocale(), loc); } + @Test + public void testResolveLocaleContextWithoutCookie() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addPreferredLocale(Locale.TAIWAN); + + CookieLocaleResolver resolver = new CookieLocaleResolver(); + + LocaleContext loc = resolver.resolveLocaleContext(request); + assertEquals(request.getLocale(), loc.getLocale()); + assertTrue(loc instanceof TimeZoneAwareLocaleContext); + assertNull(((TimeZoneAwareLocaleContext) loc).getTimeZone()); + } + + @Test public void testResolveLocaleWithoutCookieAndDefaultLocale() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(Locale.TAIWAN); @@ -112,27 +226,59 @@ public class CookieLocaleResolverTests extends TestCase { CookieLocaleResolver resolver = new CookieLocaleResolver(); resolver.setDefaultLocale(Locale.GERMAN); - Locale locale = resolver.resolveLocale(request); - assertEquals(Locale.GERMAN, locale); + Locale loc = resolver.resolveLocale(request); + assertEquals(Locale.GERMAN, loc); } + @Test + public void testResolveLocaleContextWithoutCookieAndDefaultLocale() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addPreferredLocale(Locale.TAIWAN); + + CookieLocaleResolver resolver = new CookieLocaleResolver(); + resolver.setDefaultLocale(Locale.GERMAN); + resolver.setDefaultTimeZone(TimeZone.getTimeZone("GMT+1")); + + LocaleContext loc = resolver.resolveLocaleContext(request); + assertEquals(Locale.GERMAN, loc.getLocale()); + assertTrue(loc instanceof TimeZoneAwareLocaleContext); + assertEquals(TimeZone.getTimeZone("GMT+1"), ((TimeZoneAwareLocaleContext) loc).getTimeZone()); + } + + @Test public void testResolveLocaleWithCookieWithoutLocale() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(Locale.TAIWAN); - Cookie cookie = new Cookie(CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME, ""); - request.setCookies(new Cookie[]{cookie}); + Cookie cookie = new Cookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME, ""); + request.setCookies(cookie); CookieLocaleResolver resolver = new CookieLocaleResolver(); - Locale locale = resolver.resolveLocale(request); - assertEquals(request.getLocale(), locale); + Locale loc = resolver.resolveLocale(request); + assertEquals(request.getLocale(), loc); } - public void testSetLocaleToNullLocale() throws Exception { + @Test + public void testResolveLocaleContextWithCookieWithoutLocale() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(Locale.TAIWAN); - Cookie cookie = new Cookie(CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME, Locale.UK.toString()); - request.setCookies(new Cookie[]{cookie}); + Cookie cookie = new Cookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME, ""); + request.setCookies(cookie); + + CookieLocaleResolver resolver = new CookieLocaleResolver(); + + LocaleContext loc = resolver.resolveLocaleContext(request); + assertEquals(request.getLocale(), loc.getLocale()); + assertTrue(loc instanceof TimeZoneAwareLocaleContext); + assertNull(((TimeZoneAwareLocaleContext) loc).getTimeZone()); + } + + @Test + public void testSetLocaleToNull() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addPreferredLocale(Locale.TAIWAN); + Cookie cookie = new Cookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME, Locale.UK.toString()); + request.setCookies(cookie); MockHttpServletResponse response = new MockHttpServletResponse(); CookieLocaleResolver resolver = new CookieLocaleResolver(); @@ -143,15 +289,38 @@ public class CookieLocaleResolverTests extends TestCase { Cookie[] cookies = response.getCookies(); assertEquals(1, cookies.length); Cookie localeCookie = cookies[0]; - assertEquals(CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME, localeCookie.getName()); + assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_NAME, localeCookie.getName()); assertEquals("", localeCookie.getValue()); } - public void testSetLocaleToNullLocaleWithDefault() throws Exception { + @Test + public void testSetLocaleContextToNull() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(Locale.TAIWAN); - Cookie cookie = new Cookie(CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME, Locale.UK.toString()); - request.setCookies(new Cookie[]{cookie}); + Cookie cookie = new Cookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME, Locale.UK.toString()); + request.setCookies(cookie); + MockHttpServletResponse response = new MockHttpServletResponse(); + + CookieLocaleResolver resolver = new CookieLocaleResolver(); + resolver.setLocaleContext(request, response, null); + Locale locale = (Locale) request.getAttribute(CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME); + assertEquals(Locale.TAIWAN, locale); + TimeZone timeZone = (TimeZone) request.getAttribute(CookieLocaleResolver.TIME_ZONE_REQUEST_ATTRIBUTE_NAME); + assertNull(timeZone); + + Cookie[] cookies = response.getCookies(); + assertEquals(1, cookies.length); + Cookie localeCookie = cookies[0]; + assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_NAME, localeCookie.getName()); + assertEquals("", localeCookie.getValue()); + } + + @Test + public void testSetLocaleToNullWithDefault() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addPreferredLocale(Locale.TAIWAN); + Cookie cookie = new Cookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME, Locale.UK.toString()); + request.setCookies(cookie); MockHttpServletResponse response = new MockHttpServletResponse(); CookieLocaleResolver resolver = new CookieLocaleResolver(); @@ -163,8 +332,32 @@ public class CookieLocaleResolverTests extends TestCase { Cookie[] cookies = response.getCookies(); assertEquals(1, cookies.length); Cookie localeCookie = cookies[0]; - assertEquals(CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME, localeCookie.getName()); + assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_NAME, localeCookie.getName()); assertEquals("", localeCookie.getValue()); - } + } + + @Test + public void testSetLocaleContextToNullWithDefault() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addPreferredLocale(Locale.TAIWAN); + Cookie cookie = new Cookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME, Locale.UK.toString()); + request.setCookies(cookie); + MockHttpServletResponse response = new MockHttpServletResponse(); + + CookieLocaleResolver resolver = new CookieLocaleResolver(); + resolver.setDefaultLocale(Locale.CANADA_FRENCH); + resolver.setDefaultTimeZone(TimeZone.getTimeZone("GMT+1")); + resolver.setLocaleContext(request, response, null); + Locale locale = (Locale) request.getAttribute(CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME); + assertEquals(Locale.CANADA_FRENCH, locale); + TimeZone timeZone = (TimeZone) request.getAttribute(CookieLocaleResolver.TIME_ZONE_REQUEST_ATTRIBUTE_NAME); + assertEquals(TimeZone.getTimeZone("GMT+1"), timeZone); + + Cookie[] cookies = response.getCookies(); + assertEquals(1, cookies.length); + Cookie localeCookie = cookies[0]; + assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_NAME, localeCookie.getName()); + assertEquals("", localeCookie.getValue()); + } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/LocaleResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/LocaleResolverTests.java index bb6fd2b5da..99b790eca7 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/LocaleResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/LocaleResolverTests.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,26 +17,55 @@ package org.springframework.web.servlet.i18n; import java.util.Locale; +import java.util.TimeZone; -import junit.framework.TestCase; +import org.junit.Test; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.context.i18n.SimpleLocaleContext; +import org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext; +import org.springframework.context.i18n.TimeZoneAwareLocaleContext; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.mock.web.test.MockServletContext; +import org.springframework.web.servlet.LocaleContextResolver; import org.springframework.web.servlet.LocaleResolver; +import static org.junit.Assert.*; + /** * @author Juergen Hoeller * @since 20.03.2003 */ -public class LocaleResolverTests extends TestCase { +public class LocaleResolverTests { - private void internalTest(LocaleResolver localeResolver, boolean shouldSet) { + @Test + public void testAcceptHeaderLocaleResolver() { + doTest(new AcceptHeaderLocaleResolver(), false); + } + + @Test + public void testFixedLocaleResolver() { + doTest(new FixedLocaleResolver(Locale.UK), false); + } + + @Test + public void testCookieLocaleResolver() { + doTest(new CookieLocaleResolver(), true); + } + + @Test + public void testSessionLocaleResolver() { + doTest(new SessionLocaleResolver(), true); + } + + private void doTest(LocaleResolver localeResolver, boolean shouldSet) { // create mocks MockServletContext context = new MockServletContext(); MockHttpServletRequest request = new MockHttpServletRequest(context); request.addPreferredLocale(Locale.UK); MockHttpServletResponse response = new MockHttpServletResponse(); + // check original locale Locale locale = localeResolver.resolveLocale(request); assertEquals(locale, Locale.UK); @@ -50,21 +79,68 @@ public class LocaleResolverTests extends TestCase { assertEquals(locale, Locale.GERMANY); } catch (UnsupportedOperationException ex) { - if (shouldSet) + if (shouldSet) { fail("should be able to set Locale"); + } + } + + // check LocaleContext + if (localeResolver instanceof LocaleContextResolver) { + LocaleContextResolver localeContextResolver = (LocaleContextResolver) localeResolver; + LocaleContext localeContext = localeContextResolver.resolveLocaleContext(request); + if (shouldSet) { + assertEquals(localeContext.getLocale(), Locale.GERMANY); + } + else { + assertEquals(localeContext.getLocale(), Locale.UK); + } + assertTrue(localeContext instanceof TimeZoneAwareLocaleContext); + assertNull(((TimeZoneAwareLocaleContext) localeContext).getTimeZone()); + + if (localeContextResolver instanceof AbstractLocaleContextResolver) { + ((AbstractLocaleContextResolver) localeContextResolver).setDefaultTimeZone(TimeZone.getTimeZone("GMT+1")); + assertEquals(((TimeZoneAwareLocaleContext) localeContext).getTimeZone(), TimeZone.getTimeZone("GMT+1")); + } + + try { + localeContextResolver.setLocaleContext(request, response, new SimpleLocaleContext(Locale.US)); + if (!shouldSet) { + fail("should not be able to set Locale"); + } + localeContext = localeContextResolver.resolveLocaleContext(request); + assertEquals(localeContext.getLocale(), Locale.US); + if (localeContextResolver instanceof AbstractLocaleContextResolver) { + assertEquals(((TimeZoneAwareLocaleContext) localeContext).getTimeZone(), TimeZone.getTimeZone("GMT+1")); + } + else { + assertNull(((TimeZoneAwareLocaleContext) localeContext).getTimeZone()); + } + + localeContextResolver.setLocaleContext(request, response, + new SimpleTimeZoneAwareLocaleContext(Locale.GERMANY, TimeZone.getTimeZone("GMT+2"))); + localeContext = localeContextResolver.resolveLocaleContext(request); + assertEquals(localeContext.getLocale(), Locale.GERMANY); + assertTrue(localeContext instanceof TimeZoneAwareLocaleContext); + assertEquals(((TimeZoneAwareLocaleContext) localeContext).getTimeZone(), TimeZone.getTimeZone("GMT+2")); + + localeContextResolver.setLocaleContext(request, response, + new SimpleTimeZoneAwareLocaleContext(null, TimeZone.getTimeZone("GMT+3"))); + localeContext = localeContextResolver.resolveLocaleContext(request); + assertEquals(localeContext.getLocale(), Locale.UK); + assertTrue(localeContext instanceof TimeZoneAwareLocaleContext); + assertEquals(((TimeZoneAwareLocaleContext) localeContext).getTimeZone(), TimeZone.getTimeZone("GMT+3")); + + if (localeContextResolver instanceof AbstractLocaleContextResolver) { + ((AbstractLocaleContextResolver) localeContextResolver).setDefaultLocale(Locale.GERMANY); + assertEquals(localeContext.getLocale(), Locale.GERMANY); + } + } + catch (UnsupportedOperationException ex) { + if (shouldSet) { + fail("should be able to set Locale"); + } + } } } - public void testAcceptHeaderLocaleResolver() { - internalTest(new AcceptHeaderLocaleResolver(), false); - } - - public void testCookieLocaleResolver() { - internalTest(new CookieLocaleResolver(), true); - } - - public void testSessionLocaleResolver() { - internalTest(new SessionLocaleResolver(), true); - } - } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/SessionLocaleResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/SessionLocaleResolverTests.java index 8996c227c1..3f4f8db234 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/SessionLocaleResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/SessionLocaleResolverTests.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,19 +17,21 @@ package org.springframework.web.servlet.i18n; import java.util.Locale; - import javax.servlet.http.HttpSession; -import junit.framework.TestCase; +import org.junit.Test; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; +import static org.junit.Assert.*; + /** * @author Juergen Hoeller */ -public class SessionLocaleResolverTests extends TestCase { +public class SessionLocaleResolverTests { + @Test public void testResolveLocale() { MockHttpServletRequest request = new MockHttpServletRequest(); request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, Locale.GERMAN); @@ -38,6 +40,7 @@ public class SessionLocaleResolverTests extends TestCase { assertEquals(Locale.GERMAN, resolver.resolveLocale(request)); } + @Test public void testSetAndResolveLocale() { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -54,6 +57,7 @@ public class SessionLocaleResolverTests extends TestCase { assertEquals(Locale.GERMAN, resolver.resolveLocale(request)); } + @Test public void testResolveLocaleWithoutSession() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(Locale.TAIWAN); @@ -63,6 +67,7 @@ public class SessionLocaleResolverTests extends TestCase { assertEquals(request.getLocale(), resolver.resolveLocale(request)); } + @Test public void testResolveLocaleWithoutSessionAndDefaultLocale() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(Locale.TAIWAN); @@ -73,6 +78,7 @@ public class SessionLocaleResolverTests extends TestCase { assertEquals(Locale.GERMAN, resolver.resolveLocale(request)); } + @Test public void testSetLocaleToNullLocale() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(Locale.TAIWAN); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolverTests.java index 0b0afcee8d..706cfacd5b 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolverTests.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. @@ -20,7 +20,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.HttpSession; @@ -35,15 +37,15 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.multipart.MultipartRequest; -import org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.i18n.FixedLocaleResolver; import static org.junit.Assert.*; /** - * Test fixture with {@link ServletRequestMethodArgumentResolver}. - * * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Nicholas Williams */ public class ServletRequestMethodArgumentResolverTests { @@ -60,7 +62,8 @@ public class ServletRequestMethodArgumentResolverTests { @Before public void setUp() throws Exception { method = getClass().getMethod("supportedParams", ServletRequest.class, MultipartRequest.class, - HttpSession.class, Principal.class, Locale.class, InputStream.class, Reader.class, WebRequest.class); + HttpSession.class, Principal.class, Locale.class, InputStream.class, Reader.class, + WebRequest.class, TimeZone.class, ZoneId.class); mavContainer = new ModelAndViewContainer(); servletRequest = new MockHttpServletRequest(); webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse()); @@ -121,6 +124,65 @@ public class ServletRequestMethodArgumentResolverTests { assertSame("Invalid result", locale, result); } + @Test + public void localeFromResolver() throws Exception { + Locale locale = Locale.ENGLISH; + servletRequest.setAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE, + new FixedLocaleResolver(locale)); + MethodParameter localeParameter = new MethodParameter(method, 4); + + assertTrue("Locale not supported", resolver.supportsParameter(localeParameter)); + + Object result = resolver.resolveArgument(localeParameter, null, webRequest, null); + assertSame("Invalid result", locale, result); + } + + @Test + public void timeZone() throws Exception { + MethodParameter timeZoneParameter = new MethodParameter(method, 8); + + assertTrue("TimeZone not supported", resolver.supportsParameter(timeZoneParameter)); + + Object result = resolver.resolveArgument(timeZoneParameter, null, webRequest, null); + assertEquals("Invalid result", TimeZone.getDefault(), result); + } + + @Test + public void timeZoneFromResolver() throws Exception { + TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles"); + servletRequest.setAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE, + new FixedLocaleResolver(Locale.US, timeZone)); + MethodParameter timeZoneParameter = new MethodParameter(method, 8); + + assertTrue("TimeZone not supported", resolver.supportsParameter(timeZoneParameter)); + + Object result = resolver.resolveArgument(timeZoneParameter, null, webRequest, null); + assertEquals("Invalid result", timeZone, result); + } + + @Test + public void zoneId() throws Exception { + MethodParameter zoneIdParameter = new MethodParameter(method, 9); + + assertTrue("ZoneId not supported", resolver.supportsParameter(zoneIdParameter)); + + Object result = resolver.resolveArgument(zoneIdParameter, null, webRequest, null); + assertEquals("Invalid result", ZoneId.systemDefault(), result); + } + + @Test + public void zoneIdFromResolver() throws Exception { + TimeZone timeZone = TimeZone.getTimeZone("America/New_York"); + servletRequest.setAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE, + new FixedLocaleResolver(Locale.US, timeZone)); + MethodParameter zoneIdParameter = new MethodParameter(method, 9); + + assertTrue("ZoneId not supported", resolver.supportsParameter(zoneIdParameter)); + + Object result = resolver.resolveArgument(zoneIdParameter, null, webRequest, null); + assertEquals("Invalid result", timeZone.toZoneId(), result); + } + @Test public void inputStream() throws Exception { MethodParameter inputStreamParameter = new MethodParameter(method, 5); @@ -151,6 +213,7 @@ public class ServletRequestMethodArgumentResolverTests { assertSame("Invalid result", webRequest, result); } + @SuppressWarnings("unused") public void supportedParams(ServletRequest p0, MultipartRequest p1, HttpSession p2, @@ -158,7 +221,9 @@ public class ServletRequestMethodArgumentResolverTests { Locale p4, InputStream p5, Reader p6, - WebRequest p7) { + WebRequest p7, + TimeZone p8, + ZoneId p9) { } } \ No newline at end of file