diff --git a/pom.xml b/pom.xml
index 16a8956d9..27193cbff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.hateoas
spring-hateoas
- 1.2.0-SNAPSHOT
+ 1.2.0-HATEOAS-1382-SNAPSHOT
Spring HATEOAS
https://github.com/spring-projects/spring-hateoas
diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java
index a30ae94ae..47b72dc98 100644
--- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java
+++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java
@@ -18,6 +18,7 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.function.Consumer;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
@@ -25,6 +26,8 @@
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
/**
* HAL specific configuration.
*
@@ -41,6 +44,7 @@ public class HalConfiguration {
*/
private final RenderSingleLinks renderSingleLinks;
private final Map singleLinksPerPattern;
+ private final Consumer objectMapperCustomizer;
/**
* Configures whether the Jackson property naming strategy is applied to link relations and within {@code _embedded}
@@ -63,15 +67,18 @@ public HalConfiguration() {
this.singleLinksPerPattern = new LinkedHashMap<>();
this.applyPropertyNamingStrategy = true;
this.enforceEmbeddedCollections = true;
+ this.objectMapperCustomizer = objectMapper -> {}; // Default to no action.
}
private HalConfiguration(RenderSingleLinks renderSingleLinks, Map singleLinksPerPattern,
- boolean applyPropertyNamingStrategy, boolean enforceEmbeddedCollections) {
+ boolean applyPropertyNamingStrategy, boolean enforceEmbeddedCollections,
+ Consumer objectMapperCustomizer) {
this.renderSingleLinks = renderSingleLinks;
this.singleLinksPerPattern = singleLinksPerPattern;
this.applyPropertyNamingStrategy = applyPropertyNamingStrategy;
this.enforceEmbeddedCollections = enforceEmbeddedCollections;
+ this.objectMapperCustomizer = objectMapperCustomizer;
}
/**
@@ -132,7 +139,7 @@ public HalConfiguration withRenderSingleLinks(RenderSingleLinks renderSingleLink
return this.renderSingleLinks == renderSingleLinks ? this
: new HalConfiguration(renderSingleLinks, this.singleLinksPerPattern, this.applyPropertyNamingStrategy,
- this.enforceEmbeddedCollections);
+ this.enforceEmbeddedCollections, this.objectMapperCustomizer);
}
/**
@@ -145,7 +152,7 @@ private HalConfiguration withSingleLinksPerPattern(Map objectMapperCustomizer) {
+
+ return this.objectMapperCustomizer == objectMapperCustomizer ? this
+ : new HalConfiguration(this.renderSingleLinks, this.singleLinksPerPattern, this.applyPropertyNamingStrategy,
+ this.enforceEmbeddedCollections, objectMapperCustomizer);
}
public RenderSingleLinks getRenderSingleLinks() {
@@ -188,6 +202,10 @@ public boolean isEnforceEmbeddedCollections() {
return this.enforceEmbeddedCollections;
}
+ public Consumer getObjectMapperCustomizer() {
+ return this.objectMapperCustomizer;
+ }
+
/**
* Configuration option how to render single links of a given {@link LinkRelation}.
*
diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java
index f2228e2a8..be9cd853f 100644
--- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java
+++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java
@@ -79,11 +79,14 @@ public List getMediaTypes() {
@Override
public ObjectMapper configureObjectMapper(ObjectMapper mapper) {
+ HalConfiguration halConfiguration = this.halConfiguration.getIfAvailable(HalConfiguration::new);
+
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new Jackson2HalModule());
mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider,
- curieProvider.getIfAvailable(() -> CurieProvider.NONE), resolver,
- halConfiguration.getIfAvailable(HalConfiguration::new), beanFactory));
+ curieProvider.getIfAvailable(() -> CurieProvider.NONE), resolver, halConfiguration, beanFactory));
+
+ halConfiguration.getObjectMapperCustomizer().accept(mapper);
return mapper;
}
diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java
index f0e0b5610..9c50741f8 100644
--- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java
+++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java
@@ -82,6 +82,8 @@ public ObjectMapper configureObjectMapper(ObjectMapper mapper) {
mapper.setHandlerInstantiator(new Jackson2HalFormsModule.HalFormsHandlerInstantiator(relProvider,
curieProvider.getIfAvailable(() -> CurieProvider.NONE), resolver, configuration, beanFactory));
+ configuration.getHalConfiguration().getObjectMapperCustomizer().accept(mapper);
+
return mapper;
}
diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/HalObjectMapperCustomizerTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/HalObjectMapperCustomizerTest.java
new file mode 100644
index 000000000..1aaa4ebc3
--- /dev/null
+++ b/src/test/java/org/springframework/hateoas/mediatype/hal/HalObjectMapperCustomizerTest.java
@@ -0,0 +1,68 @@
+package org.springframework.hateoas.mediatype.hal;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.hateoas.MappingTestUtils;
+import org.springframework.hateoas.config.EnableHypermediaSupport;
+import org.springframework.hateoas.support.WebMvcEmployeeController;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+/**
+ * @author Greg Turnquist
+ */
+@ExtendWith(SpringExtension.class)
+@WebAppConfiguration
+@ContextConfiguration
+public class HalObjectMapperCustomizerTest {
+
+ @Autowired WebApplicationContext context;
+
+ MockMvc mockMvc;
+
+ MappingTestUtils.ContextualMapper mapper = MappingTestUtils.createMapper(getClass());
+
+ @BeforeEach
+ void setUp() {
+
+ this.mockMvc = webAppContextSetup(this.context).build();
+ WebMvcEmployeeController.reset();
+ }
+
+ @Test // #1382
+ void objectMapperCustomizerShouldBeApplied() throws Exception {
+
+ String actualHalJson = this.mockMvc.perform(get("/employees/0")).andReturn().getResponse().getContentAsString();
+ String expectedHalJson = this.mapper.readFile("hal-custom.json");
+
+ assertThat(actualHalJson).isEqualTo(expectedHalJson);
+ }
+
+ @Configuration
+ @EnableWebMvc
+ @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
+ @Import(WebMvcEmployeeController.class)
+ static class TestConfig {
+
+ @Bean
+ HalConfiguration halConfiguration() {
+ return new HalConfiguration()
+ .withObjectMapperCustomizer(objectMapper -> objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true));
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsObjectMapperCustomizerTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsObjectMapperCustomizerTest.java
new file mode 100644
index 000000000..fe6aca1e0
--- /dev/null
+++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsObjectMapperCustomizerTest.java
@@ -0,0 +1,70 @@
+package org.springframework.hateoas.mediatype.hal.forms;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.hateoas.MappingTestUtils;
+import org.springframework.hateoas.config.EnableHypermediaSupport;
+import org.springframework.hateoas.mediatype.hal.HalConfiguration;
+import org.springframework.hateoas.support.WebMvcEmployeeController;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+/**
+ * @author Greg Turnquist
+ */
+@ExtendWith(SpringExtension.class)
+@WebAppConfiguration
+@ContextConfiguration
+public class HalFormsObjectMapperCustomizerTest {
+
+ @Autowired WebApplicationContext context;
+
+ MockMvc mockMvc;
+
+ MappingTestUtils.ContextualMapper mapper = MappingTestUtils.createMapper(getClass());
+
+ @BeforeEach
+ void setUp() {
+
+ this.mockMvc = webAppContextSetup(this.context).build();
+ WebMvcEmployeeController.reset();
+ }
+
+ @Test // #1382
+ void objectMapperCustomizerShouldBeApplied() throws Exception {
+
+ String actualHalFormsJson = this.mockMvc.perform(get("/employees/0")).andReturn().getResponse()
+ .getContentAsString();
+ String expectedHalFormsJson = this.mapper.readFile("hal-forms-custom.json");
+
+ assertThat(actualHalFormsJson).isEqualTo(expectedHalFormsJson);
+ }
+
+ @Configuration
+ @EnableWebMvc
+ @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL_FORMS)
+ @Import(WebMvcEmployeeController.class)
+ static class TestConfig {
+
+ @Bean
+ HalConfiguration halConfiguration() {
+ return new HalConfiguration()
+ .withObjectMapperCustomizer(objectMapper -> objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true));
+ }
+ }
+}
diff --git a/src/test/resources/org/springframework/hateoas/mediatype/hal/forms/hal-forms-custom.json b/src/test/resources/org/springframework/hateoas/mediatype/hal/forms/hal-forms-custom.json
new file mode 100644
index 000000000..2a107b262
--- /dev/null
+++ b/src/test/resources/org/springframework/hateoas/mediatype/hal/forms/hal-forms-custom.json
@@ -0,0 +1,31 @@
+{
+ "name" : "Frodo Baggins",
+ "role" : "ring bearer",
+ "_links" : {
+ "self" : {
+ "href" : "http://localhost/employees/0"
+ },
+ "employees" : {
+ "href" : "http://localhost/employees"
+ }
+ },
+ "_templates" : {
+ "default" : {
+ "method" : "put",
+ "properties" : [ {
+ "name" : "name",
+ "required" : true
+ }, {
+ "name" : "role"
+ } ]
+ },
+ "partiallyUpdateEmployee" : {
+ "method" : "patch",
+ "properties" : [ {
+ "name" : "name"
+ }, {
+ "name" : "role"
+ } ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/org/springframework/hateoas/mediatype/hal/hal-custom.json b/src/test/resources/org/springframework/hateoas/mediatype/hal/hal-custom.json
new file mode 100644
index 000000000..7dc8251d9
--- /dev/null
+++ b/src/test/resources/org/springframework/hateoas/mediatype/hal/hal-custom.json
@@ -0,0 +1,12 @@
+{
+ "name" : "Frodo Baggins",
+ "role" : "ring bearer",
+ "_links" : {
+ "self" : {
+ "href" : "http://localhost/employees/0"
+ },
+ "employees" : {
+ "href" : "http://localhost/employees"
+ }
+ }
+}
\ No newline at end of file