Introduce LocaleContextResolver in WebFlux

This commit introduces LocaleContextResolver interface, which is used
at ServerWebExchange level to resolve Locale, TimeZone and other i18n
related informations.

It follows Spring MVC locale resolution patterns with a few differences:
 - Only LocaleContextResolver is supported since LocaleResolver is less
   flexible
 - Support is implemented in the org.springframework.web.server.i18n
   package of spring-web module rather than in spring-webflux in order
   to be able to leverage it at ServerWebExchange level

2 implementations are provided:
 - FixedLocaleContextResolver
 - AcceptHeaderLocaleContextResolver

It can be configured with both functional or annotation-based APIs.

Issue: SPR-15036
This commit is contained in:
Sebastien Deleuze
2017-06-06 09:31:58 +02:00
parent 72a8868f84
commit e0e6736bc5
28 changed files with 853 additions and 27 deletions

View File

@@ -24,11 +24,13 @@ import java.util.function.Consumer;
import reactor.core.publisher.Mono;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.i18n.LocaleContextResolver;
/**
* Contract for an HTTP request-response interaction. Provides access to the HTTP
@@ -98,6 +100,11 @@ public interface ServerWebExchange {
*/
Mono<MultiValueMap<String, Part>> getMultipartData();
/**
* Return the {@link LocaleContext} using the configured {@link LocaleContextResolver}.
*/
LocaleContext getLocaleContext();
/**
* Returns {@code true} if the one of the {@code checkNotModified} methods
* in this contract were used and they returned true.

View File

@@ -22,6 +22,7 @@ import java.util.Optional;
import reactor.core.publisher.Mono;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
@@ -90,6 +91,11 @@ public class ServerWebExchangeDecorator implements ServerWebExchange {
return getDelegate().getPrincipal();
}
@Override
public LocaleContext getLocaleContext() {
return getDelegate().getLocaleContext();
}
@Override
public Mono<MultiValueMap<String, String>> getFormData() {
return getDelegate().getFormData();

View File

@@ -28,6 +28,7 @@ import java.util.concurrent.ConcurrentHashMap;
import reactor.core.publisher.Mono;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -45,6 +46,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.i18n.LocaleContextResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.session.WebSessionManager;
@@ -82,6 +84,8 @@ public class DefaultServerWebExchange implements ServerWebExchange {
private final Mono<WebSession> sessionMono;
private final LocaleContextResolver localeContextResolver;
private final Mono<MultiValueMap<String, String>> formDataMono;
private final Mono<MultiValueMap<String, Part>> multipartDataMono;
@@ -89,20 +93,19 @@ public class DefaultServerWebExchange implements ServerWebExchange {
private volatile boolean notModified;
/**
* Alternate constructor with a WebSessionManager parameter.
*/
public DefaultServerWebExchange(ServerHttpRequest request, ServerHttpResponse response,
WebSessionManager sessionManager, ServerCodecConfigurer codecConfigurer) {
WebSessionManager sessionManager, ServerCodecConfigurer codecConfigurer, LocaleContextResolver localeContextResolver) {
Assert.notNull(request, "'request' is required");
Assert.notNull(response, "'response' is required");
Assert.notNull(sessionManager, "'sessionManager' is required");
Assert.notNull(codecConfigurer, "'codecConfigurer' is required");
Assert.notNull(localeContextResolver, "'localeContextResolver' is required");
this.request = request;
this.response = response;
this.sessionMono = sessionManager.getSession(this).cache();
this.localeContextResolver = localeContextResolver;
this.formDataMono = initFormData(request, codecConfigurer);
this.multipartDataMono = initMultipartData(request, codecConfigurer);
}
@@ -190,6 +193,11 @@ public class DefaultServerWebExchange implements ServerWebExchange {
return Mono.empty();
}
@Override
public LocaleContext getLocaleContext() {
return this.localeContextResolver.resolveLocaleContext(this);
}
@Override
public Mono<MultiValueMap<String, String>> getFormData() {
return this.formDataMono;

View File

@@ -31,9 +31,11 @@ import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.server.i18n.LocaleContextResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.handler.WebHandlerDecorator;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
@@ -83,6 +85,8 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
private ServerCodecConfigurer codecConfigurer;
private LocaleContextResolver localeContextResolver;
public HttpWebHandlerAdapter(WebHandler delegate) {
super(delegate);
@@ -119,6 +123,16 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
this.codecConfigurer = codecConfigurer;
}
/**
* Configure a custom {@link LocaleContextResolver}. The provided instance is set on
* each created {@link DefaultServerWebExchange}.
* <p>By default this is set to {@link org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver}.
* @param localeContextResolver the locale context resolver to use
*/
public void setLocaleContextResolver(LocaleContextResolver localeContextResolver) {
this.localeContextResolver = localeContextResolver;
}
/**
* Return the configured {@link ServerCodecConfigurer}.
*/
@@ -126,6 +140,13 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
return (this.codecConfigurer != null ? this.codecConfigurer : ServerCodecConfigurer.create());
}
/**
* Return the configured {@link LocaleContextResolver}.
*/
public LocaleContextResolver getLocaleContextResolver() {
return (this.localeContextResolver != null ? this.localeContextResolver : new AcceptHeaderLocaleContextResolver());
}
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
@@ -140,7 +161,7 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
}
protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttpResponse response) {
return new DefaultServerWebExchange(request, response, this.sessionManager, getCodecConfigurer());
return new DefaultServerWebExchange(request, response, this.sessionManager, getCodecConfigurer(), getLocaleContextResolver());
}
private void logHandleFailure(Throwable ex) {

View File

@@ -27,6 +27,7 @@ import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.i18n.LocaleContextResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import org.springframework.web.server.WebFilter;
@@ -68,6 +69,9 @@ public class WebHttpHandlerBuilder {
/** Well-known name for the ServerCodecConfigurer in the bean factory. */
public static final String SERVER_CODEC_CONFIGURER_BEAN_NAME = "serverCodecConfigurer";
/** Well-known name for the LocaleContextResolver in the bean factory. */
public static final String LOCALE_CONTEXT_RESOLVER_BEAN_NAME = "localeContextResolver";
private final WebHandler webHandler;
@@ -79,6 +83,8 @@ public class WebHttpHandlerBuilder {
private ServerCodecConfigurer codecConfigurer;
private LocaleContextResolver localeContextResolver;
/**
* Private constructor.
@@ -112,6 +118,8 @@ public class WebHttpHandlerBuilder {
* {@link #WEB_SESSION_MANAGER_BEAN_NAME}.
* <li>{@link ServerCodecConfigurer} [0..1] -- looked up by the name
* {@link #SERVER_CODEC_CONFIGURER_BEAN_NAME}.
*<li>{@link LocaleContextResolver} [0..1] -- looked up by the name
* {@link #LOCALE_CONTEXT_RESOLVER_BEAN_NAME}.
* </ul>
* @param context the application context to use for the lookup
* @return the prepared builder
@@ -144,6 +152,14 @@ public class WebHttpHandlerBuilder {
// Fall back on default
}
try {
builder.localeContextResolver(
context.getBean(LOCALE_CONTEXT_RESOLVER_BEAN_NAME, LocaleContextResolver.class));
}
catch (NoSuchBeanDefinitionException ex) {
// Fall back on default
}
return builder;
}
@@ -234,6 +250,16 @@ that's */
return this;
}
/**
* Configure the {@link LocaleContextResolver} to set on the
* {@link ServerWebExchange WebServerExchange}.
* @param localeContextResolver the locale context resolver
*/
public WebHttpHandlerBuilder localeContextResolver(LocaleContextResolver localeContextResolver) {
this.localeContextResolver = localeContextResolver;
return this;
}
/**
* Build the {@link HttpHandler}.
@@ -252,6 +278,9 @@ that's */
if (this.codecConfigurer != null) {
adapted.setCodecConfigurer(this.codecConfigurer);
}
if (this.localeContextResolver != null) {
adapted.setLocaleContextResolver(this.localeContextResolver);
}
return adapted;
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2002-2017 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.server.i18n;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.SimpleLocaleContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerWebExchange;
/**
* {@link LocaleContextResolver} implementation that simply uses the primary locale
* specified in the "Accept-Language" header of the HTTP request (that is,
* the locale sent by the client browser, normally that of the client's OS).
*
* <p>Note: Does not support {@code setLocale}, since the accept header
* can only be changed through changing the client's locale settings.
*
* @author Sebastien Deleuze
* @since 5.0
*/
public class AcceptHeaderLocaleContextResolver implements LocaleContextResolver {
private final List<Locale> supportedLocales = new ArrayList<>(4);
private Locale defaultLocale;
/**
* Configure supported locales to check against the requested locales
* determined via {@link HttpHeaders#getAcceptLanguageAsLocales()}.
* @param locales the supported locales
*/
public void setSupportedLocales(List<Locale> locales) {
this.supportedLocales.clear();
if (locales != null) {
this.supportedLocales.addAll(locales);
}
}
/**
* Return the configured list of supported locales.
*/
public List<Locale> getSupportedLocales() {
return this.supportedLocales;
}
/**
* Configure a fixed default locale to fall back on if the request does not
* have an "Accept-Language" header (not set by default).
* @param defaultLocale the default locale to use
*/
public void setDefaultLocale(Locale defaultLocale) {
this.defaultLocale = defaultLocale;
}
/**
* The configured default locale, if any.
*/
@Nullable
public Locale getDefaultLocale() {
return this.defaultLocale;
}
@Override
public LocaleContext resolveLocaleContext(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
List<Locale> acceptableLocales = request.getHeaders().getAcceptLanguageAsLocales();
if (this.defaultLocale != null && acceptableLocales.isEmpty()) {
return new SimpleLocaleContext(this.defaultLocale);
}
Locale requestLocale = acceptableLocales.isEmpty() ? null : acceptableLocales.get(0);
if (isSupportedLocale(requestLocale)) {
return new SimpleLocaleContext(requestLocale);
}
Locale supportedLocale = findSupportedLocale(request);
if (supportedLocale != null) {
return new SimpleLocaleContext(supportedLocale);
}
return (defaultLocale != null ? new SimpleLocaleContext(defaultLocale) : new SimpleLocaleContext(requestLocale));
}
private boolean isSupportedLocale(@Nullable Locale locale) {
if (locale == null) {
return false;
}
List<Locale> supportedLocales = getSupportedLocales();
return (supportedLocales.isEmpty() || supportedLocales.contains(locale));
}
@Nullable
private Locale findSupportedLocale(ServerHttpRequest request) {
List<Locale> requestLocales = request.getHeaders().getAcceptLanguageAsLocales();
for (Locale locale : requestLocales) {
if (getSupportedLocales().contains(locale)) {
return locale;
}
}
return null;
}
@Override
public void setLocaleContext(ServerWebExchange exchange, @Nullable LocaleContext locale) {
throw new UnsupportedOperationException(
"Cannot change HTTP accept header - use a different locale context resolution strategy");
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.server.i18n;
import java.util.Locale;
import java.util.TimeZone;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
/**
* {@link LocaleContextResolver} implementation
* 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(Context)}, as the fixed
* locale and time zone cannot be changed.
*
* @author Sebastien Deleuze
* @since 5.0
*/
public class FixedLocaleContextResolver implements LocaleContextResolver {
private final Locale locale;
private final TimeZone timeZone;
/**
* Create a default FixedLocaleResolver, exposing a configured default
* locale (or the JVM's default locale as fallback).
*/
public FixedLocaleContextResolver() {
this(Locale.getDefault());
}
/**
* Create a FixedLocaleResolver that exposes the given locale.
* @param locale the locale to expose
*/
public FixedLocaleContextResolver(Locale locale) {
this(locale, null);
}
/**
* 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 FixedLocaleContextResolver(Locale locale, @Nullable TimeZone timeZone) {
Assert.notNull(locale, "Locale must not be null");
this.locale = locale;
this.timeZone = timeZone;
}
@Override
public LocaleContext resolveLocaleContext(ServerWebExchange exchange) {
return new TimeZoneAwareLocaleContext() {
@Override
public Locale getLocale() {
return locale;
}
@Override
public TimeZone getTimeZone() {
return timeZone;
}
};
}
@Override
public void setLocaleContext(ServerWebExchange exchange, @Nullable LocaleContext localeContext) {
throw new UnsupportedOperationException("Cannot change fixed locale - use a different locale context resolution strategy");
}
}

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.web.server.i18n;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerWebExchange;
/**
* Interface for web-based locale context resolution strategies that allows
* for both locale context resolution via the request and locale context modification
* via the HTTP exchange.
*
* <p>The {@link org.springframework.context.i18n.LocaleContext} object can potentially
* includes associated time zone and other locale related information.
*
* @author Sebastien Deleuze
* @since 5.0
* @see LocaleContext
*/
public interface LocaleContextResolver {
/**
* Resolve the current locale context via the given exchange.
*
* <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 exchange current server exchange
* @return the current locale context (never {@code null}
*/
LocaleContext resolveLocaleContext(ServerWebExchange exchange);
/**
* Set the current locale context to the given one,
* potentially including a locale with associated time zone information.
* @param exchange current server exchange
* @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 org.springframework.context.i18n.SimpleLocaleContext
* @see org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext
*/
void setLocaleContext(ServerWebExchange exchange, @Nullable LocaleContext localeContext);
}

View File

@@ -0,0 +1,8 @@
/**
* Locale related support classes.
* Provides standard LocaleContextResolver implementations.
*/
@NonNullApi
package org.springframework.web.server.i18n;
import org.springframework.lang.NonNullApi;

View File

@@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import org.reactivestreams.Publisher;
@@ -260,6 +261,13 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
*/
B acceptCharset(Charset... acceptableCharsets);
/**
* Set the list of acceptable {@linkplain Locale locales}, as specified
* by the {@code Accept-Languages} header.
* @param acceptableLocales the acceptable locales
*/
B acceptLanguageAsLocales(Locale... acceptableLocales);
/**
* Set the value of the {@code If-Modified-Since} header.
* <p>The date should be specified as the number of milliseconds since
@@ -420,6 +428,12 @@ public class MockServerHttpRequest extends AbstractServerHttpRequest {
return this;
}
@Override
public BodyBuilder acceptLanguageAsLocales(Locale... acceptableLocales) {
this.headers.setAcceptLanguageAsLocales(Arrays.asList(acceptableLocales));
return this;
}
@Override
public BodyBuilder contentLength(long contentLength) {
this.headers.setContentLength(contentLength);

View File

@@ -18,6 +18,7 @@ package org.springframework.mock.http.server.reactive.test;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.server.ServerWebExchangeDecorator;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.session.DefaultWebSessionManager;
/**
@@ -36,7 +37,8 @@ public class MockServerWebExchange extends ServerWebExchangeDecorator {
public MockServerWebExchange(MockServerHttpRequest request) {
super(new DefaultServerWebExchange(
request, new MockServerHttpResponse(), new DefaultWebSessionManager(), ServerCodecConfigurer.create()));
request, new MockServerHttpResponse(), new DefaultWebSessionManager(),
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()));
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2002-2017 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.server.i18n;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import org.junit.Test;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import static java.util.Locale.*;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link AcceptHeaderLocaleContextResolver}.
*
* @author Sebastien Deleuze
*/
public class AcceptHeaderLocaleContextResolverTests {
private AcceptHeaderLocaleContextResolver resolver = new AcceptHeaderLocaleContextResolver();
@Test
public void resolve() throws Exception {
assertEquals(CANADA, this.resolver.resolveLocaleContext(exchange(CANADA)).getLocale());
assertEquals(US, this.resolver.resolveLocaleContext(exchange(US, CANADA)).getLocale());
}
@Test
public void resolvePreferredSupported() throws Exception {
this.resolver.setSupportedLocales(Collections.singletonList(CANADA));
assertEquals(CANADA, this.resolver.resolveLocaleContext(exchange(US, CANADA)).getLocale());
}
@Test
public void resolvePreferredNotSupported() throws Exception {
this.resolver.setSupportedLocales(Collections.singletonList(CANADA));
assertEquals(US, this.resolver.resolveLocaleContext(exchange(US, UK)).getLocale());
}
@Test
public void resolvePreferredNotSupportedWithDefault() {
this.resolver.setSupportedLocales(Arrays.asList(US, JAPAN));
this.resolver.setDefaultLocale(JAPAN);
MockServerWebExchange exchange = new MockServerWebExchange(MockServerHttpRequest
.get("/")
.acceptLanguageAsLocales(KOREA)
.build());
assertEquals(JAPAN, this.resolver.resolveLocaleContext(exchange).getLocale());
}
@Test
public void defaultLocale() throws Exception {
this.resolver.setDefaultLocale(JAPANESE);
MockServerWebExchange exchange = new MockServerWebExchange(MockServerHttpRequest
.get("/")
.build());
assertEquals(JAPANESE, this.resolver.resolveLocaleContext(exchange).getLocale());
exchange = new MockServerWebExchange(MockServerHttpRequest
.get("/")
.acceptLanguageAsLocales(US)
.build());
assertEquals(US, this.resolver.resolveLocaleContext(exchange).getLocale());
}
private ServerWebExchange exchange(Locale... locales) {
return new MockServerWebExchange(MockServerHttpRequest
.get("")
.acceptLanguageAsLocales(locales)
.build());
}
}

View File

@@ -0,0 +1,64 @@
package org.springframework.web.server.i18n;
import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import static java.util.Locale.CANADA;
import static java.util.Locale.FRANCE;
import static java.util.Locale.US;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link FixedLocaleContextResolver}.
*
* @author Sebastien Deleuze
*/
public class FixedLocaleContextResolverTests {
private FixedLocaleContextResolver resolver;
@Before
public void setup() {
Locale.setDefault(US);
}
@Test
public void resolveDefaultLocale() {
this.resolver = new FixedLocaleContextResolver();
assertEquals(US, this.resolver.resolveLocaleContext(exchange()).getLocale());
assertEquals(US, this.resolver.resolveLocaleContext(exchange(CANADA)).getLocale());
}
@Test
public void resolveCustomizedLocale() {
this.resolver = new FixedLocaleContextResolver(FRANCE);
assertEquals(FRANCE, this.resolver.resolveLocaleContext(exchange()).getLocale());
assertEquals(FRANCE, this.resolver.resolveLocaleContext(exchange(CANADA)).getLocale());
}
@Test
public void resolveCustomizedAndTimeZoneLocale() {
TimeZone timeZone = TimeZone.getTimeZone(ZoneId.of("UTC"));
this.resolver = new FixedLocaleContextResolver(FRANCE, timeZone);
TimeZoneAwareLocaleContext context = (TimeZoneAwareLocaleContext)this.resolver.resolveLocaleContext(exchange());
assertEquals(FRANCE, context.getLocale());
assertEquals(timeZone, context.getTimeZone());
}
private ServerWebExchange exchange(Locale... locales) {
return new MockServerWebExchange(MockServerHttpRequest
.get("")
.acceptLanguageAsLocales(locales)
.build());
}
}

View File

@@ -32,6 +32,7 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -60,7 +61,8 @@ public class DefaultWebSessionManagerTests {
MockServerHttpRequest request = MockServerHttpRequest.get("/path").build();
MockServerHttpResponse response = new MockServerHttpResponse();
this.exchange = new DefaultServerWebExchange(request, response, this.manager, ServerCodecConfigurer.create());
this.exchange = new DefaultServerWebExchange(request, response, this.manager,
ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver());
}