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());