diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java index fea6433e25..aa562cc55d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -22,18 +22,21 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.client.LinkDiscoverers; import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; +import org.springframework.hateoas.mediatype.hal.HalConfiguration; +import org.springframework.http.MediaType; import org.springframework.plugin.core.Plugin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; @@ -53,9 +56,17 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl @AutoConfigureAfter({ WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, RepositoryRestMvcAutoConfiguration.class }) @EnableConfigurationProperties(HateoasProperties.class) -@Import(HypermediaHttpMessageConverterConfiguration.class) public class HypermediaAutoConfiguration { + @Bean + @ConditionalOnMissingBean + @ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper") + @ConditionalOnProperty(prefix = "spring.hateoas", name = "use-hal-as-default-json-media-type", + matchIfMissing = true) + HalConfiguration applicationJsonHalConfiguration() { + return new HalConfiguration().withMediaType(MediaType.APPLICATION_JSON); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(LinkDiscoverers.class) @ConditionalOnClass(ObjectMapper.class) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaHttpMessageConverterConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaHttpMessageConverterConfiguration.java index 1b6c3dcd15..915da867cb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaHttpMessageConverterConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaHttpMessageConverterConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -29,6 +29,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.hateoas.mediatype.hal.HalConfiguration; import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; @@ -41,7 +42,9 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl * * @author Andy Wilkinson * @since 1.3.0 + * @deprecated since 2.5.0 in favor of a {@link HalConfiguration} bean */ +@Deprecated @Configuration(proxyBeanMethods = false) public class HypermediaHttpMessageConverterConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfigurationTests.java index 6fe2bf093f..9931093c1d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -29,15 +29,16 @@ import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Configuration; import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.RepresentationModel; import org.springframework.hateoas.client.LinkDiscoverer; import org.springframework.hateoas.client.LinkDiscoverers; import org.springframework.hateoas.config.EnableHypermediaSupport; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; import org.springframework.hateoas.mediatype.hal.HalLinkDiscoverer; import org.springframework.hateoas.server.EntityLinks; -import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import static org.assertj.core.api.Assertions.assertThat; @@ -87,28 +88,28 @@ class HypermediaAutoConfigurationTests { } @Test - void supportedMediaTypesOfTypeConstrainedConvertersIsCustomized() { + void whenUsingTheDefaultConfigurationThenMappingJacksonConverterCanWriteHateoasTypeAsApplicationJson() { this.contextRunner.run((context) -> { RequestMappingHandlerAdapter handlerAdapter = context.getBean(RequestMappingHandlerAdapter.class); - for (HttpMessageConverter converter : handlerAdapter.getMessageConverters()) { - if (converter instanceof TypeConstrainedMappingJackson2HttpMessageConverter) { - assertThat(converter.getSupportedMediaTypes()).contains(MediaType.APPLICATION_JSON, - MediaTypes.HAL_JSON); - } - } + Optional> mappingJacksonConverter = handlerAdapter.getMessageConverters().stream() + .filter(MappingJackson2HttpMessageConverter.class::isInstance).findFirst(); + assertThat(mappingJacksonConverter).isPresent().hasValueSatisfying( + (converter) -> assertThat(converter.canWrite(RepresentationModel.class, MediaType.APPLICATION_JSON)) + .isTrue()); }); } @Test - void customizationOfSupportedMediaTypesCanBeDisabled() { + void whenHalIsNotTheDefaultJsonMediaTypeThenMappingJacksonConverterCannotWriteHateoasTypeAsApplicationJson() { this.contextRunner.withPropertyValues("spring.hateoas.use-hal-as-default-json-media-type:false") .run((context) -> { RequestMappingHandlerAdapter handlerAdapter = context.getBean(RequestMappingHandlerAdapter.class); - for (HttpMessageConverter converter : handlerAdapter.getMessageConverters()) { - if (converter instanceof TypeConstrainedMappingJackson2HttpMessageConverter) { - assertThat(converter.getSupportedMediaTypes()).containsExactly(MediaTypes.HAL_JSON); - } - } + Optional> mappingJacksonConverter = handlerAdapter.getMessageConverters() + .stream().filter(MappingJackson2HttpMessageConverter.class::isInstance).findFirst(); + assertThat(mappingJacksonConverter).isPresent() + .hasValueSatisfying((converter) -> assertThat( + converter.canWrite(RepresentationModel.class, MediaType.APPLICATION_JSON)) + .isFalse()); }); } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-hateoas/src/test/java/smoketest/hateoas/SampleHateoasApplicationTests.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-hateoas/src/test/java/smoketest/hateoas/SampleHateoasApplicationTests.java index f57bf04921..b163deca02 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-hateoas/src/test/java/smoketest/hateoas/SampleHateoasApplicationTests.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-hateoas/src/test/java/smoketest/hateoas/SampleHateoasApplicationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -16,6 +16,8 @@ package smoketest.hateoas; +import java.util.Arrays; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -38,8 +40,21 @@ class SampleHateoasApplicationTests { private TestRestTemplate restTemplate; @Test - void hasHalLinks() { - ResponseEntity entity = this.restTemplate.getForEntity("/customers/1", String.class); + void hasHalLinksWhenAnythingIsAcceptable() { + HttpHeaders headers = new HttpHeaders(); + ResponseEntity entity = this.restTemplate.exchange("/customers/1", HttpMethod.GET, + new HttpEntity<>(headers), String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(entity.getBody()).startsWith("{\"id\":1,\"firstName\":\"Oliver\",\"lastName\":\"Gierke\""); + assertThat(entity.getBody()).contains("_links\":{\"self\":{\"href\""); + } + + @Test + void hasHalLinksWhenJsonIsAcceptable() { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + ResponseEntity entity = this.restTemplate.exchange("/customers/1", HttpMethod.GET, + new HttpEntity<>(headers), String.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(entity.getBody()).startsWith("{\"id\":1,\"firstName\":\"Oliver\",\"lastName\":\"Gierke\""); assertThat(entity.getBody()).contains("_links\":{\"self\":{\"href\"");