From 17538c65d7c18edf2aaaca8b565f04545033cbaf Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Mon, 25 Jan 2021 22:55:39 +0100 Subject: [PATCH] Enable rendering as HAL FORMS. We're now registering an HttpMessageConverter to render requests for HAL FORMS using the HalFormsConfiguration present in the application. We're currently not adding any affordances yet. The change solely enables user code to add affordances explicitly, e.g. via RepresentationModelProcessor implementations. Fixes: #1991. --- .../data/rest/webmvc/jpa/JpaWebTests.java | 9 ++++ .../webmvc/RepositoryRestHandlerMapping.java | 2 + .../RepositoryRestMvcConfiguration.java | 45 +++++++++++++++++-- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java index 3ad5c3c96..90a3fa034 100755 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java @@ -45,6 +45,7 @@ import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkRelation; import org.springframework.hateoas.Links; +import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; @@ -731,6 +732,14 @@ public class JpaWebTests extends CommonWebTests { assertThat(content.read("$.calculatedName", String.class)).isEqualTo("calculated-put"); } + @Test // #1991 + public void answersToHalFormsRequests() throws Exception { + + mvc.perform(get("/") + .accept(MediaTypes.HAL_FORMS_JSON)) + .andExpect(status().isOk()); + } + private List preparePersonResources(Person primary, Person... persons) throws Exception { Link peopleLink = client.discoverUnique(LinkRelation.of("people")); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java index 616818ea8..c899bb5ce 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java @@ -34,6 +34,7 @@ import org.springframework.data.rest.core.mapping.ResourceMetadata; import org.springframework.data.rest.webmvc.support.JpaHelper; import org.springframework.data.util.ProxyUtils; import org.springframework.data.util.Streamable; +import org.springframework.hateoas.MediaTypes; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor; @@ -217,6 +218,7 @@ public class RepositoryRestHandlerMapping extends BasePathAwareHandlerMapping { Set mediaTypes = new LinkedHashSet(); mediaTypes.add(configuration.getDefaultMediaType().toString()); mediaTypes.add(MediaType.APPLICATION_JSON_VALUE); + mediaTypes.add(MediaTypes.HAL_FORMS_JSON_VALUE); return new ProducesRequestCondition(mediaTypes.toArray(new String[mediaTypes.size()])); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java index 4f423b3a7..68121476a 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java @@ -104,6 +104,8 @@ import org.springframework.hateoas.mediatype.hal.DefaultCurieProvider; import org.springframework.hateoas.mediatype.hal.HalConfiguration; import org.springframework.hateoas.mediatype.hal.Jackson2HalModule; import org.springframework.hateoas.mediatype.hal.Jackson2HalModule.HalHandlerInstantiator; +import org.springframework.hateoas.mediatype.hal.forms.HalFormsConfiguration; +import org.springframework.hateoas.mediatype.hal.forms.Jackson2HalFormsModule; import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.hateoas.server.core.EvoInflectorLinkRelationProvider; import org.springframework.hateoas.server.mvc.RepresentationModelProcessorInvoker; @@ -143,7 +145,7 @@ import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; * @author Christoph Strobl */ @Configuration(proxyBeanMethods = false) -@EnableHypermediaSupport(type = HypermediaType.HAL) +@EnableHypermediaSupport(type = { HypermediaType.HAL, HypermediaType.HAL_FORMS }) @ImportResource("classpath*:META-INF/spring-data-rest/**/*.xml") @Import({ RestControllerImportSelector.class, // SpringDataJacksonConfiguration.class, // @@ -573,13 +575,45 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon return converter; } + /** + * {@link HttpMessageConverter} to support rendering HAL FORMS. + * + * @param linkCollector + * @return + * @since 3.5 + */ + @Bean + TypeConstrainedMappingJackson2HttpMessageConverter halFormsJacksonHttpMessageConverter(LinkCollector linkCollector) { + + LinkRelationProvider defaultedRelProvider = this.relProvider.getIfUnique(EvoInflectorLinkRelationProvider::new); + HalFormsConfiguration configuration = new HalFormsConfiguration( + halConfiguration.getIfUnique(() -> new HalConfiguration())); + CurieProvider curieProvider = this.curieProvider + .getIfUnique(() -> new DefaultCurieProvider(Collections.emptyMap())); + ObjectMapper mapper = basicObjectMapper(); + + mapper.registerModule(persistentEntityJackson2Module(linkCollector)); + mapper.registerModule(new Jackson2HalFormsModule()); + mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator( + defaultedRelProvider, curieProvider, resolver.getObject(), configuration.getHalConfiguration(), + applicationContext.getAutowireCapableBeanFactory())); + + TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter( + RepresentationModel.class); + converter.setSupportedMediaTypes(Collections.singletonList(MediaTypes.HAL_FORMS_JSON)); + converter.setObjectMapper(mapper); + + return converter; + } + public ObjectMapper halObjectMapper(LinkCollector linkCollector) { LinkRelationProvider defaultedRelProvider = this.relProvider.getIfUnique(EvoInflectorLinkRelationProvider::new); HalConfiguration halConfiguration = this.halConfiguration.getIfUnique(HalConfiguration::new); - HalHandlerInstantiator instantiator = new HalHandlerInstantiator(defaultedRelProvider, - curieProvider.getIfUnique(() -> new DefaultCurieProvider(Collections.emptyMap())), resolver.getObject(), - halConfiguration, applicationContext.getAutowireCapableBeanFactory()); + CurieProvider curieProvider = this.curieProvider + .getIfUnique(() -> new DefaultCurieProvider(Collections.emptyMap())); + HalHandlerInstantiator instantiator = new HalHandlerInstantiator(defaultedRelProvider, curieProvider, + resolver.getObject(), halConfiguration, applicationContext.getAutowireCapableBeanFactory()); ObjectMapper mapper = basicObjectMapper(); mapper.registerModule(persistentEntityJackson2Module(linkCollector)); @@ -739,6 +773,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon public List> defaultMessageConverters( @Qualifier("jacksonHttpMessageConverter") TypeConstrainedMappingJackson2HttpMessageConverter jacksonHttpMessageConverter, @Qualifier("halJacksonHttpMessageConverter") TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter, + @Qualifier("halFormsJacksonHttpMessageConverter") TypeConstrainedMappingJackson2HttpMessageConverter halFormsJacksonHttpMessageConverter, AlpsJsonHttpMessageConverter alpsJsonHttpMessageConverter, UriListHttpMessageConverter uriListHttpMessageConverter, RepositoryRestConfigurerDelegate configurerDelegate, RepositoryRestConfiguration repositoryRestConfiguration) { @@ -757,6 +792,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon messageConverters.add(halJacksonHttpMessageConverter); } + messageConverters.add(halFormsJacksonHttpMessageConverter); + MappingJackson2HttpMessageConverter fallbackJsonConverter = new MappingJackson2HttpMessageConverter(); fallbackJsonConverter.setObjectMapper(basicObjectMapper());