/* * Copyright 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.aerospike.convert; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.context.ApplicationContext; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.aerospike.mapping.AerospikeMappingContext; import org.springframework.data.aerospike.mapping.AerospikePersistentEntity; import org.springframework.data.aerospike.mapping.AerospikePersistentProperty; import org.springframework.data.aerospike.mapping.AerospikeSimpleTypes; import org.springframework.data.aerospike.mapping.CachingAerospikePersistentProperty; import org.springframework.data.convert.DefaultTypeMapper; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.convert.TypeAliasAccessor; import org.springframework.data.convert.TypeMapper; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; import org.springframework.data.mapping.model.PropertyValueProvider; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import com.aerospike.client.Bin; import com.aerospike.client.Record; import com.aerospike.client.Value; import com.aerospike.client.Value.MapValue; /** * An implementation of {@link AerospikeConverter} to read domain objects from {@link AerospikeData} and write domain * objects into them. * * @author Oliver Gierke */ public class MappingAerospikeConverter implements AerospikeConverter { private final AerospikeMappingContext mappingContext; private final SimpleTypeHolder simpleTypeHolder; private final ConversionService conversionService; private final EntityInstantiators entityInstantiators; private final TypeMapper<AerospikeData> typeMapper; public static final String SPRING_ID_BIN = "SpringID"; protected ApplicationContext applicationContext; protected final SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); /** * Creates a new {@link MappingAerospikeConverter}. */ @SuppressWarnings("rawtypes") public MappingAerospikeConverter() { this.mappingContext = new AerospikeMappingContext(); DefaultConversionService defaultConversionService = new DefaultConversionService(); defaultConversionService.addConverter(new LongToBoolean()); defaultConversionService.addConverter(new StringToLocalDateTimeConverter()); defaultConversionService.addConverter(new LocalDateTimeToStringConverter()); defaultConversionService.addConverterFactory(new EnumToStringConverterFactory()); defaultConversionService.addConverterFactory(new StringToEnumConverterFactory()); this.conversionService = defaultConversionService; this.entityInstantiators = new EntityInstantiators(); this.simpleTypeHolder = AerospikeSimpleTypes.HOLDER; this.typeMapper = new DefaultTypeMapper<AerospikeData>(AerospikeTypeAliasAccessor.INSTANCE); } /* * (non-Javadoc) * @see org.springframework.data.convert.EntityConverter#getMappingContext() */ @Override public MappingContext<? extends AerospikePersistentEntity<?>, AerospikePersistentProperty> getMappingContext() { return mappingContext; } /* * (non-Javadoc) * @see org.springframework.data.convert.EntityConverter#getConversionService() */ @Override public ConversionService getConversionService() { return conversionService; } /* * (non-Javadoc) * @see org.springframework.data.convert.EntityReader#read(java.lang.Class, S) */ @Override @SuppressWarnings("unchecked") public <R> R read(Class<R> type, final AerospikeData data) { TypeInformation<?> readType = typeMapper.readType(data, ClassTypeInformation.from(type)); TypeInformation<?> typeToUse = type.isAssignableFrom(readType.getType()) ? readType : ClassTypeInformation .from(type); final AerospikePersistentEntity<?> entity = mappingContext.getPersistentEntity(typeToUse); final RecordReadingPropertyValueProvider recordReadingPropertyValueProvider = new RecordReadingPropertyValueProvider(data.getRecord(), getConversionService(), simpleTypeHolder); EntityInstantiator instantiator = entityInstantiators.getInstantiatorFor(entity); Object instance = instantiator.createInstance(entity, new PersistentEntityParameterValueProvider<AerospikePersistentProperty>(entity, recordReadingPropertyValueProvider, null)); if (data.getRecord() != null) { final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(instance); entity.doWithProperties(new PropertyHandler<AerospikePersistentProperty>() { @SuppressWarnings("rawtypes") @Override public void doWithPersistentProperty(AerospikePersistentProperty persistentProperty) { PreferredConstructor<?, AerospikePersistentProperty> constructor = entity.getPersistenceConstructor(); Record record = data.getRecord(); if (record == null) return; if (constructor.isConstructorParameter(persistentProperty)) { return; } if (persistentProperty.isIdProperty()) { Object value = recordReadingPropertyValueProvider.getPropertyValue(persistentProperty, data.getSpringId()); if (value != null) { accessor.setProperty(persistentProperty, value); } return; } Object value = recordReadingPropertyValueProvider.getPropertyValue(persistentProperty); if (value != null) { accessor.setProperty(persistentProperty, value); } } }); } else { instance = null; } return (R) instance; } @SuppressWarnings("unchecked") public <R> R read(Object instance, final AerospikeData data) { Class<R> type = (Class<R>) instance.getClass(); TypeInformation<?> readType = typeMapper.readType(data, ClassTypeInformation.from(type)); TypeInformation<?> typeToUse = type.isAssignableFrom(readType.getType()) ? readType : ClassTypeInformation .from(type); final AerospikePersistentEntity<?> entity = mappingContext.getPersistentEntity(typeToUse); final RecordReadingPropertyValueProvider recordReadingPropertyValueProvider = new RecordReadingPropertyValueProvider(data.getRecord(), getConversionService(), simpleTypeHolder); if (data.getRecord() != null) { final PersistentPropertyAccessor accessor = entity .getPropertyAccessor(instance); entity.doWithProperties(new PropertyHandler<AerospikePersistentProperty>() { @Override public void doWithPersistentProperty( AerospikePersistentProperty persistentProperty) { PreferredConstructor<?, AerospikePersistentProperty> constructor = entity.getPersistenceConstructor(); if (constructor.isConstructorParameter(persistentProperty)) { return; } if (persistentProperty.isIdProperty()) { Object value = recordReadingPropertyValueProvider.getPropertyValue(persistentProperty, data.getSpringId()); if (value != null) { accessor.setProperty(persistentProperty, value); } return; } Object value = recordReadingPropertyValueProvider.getPropertyValue(persistentProperty); if (value != null) { accessor.setProperty(persistentProperty, value); } } }); } else { instance = null; } return (R) instance; } /* * (non-Javadoc) * @see org.springframework.data.convert.EntityWriter#write(java.lang.Object, java.lang.Object) */ @Override public void write(Object source, final AerospikeData data) { if (null == source) { return; } final List<Bin> bins = new ArrayList<Bin>(); Class<?> entityType = source.getClass(); TypeInformation<? extends Object> type = ClassTypeInformation.from(entityType); writeInternal(source, data, type, bins); data.add(bins); data.addMetaDataToBin(); } /** * @param obj * @param data * @param type * @param bins */ protected void writeInternal(final Object obj, final AerospikeData data, final TypeInformation<?> type, final List<Bin> bins) { if (null == obj) { return; } Class<?> entityType = obj.getClass(); if (Map.class.isAssignableFrom(entityType)) { //writeMapInternal((Map<Object, Object>) obj, data, ClassTypeInformation.MAP,bins); return; } if (Collection.class.isAssignableFrom(entityType)) { //writeCollectionInternal((Collection<?>) obj, ClassTypeInformation.LIST, data,bins); return; } AerospikePersistentEntity<?> entity = mappingContext.getPersistentEntity(entityType); writeInternal(obj, data, entity, bins); typeMapper.writeType(entity.getTypeInformation(), data); } /** * @param obj * @param data * @param entity */ protected void writeInternal(Object obj, final AerospikeData data, AerospikePersistentEntity<?> entity, final List<Bin> bins) { if (obj == null) { return; } if (null == entity) { throw new MappingException("No mapping metadata found for entity of type " + obj.getClass().getName()); } final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(obj); final CachingAerospikePersistentProperty idProperty = (CachingAerospikePersistentProperty) entity.getIdProperty(); if (idProperty != null) { Object id = accessor.getProperty(idProperty); data.setID(id != null ? id.toString() : null); data.setSetName(entity.getSetName()); data.addMetaDataItem(SPRING_ID_BIN, id); data.addMetaDataItem(idProperty.getFieldName(), idProperty.getType()); bins.add(new Bin(idProperty.getFieldName(), accessor.getProperty(idProperty))); } entity.doWithProperties(new PropertyHandler<AerospikePersistentProperty>() { @Override public void doWithPersistentProperty(AerospikePersistentProperty persistentProperty) { if (persistentProperty.equals(idProperty) || !persistentProperty.isWritable()) { return; } Object propertyObj = accessor.getProperty(persistentProperty); if (propertyObj == null || simpleTypeHolder.isSimpleType(propertyObj.getClass())) { writeSimpleInternal(propertyObj, data, persistentProperty, accessor, bins); } else { writePropertyInternal(propertyObj, data, persistentProperty, accessor, bins); } } }); } /** * @param propertyObj * @param data * @param persistentProperty * @param accessor * @param bins */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected void writePropertyInternal(Object propertyObj, AerospikeData data, AerospikePersistentProperty persistentProperty, PersistentPropertyAccessor accessor, final List<Bin> bins) { if (propertyObj == null) { return; } TypeInformation<?> valueType = ClassTypeInformation.from(propertyObj.getClass()); //TypeInformation<?> type = persistentProperty.getTypeInformation(); String fieldName = ((CachingAerospikePersistentProperty) persistentProperty).getFieldName(); data.addMetaDataItem(fieldName, valueType.getType()); if (valueType.isCollectionLike()) { List<?> collection = asList(accessor.getProperty(persistentProperty)); List propertyList = new ArrayList(); writeCollectionInternal(collection, valueType, propertyList); Bin collectionBin = new Bin(fieldName, propertyList); bins.add(collectionBin); } else if (valueType.isMap()) { data.addMetaDataItem(fieldName, propertyObj.getClass()); Value value = new MapValue((Map<?, ?>) accessor.getProperty(persistentProperty)); bins.add(new Bin(fieldName, value)); } else if (Value.class.isAssignableFrom(valueType.getType())){ bins.add(new Bin(fieldName, propertyObj)); } else if (conversionService.canConvert(propertyObj.getClass(), String.class)) { Value value = new Value.StringValue(conversionService.convert(propertyObj, String.class)); bins.add(new Bin(fieldName, value)); } else { AerospikePersistentEntity<?> childEntity = mappingContext.getPersistentEntity(propertyObj.getClass()); AerospikeData childData = AerospikeData.forWrite(data.getNamespace()); final List<Bin> childBins = new ArrayList<Bin>(); writeInternal(propertyObj, childData, childEntity, childBins); typeMapper.writeType(childEntity.getTypeInformation(), childData); if (data.getSetName() == null) { data.setSetName(childEntity.getSetName()); } childData.add(childBins); childData.addMetaDataToBin(); bins.add(new Bin(fieldName, AerospikeData.convertToMap(childData, this.simpleTypeHolder))); //works but does not finnish } } /** * @param source * @return */ @SuppressWarnings({ "unchecked", "rawtypes" }) private static List<?> asList(Object source) { if (source instanceof Collection) { return new ArrayList((Collection<?>) source); } return null; } /** * Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a * {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element * collection for everything else. * * @param source * @return */ @SuppressWarnings("unused") private static Collection<?> asCollection(Object source) { if (source instanceof Collection) { return (Collection<?>) source; } return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); } /** * @param propertyObj * @param data * @param persistentProperty * @param bins * @param accessor */ protected void writeSimpleInternal(Object propertyObj, AerospikeData data, AerospikePersistentProperty persistentProperty, PersistentPropertyAccessor accessor, List<Bin> bins) { String fieldName = ((CachingAerospikePersistentProperty) persistentProperty).getFieldName(); data.addMetaDataItem(fieldName, persistentProperty.getType()); bins.add(new Bin(fieldName, accessor.getProperty(persistentProperty))); } /** * @param collection * @param type * @param propertyList */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected <T> void writeCollectionInternal(Collection<?> collection, TypeInformation<?> type, List<T> propertyList) { Map map = null; for (Object element : collection) { if (element == null) { continue; } Class<?> elementType = element == null ? null : element.getClass(); AerospikePersistentEntity<?> entity = mappingContext.getPersistentEntity(elementType); if (elementType == null || simpleTypeHolder.isSimpleType(elementType)) { propertyList.add((T) element); } else if (element instanceof Collection || elementType.isArray()) { //bins.add(writeCollectionInternal(asCollection(element), componentType, new BasicDBList())); } else { AerospikeData childData = AerospikeData.forWrite(entity.getSetName()); final List<Bin> childBins = new ArrayList<Bin>(); writeInternal(element, childData, entity, childBins); typeMapper.writeType(entity.getTypeInformation(), childData); childData.add(childBins); childData.addMetaDataToBin(); map = AerospikeData.convertToMap(childData, simpleTypeHolder); propertyList.add((T) map); } } } /** * @param obj * @param data * @param propertyType * @param bins */ protected void writeMapInternal(Map<Object, Object> obj, AerospikeData data, TypeInformation<?> propertyType, List<Bin> bins) { } /** * A {@link PropertyValueProvider} to lookup values on the configured {@link Record}. * * @author Oliver Gierke */ private class RecordReadingPropertyValueProvider implements PropertyValueProvider<AerospikePersistentProperty> { private final Record record; private final ConversionService conversionService; @SuppressWarnings("unused") private final SimpleTypeHolder simpleTypeHolder; /** * Creates a new {@link RecordReadingPropertyValueProvider} for the given {@link Record}. * * @param record must not be {@literal null}. * @param conversionService */ public RecordReadingPropertyValueProvider(Record record, ConversionService conversionService, SimpleTypeHolder simpleTypeHolder) { this.record = record; this.conversionService = conversionService; this.simpleTypeHolder = simpleTypeHolder; } /* * (non-Javadoc) * @see org.springframework.data.mapping.model.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty) */ @Override @SuppressWarnings("unchecked") public <T> T getPropertyValue(AerospikePersistentProperty property) { if (record == null) { return null; } Object propertyObject = record.getValue(((CachingAerospikePersistentProperty) property).getFieldName()); if (propertyObject == null) { return null; } if (propertyObject instanceof HashMap<?, ?> && ((Map) propertyObject).containsKey(MappingAerospikeConverter.SPRING_ID_BIN)) { AerospikeData aerospikeData = AerospikeData.convertToAerospikeData((Map) propertyObject); return (T) read(property.getType(), aerospikeData); } return (T) conversionService.convert(propertyObject, TypeDescriptor.valueOf(propertyObject.getClass()), TypeDescriptor.valueOf(property.getType())); } /* * (non-Javadoc) * @see org.springframework.data.mapping.model.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty) */ @SuppressWarnings("unchecked") public <T> T getPropertyValue(AerospikePersistentProperty property, Object propertyObject) { T value = null; if (record == null) return value; if (propertyObject != null) { value = (T) conversionService.convert(propertyObject, TypeDescriptor.valueOf(propertyObject.getClass()), TypeDescriptor.valueOf(property.getType())); } return value; } } private static enum AerospikeTypeAliasAccessor implements TypeAliasAccessor<AerospikeData> { INSTANCE; private static final String TYPE_BIN_NAME = "spring_class"; /* * (non-Javadoc) * @see org.springframework.data.convert.TypeAliasAccessor#readAliasFrom(java.lang.Object) */ @Override public Object readAliasFrom(AerospikeData source) { Assert.notNull(source); if (source.getRecord() == null) return null; return source.getMetaData() == null ? null : source.getMetaData().getAerospikeMetaDataUsingKey(TYPE_BIN_NAME); } /* * (non-Javadoc) * @see org.springframework.data.convert.TypeAliasAccessor#writeTypeTo(java.lang.Object, java.lang.Object) */ @Override public void writeTypeTo(AerospikeData sink, Object alias) { sink.addMetaDataItem(TYPE_BIN_NAME, alias.toString()); } } /* (non-Javadoc) * @see org.springframework.data.aerospike.core.AerospikeWriter#convertToAerospikeType(java.lang.Object) */ @Override public Object convertToAerospikeType(Object obj) { // TODO Auto-generated method stub return null; } /* (non-Javadoc) * @see org.springframework.data.aerospike.core.AerospikeWriter#convertToAerospikeType(java.lang.Object, org.springframework.data.util.TypeInformation) */ @Override public Object convertToAerospikeType(Object obj, TypeInformation<?> typeInformation) { // TODO Auto-generated method stub return null; } /* (non-Javadoc) * @see org.springframework.data.aerospike.core.AerospikeWriter#toAerospikeData(java.lang.Object, org.springframework.data.aerospike.mapping.AerospikePersistentProperty) */ @Override public AerospikeData toAerospikeData(Object object, AerospikePersistentProperty referingProperty) { // TODO Auto-generated method stub return null; } }