Comprehensive update to the framework's TimeZone handling, including a new TimeZoneAwareLocaleContext and a LocaleContextResolver for Spring MVC

A few noteworthy minor changes: LocaleContext.getLocale() may return null in special cases (not by default), which our own accessing classes are able to handle now. If there is a non-null TimeZone user setting, we're exposing it to all collaborating libraries, in particular to JSTL, Velocity and JasperReports. Our JSR-310 and Joda-Time support falls back to checking the general LocaleContext TimeZone now, adapting it to their time zone types, if no more specific setting has been provided. Our DefaultConversionService has TimeZone<->ZoneId converters registered. And finally, we're using a custom parseTimeZoneString method now that doesn't accept the TimeZone.getTimeZone(String) GMT fallback for an invalid time zone id anymore.

Issue: SPR-1528
This commit is contained in:
Juergen Hoeller 2013-10-04 23:14:08 +02:00
parent 52cca48f40
commit 4574528a27
35 changed files with 1570 additions and 270 deletions

View File

@ -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

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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,
* <i>not</i> exposing it as inheritable for child threads.
* <p>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.
* <p>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.
* <p>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.
* <p>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();
}
}

View File

@ -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;
}

View File

@ -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}.
*
* <p>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();
}
}

View File

@ -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.
*
* <p>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();
}

View File

@ -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.
* <p>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;
}

View File

@ -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 <code>java.time</code> (JSR-310) settings
@ -52,6 +57,11 @@ public class DateTimeContext {
/**
* Set the user's time zone.
* <p>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;
}

View File

@ -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();

View File

@ -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());
}
}
}

View File

@ -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}.
*
* <p>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<TimeZone, ZoneId> {
@Override
public ZoneId convert(TimeZone source) {
return source.toZoneId();
}
}

View File

@ -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}.
*
* <p>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<ZoneId, TimeZone> {
@Override
public TimeZone convert(ZoneId source) {
return TimeZone.getTimeZone(source);
}
}

View File

@ -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}.
* <p>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

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
};
}
}
/**

View File

@ -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.
* <p>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.
* <p>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.
* <p>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);
}

View File

@ -34,8 +34,19 @@ import javax.servlet.http.HttpServletResponse;
* to retrieve the current locale in controllers or views, independent
* of the actual resolution strategy.
*
* <p>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 {

View File

@ -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.
*
* <p>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));
}
}

View File

@ -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 {

View File

@ -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.
*
* <p>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.
*
* <p>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.
* <p>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.
* <p>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!
* <p>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.
* <p>Only used for overriding a cookie value if the locale has been
* changed in the course of the current request!
* <p>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.
* <p>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();
}
}

View File

@ -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.
*
* <p>Note: Does not support {@code setLocale}, as the fixed locale
* cannot be changed.
* <p>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");
}
}

View File

@ -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;
*
* <p>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.
*
* <p>Custom controllers can override the user's locale by calling
* {@code setLocale}, e.g. responding to a locale change request.
* <p>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()}
* <p>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.
* <p>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.
* <p>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.
* <p>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();
}
}

View File

@ -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;
* <li>{@link HttpSession}
* <li>{@link Principal}
* <li>{@link Locale}
* <li>{@link TimeZone} (as of Spring 4.0)
* <li>{@link java.time.ZoneId} (as of Spring 4.0 and Java 8)</li>
* <li>{@link InputStream}
* <li>{@link Reader}
* </ul>
*
* @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());
}
}
}

View File

@ -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());

View File

@ -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.
*
* <p>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;
* <p>Can be instantiated manually, or automatically exposed to views as model attribute via AbstractView's
* "requestContextAttribute" property.
*
* <p>Will also work outside of DispatcherServlet requests, accessing the root WebApplicationContext and using an
* appropriate fallback for the locale (the HttpServletRequest's primary locale).
* <p>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. <p>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.
* <p>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. <p>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. <p><b>Will only work within a
* DispatcherServlet request.</b> 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.
* <p>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.
* <p><b>Will only work within a DispatcherServlet request.</b>
* 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. <p>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. <p>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.
* <p>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.
* <p><b>Will only work within a DispatcherServlet request.</b>
* 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.
* <p>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.
* <p>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. <p>This
* works with all View implementations. It will typically be used by View implementations. <p><b>Will only work
* within a DispatcherServlet request.</b> 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.
* <p>This works with all View implementations. It will typically be used by View implementations.
* <p><b>Will only work within a DispatcherServlet request.</b>
* 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. <p>This
* works with all View implementations. It will typically be used by View implementations. <p>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.
* <p>This works with all View implementations. It will typically be used by View implementations.
* <p>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.
* <p>The default implementation checks for a JSTL locale attribute in request,
* session or application scope; if not found, returns the
* {@code HttpServletRequest.getLocale()}.
* <p>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.
* <p>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.
* <p>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}).
* <p>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).
* <p>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}).
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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. <p><b>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.
* <p>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. <p><b>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.
* <p>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);
}
}
}

View File

@ -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.
* <p>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.
* <p>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.
* <p>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.
* <p>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;
}
/**

View File

@ -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<String, Object> 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));
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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) {
}
}