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.
This commit is contained in:
Oliver Drotbohm
2021-01-25 22:55:39 +01:00
parent c89369b410
commit 17538c65d7
3 changed files with 52 additions and 4 deletions

View File

@@ -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<Link> preparePersonResources(Person primary, Person... persons) throws Exception {
Link peopleLink = client.discoverUnique(LinkRelation.of("people"));

View File

@@ -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<String> mediaTypes = new LinkedHashSet<String>();
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()]));
}

View File

@@ -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<HttpMessageConverter<?>> 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());