/* * 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.config; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; import javax.xml.bind.annotation.XmlRootElement; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.core.codec.StringDecoder; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.http.codec.xml.Jaxb2XmlDecoder; import org.springframework.http.codec.xml.Jaxb2XmlEncoder; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; import org.springframework.util.MultiValueMap; import org.springframework.validation.Validator; import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.bind.support.WebExchangeDataBinder; import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.handler.AbstractHandlerMapping; import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler; import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler; import org.springframework.web.reactive.result.view.HttpMessageWriterView; import org.springframework.web.reactive.result.view.View; import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.reactive.result.view.freemarker.FreeMarkerConfigurer; import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewResolver; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebHandler; import static org.junit.Assert.*; import static org.springframework.core.ResolvableType.forClass; import static org.springframework.core.ResolvableType.forClassWithGenerics; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; import static org.springframework.http.MediaType.APPLICATION_XML; import static org.springframework.http.MediaType.IMAGE_PNG; import static org.springframework.http.MediaType.TEXT_PLAIN; /** * Unit tests for {@link WebFluxConfigurationSupport}. * * @author Rossen Stoyanchev */ public class WebFluxConfigurationSupportTests { @Test public void requestMappingHandlerMapping() throws Exception { ApplicationContext context = loadConfig(WebFluxConfig.class); String name = "requestMappingHandlerMapping"; RequestMappingHandlerMapping mapping = context.getBean(name, RequestMappingHandlerMapping.class); assertNotNull(mapping); assertEquals(0, mapping.getOrder()); assertTrue(mapping.useSuffixPatternMatch()); assertTrue(mapping.useTrailingSlashMatch()); assertTrue(mapping.useRegisteredSuffixPatternMatch()); name = "webFluxContentTypeResolver"; RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class); assertSame(resolver, mapping.getContentTypeResolver()); ServerWebExchange exchange = MockServerHttpRequest.get("/path.json").toExchange(); List<MediaType> list = Collections.singletonList(MediaType.APPLICATION_JSON); assertEquals(list, resolver.resolveMediaTypes(exchange)); exchange = MockServerHttpRequest.get("/path.foobar").toExchange(); assertEquals(Collections.emptyList(), resolver.resolveMediaTypes(exchange)); } @Test public void customPathMatchConfig() throws Exception { ApplicationContext context = loadConfig(CustomPatchMatchConfig.class); String name = "requestMappingHandlerMapping"; RequestMappingHandlerMapping mapping = context.getBean(name, RequestMappingHandlerMapping.class); assertNotNull(mapping); assertFalse(mapping.useSuffixPatternMatch()); assertFalse(mapping.useTrailingSlashMatch()); } @Test public void requestMappingHandlerAdapter() throws Exception { ApplicationContext context = loadConfig(WebFluxConfig.class); String name = "requestMappingHandlerAdapter"; RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class); assertNotNull(adapter); List<HttpMessageReader<?>> readers = adapter.getMessageCodecConfigurer().getReaders(); assertEquals(11, readers.size()); assertHasMessageReader(readers, forClass(byte[].class), APPLICATION_OCTET_STREAM); assertHasMessageReader(readers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM); assertHasMessageReader(readers, forClass(String.class), TEXT_PLAIN); assertHasMessageReader(readers, forClass(Resource.class), IMAGE_PNG); assertHasMessageReader(readers, forClassWithGenerics(MultiValueMap.class, String.class, String.class), APPLICATION_FORM_URLENCODED); assertHasMessageReader(readers, forClass(TestBean.class), APPLICATION_XML); assertHasMessageReader(readers, forClass(TestBean.class), APPLICATION_JSON); assertHasMessageReader(readers, forClass(TestBean.class), null); WebBindingInitializer bindingInitializer = adapter.getWebBindingInitializer(); assertNotNull(bindingInitializer); WebExchangeDataBinder binder = new WebExchangeDataBinder(new Object()); bindingInitializer.initBinder(binder); name = "webFluxConversionService"; ConversionService service = context.getBean(name, ConversionService.class); assertSame(service, binder.getConversionService()); name = "webFluxValidator"; Validator validator = context.getBean(name, Validator.class); assertSame(validator, binder.getValidator()); } @Test public void customMessageConverterConfig() throws Exception { ApplicationContext context = loadConfig(CustomMessageConverterConfig.class); String name = "requestMappingHandlerAdapter"; RequestMappingHandlerAdapter adapter = context.getBean(name, RequestMappingHandlerAdapter.class); assertNotNull(adapter); List<HttpMessageReader<?>> messageReaders = adapter.getMessageCodecConfigurer().getReaders(); assertEquals(2, messageReaders.size()); assertHasMessageReader(messageReaders, forClass(String.class), TEXT_PLAIN); assertHasMessageReader(messageReaders, forClass(TestBean.class), APPLICATION_XML); } @Test public void responseEntityResultHandler() throws Exception { ApplicationContext context = loadConfig(WebFluxConfig.class); String name = "responseEntityResultHandler"; ResponseEntityResultHandler handler = context.getBean(name, ResponseEntityResultHandler.class); assertNotNull(handler); assertEquals(0, handler.getOrder()); List<HttpMessageWriter<?>> writers = handler.getMessageWriters(); assertEquals(9, writers.size()); assertHasMessageWriter(writers, forClass(byte[].class), APPLICATION_OCTET_STREAM); assertHasMessageWriter(writers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM); assertHasMessageWriter(writers, forClass(String.class), TEXT_PLAIN); assertHasMessageWriter(writers, forClass(Resource.class), IMAGE_PNG); assertHasMessageWriter(writers, forClass(TestBean.class), APPLICATION_XML); assertHasMessageWriter(writers, forClass(TestBean.class), APPLICATION_JSON); assertHasMessageWriter(writers, forClass(TestBean.class), MediaType.parseMediaType("text/event-stream")); name = "webFluxContentTypeResolver"; RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class); assertSame(resolver, handler.getContentTypeResolver()); } @Test public void responseBodyResultHandler() throws Exception { ApplicationContext context = loadConfig(WebFluxConfig.class); String name = "responseBodyResultHandler"; ResponseBodyResultHandler handler = context.getBean(name, ResponseBodyResultHandler.class); assertNotNull(handler); assertEquals(100, handler.getOrder()); List<HttpMessageWriter<?>> writers = handler.getMessageWriters(); assertEquals(9, writers.size()); assertHasMessageWriter(writers, forClass(byte[].class), APPLICATION_OCTET_STREAM); assertHasMessageWriter(writers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM); assertHasMessageWriter(writers, forClass(String.class), TEXT_PLAIN); assertHasMessageWriter(writers, forClass(Resource.class), IMAGE_PNG); assertHasMessageWriter(writers, forClass(TestBean.class), APPLICATION_XML); assertHasMessageWriter(writers, forClass(TestBean.class), APPLICATION_JSON); assertHasMessageWriter(writers, forClass(TestBean.class), null); name = "webFluxContentTypeResolver"; RequestedContentTypeResolver resolver = context.getBean(name, RequestedContentTypeResolver.class); assertSame(resolver, handler.getContentTypeResolver()); } @Test public void viewResolutionResultHandler() throws Exception { ApplicationContext context = loadConfig(CustomViewResolverConfig.class); String name = "viewResolutionResultHandler"; ViewResolutionResultHandler handler = context.getBean(name, ViewResolutionResultHandler.class); assertNotNull(handler); assertEquals(Ordered.LOWEST_PRECEDENCE, handler.getOrder()); List<ViewResolver> resolvers = handler.getViewResolvers(); assertEquals(1, resolvers.size()); assertEquals(FreeMarkerViewResolver.class, resolvers.get(0).getClass()); List<View> views = handler.getDefaultViews(); assertEquals(1, views.size()); MimeType type = MimeTypeUtils.parseMimeType("application/json;charset=UTF-8"); assertEquals(type, views.get(0).getSupportedMediaTypes().get(0)); } @Test public void resourceHandler() throws Exception { ApplicationContext context = loadConfig(CustomResourceHandlingConfig.class); String name = "resourceHandlerMapping"; AbstractHandlerMapping handlerMapping = context.getBean(name, AbstractHandlerMapping.class); assertNotNull(handlerMapping); assertEquals(Ordered.LOWEST_PRECEDENCE - 1, handlerMapping.getOrder()); assertNotNull(handlerMapping.getPathHelper()); assertNotNull(handlerMapping.getPathMatcher()); SimpleUrlHandlerMapping urlHandlerMapping = (SimpleUrlHandlerMapping) handlerMapping; WebHandler webHandler = (WebHandler) urlHandlerMapping.getUrlMap().get("/images/**"); assertNotNull(webHandler); } private void assertHasMessageReader(List<HttpMessageReader<?>> readers, ResolvableType type, MediaType mediaType) { assertTrue(readers.stream().anyMatch(c -> mediaType == null || c.canRead(type, mediaType))); } private void assertHasMessageWriter(List<HttpMessageWriter<?>> writers, ResolvableType type, MediaType mediaType) { assertTrue(writers.stream().anyMatch(c -> mediaType == null || c.canWrite(type, mediaType))); } private ApplicationContext loadConfig(Class<?>... configurationClasses) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(configurationClasses); context.refresh(); return context; } @EnableWebFlux static class WebFluxConfig { } @Configuration static class CustomPatchMatchConfig extends WebFluxConfigurationSupport { @Override public void configurePathMatching(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(false); configurer.setUseTrailingSlashMatch(false); } } @Configuration static class CustomMessageConverterConfig extends WebFluxConfigurationSupport { @Override protected void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { configurer.registerDefaults(false); configurer.customCodecs().decoder(StringDecoder.textPlainOnly(true)); configurer.customCodecs().decoder(new Jaxb2XmlDecoder()); configurer.customCodecs().encoder(CharSequenceEncoder.textPlainOnly()); configurer.customCodecs().encoder(new Jaxb2XmlEncoder()); } } @Configuration @SuppressWarnings("unused") static class CustomViewResolverConfig extends WebFluxConfigurationSupport { @Override protected void configureViewResolvers(ViewResolverRegistry registry) { registry.freeMarker(); registry.defaultViews(new HttpMessageWriterView(new Jackson2JsonEncoder())); } @Bean public FreeMarkerConfigurer freeMarkerConfig() { return new FreeMarkerConfigurer(); } } @Configuration static class CustomResourceHandlingConfig extends WebFluxConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**").addResourceLocations("/images/"); } } @XmlRootElement static class TestBean { } }