SGF-705 - Fix MappingPdxSerializer to ignore transient, non-writable and non-entity-based (simple type) properties and fields.

This commit is contained in:
John Blum
2017-12-16 18:23:29 -08:00
parent c94ef2163c
commit a5b182471a
8 changed files with 784 additions and 246 deletions

View File

@@ -44,6 +44,7 @@ public abstract class GemfireUtils extends RegionUtils {
/* (non-Javadoc) */
public static String apacheGeodeProductName() {
try {
return GemFireVersion.getProductName();
}
@@ -54,6 +55,7 @@ public abstract class GemfireUtils extends RegionUtils {
/* (non-Javadoc) */
public static String apacheGeodeVersion() {
try {
return CacheFactory.getVersion();
}
@@ -95,6 +97,7 @@ public abstract class GemfireUtils extends RegionUtils {
/* (non-Javadoc) */
public static boolean isGemfireVersion8OrAbove() {
try {
return isGemfireVersionGreaterThanEqualTo(8.0);
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.gemfire.mapping;
import static org.springframework.data.gemfire.util.CollectionUtils.*;
import java.lang.reflect.Modifier;
import java.util.Set;
import org.springframework.data.annotation.Id;
@@ -86,11 +87,17 @@ public class GemfirePersistentProperty extends AnnotationBasedPersistentProperty
return (super.isIdProperty() || SUPPORTED_IDENTIFIER_NAMES.contains(getName()));
}
@Override
public boolean isTransient() {
return super.isTransient()
|| getProperty().getField().filter(field -> Modifier.isTransient(field.getModifiers())).isPresent();
}
/**
* @inheritDoc
*/
@Override
public boolean usePropertyAccess() {
return (super.usePropertyAccess() || !getProperty().isFieldBacked());
return super.usePropertyAccess() || !getProperty().isFieldBacked();
}
}

View File

@@ -18,11 +18,11 @@ package org.springframework.data.gemfire.mapping;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geode.pdx.PdxReader;
import org.apache.geode.pdx.PdxSerializer;
import org.apache.geode.pdx.PdxWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -38,6 +38,7 @@ import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* GemFire {@link PdxSerializer} implementation that uses a Spring Data GemFire {@link GemfireMappingContext}
@@ -50,26 +51,28 @@ import org.springframework.util.Assert;
* @see org.springframework.context.ApplicationContextAware
* @see org.springframework.core.convert.ConversionService
* @see org.springframework.data.convert.EntityInstantiator
* @see org.springframework.data.convert.EntityInstantiators
* @see org.springframework.data.mapping.PersistentEntity
* @see org.springframework.data.mapping.PersistentPropertyAccessor
* @see org.springframework.data.mapping.model.PersistentEntityParameterValueProvider
* @see org.springframework.data.mapping.model.SpELContext
* @see org.springframework.data.mapping.model.ConvertingPropertyAccessor
* @see org.apache.geode.pdx.PdxReader
* @see org.apache.geode.pdx.PdxSerializer
* @see org.apache.geode.pdx.PdxWriter
* @since 1.2.0
*/
public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAware {
private final ConversionService conversionService;
private EntityInstantiators instantiators;
private EntityInstantiators entityInstantiators;
private final GemfireMappingContext mappingContext;
protected final Log log = LogFactory.getLog(getClass());
private final Logger logger = LoggerFactory.getLogger(getClass());
private Map<Class<?>, PdxSerializer> customSerializers;
// TODO: decide what to do with this; the SpELContext is not used
private SpELContext context;
/**
@@ -89,8 +92,8 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
public static MappingPdxSerializer create(GemfireMappingContext mappingContext,
ConversionService conversionService) {
mappingContext = (mappingContext != null ? mappingContext : new GemfireMappingContext());
conversionService = (conversionService != null ? conversionService : new DefaultConversionService());
mappingContext = mappingContext != null ? mappingContext : new GemfireMappingContext();
conversionService = conversionService != null ? conversionService : new DefaultConversionService();
return new MappingPdxSerializer(mappingContext, conversionService);
}
@@ -115,166 +118,67 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
*/
public MappingPdxSerializer(GemfireMappingContext mappingContext, ConversionService conversionService) {
Assert.notNull(mappingContext, "GemfireMappingContext must not be null");
Assert.notNull(conversionService, "ConversionService must not be null");
Assert.notNull(mappingContext, "MappingContext is required");
Assert.notNull(conversionService, "ConversionService is required");
this.mappingContext = mappingContext;
this.conversionService = conversionService;
this.instantiators = new EntityInstantiators();
this.entityInstantiators = new EntityInstantiators();
this.customSerializers = Collections.emptyMap();
this.context = new SpELContext(PdxReaderPropertyAccessor.INSTANCE);
}
/**
* {@inheritDoc}
* Configures a reference to the Spring {@link ApplicationContext}.
*
* @param applicationContext reference to the Spring {@link ApplicationContext}.
* @see org.springframework.context.ApplicationContext
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = new SpELContext(this.context, applicationContext);
}
/* (non-Javadoc) */
/**
* Returns a reference to the configured {@link ConversionService} used to convert data store types
* to application domain object types.
*
* @return a refernce to the configured {@link ConversionService}.
* @see org.springframework.core.convert.ConversionService
*/
protected ConversionService getConversionService() {
return conversionService;
return this.conversionService;
}
/**
* Configures custom PDX serializers to use for specific class types.
* Configures custom {@link PdxSerializer PDX serializers} used to serialize
* specific application {@link Class class types}.
*
* @param customSerializers a mapping of domain object class types and their corresponding PDX serializer.
* @param customSerializers mapping of application domain object {@link Class class types}
* to {@link PdxSerializer PDX serializers} used to handle those {@link Class class types}.
* @throws IllegalArgumentException if the {@link Map} is {@literal null}.
* @see org.apache.geode.pdx.PdxSerializer
* @see java.util.Map
*/
public void setCustomSerializers(Map<Class<?>, PdxSerializer> customSerializers) {
Assert.notNull(customSerializers, "Custom PdxSerializers must not be null");
Assert.notNull(customSerializers, "Custom PdxSerializers are required");
this.customSerializers = customSerializers;
}
/* (non-Javadoc) */
protected Map<Class<?>, PdxSerializer> getCustomSerializers() {
return Collections.unmodifiableMap(customSerializers);
}
/**
* Configures the {@link EntityInstantiator}s used to create the instances read by this PdxSerializer.
* Returns a {@link Map mapping} of application domain object {@link Class types}
* to custom {@link PdxSerializer PdxSerializers} used to handle custom serialization logic
* for the application domain objects.
*
* @param gemfireInstantiators must not be {@literal null}.
* @return a {@link Map mapping} of application domain object {@link Class types}
* to custom {@link PdxSerializer PdxSerializers}.
* @see org.apache.geode.pdx.PdxSerializer
* @see java.util.Map
*/
public void setGemfireInstantiators(Map<Class<?>, EntityInstantiator> gemfireInstantiators) {
Assert.notNull(gemfireInstantiators, "EntityInstantiators must not be null");
this.instantiators = new EntityInstantiators(gemfireInstantiators);
}
/* (non-Javadoc) */
protected EntityInstantiators getGemfireInstantiators() {
return instantiators;
}
/* (non-Javadoc) */
protected GemfireMappingContext getMappingContext() {
return mappingContext;
}
/**
* {@inheritDoc}
*/
@Override
public Object fromData(Class<?> type, PdxReader reader) {
GemfirePersistentEntity<?> entity = getPersistentEntity(type);
Object instance = getInstantiatorFor(entity).createInstance(entity,
new PersistentEntityParameterValueProvider<>(entity, new GemfirePropertyValueProvider(reader), null));
PersistentPropertyAccessor propertyAccessor =
new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance), getConversionService());
entity.doWithProperties((PropertyHandler<GemfirePersistentProperty>) persistentProperty -> {
if (!entity.isConstructorArgument(persistentProperty)) {
PdxSerializer customSerializer = getCustomSerializer(persistentProperty.getType());
Object value = null;
try {
if (log.isDebugEnabled()) {
log.debug(String.format("Setting property [%1$s] for entity [%2$s] of type [%3$s] from PDX%4$s",
persistentProperty.getName(), instance, type, (customSerializer != null ?
String.format(" using custom PdxSerializer [%1$s]", customSerializer) : "")));
}
value = (customSerializer != null
? customSerializer.fromData(persistentProperty.getType(), reader)
: reader.readField(persistentProperty.getName()));
if (log.isDebugEnabled()) {
log.debug(String.format("... with value [%s]", value));
}
propertyAccessor.setProperty(persistentProperty, value);
}
catch (Exception e) {
throw new MappingException(String.format(
"While setting value [%1$s] of property [%2$s] for entity of type [%3$s] from PDX%4$s",
value, persistentProperty.getName(), type, (customSerializer != null ?
String.format(" using custom PdxSerializer [%14s]", customSerializer) : "")), e);
}
}
});
return propertyAccessor.getBean();
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public boolean toData(Object value, PdxWriter writer) {
GemfirePersistentEntity<?> entity = getPersistentEntity(value);
PersistentPropertyAccessor propertyAccessor =
new ConvertingPropertyAccessor(entity.getPropertyAccessor(value), getConversionService());
entity.doWithProperties((PropertyHandler<GemfirePersistentProperty>) persistentProperty -> {
PdxSerializer customSerializer = getCustomSerializer(persistentProperty.getType());
Object propertyValue = null;
try {
propertyValue = propertyAccessor.getProperty(persistentProperty);
if (log.isDebugEnabled()) {
log.debug(String.format("Serializing entity property [%1$s] value [%2$s] of type [%3$s] to PDX%4$s",
persistentProperty.getName(), propertyValue, value.getClass(), (customSerializer != null ?
String.format(" using custom PdxSerializer [%s]", customSerializer) : "")));
}
if (customSerializer != null) {
customSerializer.toData(propertyValue, writer);
}
else {
writer.writeField(persistentProperty.getName(), propertyValue,
(Class<Object>) persistentProperty.getType());
}
}
catch (Exception e) {
throw new MappingException(String.format(
"While serializing entity property [%1$s] value [%2$s] of type [%3$s] to PDX%4$s",
persistentProperty.getName(), propertyValue, value.getClass(),
(customSerializer != null ? String.format(" using custom PdxSerializer [%1$s].",
customSerializer.getClass().getName()) : ".")), e);
}
});
GemfirePersistentProperty idProperty = entity.getIdProperty();
if (idProperty != null) {
writer.markIdentityField(idProperty.getName());
}
return true;
protected Map<Class<?>, PdxSerializer> getCustomSerializers() {
return Collections.unmodifiableMap(this.customSerializers);
}
/**
@@ -285,11 +189,50 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
* for the given class type was registered.
* @see #getCustomSerializers()
* @see org.apache.geode.pdx.PdxSerializer
* @see java.lang.Class
*/
protected PdxSerializer getCustomSerializer(Class<?> type) {
return getCustomSerializers().get(type);
}
/**
* Configures the {@link EntityInstantiator EntityInstantiators} used to create the instances
* read by this {@link PdxSerializer}.
*
* @param entityInstantiators {@link EntityInstantiator EntityInstantiators} used to create the instances
* read by this {@link PdxSerializer}; must not be {@literal null}.
* @see org.springframework.data.convert.EntityInstantiator
*/
public void setGemfireInstantiators(EntityInstantiators entityInstantiators) {
Assert.notNull(entityInstantiators, "EntityInstantiators are required");
this.entityInstantiators = entityInstantiators;
}
/**
* Configures the {@link EntityInstantiator EntityInstantiators} used to create the instances
* read by this {@link PdxSerializer}.
*
* @param gemfireInstantiators mapping of {@link Class types} to {@link EntityInstantiator} objects;
* must not be {@literal null}.
* @see org.springframework.data.convert.EntityInstantiator
* @see java.util.Map
*/
public void setGemfireInstantiators(Map<Class<?>, EntityInstantiator> gemfireInstantiators) {
setGemfireInstantiators(new EntityInstantiators(gemfireInstantiators));
}
/**
* Returns the configured {@link EntityInstantiators} handling instantiation for GemFire persistent entities.
*
* @return the configured {@link EntityInstantiators} handling instantiation for GemFire persistent entities.
* @see org.springframework.data.convert.EntityInstantiators
*/
protected EntityInstantiators getGemfireInstantiators() {
return this.entityInstantiators;
}
/**
* Looks up and returns an EntityInstantiator to construct and initialize an instance of the object defined
* by the given PersistentEntity (meta-data).
@@ -303,6 +246,28 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
return getGemfireInstantiators().getInstantiatorFor(entity);
}
/**
* Returns a reference to the configured {@link Logger} used to log {@link String messages}
* about the functions of this {@link PdxSerializer}.
*
* @return a reference to the configured {@link Logger}.
* @see org.slf4j.Logger
*/
protected Logger getLogger() {
return this.logger;
}
/**
* Returns a reference to the configured {@link GemfireMappingContext mapping context} used to handling mapping
* logic between GemFire persistent entities and application domain object {@link Class types}.
*
* @return a reference to the configured {@link GemfireMappingContext mapping context} for Pivotal GemFire.
* @see org.springframework.data.gemfire.mapping.GemfireMappingContext
*/
protected GemfireMappingContext getMappingContext() {
return this.mappingContext;
}
/**
* Looks up and returns the {@link PersistentEntity} meta-data for the given entity object.
*
@@ -326,4 +291,128 @@ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAw
protected GemfirePersistentEntity<?> getPersistentEntity(Class<?> entityType) {
return getMappingContext().getPersistentEntity(entityType);
}
@Override
public Object fromData(Class<?> type, PdxReader reader) {
GemfirePersistentEntity<?> entity = getPersistentEntity(type);
Object instance = getInstantiatorFor(entity)
.createInstance(entity, new PersistentEntityParameterValueProvider<>(entity,
new GemfirePropertyValueProvider(reader), null));
PersistentPropertyAccessor propertyAccessor =
new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance), getConversionService());
entity.doWithProperties((PropertyHandler<GemfirePersistentProperty>) persistentProperty -> {
if (isWritable(entity, persistentProperty)) {
PdxSerializer customSerializer = getCustomSerializer(persistentProperty.getType());
Object value = null;
try {
if (getLogger().isDebugEnabled()) {
getLogger().debug(String.format("Setting property [%1$s] for entity [%2$s] of type [%3$s] from PDX%4$s",
persistentProperty.getName(), instance, type, (customSerializer != null ?
String.format(" using custom PdxSerializer [%1$s]", customSerializer) : "")));
}
value = (customSerializer != null
? customSerializer.fromData(persistentProperty.getType(), reader)
: reader.readField(persistentProperty.getName()));
if (getLogger().isDebugEnabled()) {
getLogger().debug(String.format("... with value [%s]", value));
}
propertyAccessor.setProperty(persistentProperty, value);
}
catch (Exception cause) {
throw new MappingException(String.format(
"While setting value [%1$s] of property [%2$s] for entity of type [%3$s] from PDX%4$s",
value, persistentProperty.getName(), type, (customSerializer != null ?
String.format(" using custom PdxSerializer [%14s]", customSerializer) : "")), cause);
}
}
});
return propertyAccessor.getBean();
}
/* (non-Javadoc) */
boolean isWritable(GemfirePersistentEntity<?> entity, GemfirePersistentProperty persistentProperty) {
return !entity.isConstructorArgument(persistentProperty)
&& persistentProperty.isWritable()
&& !persistentProperty.isTransient();
}
@Override
@SuppressWarnings("unchecked")
public boolean toData(Object value, PdxWriter writer) {
GemfirePersistentEntity<?> entity = getPersistentEntity(value);
// Entity will be null for simple types
if (entity != null) {
PersistentPropertyAccessor propertyAccessor =
new ConvertingPropertyAccessor(entity.getPropertyAccessor(value), getConversionService());
entity.doWithProperties((PropertyHandler<GemfirePersistentProperty>) persistentProperty -> {
if (isReadable(persistentProperty)) {
PdxSerializer customSerializer = getCustomSerializer(persistentProperty.getType());
Object propertyValue = null;
try {
propertyValue = propertyAccessor.getProperty(persistentProperty);
if (getLogger().isDebugEnabled()) {
getLogger().debug(String.format("Serializing entity [%1$s] property [%2$s] value [%3$s] of type [%4$s] to PDX%5$s",
entity.getType().getName(), persistentProperty.getName(), propertyValue,
ObjectUtils.nullSafeClassName(propertyValue), (customSerializer != null
? String.format(" using custom PdxSerializer [%s]", customSerializer) : "")));
}
if (customSerializer != null) {
customSerializer.toData(propertyValue, writer);
}
else {
writer.writeField(persistentProperty.getName(), propertyValue,
(Class<Object>) persistentProperty.getType());
}
}
catch (Exception cause) {
throw new MappingException(String.format(
"While serializing entity [%1$s] property [%2$s] value [%3$s] of type [%4$s] to PDX%5$s",
entity.getType().getName(), persistentProperty.getName(), propertyValue,
ObjectUtils.nullSafeClassName(propertyValue), (customSerializer != null
? String.format(" using custom PdxSerializer [%1$s].",
customSerializer.getClass().getName()) : "")), cause);
}
}
});
GemfirePersistentProperty idProperty = entity.getIdProperty();
if (idProperty != null) {
writer.markIdentityField(idProperty.getName());
}
return true;
}
return false;
}
/* (non-Javadoc) */
boolean isReadable(GemfirePersistentProperty persistentProperty) {
return !persistentProperty.isTransient();
}
}

View File

@@ -91,6 +91,31 @@ public abstract class CacheUtils extends DistributedSystemUtils {
return peer;
}
/* (non-Javadoc) */
public static boolean close() {
return close(resolveGemFireCache());
}
/* (non-Javadoc) */
public static boolean close(GemFireCache gemfireCache) {
return close(gemfireCache, () -> {});
}
/* (non-Javadoc) */
public static boolean close(GemFireCache gemfireCache, Runnable shutdownHook) {
try {
gemfireCache.close();
return true;
}
catch (Exception ignore) {
return false;
}
finally {
Optional.ofNullable(shutdownHook).ifPresent(Runnable::run);
}
}
/* (non-Javadoc) */
public static boolean closeCache() {

View File

@@ -16,14 +16,12 @@
package org.springframework.data.gemfire.mapping;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.time.LocalDateTime;
import org.apache.geode.DataSerializable;
import org.apache.geode.Instantiator;
@@ -35,9 +33,15 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Transient;
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.data.gemfire.repository.sample.Address;
import org.springframework.data.gemfire.repository.sample.Person;
import lombok.Getter;
import lombok.Setter;
/**
* Integration tests for {@link MappingPdxSerializer}.
*
@@ -46,75 +50,115 @@ import org.springframework.data.gemfire.repository.sample.Person;
*/
public class MappingPdxSerializerIntegrationTests {
static Region<Object, Object> region;
static Cache cache;
static Region<Object, Object> region;
@BeforeClass
public static void setUp() {
MappingPdxSerializer serializer = new MappingPdxSerializer(new GemfireMappingContext(),
new DefaultConversionService());
MappingPdxSerializer serializer =
new MappingPdxSerializer(new GemfireMappingContext(), new DefaultConversionService());
cache = new CacheFactory()
.set("name", MappingPdxSerializerIntegrationTests.class.getSimpleName())
.set("mcast-port", "0")
.set("log-level", "warning")
.set("log-level", "error")
.setPdxSerializer(serializer)
.setPdxPersistent(true)
.create();
region = cache.createRegionFactory()
.setDataPolicy(DataPolicy.PERSISTENT_REPLICATE)
.create("foo");
.setDataPolicy(DataPolicy.PARTITION)
.create("TemporaryRegion");
}
@AfterClass
@SuppressWarnings("all")
public static void tearDown() {
try {
cache.close();
}
catch (Exception ignore) {
}
finally {
for (String name : new File(".").list(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith("BACKUP");
}
})) {
new File(name).delete();
}
}
GemfireUtils.close(cache);
}
@Test
public void serializeAndDeserializeCorrectly() {
public void handlesEntityWithReadOnlyProperty() {
EntityWithReadOnlyProperty entity = new EntityWithReadOnlyProperty();
entity.setName("ReadOnlyEntity");
entity.setTimestamp(LocalDateTime.now());
entity.processId = 123;
region.put(100L, entity);
Object target = region.get(100L);
assertThat(target).isInstanceOf(EntityWithReadOnlyProperty.class);
assertThat(target).isNotSameAs(entity);
EntityWithReadOnlyProperty deserializedEntity = (EntityWithReadOnlyProperty) target;
assertThat(deserializedEntity.getName()).isEqualTo(entity.getName());
assertThat(deserializedEntity.getTimestamp()).isEqualTo(entity.getTimestamp());
assertThat(deserializedEntity.getProcessId()).isNull();
}
@Test
public void handlesEntityWithTransientProperty() {
EntityWithTransientProperty entity = new EntityWithTransientProperty();
entity.setName("TransientEntity");
entity.setValueOne("testOne");
entity.setValueTwo("testTwo");
region.put(101L, entity);
Object target = region.get(101L);
assertThat(target).isInstanceOf(EntityWithTransientProperty.class);
assertThat(target).isNotSameAs(entity);
EntityWithTransientProperty deserializedEntity = (EntityWithTransientProperty) target;
assertThat(deserializedEntity.getName()).isEqualTo(entity.getName());
assertThat(deserializedEntity.getValueOne()).isNull();
assertThat(deserializedEntity.getValueTwo()).isNull();
}
@Test
@SuppressWarnings("unchecked")
public void serializesAndDeserializesEntityCorrectly() {
Address address = new Address();
address.zipCode = "01234";
address.street = "100 Main St.";
address.city = "London";
address.zipCode = "01234";
Person person = new Person(1L, "Oliver", "Gierke");
person.address = address;
person.address = address;
region.put(1L, person);
Object result = region.get(1L);
assertThat(result instanceof Person, is(true));
assertThat(result).isInstanceOf(Person.class);
assertThat(result).isNotSameAs(person);
Person reference = person;
assertThat(reference.getFirstname(), is(person.getFirstname()));
assertThat(reference.getLastname(), is(person.getLastname()));
assertThat(reference.address, is(person.address));
Person reference = (Person) result;
assertThat(reference.getFirstname()).isEqualTo(person.getFirstname());
assertThat(reference.getLastname()).isEqualTo(person.getLastname());
assertThat(reference.getAddress()).isEqualTo(person.getAddress());
}
@Test
public void serializeAndDeserializeCorrectlyWithDataSerializable() {
public void serializesAndDeserializesEntityWithDataSerializableProperty() {
Address address = new Address();
address.zipCode = "01234";
address.street = "100 Main St.";
address.city = "London";
address.zipCode = "01234";
PersonWithDataSerializableProperty person =
new PersonWithDataSerializableProperty(2L, "Oliver", "Gierke",
@@ -123,32 +167,40 @@ public class MappingPdxSerializerIntegrationTests {
person.address = address;
region.put(2L, person);
Object result = region.get(2L);
assertThat(result instanceof PersonWithDataSerializableProperty, is(true));
assertThat(result).isInstanceOf(PersonWithDataSerializableProperty.class);
assertThat(result).isNotSameAs(person);
PersonWithDataSerializableProperty reference = person;
assertThat(reference.getFirstname(), is(person.getFirstname()));
assertThat(reference.getLastname(), is(person.getLastname()));
assertThat(reference.address, is(person.address));
assertThat(reference.dsProperty.getValue(),is("foo"));
PersonWithDataSerializableProperty reference = (PersonWithDataSerializableProperty) result;
assertThat(reference.getFirstname()).isEqualTo(person.getFirstname());
assertThat(reference.getLastname()).isEqualTo(person.getLastname());
assertThat(reference.getAddress()).isEqualTo(person.getAddress());
assertThat(reference.property.getValue()).isEqualTo("foo");
}
@SuppressWarnings("serial")
@SuppressWarnings({ "serial", "unused" })
public static class PersonWithDataSerializableProperty extends Person {
private DataSerializableProperty dsProperty;
private DataSerializableProperty property;
public PersonWithDataSerializableProperty(Long id, String firstname,
String lastname, DataSerializableProperty dsProperty) {
String lastname, DataSerializableProperty property) {
super(id, firstname, lastname);
this.dsProperty = dsProperty;
this.property = property;
}
public DataSerializableProperty getDataSerializableProperty() {
return this.dsProperty;
return this.property;
}
public void setDataSerializableProperty(DataSerializableProperty property) {
this.property = property;
}
}
@SuppressWarnings("serial")
@@ -170,20 +222,44 @@ public class MappingPdxSerializerIntegrationTests {
@Override
public void fromData(DataInput dataInput) throws IOException,
ClassNotFoundException {
value = dataInput.readUTF();
public void fromData(DataInput dataInput) throws IOException, ClassNotFoundException {
this.value = dataInput.readUTF();
}
@Override
public void toData(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(value);
dataOutput.writeUTF(this.value);
}
public String getValue() {
return this.value;
}
}
@Getter
static class EntityWithReadOnlyProperty {
@Setter
LocalDateTime timestamp;
@Setter
String name;
// TODO: if there is not setter, then effectively this field/property is read-only
// and should not require the @ReadOnlyProperty
@ReadOnlyProperty
Object processId;
}
@Getter @Setter
static class EntityWithTransientProperty {
private String name;
private transient Object valueOne;
@Transient
private Object valueTwo;
}
}

View File

@@ -17,36 +17,40 @@
package org.springframework.data.gemfire.mapping;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.isA;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import org.apache.geode.pdx.PdxReader;
import org.apache.geode.pdx.PdxSerializer;
import org.apache.geode.pdx.PdxWriter;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.gemfire.repository.sample.Address;
import org.springframework.data.gemfire.repository.sample.Person;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.ParameterValueProvider;
/**
@@ -72,12 +76,9 @@ public class MappingPdxSerializerUnitTests {
ConversionService conversionService;
GemfireMappingContext context;
GemfireMappingContext mappingContext;
MappingPdxSerializer serializer;
@Rule
public ExpectedException exception = ExpectedException.none();
MappingPdxSerializer pdxSerializer;
@Mock
EntityInstantiator mockInstantiator;
@@ -93,14 +94,70 @@ public class MappingPdxSerializerUnitTests {
@Before
public void setUp() {
context = new GemfireMappingContext();
conversionService = new GenericConversionService();
serializer = new MappingPdxSerializer(context, conversionService);
serializer.setCustomSerializers(Collections.singletonMap(Address.class, mockAddressSerializer));
this.conversionService = new GenericConversionService();
this.mappingContext = new GemfireMappingContext();
this.pdxSerializer = new MappingPdxSerializer(this.mappingContext, this.conversionService);
this.pdxSerializer.setCustomSerializers(Collections.singletonMap(Address.class, this.mockAddressSerializer));
}
@Test
public void createFullyInitialized() {
public void constructDefaultMappingPdxSerializer() {
MappingPdxSerializer pdxSerializer = new MappingPdxSerializer();
assertThat(pdxSerializer.getConversionService()).isInstanceOf(DefaultConversionService.class);
assertThat(pdxSerializer.getCustomSerializers()).isEmpty();
assertThat(pdxSerializer.getGemfireInstantiators()).isInstanceOf(EntityInstantiators.class);
assertThat(pdxSerializer.getMappingContext()).isInstanceOf(GemfireMappingContext.class);
}
@Test
public void constructMappingPdxSerializer() {
ConversionService mockConversionService = mock(ConversionService.class);
GemfireMappingContext mockMappingContext = mock(GemfireMappingContext.class);
MappingPdxSerializer pdxSerializer = new MappingPdxSerializer(mockMappingContext, mockConversionService);
assertThat(pdxSerializer.getConversionService()).isEqualTo(mockConversionService);
assertThat(pdxSerializer.getCustomSerializers()).isEmpty();
assertThat(pdxSerializer.getGemfireInstantiators()).isInstanceOf(EntityInstantiators.class);
assertThat(pdxSerializer.getMappingContext()).isEqualTo(mockMappingContext);
}
@Test(expected = IllegalArgumentException.class)
public void constructMappingPdxSerializerWithNullConversionService() {
try {
new MappingPdxSerializer(this.mappingContext, null);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("ConversionService is required");
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test(expected = IllegalArgumentException.class)
public void constructMappingPdxSerializerWithNullMappingContext() {
try {
new MappingPdxSerializer(null, this.conversionService);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("MappingContext is required");
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test
public void createMappingPdxSerializer() {
ConversionService mockConversionService = mock(ConversionService.class);
GemfireMappingContext mockMappingContext = mock(GemfireMappingContext.class);
@@ -147,39 +204,246 @@ public class MappingPdxSerializerUnitTests {
}
@Test
@SuppressWarnings("unchecked")
public void usesRegisteredInstantiator() {
public void setCustomSerializersWithMappingOfClassTypesToPdxSerializers() {
Address address = new Address();
Map<Class<?>, PdxSerializer> customSerializers =
Collections.singletonMap(Person.class, mock(PdxSerializer.class));
address.city = "London";
address.zipCode = "01234";
this.pdxSerializer.setCustomSerializers(customSerializers);
Person person = new Person(1L, "Oliver", "Gierke");
assertThat(this.pdxSerializer.getCustomSerializers()).isEqualTo(customSerializers);
}
person.address = address;
@Test(expected = IllegalArgumentException.class)
public void setCustomSerializersToNull() {
when(mockInstantiator.createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class)))
.thenReturn(person);
try {
this.pdxSerializer.setCustomSerializers(null);
}
catch (IllegalArgumentException expected) {
serializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockInstantiator));
assertThat(expected).hasMessage("Custom PdxSerializers are required");
assertThat(expected).hasNoCause();
serializer.fromData(Person.class, mockReader);
throw expected;
}
}
GemfirePersistentEntity<?> persistentEntity = context.getPersistentEntity(Person.class);
@Test
public void getCustomSerializerForMappedType() {
verify(mockInstantiator, times(1)).createInstance(eq(persistentEntity),
any(ParameterValueProvider.class));
PdxSerializer mockPdxSerializer = mock(PdxSerializer.class);
verify(mockAddressSerializer, times(1)).fromData(eq(Address.class), any(PdxReader.class));
Map<Class<?>, PdxSerializer> customSerializers = Collections.singletonMap(Person.class, mockPdxSerializer);
this.pdxSerializer.setCustomSerializers(customSerializers);
assertThat(this.pdxSerializer.getCustomSerializer(Person.class)).isEqualTo(mockPdxSerializer);
}
@Test
public void getCustomSerializerForNonMappedType() {
PdxSerializer mockPdxSerializer = mock(PdxSerializer.class);
Map<Class<?>, PdxSerializer> customSerializers = Collections.singletonMap(Person.class, mockPdxSerializer);
this.pdxSerializer.setCustomSerializers(customSerializers);
assertThat(this.pdxSerializer.getCustomSerializer(Address.class)).isNull();
}
@Test
public void setGemfireInstantiatorsWithEntityInstantiators() {
EntityInstantiators mockEntityInstantiators = mock(EntityInstantiators.class);
this.pdxSerializer.setGemfireInstantiators(mockEntityInstantiators);
assertThat(this.pdxSerializer.getGemfireInstantiators()).isSameAs(mockEntityInstantiators);
}
@Test(expected = IllegalArgumentException.class)
public void setGemfireInstantiatorsWithNullEntityInstantiators() {
try {
this.pdxSerializer.setGemfireInstantiators((EntityInstantiators) null);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("EntityInstantiators are required");
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test
public void setGemfireInstantiatorsWithMappingOfClassTypesToEntityInstantiators() {
Map<Class<?>, EntityInstantiator> entityInstantiators =
Collections.singletonMap(Person.class, mock(EntityInstantiator.class));
this.pdxSerializer.setGemfireInstantiators(entityInstantiators);
assertThat(this.pdxSerializer.getGemfireInstantiators()).isInstanceOf(EntityInstantiators.class);
}
@Test(expected = IllegalArgumentException.class)
public void setGemfireInstantiatorsWithNullMap() {
try {
this.pdxSerializer.setGemfireInstantiators((Map<Class<?>, EntityInstantiator>) null);
}
catch (IllegalArgumentException expected) {
assertThat(expected).hasMessage("CustomInstantiators must not be null!");
assertThat(expected).hasNoCause();
throw expected;
}
}
@Test
public void getInstantiatorForManagedPersistentEntityWithInstantiator() {
EntityInstantiator mockEntityInstantiator = mock(EntityInstantiator.class);
Map<Class<?>, EntityInstantiator> entityInstantiators =
Collections.singletonMap(Person.class, mockEntityInstantiator);
PersistentEntity mockEntity = mock(PersistentEntity.class);
when(mockEntity.getType()).thenReturn(Person.class);
this.pdxSerializer.setGemfireInstantiators(entityInstantiators);
assertThat(this.pdxSerializer.getInstantiatorFor(mockEntity)).isEqualTo(mockEntityInstantiator);
verify(mockEntity, atLeast(1)).getType();
verifyZeroInteractions(mockEntityInstantiator);
}
@Test
public void getInstantiatorForNonManagedPersistentEntityWithNoInstantiator() {
EntityInstantiator mockEntityInstantiator = mock(EntityInstantiator.class);
Map<Class<?>, EntityInstantiator> entityInstantiators =
Collections.singletonMap(Person.class, mockEntityInstantiator);
PersistentEntity mockEntity = mock(PersistentEntity.class);
when(mockEntity.getType()).thenReturn(Address.class);
this.pdxSerializer.setGemfireInstantiators(entityInstantiators);
assertThat(this.pdxSerializer.getInstantiatorFor(mockEntity)).isNotEqualTo(mockEntityInstantiator);
verify(mockEntity, atLeast(1)).getType();
verifyZeroInteractions(mockEntityInstantiator);
}
@Test
public void isReadableWithNonTransientPropertyReturnsTrue() {
GemfirePersistentProperty mockPersistentProperty = mock(GemfirePersistentProperty.class);
when(mockPersistentProperty.isTransient()).thenReturn(false);
assertThat(this.pdxSerializer.isReadable(mockPersistentProperty)).isTrue();
verify(mockPersistentProperty, times(1)).isTransient();
}
@Test
public void isReadableWithTransientPropertyReturnsFalse() {
GemfirePersistentProperty mockPersistentProperty = mock(GemfirePersistentProperty.class);
when(mockPersistentProperty.isTransient()).thenReturn(true);
assertThat(this.pdxSerializer.isReadable(mockPersistentProperty)).isFalse();
verify(mockPersistentProperty, times(1)).isTransient();
}
@Test
public void isWritableWithWritablePropertyReturnsTrue() {
GemfirePersistentEntity<?> mockEntity = mock(GemfirePersistentEntity.class);
GemfirePersistentProperty mockProperty = mock(GemfirePersistentProperty.class);
when(mockEntity.isConstructorArgument(any(GemfirePersistentProperty.class))).thenReturn(false);
when(mockProperty.isTransient()).thenReturn(false);
when(mockProperty.isWritable()).thenReturn(true);
assertThat(this.pdxSerializer.isWritable(mockEntity, mockProperty)).isTrue();
verify(mockEntity, times(1)).isConstructorArgument(eq(mockProperty));
verify(mockProperty, times(1)).isWritable();
verify(mockProperty, times(1)).isTransient();
}
@Test
public void isWritableWithConstructorArgumentPropertyReturnsFalse() {
GemfirePersistentEntity<?> mockEntity = mock(GemfirePersistentEntity.class);
GemfirePersistentProperty mockProperty = mock(GemfirePersistentProperty.class);
when(mockEntity.isConstructorArgument(any(GemfirePersistentProperty.class))).thenReturn(true);
assertThat(this.pdxSerializer.isWritable(mockEntity, mockProperty)).isFalse();
verify(mockEntity, times(1)).isConstructorArgument(eq(mockProperty));
verify(mockProperty, never()).isWritable();
verify(mockProperty, never()).isTransient();
}
@Test
public void isWritableWithNonWritablePropertyReturnsFalse() {
GemfirePersistentEntity<?> mockEntity = mock(GemfirePersistentEntity.class);
GemfirePersistentProperty mockProperty = mock(GemfirePersistentProperty.class);
when(mockEntity.isConstructorArgument(any(GemfirePersistentProperty.class))).thenReturn(false);
when(mockProperty.isWritable()).thenReturn(false);
assertThat(this.pdxSerializer.isWritable(mockEntity, mockProperty)).isFalse();
verify(mockEntity, times(1)).isConstructorArgument(eq(mockProperty));
verify(mockProperty, times(1)).isWritable();
verify(mockProperty, never()).isTransient();
}
@Test
public void isWritableWithTransientPropertyReturnsFalse() {
GemfirePersistentEntity<?> mockEntity = mock(GemfirePersistentEntity.class);
GemfirePersistentProperty mockProperty = mock(GemfirePersistentProperty.class);
when(mockEntity.isConstructorArgument(any(GemfirePersistentProperty.class))).thenReturn(false);
when(mockProperty.isTransient()).thenReturn(true);
when(mockProperty.isWritable()).thenReturn(true);
assertThat(this.pdxSerializer.isWritable(mockEntity, mockProperty)).isFalse();
verify(mockEntity, times(1)).isConstructorArgument(eq(mockProperty));
verify(mockProperty, times(1)).isWritable();
verify(mockProperty, times(1)).isTransient();
}
@Test
@SuppressWarnings("unchecked")
public void fromDataMapsPdxDataToApplicationDomainObject() {
public void fromDataDeserializesPdxAndMapsToApplicationDomainObject() {
Address expectedAddress = new Address();
expectedAddress.street = "100 Main St.";
expectedAddress.city = "Portland";
expectedAddress.zipCode = "12345";
@@ -190,9 +454,9 @@ public class MappingPdxSerializerUnitTests {
when(mockReader.readField(eq("lastname"))).thenReturn("Doe");
when(mockAddressSerializer.fromData(eq(Address.class), eq(mockReader))).thenReturn(expectedAddress);
serializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockInstantiator));
this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockInstantiator));
Object obj = serializer.fromData(Person.class, mockReader);
Object obj = this.pdxSerializer.fromData(Person.class, mockReader);
assertThat(obj).isInstanceOf(Person.class);
@@ -203,14 +467,15 @@ public class MappingPdxSerializerUnitTests {
assertThat(jonDoe.getFirstname()).isEqualTo("Jon");
assertThat(jonDoe.getLastname()).isEqualTo("Doe");
verify(mockInstantiator, times(1)).createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class));
verify(mockInstantiator, times(1))
.createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class));
verify(mockReader, times(1)).readField(eq("id"));
verify(mockReader, times(1)).readField(eq("firstname"));
verify(mockReader, times(1)).readField(eq("lastname"));
verify(mockAddressSerializer, times(1)).fromData(eq(Address.class), eq(mockReader));
}
@Test
@Test(expected = MappingException.class)
@SuppressWarnings("unchecked")
public void fromDataHandlesExceptionProperly() {
@@ -219,15 +484,18 @@ public class MappingPdxSerializerUnitTests {
when(mockReader.readField(eq("id"))).thenThrow(newIllegalArgumentException("test"));
serializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockInstantiator));
try {
exception.expect(MappingException.class);
exception.expectCause(isA(IllegalArgumentException.class));
exception.expectMessage(String.format(
"While setting value [null] of property [id] for entity of type [%s] from PDX", Person.class));
this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockInstantiator));
this.pdxSerializer.fromData(Person.class, mockReader);
}
catch (MappingException expected) {
serializer.fromData(Person.class, mockReader);
assertThat(expected).hasMessage("While setting value [null] of property [id] for entity of type [%s] from PDX", Person.class);
assertThat(expected).hasCauseInstanceOf(IllegalArgumentException.class);
assertThat(expected.getCause()).hasMessage("test");
assertThat(expected.getCause()).hasNoCause();
throw expected;
}
finally {
verify(mockInstantiator, times(1))
@@ -237,11 +505,42 @@ public class MappingPdxSerializerUnitTests {
}
}
@Test
@SuppressWarnings("unchecked")
public void fromDataUsesRegisteredInstantiator() {
Address address = new Address();
address.street = "100 Main St.";
address.city = "London";
address.zipCode = "01234";
Person person = new Person(1L, "Oliver", "Gierke");
person.address = address;
when(mockInstantiator.createInstance(any(GemfirePersistentEntity.class), any(ParameterValueProvider.class)))
.thenReturn(person);
this.pdxSerializer.setGemfireInstantiators(Collections.singletonMap(Person.class, mockInstantiator));
this.pdxSerializer.fromData(Person.class, mockReader);
GemfirePersistentEntity<?> persistentEntity =
Optional.ofNullable(mappingContext.getPersistentEntity(Person.class)).orElse(null);
verify(mockInstantiator, times(1))
.createInstance(eq(persistentEntity), any(ParameterValueProvider.class));
verify(mockAddressSerializer, times(1))
.fromData(eq(Address.class), any(PdxReader.class));
}
@Test
public void toDataSerializesApplicationDomainObjectToPdx() {
Address address = new Address();
address.street = "100 Main St.";
address.city = "Portland";
address.zipCode = "12345";
@@ -249,9 +548,9 @@ public class MappingPdxSerializerUnitTests {
jonDoe.address = address;
serializer.setCustomSerializers(Collections.singletonMap(Address.class, mockAddressSerializer));
this.pdxSerializer.setCustomSerializers(Collections.singletonMap(Address.class, mockAddressSerializer));
assertThat(serializer.toData(jonDoe, mockWriter)).isTrue();
assertThat(this.pdxSerializer.toData(jonDoe, mockWriter)).isTrue();
verify(mockAddressSerializer, times(1)).toData(eq(address), eq(mockWriter));
verify(mockWriter, times(1)).writeField(eq("id"), eq(1L), eq(Long.class));
@@ -260,11 +559,12 @@ public class MappingPdxSerializerUnitTests {
verify(mockWriter, times(1)).markIdentityField(eq("id"));
}
@Test
@Test(expected = MappingException.class)
public void toDataHandlesExceptionProperly() {
Address address = new Address();
address.street = "100 Main St.";
address.city = "Portland";
address.zipCode = "12345";
@@ -276,13 +576,19 @@ public class MappingPdxSerializerUnitTests {
.thenThrow(newIllegalArgumentException("test"));
try {
exception.expect(MappingException.class);
exception.expectCause(isA(IllegalArgumentException.class));
exception.expectMessage(String.format(
"While serializing entity property [address] value [Portland, 12345] of type [%s] to PDX",
Person.class));
this.pdxSerializer.setCustomSerializers(Collections.emptyMap());
this.pdxSerializer.toData(jonDoe, mockWriter);
}
catch (MappingException expected) {
new MappingPdxSerializer(context, conversionService).toData(jonDoe, mockWriter);
assertThat(expected).hasMessage("While serializing entity [%1$s] property [address]"
+ " value [100 Main St. Portland, 12345] of type [%2$s] to PDX",
Person.class.getName(), Address.class.getName());
assertThat(expected).hasCauseInstanceOf(IllegalArgumentException.class);
assertThat(expected.getCause()).hasMessage("test");
assertThat(expected.getCause()).hasNoCause();
throw expected;
}
finally {
verify(mockWriter, atMost(1)).writeField(eq("id"), eq(1L), eq(Long.class));

View File

@@ -17,6 +17,7 @@ package org.springframework.data.gemfire.repository.sample;
import org.springframework.data.annotation.Id;
import org.springframework.data.gemfire.mapping.annotation.Region;
import org.springframework.util.ObjectUtils;
/**
*
@@ -25,14 +26,44 @@ import org.springframework.data.gemfire.mapping.annotation.Region;
@Region("address")
public class Address {
public String street;
public String city;
@Id
public String zipCode;
public String city;
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Address)) {
return false;
}
Address that = (Address) obj;
return ObjectUtils.nullSafeEquals(this.street, that.street)
&& ObjectUtils.nullSafeEquals(this.city, that.city)
&& ObjectUtils.nullSafeEquals(this.zipCode, that.zipCode);
}
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(this.street);
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(this.city);
hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(this.zipCode);
return hashValue;
}
@Override
public String toString() {
return String.format("%1$s, %2$s", city, zipCode);
return String.format("%1$s %2$s, %3$s", this.street, this.city, this.zipCode);
}
}

View File

@@ -119,6 +119,7 @@ public class Person implements Serializable {
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}