diff --git a/src/main/asciidoc/configuration.adoc b/src/main/asciidoc/configuration.adoc index e1ea6842..8dc3dd8b 100644 --- a/src/main/asciidoc/configuration.adoc +++ b/src/main/asciidoc/configuration.adoc @@ -13,3 +13,18 @@ To let the `RepresentationModel` subtypes be rendered according to the specifica * By default, it enables `@EnableEntityLinks` (see <>) and automatically picks up `EntityLinks` implementations and bundles them into a `DelegatingEntityLinks` instance that you can autowire. * It automatically picks up all `RelProvider` implementations in the `ApplicationContext` and bundles them into a `DelegatingRelProvider` that you can autowire. It registers providers to consider `@Relation` on domain types as well as Spring MVC controllers. If the https://github.com/atteo/evo-inflector[EVO inflector] is on the classpath, collection `rel` values are derived by using the pluralizing algorithm implemented in the library (see <>). +[[configuration.at-enable.stacks]] +=== Explicitly enabling support for dedicated web stacks + +By default, `@EnableHypermediaSupport` will reflectively detect the web application stack you're using and hook into the Spring components registered for those to enable support for hypermedia representations. +However, there are situations in which you'd only explicitly want to activate support for a particular stack. +E.g. if your Spring WebMVC based application uses WebFlux' `WebClient` to make outgoing requests and that one is not supposed to work with hypermedia elements, you can restrict the functionality to be enabled by explicitly declaring WebMvc in the configuration: + +.Explicitly activating hypermedia support for a particular web stack +==== +[source, java] +---- +@EnableHypermediaSupport(…, stacks = WebStack.WEBMVC) +class MyHypermediaConfiguration { … } +---- +==== diff --git a/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java b/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java index 6ec0af25..27075d45 100644 --- a/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java +++ b/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java @@ -26,7 +26,9 @@ import java.util.List; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Import; import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.support.WebStack; import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.client.WebClient; /** * Activates hypermedia support in the {@link ApplicationContext}. Will register infrastructure beans to support all @@ -49,6 +51,15 @@ public @interface EnableHypermediaSupport { */ HypermediaType[] type(); + /** + * Configures which {@link WebStack}s we're supposed to enable support for. By default we're activating it for all + * available ones if they happen to be in use. Configure this explicitly in case you're using WebFlux components like + * {@link WebClient} but don't want to use hypermedia operations with it. + * + * @return + */ + WebStack[] stacks() default { WebStack.WEBMVC, WebStack.WEBFLUX }; + /** * Hypermedia representation types supported. * diff --git a/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java b/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java index 9d9969c6..c13145d6 100644 --- a/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java +++ b/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java @@ -15,8 +15,10 @@ */ package org.springframework.hateoas.config; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; @@ -29,23 +31,44 @@ import org.springframework.hateoas.support.WebStack; */ 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) */ @Override - public String[] selectImports(AnnotationMetadata importingClassMetadata) { + public String[] selectImports(AnnotationMetadata metadata) { - List imports = new ArrayList<>(); + Map attributes = metadata.getAnnotationAttributes(EnableHypermediaSupport.class.getName()); - if (WebStack.WEBMVC.isAvailable()) { - imports.add("org.springframework.hateoas.config.WebMvcHateoasConfiguration"); + // Configuration class imported but not through @EnableHypermediaSupport + + if (attributes == null) { + return new String[0]; } - if (WebStack.WEBFLUX.isAvailable()) { - imports.add("org.springframework.hateoas.config.WebFluxHateoasConfiguration"); + WebStack[] stacks = (WebStack[]) attributes.get("stacks"); + + if (stacks.length == 0) { + throw new IllegalStateException(String.format(WEB_STACK_MISSING, metadata.getClassName())); } - return imports.toArray(new String[0]); + return Arrays.stream(stacks) // + .filter(WebStack::isAvailable) // + .map(CONFIGS::get) // + .toArray(String[]::new); } } diff --git a/src/test/java/org/springframework/hateoas/config/WebStackImportSelectorUnitTest.java b/src/test/java/org/springframework/hateoas/config/WebStackImportSelectorUnitTest.java new file mode 100644 index 00000000..98574e54 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/config/WebStackImportSelectorUnitTest.java @@ -0,0 +1,81 @@ +/* + * 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 + * + * 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.hateoas.config; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; +import org.springframework.hateoas.support.WebStack; + +/** + * Unit tests for {@link WebStackImportSelector}. + * + * @author Oliver Drotbohm + */ +public class WebStackImportSelectorUnitTest { + + WebStackImportSelector selector = new WebStackImportSelector(); + + @Test // #973 + public void activatesAllWebStacksByDefault() { + + AnnotationMetadata metadata = new StandardAnnotationMetadata(DefaultHypermedia.class); + + assertThat(selector.selectImports(metadata)) + .containsExactlyInAnyOrderElementsOf(WebStackImportSelector.CONFIGS.values()); + } + + @Test // #973 + public void activatesWebMvcOnlyifConfigured() { + + AnnotationMetadata metadata = new StandardAnnotationMetadata(WebMvcHypermedia.class); + + assertThat(selector.selectImports(metadata)).containsExactly(WebStackImportSelector.CONFIGS.get(WebStack.WEBMVC)); + } + + @Test // #973 + public void activatesWebFluxOnlyIfConfigured() { + + AnnotationMetadata metadata = new StandardAnnotationMetadata(WebFluxHypermedia.class); + + assertThat(selector.selectImports(metadata)).containsExactly(WebStackImportSelector.CONFIGS.get(WebStack.WEBFLUX)); + } + + @Test // #973 + public void rejectsNoStacksSelected() { + + AnnotationMetadata metadata = new StandardAnnotationMetadata(NoStacksHypermedia.class); + + assertThatExceptionOfType(IllegalStateException.class) // + .isThrownBy(() -> selector.selectImports(metadata)) // + .withMessageContaining(NoStacksHypermedia.class.getName()); + } + + @EnableHypermediaSupport(type = HypermediaType.HAL) + static class DefaultHypermedia {} + + @EnableHypermediaSupport(type = HypermediaType.HAL, stacks = WebStack.WEBMVC) + static class WebMvcHypermedia {} + + @EnableHypermediaSupport(type = HypermediaType.HAL, stacks = WebStack.WEBFLUX) + static class WebFluxHypermedia {} + + @EnableHypermediaSupport(type = HypermediaType.HAL, stacks = {}) + static class NoStacksHypermedia {} +}