diff --git a/src/main/java/org/springframework/hateoas/config/RestTemplateHateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/RestTemplateHateoasConfiguration.java new file mode 100644 index 00000000..600c9aad --- /dev/null +++ b/src/main/java/org/springframework/hateoas/config/RestTemplateHateoasConfiguration.java @@ -0,0 +1,73 @@ +/* + * Copyright 2019 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 + * + * https://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.hateoas.config; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.hateoas.config.WebMvcHateoasConfiguration.HypermediaWebMvcConfigurer; +import org.springframework.lang.NonNull; +import org.springframework.web.client.RestTemplate; + +/** + * Spring MVC HATEOAS Configuration + * + * @author Greg Turnquist + * @author Oliver Drotbohm + */ +@Configuration +class RestTemplateHateoasConfiguration { + + @Bean + static HypermediaRestTemplateBeanPostProcessor hypermediaRestTemplateBeanPostProcessor( + ObjectProvider configurer) { + return new HypermediaRestTemplateBeanPostProcessor(configurer); + } + + /** + * {@link BeanPostProcessor} to register hypermedia support with {@link RestTemplate} instances found in the + * application context. + * + * @author Oliver Gierke + * @author Greg Turnquist + */ + @RequiredArgsConstructor + static class HypermediaRestTemplateBeanPostProcessor implements BeanPostProcessor { + + private final ObjectProvider configurer; + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String) + */ + @NonNull + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + + if (!RestTemplate.class.isInstance(bean)) { + return bean; + } + + configurer.getObject().extendMessageConverters(((RestTemplate) bean).getMessageConverters()); + + return bean; + } + } +} diff --git a/src/main/java/org/springframework/hateoas/config/WebClientHateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/WebClientHateoasConfiguration.java new file mode 100644 index 00000000..ca264ea7 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/config/WebClientHateoasConfiguration.java @@ -0,0 +1,80 @@ +/* + * Copyright 2019 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 + * + * https://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.hateoas.config; + +import lombok.RequiredArgsConstructor; + +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.lang.NonNull; +import org.springframework.web.reactive.function.client.WebClient; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Spring WebFlux HATEOAS configuration. + * + * @author Greg Turnquist + * @since 1.0 TODO: Inspect ApplicationContext -> WebApplicationContext -> WebMVC + */ +@Configuration +class WebClientHateoasConfiguration { + + @Bean + WebClientConfigurer webClientConfigurer(ObjectProvider mapper, + List hypermediaTypes) { + return new WebClientConfigurer(mapper.getIfAvailable(ObjectMapper::new), hypermediaTypes); + } + + @Bean + static HypermediaWebClientBeanPostProcessor webClientBeanPostProcessor( + ObjectProvider configurer) { + return new HypermediaWebClientBeanPostProcessor(configurer); + } + + /** + * {@link BeanPostProcessor} to register the proper handlers in {@link WebClient} instances found in the application + * context. + * + * @author Greg Turnquist + * @since 1.0 + */ + @RequiredArgsConstructor + static class HypermediaWebClientBeanPostProcessor implements BeanPostProcessor { + + private final ObjectProvider configurer; + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String) + */ + @NonNull + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + + if (bean instanceof WebClient) { + return this.configurer.getObject().registerHypermediaTypes((WebClient) bean); + } + + return bean; + } + } +} diff --git a/src/main/java/org/springframework/hateoas/config/WebFluxHateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/WebFluxHateoasConfiguration.java index de8fbb09..03762ef6 100644 --- a/src/main/java/org/springframework/hateoas/config/WebFluxHateoasConfiguration.java +++ b/src/main/java/org/springframework/hateoas/config/WebFluxHateoasConfiguration.java @@ -19,20 +19,16 @@ import lombok.RequiredArgsConstructor; import java.util.List; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; -import org.springframework.lang.NonNull; import org.springframework.util.MimeType; import org.springframework.web.filter.reactive.ServerWebExchangeContextFilter; import org.springframework.web.reactive.config.WebFluxConfigurer; -import org.springframework.web.reactive.function.client.WebClient; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,18 +41,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; @Configuration class WebFluxHateoasConfiguration { - @Bean - WebClientConfigurer webClientConfigurer(ObjectProvider mapper, - List hypermediaTypes) { - return new WebClientConfigurer(mapper.getIfAvailable(ObjectMapper::new), hypermediaTypes); - } - - @Bean - static HypermediaWebClientBeanPostProcessor webClientBeanPostProcessor( - ObjectProvider configurer) { - return new HypermediaWebClientBeanPostProcessor(configurer); - } - @Bean HypermediaWebFluxConfigurer hypermediaWebFluxConfigurer(ObjectProvider mapper, List hypermediaTypes) { @@ -70,34 +54,6 @@ class WebFluxHateoasConfiguration { return new ServerWebExchangeContextFilter(); } - /** - * {@link BeanPostProcessor} to register the proper handlers in {@link WebClient} instances found in the application - * context. - * - * @author Greg Turnquist - * @since 1.0 - */ - @RequiredArgsConstructor - static class HypermediaWebClientBeanPostProcessor implements BeanPostProcessor { - - private final ObjectProvider configurer; - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String) - */ - @NonNull - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - - if (bean instanceof WebClient) { - return this.configurer.getObject().registerHypermediaTypes((WebClient) bean); - } - - return bean; - } - } - /** * {@link WebFluxConfigurer} to register hypermedia-aware {@link org.springframework.core.codec.Encoder}s and * {@link org.springframework.core.codec.Decoder}s that will render hypermedia for WebFlux controllers. diff --git a/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java index 0535d532..b2c8b523 100644 --- a/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java +++ b/src/main/java/org/springframework/hateoas/config/WebMvcHateoasConfiguration.java @@ -36,7 +36,6 @@ import org.springframework.hateoas.server.mvc.UriComponentsContributor; import org.springframework.hateoas.server.mvc.WebMvcLinkBuilderFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.lang.NonNull; -import org.springframework.web.client.RestTemplate; import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; @@ -73,12 +72,6 @@ class WebMvcHateoasConfiguration { return new HypermediaRepresentationModelBeanProcessorPostProcessor(invoker); } - @Bean - static HypermediaRestTemplateBeanPostProcessor restTemplateBeanPostProcessor( - ObjectProvider configurer) { - return new HypermediaRestTemplateBeanPostProcessor(configurer); - } - @Bean WebMvcLinkBuilderFactory webMvcLinkBuilderFactory(ObjectProvider contributors) { @@ -146,34 +139,4 @@ class WebMvcHateoasConfiguration { return bean; } } - - /** - * {@link BeanPostProcessor} to register hypermedia support with {@link RestTemplate} instances found in the - * application context. - * - * @author Oliver Gierke - * @author Greg Turnquist - */ - @RequiredArgsConstructor - static class HypermediaRestTemplateBeanPostProcessor implements BeanPostProcessor { - - private final ObjectProvider configurer; - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String) - */ - @NonNull - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - - if (!RestTemplate.class.isInstance(bean)) { - return bean; - } - - configurer.getObject().extendMessageConverters(((RestTemplate) bean).getMessageConverters()); - - return bean; - } - } } diff --git a/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java b/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java index c13145d6..4226f0fe 100644 --- a/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java +++ b/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java @@ -16,8 +16,6 @@ package org.springframework.hateoas.config; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import org.springframework.context.annotation.ImportSelector; @@ -33,18 +31,6 @@ class WebStackImportSelector implements ImportSelector { private static final String WEB_STACK_MISSING = "At least one web stack has to be selected in @EnableHypermediaSupport on %s!"; - static Map CONFIGS; - - static { - - Map configs = new HashMap<>(); - - configs.put(WebStack.WEBMVC, "org.springframework.hateoas.config.WebMvcHateoasConfiguration"); - configs.put(WebStack.WEBFLUX, "org.springframework.hateoas.config.WebFluxHateoasConfiguration"); - - CONFIGS = Collections.unmodifiableMap(configs); - } - /* * (non-Javadoc) * @see org.springframework.context.annotation.ImportSelector#selectImports(org.springframework.core.type.AnnotationMetadata) @@ -67,8 +53,7 @@ class WebStackImportSelector implements ImportSelector { } return Arrays.stream(stacks) // - .filter(WebStack::isAvailable) // - .map(CONFIGS::get) // + .flatMap(webStack -> webStack.getAvailableConfigurations().stream()) // .toArray(String[]::new); } } diff --git a/src/main/java/org/springframework/hateoas/support/WebStack.java b/src/main/java/org/springframework/hateoas/support/WebStack.java index eff24ef1..28aaded4 100644 --- a/src/main/java/org/springframework/hateoas/support/WebStack.java +++ b/src/main/java/org/springframework/hateoas/support/WebStack.java @@ -15,6 +15,9 @@ */ package org.springframework.hateoas.support; +import java.util.ArrayList; +import java.util.List; + import org.springframework.util.ClassUtils; /** @@ -24,23 +27,61 @@ import org.springframework.util.ClassUtils; */ public enum WebStack { - WEBMVC("org.springframework.web.servlet.DispatcherServlet"), + WEBMVC("org.springframework.web.servlet.DispatcherServlet", // + "org.springframework.hateoas.config.WebMvcHateoasConfiguration", // + "org.springframework.web.client.RestTemplate", // + "org.springframework.hateoas.config.RestTemplateHateoasConfiguration"), - WEBFLUX("org.springframework.web.reactive.DispatcherHandler"); + WEBFLUX("org.springframework.web.reactive.DispatcherHandler", // + "org.springframework.hateoas.config.WebFluxHateoasConfiguration", // + "org.springframework.web.reactive.function.client.WebClient", // + "org.springframework.hateoas.config.WebClientHateoasConfiguration"); - private final boolean isAvailable; + private final boolean isServerAvailable; + private final String serverConfiguration; + + private final boolean isClientAvailable; + private final String clientConfiguration; /** * Initialize the {@link #isAvailable} based upon a defined signature class. */ - WebStack(String signatureClass) { - this.isAvailable = ClassUtils.isPresent(signatureClass, null); + WebStack(String serverAvailableClazz, String serverConfigurationClazz, String clientAvailableClazz, + String clientConfigurationClazz) { + + this.isServerAvailable = ClassUtils.isPresent(serverAvailableClazz, null); + this.serverConfiguration = serverConfigurationClazz; + + this.isClientAvailable = ClassUtils.isPresent(clientAvailableClazz, null); + this.clientConfiguration = clientConfigurationClazz; + } + + /** + * Based on what client/server components are on the classpath, return what configuration classes should be + * registered. + */ + public List getAvailableConfigurations() { + + List configurations = new ArrayList<>(); + + if (this.isServerAvailable) { + configurations.add(this.serverConfiguration); + } + + if (this.isClientAvailable) { + configurations.add(this.clientConfiguration); + } + + return configurations; } /** * Is this web stack on the classpath? + * + * @deprecated Will be removed in 1.2 in light of {@link #getAvailableConfigurations()}. */ + @Deprecated public boolean isAvailable() { - return this.isAvailable; + return this.isServerAvailable; } } diff --git a/src/test/java/org/springframework/hateoas/config/HypermediaWebClientBeanPostProcessorTest.java b/src/test/java/org/springframework/hateoas/config/HypermediaWebClientBeanPostProcessorTest.java index b4a0b5e1..c093b5ba 100644 --- a/src/test/java/org/springframework/hateoas/config/HypermediaWebClientBeanPostProcessorTest.java +++ b/src/test/java/org/springframework/hateoas/config/HypermediaWebClientBeanPostProcessorTest.java @@ -18,13 +18,12 @@ package org.springframework.hateoas.config; import static org.assertj.core.api.Assertions.*; import static org.springframework.hateoas.support.ContextTester.*; -import reactor.test.StepVerifier; - import java.net.URI; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.ParameterizedTypeReference; @@ -40,7 +39,7 @@ import org.springframework.hateoas.server.core.TypeReferences.EntityModelType; import org.springframework.web.reactive.function.client.WebClient; /** - * Tests registration of proper decoders by the {@link HypermediaWebClientBeanPostProcessor}. + * Tests registration of proper decoders by the {@link org.springframework.hateoas.config.WebClientHateoasConfiguration.HypermediaWebClientBeanPostProcessor}. * * @author Greg Turnquist */ diff --git a/src/test/java/org/springframework/hateoas/config/WebStackImportSelectorUnitTest.java b/src/test/java/org/springframework/hateoas/config/WebStackImportSelectorUnitTest.java index 25b6bcf7..d6e5e9a2 100644 --- a/src/test/java/org/springframework/hateoas/config/WebStackImportSelectorUnitTest.java +++ b/src/test/java/org/springframework/hateoas/config/WebStackImportSelectorUnitTest.java @@ -17,6 +17,9 @@ package org.springframework.hateoas.config; import static org.assertj.core.api.Assertions.*; +import java.util.ArrayList; +import java.util.List; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ConfigurableApplicationContext; @@ -40,8 +43,11 @@ class WebStackImportSelectorUnitTest { AnnotationMetadata metadata = AnnotationMetadata.introspect(DefaultHypermedia.class); - assertThat(selector.selectImports(metadata)) - .containsExactlyInAnyOrderElementsOf(WebStackImportSelector.CONFIGS.values()); + List configs = new ArrayList<>(); + configs.addAll(WebStack.WEBMVC.getAvailableConfigurations()); + configs.addAll(WebStack.WEBFLUX.getAvailableConfigurations()); + + assertThat(selector.selectImports(metadata)).containsExactlyInAnyOrderElementsOf(configs); } @Test // #973 @@ -49,7 +55,7 @@ class WebStackImportSelectorUnitTest { AnnotationMetadata metadata = AnnotationMetadata.introspect(WebMvcHypermedia.class); - assertThat(selector.selectImports(metadata)).containsExactly(WebStackImportSelector.CONFIGS.get(WebStack.WEBMVC)); + assertThat(selector.selectImports(metadata)).containsExactlyInAnyOrderElementsOf(WebStack.WEBMVC.getAvailableConfigurations()); } @Test // #973 @@ -57,7 +63,7 @@ class WebStackImportSelectorUnitTest { AnnotationMetadata metadata = AnnotationMetadata.introspect(WebFluxHypermedia.class); - assertThat(selector.selectImports(metadata)).containsExactly(WebStackImportSelector.CONFIGS.get(WebStack.WEBFLUX)); + assertThat(selector.selectImports(metadata)).containsExactlyInAnyOrderElementsOf(WebStack.WEBFLUX.getAvailableConfigurations()); } @Test // #973