/* * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.gemfire.mapping; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException; import java.util.Collections; import java.util.Map; import java.util.Optional; 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.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; import org.springframework.data.mapping.model.SpELContext; import org.springframework.util.Assert; /** * GemFire {@link PdxSerializer} implementation that uses a Spring Data GemFire {@link GemfireMappingContext} * to read and write entities. * * @author Oliver Gierke * @author David Turanski * @author John Blum * @see org.springframework.context.ApplicationContext * @see org.springframework.context.ApplicationContextAware * @see org.springframework.core.convert.ConversionService * @see org.springframework.data.convert.EntityInstantiator * @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.apache.geode.pdx.PdxReader * @see org.apache.geode.pdx.PdxSerializer * @see org.apache.geode.pdx.PdxWriter */ public class MappingPdxSerializer implements PdxSerializer, ApplicationContextAware { private final ConversionService conversionService; private EntityInstantiators instantiators; private final GemfireMappingContext mappingContext; protected final Log log = LogFactory.getLog(getClass()); private Map<Class<?>, PdxSerializer> customSerializers; private SpELContext context; /** * Factory method to construct a new instance of the {@link MappingPdxSerializer} initialized with the given * {@link GemfireMappingContext} and Spring {@link ConversionService}. If either the {@link GemfireMappingContext} * or Spring {@link ConversionService} are {@literal null}, then this factory method will construct default * instances of each. * * @param mappingContext {@link GemfireMappingContext} used by this {@link PdxSerializer} to handle mappings * between application domain object types and PDX Serialization meta-data/data. * @param conversionService Spring's {@link ConversionService} used to convert PDX deserialized data to application * object property types. * @return an initialized instance of the {@link MappingPdxSerializer}. * @see org.springframework.core.convert.ConversionService * @see org.springframework.data.gemfire.mapping.MappingPdxSerializer */ public static MappingPdxSerializer create(GemfireMappingContext mappingContext, ConversionService conversionService) { mappingContext = (mappingContext != null ? mappingContext : new GemfireMappingContext()); conversionService = (conversionService != null ? conversionService : new DefaultConversionService()); return new MappingPdxSerializer(mappingContext, conversionService); } /** * Creates a new {@link MappingPdxSerializer} using the default {@link GemfireMappingContext} * and {@link DefaultConversionService}. * * @see org.springframework.core.convert.support.DefaultConversionService * @see org.springframework.data.gemfire.mapping.GemfireMappingContext */ public MappingPdxSerializer() { this(new GemfireMappingContext(), new DefaultConversionService()); } /** * Creates a new {@link MappingPdxSerializer} using the given * {@link GemfireMappingContext} and {@link ConversionService}. * * @param mappingContext must not be {@literal null}. * @param conversionService must not be {@literal null}. */ public MappingPdxSerializer(GemfireMappingContext mappingContext, ConversionService conversionService) { Assert.notNull(mappingContext, "GemfireMappingContext must not be null"); Assert.notNull(conversionService, "ConversionService must not be null"); this.mappingContext = mappingContext; this.conversionService = conversionService; this.instantiators = new EntityInstantiators(); this.customSerializers = Collections.emptyMap(); this.context = new SpELContext(PdxReaderPropertyAccessor.INSTANCE); } /** * {@inheritDoc} */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = new SpELContext(this.context, applicationContext); } /* (non-Javadoc) */ protected ConversionService getConversionService() { return conversionService; } /** * Configures custom PDX serializers to use for specific class types. * * @param customSerializers a mapping of domain object class types and their corresponding PDX serializer. */ public void setCustomSerializers(Map<Class<?>, PdxSerializer> customSerializers) { Assert.notNull(customSerializers, "Custom PdxSerializers must not be null"); 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. * * @param gemfireInstantiators must not be {@literal null}. */ 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), Optional.empty())); 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 [%1$s]", value)); } propertyAccessor.setProperty(persistentProperty, Optional.ofNullable(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()); Optional<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.orElse(null), writer); } else { writer.writeField(persistentProperty.getName(), propertyValue.orElse(null), (Class<Object>) persistentProperty.getType()); } } catch (Exception e) { Object resolvedPropertyValue = (propertyValue != null ? propertyValue.orElse(null) : null); throw new MappingException(String.format( "While serializing entity property [%1$s] value [%2$s] of type [%3$s] to PDX%4$s", persistentProperty.getName(), resolvedPropertyValue, value.getClass(), (customSerializer != null ? String.format(" using custom PdxSerializer [%1$s].", customSerializer.getClass().getName()) : ".")), e); } }); Optional<GemfirePersistentProperty> idProperty = entity.getIdProperty(); idProperty.ifPresent(gemfirePersistentProperty -> writer.markIdentityField(gemfirePersistentProperty.getName())); return true; } /** * Looks up and returns a custom PdxSerializer based on the class type of the object to (de)serialize. * * @param type the Class type of the object to (de)serialize. * @return a "custom" PdxSerializer for the given class type or null if no custom PdxSerializer * for the given class type was registered. * @see #getCustomSerializers() * @see org.apache.geode.pdx.PdxSerializer */ protected PdxSerializer getCustomSerializer(Class<?> type) { return getCustomSerializers().get(type); } /** * Looks up and returns an EntityInstantiator to construct and initialize an instance of the object defined * by the given PersistentEntity (meta-data). * * @param entity the PersistentEntity object used to lookup the custom EntityInstantiator. * @return an EntityInstantiator for the given PersistentEntity. * @see org.springframework.data.convert.EntityInstantiator * @see org.springframework.data.mapping.PersistentEntity */ protected EntityInstantiator getInstantiatorFor(PersistentEntity entity) { return getGemfireInstantiators().getInstantiatorFor(entity); } /** * Looks up and returns the {@link PersistentEntity} meta-data for the given entity object. * * @param entity actual persistent entity, application domain object. * @return the {@link PersistentEntity} meta-data for the given entity object. * @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity * @see #getPersistentEntity(Class) */ protected GemfirePersistentEntity<?> getPersistentEntity(Object entity) { return getPersistentEntity(entity.getClass()); } /** * Looks up and returns the {@link PersistentEntity} meta-data for the given entity {@link Class} type. * * @param entityType {@link Class} type of the actual persistent entity, application domain object {@link Class}. * @return the {@link PersistentEntity} meta-data for the given entity {@link Class} type. * @see org.springframework.data.gemfire.mapping.GemfirePersistentEntity * @see #getMappingContext() */ protected GemfirePersistentEntity<?> getPersistentEntity(Class<?> entityType) { return getMappingContext().getPersistentEntity(entityType).orElseThrow( () -> newIllegalArgumentException("Unable to resolve PersistentEntity for type [%]", entityType)); } }