DATAREST-199 - Tweaked unmarshalling of JSON payloads.
The BeanWrapper used to set and get values of entities now gets a autowired ConversionService applied. Simplified collection and map creation by using Spring API. Added test case to update an entity with an embedded collection.
This commit is contained in:
@@ -353,7 +353,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
*/
|
||||
@Bean
|
||||
public Module persistentEntityJackson2Module() {
|
||||
return new PersistentEntityJackson2Module(resourceMappings());
|
||||
return new PersistentEntityJackson2Module(resourceMappings(), defaultConversionService());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,17 +6,15 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
@@ -60,12 +58,18 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
|
||||
@Autowired private RepositoryRestConfiguration config;
|
||||
@Autowired private UriDomainClassConverter uriDomainClassConverter;
|
||||
private final ResourceMappings mappings;
|
||||
private final ConversionService conversionService;
|
||||
|
||||
public PersistentEntityJackson2Module(ResourceMappings resourceMappings) {
|
||||
public PersistentEntityJackson2Module(ResourceMappings resourceMappings, ConversionService conversionService) {
|
||||
|
||||
super(new Version(1, 1, 0, "BUILD-SNAPSHOT", "org.springframework.data.rest", "jackson-module"));
|
||||
|
||||
Assert.notNull(resourceMappings, "ResourceMappings must not be null!");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
|
||||
this.mappings = resourceMappings;
|
||||
this.conversionService = conversionService;
|
||||
|
||||
addSerializer(new ResourceSerializer());
|
||||
}
|
||||
|
||||
@@ -116,11 +120,12 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
|
||||
this.persistentEntity = persistentEntity;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "incomplete-switch", "null", "unused" })
|
||||
@SuppressWarnings({ "unchecked", "incomplete-switch", "unused" })
|
||||
@Override
|
||||
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
Object entity = instantiateClass(getValueClass());
|
||||
BeanWrapper<?, Object> wrapper = BeanWrapper.create(entity, null);
|
||||
|
||||
BeanWrapper<?, Object> wrapper = BeanWrapper.create(entity, conversionService);
|
||||
|
||||
ResourceMetadata metadata = mappings.getMappingFor(getValueClass());
|
||||
|
||||
@@ -172,34 +177,27 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
|
||||
// Try and read the value of this attribute.
|
||||
// The method of doing that varies based on the type of the property.
|
||||
if (persistentProperty.isCollectionLike()) {
|
||||
Class<? extends Collection<?>> ctype = (Class<? extends Collection<?>>) persistentProperty.getType();
|
||||
Collection<Object> c = (Collection<Object>) wrapper.getProperty(persistentProperty);
|
||||
if (null == c || c == Collections.EMPTY_LIST || c == Collections.EMPTY_SET) {
|
||||
if (Collection.class.isAssignableFrom(ctype)) {
|
||||
c = new ArrayList<Object>();
|
||||
} else if (Set.class.isAssignableFrom(ctype)) {
|
||||
c = new HashSet<Object>();
|
||||
}
|
||||
}
|
||||
|
||||
Class<? extends Collection<?>> collectionType = (Class<? extends Collection<?>>) persistentProperty
|
||||
.getType();
|
||||
Collection<Object> collection = CollectionFactory.createCollection(collectionType, 0);
|
||||
|
||||
if ((tok = jp.nextToken()) == JsonToken.START_ARRAY) {
|
||||
while ((tok = jp.nextToken()) != JsonToken.END_ARRAY) {
|
||||
Object cval = jp.readValueAs(persistentProperty.getComponentType());
|
||||
c.add(cval);
|
||||
collection.add(cval);
|
||||
}
|
||||
|
||||
val = c;
|
||||
val = collection;
|
||||
} else if (tok == JsonToken.VALUE_NULL) {
|
||||
val = null;
|
||||
} else {
|
||||
throw new HttpMessageNotReadableException("Cannot read a JSON " + tok + " as a Collection.");
|
||||
}
|
||||
} else if (persistentProperty.isMap()) {
|
||||
Class<? extends Map<?, ?>> mtype = (Class<? extends Map<?, ?>>) persistentProperty.getType();
|
||||
Map<Object, Object> m = (Map<Object, Object>) wrapper.getProperty(persistentProperty);
|
||||
if (null == m || m == Collections.EMPTY_MAP) {
|
||||
m = new HashMap<Object, Object>();
|
||||
}
|
||||
|
||||
Class<? extends Map<?, ?>> mapType = (Class<? extends Map<?, ?>>) persistentProperty.getType();
|
||||
Map<Object, Object> map = CollectionFactory.createMap(mapType, 0);
|
||||
|
||||
if ((tok = jp.nextToken()) == JsonToken.START_OBJECT) {
|
||||
do {
|
||||
@@ -208,10 +206,10 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
|
||||
tok = jp.nextToken();
|
||||
Object mval = jp.readValueAs(persistentProperty.getMapValueType());
|
||||
|
||||
m.put(name, mval);
|
||||
map.put(name, mval);
|
||||
} while ((tok = jp.nextToken()) != JsonToken.END_OBJECT);
|
||||
|
||||
val = m;
|
||||
val = map;
|
||||
} else if (tok == JsonToken.VALUE_NULL) {
|
||||
val = null;
|
||||
} else {
|
||||
|
||||
@@ -133,6 +133,10 @@ public abstract class AbstractWebIntegrationTests {
|
||||
assertThat("Expected not to find link with rel " + rel + " but found " + link + "!", link, is(nullValue()));
|
||||
}
|
||||
|
||||
protected void assertHasJsonPathValue(String path, MockHttpServletResponse response) throws Exception {
|
||||
assertThat(JsonPath.read(response.getContentAsString(), path), is(notNullValue()));
|
||||
}
|
||||
|
||||
protected ResultMatcher hasLinkWithRel(final String rel) {
|
||||
|
||||
return new ResultMatcher() {
|
||||
|
||||
@@ -20,17 +20,23 @@ import static org.junit.Assert.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.webmvc.AbstractWebIntegrationTests;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Web integration tests specific to JPA.
|
||||
@@ -120,4 +126,23 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
MockHttpServletResponse orders = request(ordersLink);
|
||||
assertHasJsonPathValue("$..lineItems", orders);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-199
|
||||
*/
|
||||
@Test
|
||||
public void createsOrderUsingPut() throws Exception {
|
||||
|
||||
mvc.perform(//
|
||||
put("/orders/{id}", 4711).//
|
||||
content(readFile("order.json")).contentType(MediaType.APPLICATION_JSON)//
|
||||
).andExpect(status().isCreated());
|
||||
}
|
||||
|
||||
private String readFile(String name) throws Exception {
|
||||
|
||||
ClassPathResource file = new ClassPathResource(name, getClass());
|
||||
List<String> lines = Files.readAllLines(file.getFile().toPath(), Charset.forName("UTF-8"));
|
||||
return StringUtils.collectionToDelimitedString(lines, System.lineSeparator());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ public class RepositoryTestsConfig {
|
||||
|
||||
@Bean
|
||||
public Module persistentEntityModule() {
|
||||
return new PersistentEntityJackson2Module(new ResourceMappings(config(), repositories()));
|
||||
return new PersistentEntityJackson2Module(new ResourceMappings(config(), repositories()),
|
||||
defaultConversionService());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{ "creator" : {
|
||||
"href" : "http://localhost:8080/persons/1"
|
||||
},
|
||||
"lineItems" : [
|
||||
{ "name" : "Java Chip" },
|
||||
{ "name" : "Chocolate Mocca " } ]
|
||||
}
|
||||
Reference in New Issue
Block a user