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

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