Browse Source
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-15036pull/1459/merge
28 changed files with 853 additions and 27 deletions
@ -0,0 +1,127 @@
@@ -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"); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,92 @@
@@ -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"); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,63 @@
@@ -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); |
||||
|
||||
} |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
/** |
||||
* Locale related support classes. |
||||
* Provides standard LocaleContextResolver implementations. |
||||
*/ |
||||
@NonNullApi |
||||
package org.springframework.web.server.i18n; |
||||
|
||||
import org.springframework.lang.NonNullApi; |
@ -0,0 +1,95 @@
@@ -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()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,64 @@
@@ -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()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
/* |
||||
* 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.reactive.function.server; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.Test; |
||||
import reactor.core.publisher.Mono; |
||||
import reactor.test.StepVerifier; |
||||
|
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.web.reactive.function.client.ClientResponse; |
||||
import org.springframework.web.reactive.function.client.WebClient; |
||||
import org.springframework.web.reactive.result.view.View; |
||||
import org.springframework.web.reactive.result.view.ViewResolver; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import org.springframework.web.server.i18n.FixedLocaleContextResolver; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
|
||||
/** |
||||
* @author Sebastien Deleuze |
||||
*/ |
||||
public class LocaleContextResolverIntegrationTests extends AbstractRouterFunctionIntegrationTests { |
||||
|
||||
private final WebClient webClient = WebClient.create(); |
||||
|
||||
@Test |
||||
public void fixedLocale() { |
||||
Mono<ClientResponse> result = webClient |
||||
.get() |
||||
.uri("http://localhost:" + this.port + "/") |
||||
.exchange(); |
||||
|
||||
StepVerifier |
||||
.create(result) |
||||
.consumeNextWith(response -> { |
||||
assertEquals(HttpStatus.OK, response.statusCode()); |
||||
assertEquals(Locale.GERMANY, response.headers().asHttpHeaders().getContentLanguage()); |
||||
}) |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Override |
||||
protected RouterFunction<?> routerFunction() { |
||||
return RouterFunctions.route(RequestPredicates.path("/"), this::render); |
||||
} |
||||
|
||||
public Mono<RenderingResponse> render(ServerRequest request) { |
||||
return RenderingResponse.create("foo").build(); |
||||
} |
||||
|
||||
@Override |
||||
protected HandlerStrategies handlerStrategies() { |
||||
return HandlerStrategies.builder() |
||||
.viewResolver(new DummyViewResolver()) |
||||
.localeContextResolver(new FixedLocaleContextResolver(Locale.GERMANY)) |
||||
.build(); |
||||
} |
||||
|
||||
private static class DummyViewResolver implements ViewResolver { |
||||
|
||||
@Override |
||||
public Mono<View> resolveViewName(String viewName, Locale locale) { |
||||
return Mono.just(new DummyView(locale)); |
||||
} |
||||
} |
||||
|
||||
private static class DummyView implements View { |
||||
|
||||
private final Locale locale; |
||||
|
||||
public DummyView(Locale locale) { |
||||
this.locale = locale; |
||||
} |
||||
|
||||
@Override |
||||
public List<MediaType> getSupportedMediaTypes() { |
||||
return Collections.singletonList(MediaType.TEXT_HTML); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<Void> render(@Nullable Map<String, ?> model, @Nullable MediaType contentType, |
||||
ServerWebExchange exchange) { |
||||
exchange.getResponse().getHeaders().setContentLanguage(locale); |
||||
return Mono.empty(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,129 @@
@@ -0,0 +1,129 @@
|
||||
/* |
||||
* 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.reactive.result.view; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.Test; |
||||
import reactor.core.publisher.Mono; |
||||
import reactor.test.StepVerifier; |
||||
|
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
||||
import org.springframework.context.annotation.ComponentScan; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.reactive.config.ViewResolverRegistry; |
||||
import org.springframework.web.reactive.config.WebFluxConfigurationSupport; |
||||
import org.springframework.web.reactive.function.client.ClientResponse; |
||||
import org.springframework.web.reactive.function.client.WebClient; |
||||
import org.springframework.web.reactive.result.method.annotation.AbstractRequestMappingIntegrationTests; |
||||
import org.springframework.web.server.i18n.LocaleContextResolver; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import org.springframework.web.server.i18n.FixedLocaleContextResolver; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
|
||||
/** |
||||
* @author Sebastien Deleuze |
||||
*/ |
||||
public class LocaleContextResolverIntegrationTests extends AbstractRequestMappingIntegrationTests { |
||||
|
||||
private final WebClient webClient = WebClient.create(); |
||||
|
||||
@Test |
||||
public void fixedLocale() { |
||||
Mono<ClientResponse> result = webClient |
||||
.get() |
||||
.uri("http://localhost:" + this.port + "/") |
||||
.exchange(); |
||||
|
||||
StepVerifier |
||||
.create(result) |
||||
.consumeNextWith(response -> { |
||||
assertEquals(HttpStatus.OK, response.statusCode()); |
||||
assertEquals(Locale.GERMANY, response.headers().asHttpHeaders().getContentLanguage()); |
||||
}) |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Override |
||||
protected ApplicationContext initApplicationContext() { |
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); |
||||
context.register(WebConfig.class); |
||||
context.refresh(); |
||||
return context; |
||||
} |
||||
|
||||
|
||||
@Configuration |
||||
@ComponentScan(resourcePattern = "**/LocaleContextResolverIntegrationTests*.class") |
||||
@SuppressWarnings({"unused", "WeakerAccess"}) |
||||
static class WebConfig extends WebFluxConfigurationSupport { |
||||
|
||||
@Override |
||||
protected LocaleContextResolver createLocaleContextResolver() { |
||||
return new FixedLocaleContextResolver(Locale.GERMANY); |
||||
} |
||||
|
||||
@Override |
||||
protected void configureViewResolvers(ViewResolverRegistry registry) { |
||||
registry.viewResolver((viewName, locale) -> Mono.just(new DummyView(locale))); |
||||
} |
||||
|
||||
private static class DummyView implements View { |
||||
|
||||
private final Locale locale; |
||||
|
||||
public DummyView(Locale locale) { |
||||
this.locale = locale; |
||||
} |
||||
|
||||
@Override |
||||
public List<MediaType> getSupportedMediaTypes() { |
||||
return Collections.singletonList(MediaType.TEXT_HTML); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<Void> render(@Nullable Map<String, ?> model, @Nullable MediaType contentType, |
||||
ServerWebExchange exchange) { |
||||
exchange.getResponse().getHeaders().setContentLanguage(locale); |
||||
return Mono.empty(); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
@Controller |
||||
@SuppressWarnings("unused") |
||||
static class TestController { |
||||
|
||||
@GetMapping("/") |
||||
public String foo() { |
||||
return "foo"; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue