Fix bugs found when using this with the RESTBucks application.

This commit is contained in:
Jon Brisbin
2013-03-02 11:54:57 -06:00
parent 6ef500804b
commit de80e462f4
8 changed files with 493 additions and 426 deletions

View File

@@ -0,0 +1,51 @@
package org.springframework.data.rest.repository.json;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;
/**
* Helper class to register datatype modules based on their presence in the classpath.
*
* @author Jon Brisbin
*/
public class Jackson2DatatypeHelper {
private static final Logger LOG = LoggerFactory.getLogger(Jackson2DatatypeHelper.class);
private static final boolean IS_HIBERNATE4_MODULE_AVAILABLE = ClassUtils.isPresent(
"com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module",
Jackson2DatatypeHelper.class.getClassLoader()
);
private static final boolean IS_JODA_MODULE_AVAILABLE = ClassUtils.isPresent(
"com.fasterxml.jackson.datatype.joda.JodaModule",
Jackson2DatatypeHelper.class.getClassLoader()
);
public static void configureObjectMapper(ObjectMapper mapper) {
// Hibernate types
if(IS_HIBERNATE4_MODULE_AVAILABLE) {
try {
mapper.registerModule((Module)Class.forName("com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module")
.newInstance());
} catch(Throwable t) {
if(LOG.isDebugEnabled()) {
LOG.debug(t.getMessage(), t);
}
}
}
// JODA time
if(IS_JODA_MODULE_AVAILABLE) {
try {
mapper.registerModule((Module)Class.forName("com.fasterxml.jackson.datatype.joda.JodaModule")
.newInstance());
} catch(Throwable t) {
if(LOG.isDebugEnabled()) {
LOG.debug(t.getMessage(), t);
}
}
}
}
}

View File

@@ -116,24 +116,11 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
private class ResourceDeserializer<T extends Object> extends StdDeserializer<T> {
private final PersistentEntity persistentEntity;
private final Object defaultObject;
private final Map<String, Object> defaultValues = new HashMap<String, Object>();
@SuppressWarnings({"unchecked"})
private ResourceDeserializer(PersistentEntity persistentEntity) {
private ResourceDeserializer(final PersistentEntity persistentEntity) {
super(persistentEntity.getType());
this.persistentEntity = persistentEntity;
this.defaultObject = instantiateClass(getValueClass());
final BeanWrapper wrapper = BeanWrapper.create(defaultObject, conversionService);
persistentEntity.doWithProperties(new PropertyHandler() {
@Override public void doWithPersistentProperty(PersistentProperty prop) {
Object defaultValue = wrapper.getProperty(prop);
if(null != defaultValue) {
defaultValues.put(prop.getName(), defaultValue);
}
}
});
}
@SuppressWarnings({"unchecked"})
@@ -253,12 +240,7 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
}
}
if(null != val) {
Object defaultValue = defaultValues.get(persistentProperty.getName());
if(null == defaultValue || defaultValue != val) {
wrapper.setProperty(persistentProperty, val, false);
}
}
wrapper.setProperty(persistentProperty, val, false);
break;
}

View File

@@ -1,10 +1,5 @@
package org.springframework.data.rest.repository.support;
import static org.springframework.beans.BeanUtils.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.Association;
@@ -20,8 +15,6 @@ import org.springframework.data.repository.support.Repositories;
*/
public class DomainObjectMerger {
private final Map<Class<?>, PersistentEntity> entities = new ConcurrentHashMap<Class<?>, PersistentEntity>();
private final Map<String, Object> defaultValues = new ConcurrentHashMap<String, Object>();
private final Repositories repositories;
private final ConversionService conversionService;
@@ -40,61 +33,25 @@ public class DomainObjectMerger {
final BeanWrapper fromWrapper = BeanWrapper.create(from, conversionService);
final BeanWrapper targetWrapper = BeanWrapper.create(target, conversionService);
PersistentEntity entity = getPerisistentEntity(target.getClass());
Class<?> clazz = entity.getType();
final String clazzName = clazz.getSimpleName();
PersistentEntity entity = repositories.getPersistentEntity(target.getClass());
entity.doWithProperties(new PropertyHandler() {
@Override public void doWithPersistentProperty(PersistentProperty persistentProperty) {
String mapKey = clazzName + "." + persistentProperty.getName();
Object fromVal = fromWrapper.getProperty(persistentProperty);
Object defaultVal = defaultValues.get(mapKey);
if(null != fromVal && !fromVal.equals(defaultVal)) {
if(null != fromVal && !fromVal.equals(targetWrapper.getProperty(persistentProperty))) {
targetWrapper.setProperty(persistentProperty, fromVal);
}
}
});
entity.doWithAssociations(new AssociationHandler() {
@Override public void doWithAssociation(Association association) {
PersistentProperty persistentProperty = association.getInverse();
String mapKey = clazzName + "." + persistentProperty.getName();
Object fromVal = fromWrapper.getProperty(persistentProperty);
Object defaultVal = defaultValues.get(mapKey);
if(null != fromVal && !fromVal.equals(defaultVal)) {
if(null != fromVal && !fromVal.equals(targetWrapper.getProperty(persistentProperty))) {
targetWrapper.setProperty(persistentProperty, fromVal);
}
}
});
}
@SuppressWarnings({"unchecked"})
private PersistentEntity getPerisistentEntity(Class<?> clazz) {
PersistentEntity entity = entities.get(clazz);
if(null == entity) {
entity = repositories.getPersistentEntity(clazz);
final String clazzName = clazz.getSimpleName();
final BeanWrapper wrapper = BeanWrapper.create(instantiateClass(clazz), conversionService);
entity.doWithProperties(new PropertyHandler() {
@Override public void doWithPersistentProperty(PersistentProperty persistentProperty) {
Object val = wrapper.getProperty(persistentProperty);
if(null != val) {
defaultValues.put(clazzName + "." + persistentProperty.getName(), val);
}
}
});
entity.doWithAssociations(new AssociationHandler() {
@Override public void doWithAssociation(Association association) {
PersistentProperty persistentProperty = association.getInverse();
Object val = wrapper.getProperty(persistentProperty);
if(null != val) {
defaultValues.put(clazzName + "." + persistentProperty.getName(), val);
}
}
});
entities.put(clazz, entity);
}
return entity;
}
}

View File

@@ -1,16 +1,21 @@
package org.springframework.data.rest.repository;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.domain.jpa.ConfiguredPersonRepository;
import org.springframework.data.rest.repository.domain.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.repository.domain.jpa.Person;
import org.springframework.data.rest.repository.domain.jpa.PersonRepository;
import org.springframework.data.rest.repository.json.PersistentEntityJackson2Module;
import org.springframework.format.support.DefaultFormattingConversionService;
/**
* @author Jon Brisbin
@@ -47,4 +52,26 @@ public class RepositoryTestsConfig {
return config;
}
@Bean public DefaultFormattingConversionService defaultConversionService() {
return new DefaultFormattingConversionService();
}
@Bean public DomainClassConverter<?> domainClassConverter() {
return new DomainClassConverter<DefaultFormattingConversionService>(defaultConversionService());
}
@Bean public UriDomainClassConverter uriDomainClassConverter() {
return new UriDomainClassConverter();
}
@Bean public Module persistentEntityModule() {
return new PersistentEntityJackson2Module(defaultConversionService());
}
@Bean public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(persistentEntityModule());
return mapper;
}
}

View File

@@ -0,0 +1,61 @@
package org.springframework.data.rest.repository.json;
import static junit.framework.Assert.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.regex.Pattern;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.RepositoryTestsConfig;
import org.springframework.data.rest.repository.domain.jpa.Person;
import org.springframework.data.rest.repository.domain.jpa.PersonRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Jon Brisbin
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RepositoryTestsConfig.class)
public class PersistentEntitySerializationTests {
private static final String PERSON_JSON_IN = "{\"firstName\": \"John\",\"lastName\": \"Doe\"}";
private static final Pattern PERSON_JSON_OUT = Pattern.compile(
"\\{\"lastName\":\"Doe\",\"created\":([0-9]+),\"firstName\":\"John\",\"links\":\\[\\{\"rel\":\"people.person.siblings\",\"href\":\"http://localhost:8080/people/2/siblings\"}]}");
@Autowired
private ObjectMapper mapper;
@Autowired
private Repositories repositories;
@Autowired
private PersonRepository people;
@Test
public void deserializesPersonEntity() throws IOException {
Person p = mapper.readValue(PERSON_JSON_IN, Person.class);
assertThat(p.getFirstName(), is("John"));
assertThat(p.getLastName(), is("Doe"));
assertThat(p.getSiblings(), is(Collections.EMPTY_LIST));
}
@Test
public void serializesPersonEntity() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
mapper.writeValue(out, PersistentEntityResource.wrap(repositories.getPersistentEntity(Person.class),
people.save(new Person("John", "Doe")),
URI.create("http://localhost:8080")));
out.flush();
String s = new String(out.toByteArray());
assertTrue("Matches pre-serialized version", PERSON_JSON_OUT.matcher(s).matches());
}
}