#1382 - Add ability to customize the ObjectMapper for HAL and HAL-FORMS.
Use HalConfiguration to allow applying customizations to the ObjectMapper. Original pull request: #1383.
This commit is contained in:
committed by
Oliver Drotbohm
parent
0643624e7c
commit
89fade21c2
@@ -18,6 +18,7 @@ package org.springframework.hateoas.mediatype.hal;
|
||||
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.AntPathMatcher;
|
||||
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<String, RenderSingleLinks> singleLinksPerPattern;
|
||||
private final Consumer<ObjectMapper> objectMapperCustomizer;
|
||||
|
||||
/**
|
||||
* Configures whether the Jackson property naming strategy is applied to link relations and within {@code _embedded}
|
||||
@@ -63,15 +67,18 @@ public class HalConfiguration {
|
||||
this.singleLinksPerPattern = new LinkedHashMap<>();
|
||||
this.applyPropertyNamingStrategy = true;
|
||||
this.enforceEmbeddedCollections = true;
|
||||
this.objectMapperCustomizer = objectMapper -> {}; // Default to no action.
|
||||
}
|
||||
|
||||
private HalConfiguration(RenderSingleLinks renderSingleLinks, Map<String, RenderSingleLinks> singleLinksPerPattern,
|
||||
boolean applyPropertyNamingStrategy, boolean enforceEmbeddedCollections) {
|
||||
boolean applyPropertyNamingStrategy, boolean enforceEmbeddedCollections,
|
||||
Consumer<ObjectMapper> objectMapperCustomizer) {
|
||||
|
||||
this.renderSingleLinks = renderSingleLinks;
|
||||
this.singleLinksPerPattern = singleLinksPerPattern;
|
||||
this.applyPropertyNamingStrategy = applyPropertyNamingStrategy;
|
||||
this.enforceEmbeddedCollections = enforceEmbeddedCollections;
|
||||
this.objectMapperCustomizer = objectMapperCustomizer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +139,7 @@ public class HalConfiguration {
|
||||
|
||||
return this.renderSingleLinks == renderSingleLinks ? this
|
||||
: new HalConfiguration(renderSingleLinks, this.singleLinksPerPattern, this.applyPropertyNamingStrategy,
|
||||
this.enforceEmbeddedCollections);
|
||||
this.enforceEmbeddedCollections, this.objectMapperCustomizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,7 +152,7 @@ public class HalConfiguration {
|
||||
|
||||
return this.singleLinksPerPattern == singleLinksPerPattern ? this
|
||||
: new HalConfiguration(this.renderSingleLinks, singleLinksPerPattern, this.applyPropertyNamingStrategy,
|
||||
this.enforceEmbeddedCollections);
|
||||
this.enforceEmbeddedCollections, this.objectMapperCustomizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +166,7 @@ public class HalConfiguration {
|
||||
|
||||
return this.applyPropertyNamingStrategy == applyPropertyNamingStrategy ? this
|
||||
: new HalConfiguration(this.renderSingleLinks, this.singleLinksPerPattern, applyPropertyNamingStrategy,
|
||||
this.enforceEmbeddedCollections);
|
||||
this.enforceEmbeddedCollections, this.objectMapperCustomizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,7 +180,14 @@ public class HalConfiguration {
|
||||
|
||||
return this.enforceEmbeddedCollections == enforceEmbeddedCollections ? this
|
||||
: new HalConfiguration(this.renderSingleLinks, this.singleLinksPerPattern, this.applyPropertyNamingStrategy,
|
||||
enforceEmbeddedCollections);
|
||||
enforceEmbeddedCollections, this.objectMapperCustomizer);
|
||||
}
|
||||
|
||||
public HalConfiguration withObjectMapperCustomizer(Consumer<ObjectMapper> 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 class HalConfiguration {
|
||||
return this.enforceEmbeddedCollections;
|
||||
}
|
||||
|
||||
public Consumer<ObjectMapper> getObjectMapperCustomizer() {
|
||||
return this.objectMapperCustomizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration option how to render single links of a given {@link LinkRelation}.
|
||||
*
|
||||
|
||||
@@ -79,11 +79,14 @@ public class HalMediaTypeConfiguration implements HypermediaMappingInformation {
|
||||
@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;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,8 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation {
|
||||
mapper.setHandlerInstantiator(new Jackson2HalFormsModule.HalFormsHandlerInstantiator(relProvider,
|
||||
curieProvider.getIfAvailable(() -> CurieProvider.NONE), resolver, configuration, beanFactory));
|
||||
|
||||
configuration.getHalConfiguration().getObjectMapperCustomizer().accept(mapper);
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
} ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name" : "Frodo Baggins",
|
||||
"role" : "ring bearer",
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "http://localhost/employees/0"
|
||||
},
|
||||
"employees" : {
|
||||
"href" : "http://localhost/employees"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user