From 576274c1b220016099bec0b0d2be9c57471f4f47 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Mon, 11 Feb 2019 10:16:57 +0100 Subject: [PATCH] #728 - Fix dependency cycles in WebFlux / WebMVC code WebHandler is now independent of a particular web stack as the final LinkBuilder creation is externalized through a SAM type that client code now uses to pass a constructor. Refactored the ReactiveLinkBuilder to avoid a dependency on ControllerLinkBuilder (read: the WebMVC stack). With that change, WebHandler is now moved back to the core package. Refactored LinkBuilderSupport to keep a UriComponentsBuilder instance around instead of UriComponents. It creates a defensive copy of the builder instance on state modifying invocations and returns a new LinkBuilderSupport instance. Moved SpringMvcAffordanceBuilder to the core package and removed Mvc segment from its name to make obvious it's not tied to SpringMVC. Introduced TemplateVariableAwareLinkBuilderSupport as common base class between ControllerLinkBuilder and ReactiveLinkBuilder. It extracts the TemplateVariable behavior from ControllerLinkBuilder. Extracted import of web stack specific configuration into dedicated ImportSelector and make use of String based class names to avoid cyclic dependencies between the config packages. Removed BasicLinkBuilder. --- .../config/EnableHypermediaSupport.java | 16 ++-- ...ermediaSupportBeanDefinitionRegistrar.java | 28 +----- .../config/WebStackImportSelector.java | 51 ++++++++++ .../mvc/HypermediaWebMvcConfigurer.java | 13 +-- .../mvc/WebMvcHateoasConfiguration.java | 22 ++--- .../reactive/WebFluxHateoasConfiguration.java | 45 ++++----- .../hateoas/core/DummyInvocationUtils.java | 2 +- .../hateoas/core/LinkBuilderSupport.java | 93 ++++++++++++------- .../SpringAffordanceBuilder.java} | 11 +-- ...mplateVariableAwareLinkBuilderSupport.java | 83 +++++++++++++++++ .../hateoas/{ => core}/WebHandler.java | 56 ++++++----- .../hateoas/mvc/BasicLinkBuilder.java | 65 ------------- .../hateoas/mvc/ControllerLinkBuilder.java | 71 +++----------- .../mvc/ControllerLinkBuilderFactory.java | 39 ++++---- .../hateoas/reactive/ReactiveLinkBuilder.java | 61 ++++++------ ...nableHypermediaSupportIntegrationTest.java | 5 +- ...ediaRestTemplateBeanPostProcessorTest.java | 82 ++++++++-------- ...ermediaWebClientBeanPostProcessorTest.java | 9 +- .../core/ControllerEntityLinksUnitTest.java | 12 +-- .../core/LinkBuilderSupportUnitTest.java | 24 +++-- .../reactive/HypermediaWebFilterTest.java | 3 + .../reactive/ReactiveLinkBuilderTest.java | 27 ++++++ .../ReactiveResourceAssemblerUnitTest.java | 10 +- .../SimpleReactiveResourceAssemblerTest.java | 8 +- 24 files changed, 454 insertions(+), 382 deletions(-) create mode 100644 src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java rename src/main/java/org/springframework/hateoas/{mvc/SpringMvcAffordanceBuilder.java => core/SpringAffordanceBuilder.java} (88%) create mode 100644 src/main/java/org/springframework/hateoas/core/TemplateVariableAwareLinkBuilderSupport.java rename src/main/java/org/springframework/hateoas/{ => core}/WebHandler.java (88%) delete mode 100644 src/main/java/org/springframework/hateoas/mvc/BasicLinkBuilder.java diff --git a/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java b/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java index 58e2867d..77f81571 100644 --- a/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java +++ b/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java @@ -20,8 +20,6 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.EnumSet; -import java.util.Set; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Import; @@ -37,7 +35,7 @@ import org.springframework.hateoas.LinkDiscoverer; *
  • {@link LinkDiscoverer}
  • *
  • a Jackson 2 module to correctly marshal the resource model classes into the appropriate representation. * - * + * * @see LinkDiscoverer * @see EntityLinks * @author Oliver Gierke @@ -47,19 +45,19 @@ import org.springframework.hateoas.LinkDiscoverer; @Target(ElementType.TYPE) @Documented @EnableEntityLinks -@Import({ HypermediaSupportBeanDefinitionRegistrar.class, HateoasConfiguration.class }) +@Import({ HypermediaSupportBeanDefinitionRegistrar.class, HateoasConfiguration.class, WebStackImportSelector.class }) public @interface EnableHypermediaSupport { /** * The hypermedia type to be supported. - * + * * @return */ HypermediaType[] type(); /** * Hypermedia representation types supported. - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -67,7 +65,7 @@ public @interface EnableHypermediaSupport { /** * HAL - Hypermedia Application Language. - * + * * @see http://stateless.co/hal_specification.html * @see http://tools.ietf.org/html/draft-kelly-json-hal-05 */ @@ -75,7 +73,7 @@ public @interface EnableHypermediaSupport { /** * HAL-FORMS - Independent, backward-compatible extension of the HAL designed to add runtime FORM support - * + * * @see https://rwcbook.github.io/hal-forms/ */ HAL_FORMS, @@ -93,7 +91,5 @@ public @interface EnableHypermediaSupport { * @see http://uberhypermedia.org/ */ UBER; - - private static Set HAL_BASED_MEDIATYPES = EnumSet.of(HAL, HAL_FORMS); } } diff --git a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java index e75e85fd..925a3daf 100644 --- a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java +++ b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java @@ -35,11 +35,8 @@ import org.springframework.hateoas.EntityLinks; import org.springframework.hateoas.LinkDiscoverer; import org.springframework.hateoas.collectionjson.CollectionJsonLinkDiscoverer; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; -import org.springframework.hateoas.config.mvc.WebMvcHateoasConfiguration; -import org.springframework.hateoas.config.reactive.WebFluxHateoasConfiguration; import org.springframework.hateoas.hal.HalLinkDiscoverer; import org.springframework.hateoas.hal.forms.HalFormsLinkDiscoverer; -import org.springframework.hateoas.support.WebStack; import org.springframework.hateoas.uber.UberLinkDiscoverer; import org.springframework.util.ClassUtils; @@ -47,7 +44,7 @@ import org.springframework.util.ClassUtils; * {@link ImportBeanDefinitionRegistrar} implementation to activate hypermedia support based on the configured * hypermedia type. Activates {@link EntityLinks} support as well (essentially as if {@link EnableEntityLinks} was * activated as well). - * + * * @author Oliver Gierke * @author Greg Turnquist */ @@ -70,7 +67,7 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe * collection using the application context. */ for (HypermediaType type : types) { - + BeanDefinitionBuilder hypermediaTypeBeanDefinition = genericBeanDefinition(HypermediaType.class, () -> type); registerSourcedBeanDefinition(hypermediaTypeBeanDefinition, metadata, registry); } @@ -87,30 +84,11 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe BeanDefinitionReaderUtils.generateBeanName(linkDiscovererBeanDefinition, registry)), registry); } } - - /* - * Register a Spring MVC-specific HATEOAS configuration. - */ - if (WebStack.WEBMVC.isAvailable()) { - - BeanDefinitionBuilder webMvcHateosConfiguration = rootBeanDefinition(WebMvcHateoasConfiguration.class); - registerSourcedBeanDefinition(webMvcHateosConfiguration, metadata, registry); - } - - /* - * Register a Spring WebFlux-specific HATEOAS configuration. - */ - if (WebStack.WEBFLUX.isAvailable()) { - - BeanDefinitionBuilder webFluxHateoasConfiguration = rootBeanDefinition(WebFluxHateoasConfiguration.class); - registerSourcedBeanDefinition(webFluxHateoasConfiguration, metadata, registry); - } - } /** * Returns a {@link LinkDiscoverer} {@link BeanDefinition} suitable for the given {@link HypermediaType}. - * + * * @param type * @return */ diff --git a/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java b/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java new file mode 100644 index 00000000..93c05e82 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/config/WebStackImportSelector.java @@ -0,0 +1,51 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.hateoas.support.WebStack; + +/** + * {@link ImportSelector} to include web stack specific configuration. + * + * @author Oliver Drotbohm + */ +class WebStackImportSelector implements ImportSelector { + + /* + * (non-Javadoc) + * @see org.springframework.context.annotation.ImportSelector#selectImports(org.springframework.core.type.AnnotationMetadata) + */ + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + + List imports = new ArrayList<>(); + + if (WebStack.WEBMVC.isAvailable()) { + imports.add("org.springframework.hateoas.config.mvc.WebMvcHateoasConfiguration"); + } + + if (WebStack.WEBFLUX.isAvailable()) { + imports.add("org.springframework.hateoas.config.reactive.WebFluxHateoasConfiguration"); + } + + return imports.toArray(new String[imports.size()]); + } +} diff --git a/src/main/java/org/springframework/hateoas/config/mvc/HypermediaWebMvcConfigurer.java b/src/main/java/org/springframework/hateoas/config/mvc/HypermediaWebMvcConfigurer.java index b677642d..6d74947c 100644 --- a/src/main/java/org/springframework/hateoas/config/mvc/HypermediaWebMvcConfigurer.java +++ b/src/main/java/org/springframework/hateoas/config/mvc/HypermediaWebMvcConfigurer.java @@ -22,6 +22,7 @@ import lombok.RequiredArgsConstructor; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.springframework.beans.BeansException; @@ -101,10 +102,10 @@ public class HypermediaWebMvcConfigurer implements WebMvcConfigurer, BeanFactory if (this.hypermediaTypes.contains(HypermediaType.HAL_FORMS)) { - converters.add(0, - new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class, - Collections.singletonList(HAL_FORMS_JSON), createHalFormsObjectMapper(this.mapper, this.curieProvider, - this.relProvider, linkRelationMessageSource, this.halFormsConfiguration))); + converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter( + ResourceSupport.class, Collections.singletonList(HAL_FORMS_JSON), + createHalFormsObjectMapper(this.mapper, this.curieProvider, this.relProvider, linkRelationMessageSource, + this.halFormsConfiguration))); } if (this.hypermediaTypes.contains(HypermediaType.COLLECTION_JSON)) { @@ -115,8 +116,8 @@ public class HypermediaWebMvcConfigurer implements WebMvcConfigurer, BeanFactory if (this.hypermediaTypes.contains(HypermediaType.UBER)) { - converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class, - Collections.singletonList(UBER_JSON), createUberObjectMapper(this.mapper))); + converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter( + ResourceSupport.class, Collections.singletonList(UBER_JSON), createUberObjectMapper(this.mapper))); } } } diff --git a/src/main/java/org/springframework/hateoas/config/mvc/WebMvcHateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/mvc/WebMvcHateoasConfiguration.java index c3afa0db..98908657 100644 --- a/src/main/java/org/springframework/hateoas/config/mvc/WebMvcHateoasConfiguration.java +++ b/src/main/java/org/springframework/hateoas/config/mvc/WebMvcHateoasConfiguration.java @@ -30,7 +30,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** * Spring MVC HATEOAS Configuration - * + * * @author Greg Turnquist */ @Configuration @@ -38,19 +38,13 @@ public class WebMvcHateoasConfiguration { @Bean HypermediaWebMvcConfigurer hypermediaWebMvcConfigurer(ObjectProvider mapper, - DelegatingRelProvider relProvider, - ObjectProvider curieProvider, - ObjectProvider halConfiguration, - ObjectProvider halFormsConfiguration, - Collection hypermediaTypes) { - - return new HypermediaWebMvcConfigurer( - mapper.getIfAvailable(ObjectMapper::new), - relProvider, - curieProvider.getIfAvailable(), - halConfiguration.getIfAvailable(HalConfiguration::new), - halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new), - hypermediaTypes); + DelegatingRelProvider relProvider, ObjectProvider curieProvider, + ObjectProvider halConfiguration, ObjectProvider halFormsConfiguration, + Collection hypermediaTypes) { + + return new HypermediaWebMvcConfigurer(mapper.getIfAvailable(ObjectMapper::new), relProvider, + curieProvider.getIfAvailable(), halConfiguration.getIfAvailable(HalConfiguration::new), + halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new), hypermediaTypes); } @Bean diff --git a/src/main/java/org/springframework/hateoas/config/reactive/WebFluxHateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/reactive/WebFluxHateoasConfiguration.java index 8c1e6d0f..9f82f4ee 100644 --- a/src/main/java/org/springframework/hateoas/config/reactive/WebFluxHateoasConfiguration.java +++ b/src/main/java/org/springframework/hateoas/config/reactive/WebFluxHateoasConfiguration.java @@ -31,7 +31,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** * Spring WebFlux HATEOAS configuration. - * + * * @author Greg Turnquist * @since 1.0 */ @@ -39,45 +39,34 @@ import com.fasterxml.jackson.databind.ObjectMapper; public class WebFluxHateoasConfiguration { @Bean - WebClientConfigurer webClientConfigurer(ObjectProvider mapper, - DelegatingRelProvider relProvider, - ObjectProvider curieProvider, - ObjectProvider halConfiguration, - ObjectProvider halFormsConfiguration, - Collection hypermediaTypes) { - - return new WebClientConfigurer( - mapper.getIfAvailable(ObjectMapper::new), - relProvider, - curieProvider.getIfAvailable(), - halConfiguration.getIfAvailable(HalConfiguration::new), - halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new), - hypermediaTypes); + WebClientConfigurer webClientConfigurer(ObjectProvider mapper, DelegatingRelProvider relProvider, + ObjectProvider curieProvider, ObjectProvider halConfiguration, + ObjectProvider halFormsConfiguration, Collection hypermediaTypes) { + + return new WebClientConfigurer(mapper.getIfAvailable(ObjectMapper::new), relProvider, + curieProvider.getIfAvailable(), halConfiguration.getIfAvailable(HalConfiguration::new), + halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new), hypermediaTypes); } @Bean HypermediaWebClientBeanPostProcessor webClientBeanPostProcessor(WebClientConfigurer configurer) { return new HypermediaWebClientBeanPostProcessor(configurer); } - + @Bean HypermediaWebFluxConfigurer hypermediaWebFluxConfigurer(ObjectProvider mapper, - DelegatingRelProvider relProvider, - ObjectProvider curieProvider, - ObjectProvider halConfiguration, - ObjectProvider halFormsConfiguration, - Collection hypermediaTypes) { + DelegatingRelProvider relProvider, ObjectProvider curieProvider, + ObjectProvider halConfiguration, ObjectProvider halFormsConfiguration, + Collection hypermediaTypes) { - return new HypermediaWebFluxConfigurer( - mapper.getIfAvailable(ObjectMapper::new), - relProvider, - curieProvider.getIfAvailable(), - halConfiguration.getIfAvailable(HalConfiguration::new), - halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new), hypermediaTypes); + return new HypermediaWebFluxConfigurer(mapper.getIfAvailable(ObjectMapper::new), relProvider, + curieProvider.getIfAvailable(), halConfiguration.getIfAvailable(HalConfiguration::new), + halFormsConfiguration.getIfAvailable(HalFormsConfiguration::new), hypermediaTypes); } /** - * TODO: Replace with Spring Framework filter when https://github.com/spring-projects/spring-framework/issues/21746 is completed. + * TODO: Replace with Spring Framework filter when https://github.com/spring-projects/spring-framework/issues/21746 is + * completed. */ @Bean HypermediaWebFilter hypermediaWebFilter() { diff --git a/src/main/java/org/springframework/hateoas/core/DummyInvocationUtils.java b/src/main/java/org/springframework/hateoas/core/DummyInvocationUtils.java index 7057a37d..e60181e5 100644 --- a/src/main/java/org/springframework/hateoas/core/DummyInvocationUtils.java +++ b/src/main/java/org/springframework/hateoas/core/DummyInvocationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-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. diff --git a/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java b/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java index 0e8a24a9..74eed704 100644 --- a/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java +++ b/src/main/java/org/springframework/hateoas/core/LinkBuilderSupport.java @@ -16,15 +16,16 @@ package org.springframework.hateoas.core; import static org.springframework.hateoas.core.EncodingUtils.*; -import static org.springframework.web.util.UriComponentsBuilder.*; import lombok.Getter; import java.net.URI; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Function; import org.springframework.hateoas.Affordance; import org.springframework.hateoas.IanaLinkRelations; @@ -48,32 +49,36 @@ import org.springframework.web.util.UriComponentsBuilder; */ public abstract class LinkBuilderSupport implements LinkBuilder { - private final UriComponents uriComponents; - - private @Getter final List affordances; - - /** - * Creates a new {@link LinkBuilderSupport} using the given {@link UriComponentsBuilder}. - * - * @param builder must not be {@literal null}. - */ - public LinkBuilderSupport(UriComponentsBuilder builder) { - - Assert.notNull(builder, "UriComponentsBuilder must not be null!"); - this.uriComponents = builder.build(); - this.affordances = new ArrayList<>(); - } + private final UriComponentsBuilder builder; + private final @Getter List affordances; /** * Creates a new {@link LinkBuilderSupport} using the given {@link UriComponents}. * - * @param uriComponents must not be {@literal null}. + * @param builder must not be {@literal null}. */ - public LinkBuilderSupport(UriComponents uriComponents) { + protected LinkBuilderSupport(UriComponentsBuilder builder) { + this(builder, Collections.emptyList()); + } - Assert.notNull(uriComponents, "UriComponents must not be null!"); - this.uriComponents = uriComponents; - this.affordances = new ArrayList<>(); + protected LinkBuilderSupport(UriComponentsBuilder builder, List affordances) { + + Assert.notNull(builder, "UriComponents must not be null!"); + Assert.notNull(affordances, "Affordances must not be null!"); + + this.builder = builder.cloneBuilder(); + this.affordances = affordances; + } + + protected LinkBuilderSupport(UriComponents components, List affordances) { + + String uriString = components.toUriString(); + UriComponentsBuilder builder = uriString.isEmpty() // + ? UriComponentsBuilder.fromUri(components.toUri()) // + : UriComponentsBuilder.fromUriString(uriString); + + this.builder = builder; + this.affordances = affordances; } /* @@ -109,20 +114,20 @@ public abstract class LinkBuilderSupport implements LinkB protected T slash(UriComponents components, boolean encoded) { - String uriString = uriComponents.toUriString(); - UriComponentsBuilder builder = uriString.isEmpty() ? fromUri(uriComponents.toUri()) : fromUriString(uriString); + return withFreshBuilder(builder -> { - for (String pathSegment : components.getPathSegments()) { - builder.pathSegment(encoded ? pathSegment : encodePath(pathSegment)); - } + for (String pathSegment : components.getPathSegments()) { + builder.pathSegment(encoded ? pathSegment : encodePath(pathSegment)); + } - String fragment = components.getFragment(); + String fragment = components.getFragment(); - if (StringUtils.hasText(fragment)) { - builder.fragment(encoded ? fragment : encodeFragment(fragment)); - } + if (StringUtils.hasText(fragment)) { + builder.fragment(encoded ? fragment : encodeFragment(fragment)); + } - return createNewInstance(builder.query(components.getQuery())); + return createNewInstance(builder.query(components.getQuery()), affordances); + }); } /* @@ -143,13 +148,16 @@ public abstract class LinkBuilderSupport implements LinkB * @see org.springframework.hateoas.LinkBuilder#toUri() */ public URI toUri() { - return uriComponents.encode().toUri().normalize(); + return builder.build().toUri().normalize(); } public T addAffordances(Collection affordances) { - this.affordances.addAll(affordances); - return getThis(); + List newAffordances = new ArrayList<>(); + newAffordances.addAll(this.affordances); + newAffordances.addAll(affordances); + + return createNewInstance(builder, newAffordances); } /* @@ -176,7 +184,20 @@ public abstract class LinkBuilderSupport implements LinkB */ @Override public String toString() { - return uriComponents.toUriString(); + return builder.build().toUriString(); + } + + /** + * Executes the given {@link Function} using a freshly cloned {@link UriComponentsBuilder}. + * + * @param function must not be {@literal null}. + * @return + */ + protected S withFreshBuilder(Function function) { + + Assert.notNull(function, "Function must not be null!"); + + return function.apply(builder.cloneBuilder()); } /** @@ -192,5 +213,5 @@ public abstract class LinkBuilderSupport implements LinkB * @param builder will never be {@literal null}. * @return */ - protected abstract T createNewInstance(UriComponentsBuilder builder); + protected abstract T createNewInstance(UriComponentsBuilder builder, List affordances); } diff --git a/src/main/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilder.java b/src/main/java/org/springframework/hateoas/core/SpringAffordanceBuilder.java similarity index 88% rename from src/main/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilder.java rename to src/main/java/org/springframework/hateoas/core/SpringAffordanceBuilder.java index 00947875..52ab1c49 100644 --- a/src/main/java/org/springframework/hateoas/mvc/SpringMvcAffordanceBuilder.java +++ b/src/main/java/org/springframework/hateoas/core/SpringAffordanceBuilder.java @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.hateoas.mvc; +package org.springframework.hateoas.core; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -26,9 +25,6 @@ import org.springframework.hateoas.AffordanceModelFactory; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.QueryParameter; -import org.springframework.hateoas.core.MappingDiscoverer; -import org.springframework.hateoas.core.MethodInvocation; -import org.springframework.hateoas.core.MethodParameters; import org.springframework.http.HttpMethod; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; @@ -38,8 +34,9 @@ import org.springframework.web.util.UriComponents; * Extract information needed to assemble an {@link Affordance} from a Spring MVC web method. * * @author Greg Turnquist + * @author Oliver Drotbohm */ -class SpringMvcAffordanceBuilder { +public class SpringAffordanceBuilder { /** * Use the attributes of the current method call along with a collection of {@link AffordanceModelFactory}'s to create @@ -50,7 +47,7 @@ class SpringMvcAffordanceBuilder { * @param components * @return */ - public static Collection create(MethodInvocation invocation, MappingDiscoverer discoverer, + public static List create(MethodInvocation invocation, MappingDiscoverer discoverer, UriComponents components) { List affordances = new ArrayList<>(); diff --git a/src/main/java/org/springframework/hateoas/core/TemplateVariableAwareLinkBuilderSupport.java b/src/main/java/org/springframework/hateoas/core/TemplateVariableAwareLinkBuilderSupport.java new file mode 100644 index 00000000..7a8ce1a8 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/core/TemplateVariableAwareLinkBuilderSupport.java @@ -0,0 +1,83 @@ +/* + * 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.core; + +import java.util.List; + +import org.springframework.hateoas.Affordance; +import org.springframework.hateoas.TemplateVariables; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * A {@link LinkBuilderSupport} extension that can keep a list of {@link TemplateVariables} around. + * + * @author Oliver Gierke + */ +public abstract class TemplateVariableAwareLinkBuilderSupport> + extends LinkBuilderSupport { + + private final TemplateVariables variables; + + protected TemplateVariableAwareLinkBuilderSupport(UriComponentsBuilder builder, TemplateVariables variables, + List affordances) { + + super(builder, affordances); + + this.variables = variables; + } + + protected TemplateVariableAwareLinkBuilderSupport(UriComponents components, TemplateVariables variables, + List affordances) { + + super(components, affordances); + + this.variables = variables; + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.LinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List) + */ + @Override + protected final T createNewInstance(UriComponentsBuilder builder, List affordances) { + return createNewInstance(builder, affordances, variables); + } + + protected abstract T createNewInstance(UriComponentsBuilder builder, List affordances, + TemplateVariables variables); + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.LinkBuilderSupport#toString() + */ + @Override + public String toString() { + + String result = super.toString(); + + if (variables == TemplateVariables.NONE) { + return result; + } + + if (!result.contains("#")) { + return result.concat(variables.toString()); + } + + String[] parts = result.split("#"); + return parts[0].concat(variables.toString()).concat("#").concat(parts[0]); + } +} diff --git a/src/main/java/org/springframework/hateoas/WebHandler.java b/src/main/java/org/springframework/hateoas/core/WebHandler.java similarity index 88% rename from src/main/java/org/springframework/hateoas/WebHandler.java rename to src/main/java/org/springframework/hateoas/core/WebHandler.java index 11d4a526..2016043b 100644 --- a/src/main/java/org/springframework/hateoas/WebHandler.java +++ b/src/main/java/org/springframework/hateoas/core/WebHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.hateoas; +package org.springframework.hateoas.core; import static org.springframework.hateoas.TemplateVariable.VariableType.*; import static org.springframework.hateoas.TemplateVariables.*; @@ -38,12 +38,10 @@ import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.format.support.DefaultFormattingConversionService; -import org.springframework.hateoas.core.AnnotationAttribute; -import org.springframework.hateoas.core.AnnotationMappingDiscoverer; -import org.springframework.hateoas.core.LastInvocationAware; -import org.springframework.hateoas.core.MappingDiscoverer; -import org.springframework.hateoas.core.MethodInvocation; -import org.springframework.hateoas.core.MethodParameters; +import org.springframework.hateoas.Affordance; +import org.springframework.hateoas.LinkBuilder; +import org.springframework.hateoas.TemplateVariable; +import org.springframework.hateoas.TemplateVariables; import org.springframework.hateoas.mvc.ControllerLinkBuilder; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; @@ -59,19 +57,29 @@ import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriTemplate; /** - * Utility for taking a a method invocation and extracting a {@link ControllerLinkBuilder}. + * Utility for taking a method invocation and extracting a {@link ControllerLinkBuilder}. * * @author Greg Turnquist */ public class WebHandler { private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(RequestMapping.class); - private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR = new AnnotatedParametersParameterAccessor( - new AnnotationAttribute(PathVariable.class)); - private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR = new RequestParamParameterAccessor(); + private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR // + = new AnnotatedParametersParameterAccessor(new AnnotationAttribute(PathVariable.class)); + private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR // + = new RequestParamParameterAccessor(); - public static ControllerLinkBuilder linkTo(Object invocationValue, - Function mappingToUriComponentsBuilder, + public interface LinkBuilderCreator { + T createBuilder(UriComponents components, TemplateVariables variables, List affordances); + } + + public static T linkTo(Object invocationValue, + Function mappingToUriComponentsBuilder, LinkBuilderCreator creator) { + return linkTo(invocationValue, mappingToUriComponentsBuilder, creator, null); + } + + public static T linkTo(Object invocationValue, + Function mappingToUriComponentsBuilder, LinkBuilderCreator creator, BiFunction additionalUriHandler) { Assert.isInstanceOf(LastInvocationAware.class, invocationValue); @@ -92,13 +100,15 @@ public class WebHandler { values.put(names.next(), encodePath(classMappingParameters.next())); } - for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation)) { + for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR + .getBoundParameters(invocation)) { values.put(parameter.getVariableName(), encodePath(parameter.asString())); } List optionalEmptyParameters = new ArrayList<>(); - for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation)) { + for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR + .getBoundParameters(invocation)) { bindRequestParameters(builder, parameter); @@ -132,7 +142,9 @@ public class WebHandler { variables = variables.concat(variable); } - return new ControllerLinkBuilder(components, variables, invocation); + List affordances = SpringAffordanceBuilder.create(invocation, DISCOVERER, components); + + return creator.createBuilder(components, variables, affordances); } /** @@ -143,7 +155,8 @@ public class WebHandler { * @param parameter must not be {@literal null}. */ @SuppressWarnings("unchecked") - private static void bindRequestParameters(UriComponentsBuilder builder, AnnotatedParametersParameterAccessor.BoundMethodParameter parameter) { + private static void bindRequestParameters(UriComponentsBuilder builder, + AnnotatedParametersParameterAccessor.BoundMethodParameter parameter) { Object value = parameter.getValue(); String key = parameter.getVariableName(); @@ -248,15 +261,16 @@ public class WebHandler { } /** - * Value object to allow accessing {@link MethodInvocation} parameters with the configured {@link AnnotationAttribute}. + * Value object to allow accessing {@link MethodInvocation} parameters with the configured + * {@link AnnotationAttribute}. * * @author Oliver Gierke */ @RequiredArgsConstructor private static class AnnotatedParametersParameterAccessor { - private static final Map METHOD_PARAMETERS_CACHE = new ConcurrentReferenceHashMap<>( - 16, ConcurrentReferenceHashMap.ReferenceType.WEAK); + private static final Map METHOD_PARAMETERS_CACHE = new ConcurrentReferenceHashMap<>(16, + ConcurrentReferenceHashMap.ReferenceType.WEAK); private final @NonNull AnnotationAttribute attribute; @@ -297,7 +311,7 @@ public class WebHandler { * @return */ protected BoundMethodParameter createParameter(MethodParameter parameter, Object value, - AnnotationAttribute attribute) { + AnnotationAttribute attribute) { return new BoundMethodParameter(parameter, value, attribute); } diff --git a/src/main/java/org/springframework/hateoas/mvc/BasicLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/BasicLinkBuilder.java deleted file mode 100644 index 9913a5d5..00000000 --- a/src/main/java/org/springframework/hateoas/mvc/BasicLinkBuilder.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2012 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.mvc; - -import org.springframework.hateoas.LinkBuilder; -import org.springframework.hateoas.core.LinkBuilderSupport; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * Simples {@link LinkBuilder} implementation possible. Exposes a link to the current servlet mapping only. - * - * @author Oliver Gierke - */ -public class BasicLinkBuilder extends LinkBuilderSupport { - - /** - * Creates a new {@link BasicLinkBuilder} using the given {@link UriComponentsBuilder}. - * - * @param builder must not be {@literal null}. - */ - private BasicLinkBuilder(UriComponentsBuilder builder) { - super(builder); - } - - /** - * Creates a new {@link BasicLinkBuilder} to link to the current servlet mapping. - * - * @return - */ - public static BasicLinkBuilder linkToCurrentMapping() { - return new BasicLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping()); - } - - /* - * (non-Javadoc) - * @see org.springframework.hateoas.mvc.LinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder) - */ - @Override - protected BasicLinkBuilder createNewInstance(UriComponentsBuilder builder) { - return new BasicLinkBuilder(builder); - } - - /* - * (non-Javadoc) - * @see org.springframework.hateoas.mvc.LinkBuilderSupport#getThis() - */ - @Override - protected BasicLinkBuilder getThis() { - return this; - } -} diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java index 21224490..ffabd33a 100755 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java @@ -17,7 +17,8 @@ package org.springframework.hateoas.mvc; import java.lang.reflect.Method; import java.net.URI; -import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; import org.springframework.hateoas.Affordance; @@ -26,9 +27,8 @@ import org.springframework.hateoas.TemplateVariables; import org.springframework.hateoas.core.AnnotationMappingDiscoverer; import org.springframework.hateoas.core.CachingMappingDiscoverer; import org.springframework.hateoas.core.DummyInvocationUtils; -import org.springframework.hateoas.core.LinkBuilderSupport; import org.springframework.hateoas.core.MappingDiscoverer; -import org.springframework.hateoas.core.MethodInvocation; +import org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.request.RequestContextHolder; @@ -48,42 +48,28 @@ import org.springframework.web.util.UriTemplate; * @author Oliver Trosien * @author Greg Turnquist */ -public class ControllerLinkBuilder extends LinkBuilderSupport { +public class ControllerLinkBuilder extends TemplateVariableAwareLinkBuilderSupport { private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer .of(new AnnotationMappingDiscoverer(RequestMapping.class)); private static final ControllerLinkBuilderFactory FACTORY = new ControllerLinkBuilderFactory(); private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler(); - private final TemplateVariables variables; - /** * Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponentsBuilder}. * * @param builder must not be {@literal null}. */ ControllerLinkBuilder(UriComponentsBuilder builder) { - - super(builder); - - this.variables = TemplateVariables.NONE; + this(builder, TemplateVariables.NONE, Collections.emptyList()); } - /** - * Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponents}. - * - * @param uriComponents must not be {@literal null}. - */ - ControllerLinkBuilder(UriComponents uriComponents) { - this(uriComponents, TemplateVariables.NONE, null); + ControllerLinkBuilder(UriComponentsBuilder builder, TemplateVariables variables, List affordances) { + super(builder, variables, affordances); } - public ControllerLinkBuilder(UriComponents uriComponents, TemplateVariables variables, MethodInvocation invocation) { - - super(uriComponents); - - this.variables = variables; - this.addAffordances(findAffordances(invocation, uriComponents)); + ControllerLinkBuilder(UriComponents uriComponents, TemplateVariables variables, List affordances) { + super(uriComponents, variables, affordances); } /** @@ -233,11 +219,12 @@ public class ControllerLinkBuilder extends LinkBuilderSupport affordances, + TemplateVariables variables) { + return new ControllerLinkBuilder(builder, variables, affordances); } /** @@ -249,27 +236,6 @@ public class ControllerLinkBuilder extends LinkBuilderSupport findAffordances(MethodInvocation invocation, UriComponents components) { - return SpringMvcAffordanceBuilder.create(invocation, DISCOVERER, components); - } - private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler { public CustomUriTemplateHandler() { diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java index 259b2205..a7197b68 100644 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java @@ -28,7 +28,7 @@ import org.springframework.hateoas.Link; import org.springframework.hateoas.MethodLinkBuilderFactory; import org.springframework.hateoas.core.LinkBuilderSupport; import org.springframework.hateoas.core.MethodParameters; -import org.springframework.hateoas.WebHandler; +import org.springframework.hateoas.core.WebHandler; /** * Factory for {@link LinkBuilderSupport} instances based on the request mapping annotated on the given controller. @@ -45,12 +45,6 @@ import org.springframework.hateoas.WebHandler; */ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory { - private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer - .of(new AnnotationMappingDiscoverer(RequestMapping.class)); - private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR = new AnnotatedParametersParameterAccessor( - new AnnotationAttribute(PathVariable.class)); - private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR = new RequestParamParameterAccessor(); - private List uriComponentsContributors = new ArrayList<>(); /** @@ -107,26 +101,25 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory ControllerLinkBuilder.getBuilder().path(mapping), - (builder, invocation) -> { - - MethodParameters parameters = new MethodParameters(invocation.getMethod()); - Iterator parameterValues = Arrays.asList(invocation.getArguments()).iterator(); + return WebHandler.linkTo(invocationValue, mapping -> ControllerLinkBuilder.getBuilder().path(mapping), + ControllerLinkBuilder::new, (builder, invocation) -> { - for (MethodParameter parameter : parameters.getParameters()) { - Object parameterValue = parameterValues.next(); - - for (UriComponentsContributor contributor : this.uriComponentsContributors) { - - if (contributor.supportsParameter(parameter)) { - contributor.enhance(builder, parameter, parameterValue); + MethodParameters parameters = new MethodParameters(invocation.getMethod()); + Iterator parameterValues = Arrays.asList(invocation.getArguments()).iterator(); + + for (MethodParameter parameter : parameters.getParameters()) { + Object parameterValue = parameterValues.next(); + + for (UriComponentsContributor contributor : this.uriComponentsContributors) { + + if (contributor.supportsParameter(parameter)) { + contributor.enhance(builder, parameter, parameterValue); + } } } - } - return builder; - }); + return builder; + }); } /* diff --git a/src/main/java/org/springframework/hateoas/reactive/ReactiveLinkBuilder.java b/src/main/java/org/springframework/hateoas/reactive/ReactiveLinkBuilder.java index 2b481e70..1b44d7f1 100644 --- a/src/main/java/org/springframework/hateoas/reactive/ReactiveLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/reactive/ReactiveLinkBuilder.java @@ -19,10 +19,15 @@ import static org.springframework.hateoas.reactive.HypermediaWebFilter.*; import reactor.core.publisher.Mono; +import java.util.List; + +import org.springframework.hateoas.Affordance; import org.springframework.hateoas.Link; -import org.springframework.hateoas.WebHandler; -import org.springframework.hateoas.mvc.ControllerLinkBuilder; +import org.springframework.hateoas.TemplateVariables; +import org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport; +import org.springframework.hateoas.core.WebHandler; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; /** @@ -31,12 +36,14 @@ import org.springframework.web.util.UriComponentsBuilder; * @author Greg Turnquist * @since 1.0 */ -public class ReactiveLinkBuilder { +public class ReactiveLinkBuilder extends TemplateVariableAwareLinkBuilderSupport { - private final ControllerLinkBuilder controllerLinkBuilder; + private ReactiveLinkBuilder(UriComponentsBuilder builder, TemplateVariables variables, List affordances) { + super(builder, variables, affordances); + } - private ReactiveLinkBuilder(ControllerLinkBuilder controllerLinkBuilder) { - this.controllerLinkBuilder = controllerLinkBuilder; + private ReactiveLinkBuilder(UriComponents components, TemplateVariables variables, List affordances) { + super(components, variables, affordances); } /** @@ -61,26 +68,9 @@ public class ReactiveLinkBuilder { */ public static ReactiveLinkBuilder linkTo(Object invocationValue, ServerWebExchange exchange) { - ControllerLinkBuilder controllerLinkBuilder = WebHandler // - .linkTo(invocationValue, path -> getBuilder(exchange).replacePath(path == null ? "/" : path), null); - - return new ReactiveLinkBuilder(controllerLinkBuilder); - } - - /** - * Utility method to transform {@link ReactiveLinkBuilder} into a {@link Link}. - */ - public Link withSelfRel() { - return this.controllerLinkBuilder.withSelfRel(); - } - - /** - * Utility method to transform {@link ReactiveLinkBuilder} into a {@link Link}. - * - * @param rel - */ - public Link withRel(String rel) { - return this.controllerLinkBuilder.withRel(rel); + return WebHandler.linkTo(invocationValue, // + path -> getBuilder(exchange).replacePath(path == null ? "/" : path), // + ReactiveLinkBuilder::new); } /** @@ -94,4 +84,23 @@ public class ReactiveLinkBuilder { ? UriComponentsBuilder.fromPath("/") // : UriComponentsBuilder.fromHttpRequest(exchange.getRequest()); } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List, org.springframework.hateoas.TemplateVariables) + */ + @Override + protected ReactiveLinkBuilder createNewInstance(UriComponentsBuilder builder, List affordances, + TemplateVariables variables) { + return new ReactiveLinkBuilder(builder, variables, affordances); + } + + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.LinkBuilderSupport#getThis() + */ + @Override + protected ReactiveLinkBuilder getThis() { + return this; + } } diff --git a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java index a2aa590c..6d345d82 100755 --- a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java @@ -17,7 +17,7 @@ package org.springframework.hateoas.config; import static org.assertj.core.api.Assertions.*; import static org.springframework.hateoas.hal.HalConfiguration.RenderSingleLinks.*; -import static org.springframework.hateoas.support.ContextTester.withServletContext; +import static org.springframework.hateoas.support.ContextTester.*; import java.lang.reflect.Method; import java.util.List; @@ -52,12 +52,9 @@ import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.mock.web.MockServletContext; import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.ReflectionUtils; import org.springframework.web.client.RestTemplate; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite; import org.springframework.web.servlet.config.annotation.EnableWebMvc; diff --git a/src/test/java/org/springframework/hateoas/config/mvc/HypermediaRestTemplateBeanPostProcessorTest.java b/src/test/java/org/springframework/hateoas/config/mvc/HypermediaRestTemplateBeanPostProcessorTest.java index de0b6cc1..6a979d31 100644 --- a/src/test/java/org/springframework/hateoas/config/mvc/HypermediaRestTemplateBeanPostProcessorTest.java +++ b/src/test/java/org/springframework/hateoas/config/mvc/HypermediaRestTemplateBeanPostProcessorTest.java @@ -35,64 +35,72 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert import org.springframework.web.client.RestTemplate; /** + * Tests the registration of media types by the {@link HypermediaRestTemplateBeanPostProcessor}. + * * @author Greg Turnquist */ public class HypermediaRestTemplateBeanPostProcessorTest { + /** + * @see #728 + */ @Test public void shouldRegisterJustHal() { withContext(HalConfig.class, context -> { - assertThat(lookupSupportedHypermediaTypes(context.getBean(RestTemplate.class))) - .containsExactlyInAnyOrder( - MediaTypes.HAL_JSON, - MediaTypes.HAL_JSON_UTF8, - MediaType.APPLICATION_JSON, - MediaType.parseMediaType("application/*+json")); + assertThat(lookupSupportedHypermediaTypes(context.getBean(RestTemplate.class))) // + .containsExactlyInAnyOrder( // + MediaTypes.HAL_JSON, // + MediaTypes.HAL_JSON_UTF8, // + MediaType.APPLICATION_JSON, // + MediaType.parseMediaType("application/*+json")); }); } + /** + * @see #728 + */ @Test public void shouldRegisterHalAndCollectionJsonMessageConverters() { withContext(HalAndCollectionJsonConfig.class, context -> { - assertThat(lookupSupportedHypermediaTypes(context.getBean(RestTemplate.class))) - .containsExactlyInAnyOrder( - MediaTypes.HAL_JSON, - MediaTypes.HAL_JSON_UTF8, - MediaTypes.COLLECTION_JSON, - MediaType.APPLICATION_JSON, - MediaType.parseMediaType("application/*+json")); + assertThat(lookupSupportedHypermediaTypes(context.getBean(RestTemplate.class))) // + .containsExactlyInAnyOrder( // + MediaTypes.HAL_JSON, // + MediaTypes.HAL_JSON_UTF8, // + MediaTypes.COLLECTION_JSON, // + MediaType.APPLICATION_JSON, // + MediaType.parseMediaType("application/*+json")); }); } + /** + * @see #728 + */ @Test public void shouldRegisterHypermediaMessageConverters() { withContext(AllHypermediaConfig.class, context -> { - assertThat(lookupSupportedHypermediaTypes(context.getBean(RestTemplate.class))) - .containsExactlyInAnyOrder( - MediaTypes.HAL_JSON, - MediaTypes.HAL_JSON_UTF8, - MediaTypes.HAL_FORMS_JSON, - MediaTypes.COLLECTION_JSON, - MediaTypes.UBER_JSON, - MediaType.APPLICATION_JSON, - MediaType.parseMediaType("application/*+json")); + assertThat(lookupSupportedHypermediaTypes(context.getBean(RestTemplate.class))) // + .containsExactlyInAnyOrder( // + MediaTypes.HAL_JSON, // + MediaTypes.HAL_JSON_UTF8, // + MediaTypes.HAL_FORMS_JSON, // + MediaTypes.COLLECTION_JSON, // + MediaTypes.UBER_JSON, // + MediaType.APPLICATION_JSON, // + MediaType.parseMediaType("application/*+json")); }); } private List lookupSupportedHypermediaTypes(RestTemplate restTemplate) { - - return restTemplate.getMessageConverters().stream() - .filter(MappingJackson2HttpMessageConverter.class::isInstance) - .map(AbstractJackson2HttpMessageConverter.class::cast) - .map(AbstractHttpMessageConverter::getSupportedMediaTypes) - .flatMap(Collection::stream) - .collect(Collectors.toList()); + + return restTemplate.getMessageConverters().stream().filter(MappingJackson2HttpMessageConverter.class::isInstance) + .map(AbstractJackson2HttpMessageConverter.class::cast).map(AbstractHttpMessageConverter::getSupportedMediaTypes) + .flatMap(Collection::stream).collect(Collectors.toList()); } static class BaseConfig { @@ -105,16 +113,14 @@ public class HypermediaRestTemplateBeanPostProcessorTest { @Configuration @EnableHypermediaSupport(type = HypermediaType.HAL) - static class HalConfig extends BaseConfig { - } + static class HalConfig extends BaseConfig {} @Configuration - @EnableHypermediaSupport(type = {HypermediaType.HAL, HypermediaType.COLLECTION_JSON}) - static class HalAndCollectionJsonConfig extends BaseConfig { - } + @EnableHypermediaSupport(type = { HypermediaType.HAL, HypermediaType.COLLECTION_JSON }) + static class HalAndCollectionJsonConfig extends BaseConfig {} @Configuration - @EnableHypermediaSupport(type = {HypermediaType.HAL, HypermediaType.HAL_FORMS, HypermediaType.COLLECTION_JSON, HypermediaType.UBER}) - static class AllHypermediaConfig extends BaseConfig { - } -} \ No newline at end of file + @EnableHypermediaSupport( + type = { HypermediaType.HAL, HypermediaType.HAL_FORMS, HypermediaType.COLLECTION_JSON, HypermediaType.UBER }) + static class AllHypermediaConfig extends BaseConfig {} +} diff --git a/src/test/java/org/springframework/hateoas/config/reactive/HypermediaWebClientBeanPostProcessorTest.java b/src/test/java/org/springframework/hateoas/config/reactive/HypermediaWebClientBeanPostProcessorTest.java index ac1d7954..36abd55b 100644 --- a/src/test/java/org/springframework/hateoas/config/reactive/HypermediaWebClientBeanPostProcessorTest.java +++ b/src/test/java/org/springframework/hateoas/config/reactive/HypermediaWebClientBeanPostProcessorTest.java @@ -20,7 +20,6 @@ import static org.springframework.hateoas.support.ContextTester.*; import reactor.test.StepVerifier; -import java.io.IOException; import java.net.URI; import org.junit.After; @@ -42,6 +41,8 @@ import org.springframework.hateoas.mvc.TypeReferences.ResourceType; import org.springframework.web.reactive.function.client.WebClient; /** + * Tests registration of proper decoders by the {@link HypermediaWebClientBeanPostProcessor}. + * * @author Greg Turnquist */ public class HypermediaWebClientBeanPostProcessorTest { @@ -75,6 +76,9 @@ public class HypermediaWebClientBeanPostProcessorTest { } } + /** + * @see #728 + */ @Test public void shouldHandleRootHalDocument() { @@ -96,6 +100,9 @@ public class HypermediaWebClientBeanPostProcessorTest { }); } + /** + * @see #728 + */ @Test public void shouldHandleNavigatingToAResourceObject() { diff --git a/src/test/java/org/springframework/hateoas/core/ControllerEntityLinksUnitTest.java b/src/test/java/org/springframework/hateoas/core/ControllerEntityLinksUnitTest.java index 232b8f57..d3a59145 100755 --- a/src/test/java/org/springframework/hateoas/core/ControllerEntityLinksUnitTest.java +++ b/src/test/java/org/springframework/hateoas/core/ControllerEntityLinksUnitTest.java @@ -15,9 +15,9 @@ */ package org.springframework.hateoas.core; +import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; @@ -49,7 +49,7 @@ public class ControllerEntityLinksUnitTest extends TestUtils { public void rejectsUnannotatedController() { assertThatExceptionOfType(IllegalArgumentException.class) // - .isThrownBy(() -> new ControllerEntityLinks(Arrays.asList(InvalidController.class), linkBuilderFactory)) // + .isThrownBy(() -> new ControllerEntityLinks(singletonList(InvalidController.class), linkBuilderFactory)) // .withMessageContaining(InvalidController.class.getName()); } @@ -63,14 +63,14 @@ public class ControllerEntityLinksUnitTest extends TestUtils { public void rejectsNullLinkBuilderFactory() { assertThatExceptionOfType(IllegalArgumentException.class) // - .isThrownBy(() -> new ControllerEntityLinks(Arrays.asList(SampleController.class), null)); + .isThrownBy(() -> new ControllerEntityLinks(singletonList(SampleController.class), null)); } @Test public void registersControllerForEntity() { when(linkBuilderFactory.linkTo(SampleController.class, new Object[0])).thenReturn(linkTo(SampleController.class)); - EntityLinks links = new ControllerEntityLinks(Arrays.asList(SampleController.class), linkBuilderFactory); + EntityLinks links = new ControllerEntityLinks(singletonList(SampleController.class), linkBuilderFactory); assertThat(links.supports(Person.class)).isTrue(); assertThat(links.linkFor(Person.class)).isNotNull(); @@ -85,7 +85,7 @@ public class ControllerEntityLinksUnitTest extends TestUtils { when(linkBuilderFactory.linkTo(eq(ControllerWithParameters.class), (Object[]) any())) // .thenReturn(linkTo(ControllerWithParameters.class, "1")); - ControllerEntityLinks links = new ControllerEntityLinks(Arrays.asList(ControllerWithParameters.class), + ControllerEntityLinks links = new ControllerEntityLinks(singletonList(ControllerWithParameters.class), linkBuilderFactory); LinkBuilder builder = links.linkFor(Order.class, "1"); diff --git a/src/test/java/org/springframework/hateoas/core/LinkBuilderSupportUnitTest.java b/src/test/java/org/springframework/hateoas/core/LinkBuilderSupportUnitTest.java index 67f58664..dc252848 100755 --- a/src/test/java/org/springframework/hateoas/core/LinkBuilderSupportUnitTest.java +++ b/src/test/java/org/springframework/hateoas/core/LinkBuilderSupportUnitTest.java @@ -17,13 +17,17 @@ package org.springframework.hateoas.core; import static org.assertj.core.api.Assertions.*; +import java.util.Collections; +import java.util.List; + import org.junit.Test; +import org.springframework.hateoas.Affordance; import org.springframework.hateoas.TestUtils; import org.springframework.web.util.UriComponentsBuilder; /** * Unit tests for {@link LinkBuilderSupport}. - * + * * @author Oliver Gierke * @author Kamill Sokol */ @@ -32,14 +36,14 @@ public class LinkBuilderSupportUnitTest extends TestUtils { @Test public void callingSlashWithEmptyStringIsNoOp() { - SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance()); + SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance(), Collections.emptyList()); assertThat(builder.slash("")).isEqualTo(builder); } @Test public void appendsFragmentCorrectly() { - SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance()); + SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance(), Collections.emptyList()); builder = builder.slash("foo#bar"); assertThat(builder.toString()).endsWith("foo#bar"); builder = builder.slash("bar"); @@ -58,7 +62,7 @@ public class LinkBuilderSupportUnitTest extends TestUtils { @Test public void appendsPathContainingColonsCorrectly() { - SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance()); + SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance(), Collections.emptyList()); builder = builder.slash("47:11"); @@ -67,8 +71,8 @@ public class LinkBuilderSupportUnitTest extends TestUtils { static class SampleLinkBuilder extends LinkBuilderSupport { - public SampleLinkBuilder(UriComponentsBuilder builder) { - super(builder); + public SampleLinkBuilder(UriComponentsBuilder builder, List afforances) { + super(builder, afforances); } @Override @@ -76,9 +80,13 @@ public class LinkBuilderSupportUnitTest extends TestUtils { return this; } + /* + * (non-Javadoc) + * @see org.springframework.hateoas.core.LinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List) + */ @Override - protected SampleLinkBuilder createNewInstance(UriComponentsBuilder builder) { - return new SampleLinkBuilder(builder); + protected SampleLinkBuilder createNewInstance(UriComponentsBuilder builder, List affordances) { + return new SampleLinkBuilder(builder, affordances); } } } diff --git a/src/test/java/org/springframework/hateoas/reactive/HypermediaWebFilterTest.java b/src/test/java/org/springframework/hateoas/reactive/HypermediaWebFilterTest.java index 0e8fc43b..99eb54f7 100644 --- a/src/test/java/org/springframework/hateoas/reactive/HypermediaWebFilterTest.java +++ b/src/test/java/org/springframework/hateoas/reactive/HypermediaWebFilterTest.java @@ -60,6 +60,9 @@ public class HypermediaWebFilterTest { .exchangeStrategies(webClientConfigurer.hypermediaExchangeStrategies()).build(); } + /** + * @see #728 + */ @Test public void webFilterShouldEmbedExchangeIntoContext() { diff --git a/src/test/java/org/springframework/hateoas/reactive/ReactiveLinkBuilderTest.java b/src/test/java/org/springframework/hateoas/reactive/ReactiveLinkBuilderTest.java index aadeb777..d45787db 100644 --- a/src/test/java/org/springframework/hateoas/reactive/ReactiveLinkBuilderTest.java +++ b/src/test/java/org/springframework/hateoas/reactive/ReactiveLinkBuilderTest.java @@ -50,6 +50,9 @@ public class ReactiveLinkBuilderTest { @Mock ServerWebExchange exchange; @Mock ServerHttpRequest request; + /** + * @see #728 + */ @Test public void linkAtSameLevelAsExplicitServerExchangeShouldWork() throws URISyntaxException { @@ -64,6 +67,9 @@ public class ReactiveLinkBuilderTest { assertThat(link.getHref()).isEqualTo("http://localhost:8080/api"); } + /** + * @see #728 + */ @Test public void linkAtSameLevelAsContextProvidedServerExchangeShouldWork() throws URISyntaxException { @@ -83,6 +89,9 @@ public class ReactiveLinkBuilderTest { }).verifyComplete(); } + /** + * @see #728 + */ @Test public void shallowLinkFromDeepExplicitServerExchangeShouldWork() throws URISyntaxException { @@ -97,6 +106,9 @@ public class ReactiveLinkBuilderTest { assertThat(link.getHref()).isEqualTo("http://localhost:8080/api"); } + /** + * @see #728 + */ @Test public void shallowLinkFromDeepContextProvidedServerExchangeShouldWork() throws URISyntaxException { @@ -116,6 +128,9 @@ public class ReactiveLinkBuilderTest { }).verifyComplete(); } + /** + * @see #728 + */ @Test public void deepLinkFromShallowExplicitServerExchangeShouldWork() throws URISyntaxException { @@ -130,6 +145,9 @@ public class ReactiveLinkBuilderTest { assertThat(link.getHref()).isEqualTo("http://localhost:8080/api/employees"); } + /** + * @see #728 + */ @Test public void deepLinkFromShallowContextProvidedServerExchangeShouldWork() throws URISyntaxException { @@ -149,6 +167,9 @@ public class ReactiveLinkBuilderTest { }).verifyComplete(); } + /** + * @see #728 + */ @Test public void linkToRouteWithNoMappingShouldWork() throws URISyntaxException { @@ -168,6 +189,9 @@ public class ReactiveLinkBuilderTest { }).verifyComplete(); } + /** + * @see #728 + */ @Test public void linkToRouteWithNoExchangeInTheContextShouldFallbackToRelativeUris() throws URISyntaxException { @@ -183,6 +207,9 @@ public class ReactiveLinkBuilderTest { }).verifyComplete(); } + /** + * @see #728 + */ @Test public void linkToRouteWithExplictExchangeBeingNullShouldFallbackToRelativeUris() throws URISyntaxException { diff --git a/src/test/java/org/springframework/hateoas/reactive/ReactiveResourceAssemblerUnitTest.java b/src/test/java/org/springframework/hateoas/reactive/ReactiveResourceAssemblerUnitTest.java index 90354258..d872b384 100644 --- a/src/test/java/org/springframework/hateoas/reactive/ReactiveResourceAssemblerUnitTest.java +++ b/src/test/java/org/springframework/hateoas/reactive/ReactiveResourceAssemblerUnitTest.java @@ -57,6 +57,9 @@ public class ReactiveResourceAssemblerUnitTest { this.exchange = mock(ServerWebExchange.class); } + /** + * @see #728 + */ @Test public void simpleConversionShouldWork() { @@ -71,6 +74,9 @@ public class ReactiveResourceAssemblerUnitTest { }).verifyComplete(); } + /** + * @see #728 + */ @Test public void defaultResourcesConversionShouldWork() { @@ -90,6 +96,9 @@ public class ReactiveResourceAssemblerUnitTest { }).verifyComplete(); } + /** + * @see #728 + */ @Test public void customResourcesShouldWork() { @@ -151,5 +160,4 @@ public class ReactiveResourceAssemblerUnitTest { class EmployeeResource extends ResourceSupport { private Employee employee; } - } diff --git a/src/test/java/org/springframework/hateoas/reactive/SimpleReactiveResourceAssemblerTest.java b/src/test/java/org/springframework/hateoas/reactive/SimpleReactiveResourceAssemblerTest.java index 3ca6e330..06c5041f 100644 --- a/src/test/java/org/springframework/hateoas/reactive/SimpleReactiveResourceAssemblerTest.java +++ b/src/test/java/org/springframework/hateoas/reactive/SimpleReactiveResourceAssemblerTest.java @@ -48,7 +48,7 @@ public class SimpleReactiveResourceAssemblerTest { } /** - * @see # + * @see #728 */ @Test public void convertingToResourceShouldWork() { @@ -65,7 +65,7 @@ public class SimpleReactiveResourceAssemblerTest { } /** - * @see # + * @see #728 */ @Test public void convertingToResourcesShouldWork() { @@ -82,7 +82,7 @@ public class SimpleReactiveResourceAssemblerTest { } /** - * @see # + * @see #728 */ @Test public void convertingToResourceWithCustomLinksShouldWork() { @@ -100,7 +100,7 @@ public class SimpleReactiveResourceAssemblerTest { } /** - * @see # + * @see #728 */ @Test public void convertingToResourcesWithCustomLinksShouldWork() {