All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.data.cassandra.convert.MappingCassandraConverter Maven / Gradle / Ivy

There is a newer version: 4.3.4
Show newest version
/*
 * Copyright 2013-2014 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.cassandra.convert;

import java.io.Serializable;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.repository.MapId;
import org.springframework.data.cassandra.repository.MapIdentifiable;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import com.datastax.driver.core.Row;
import com.datastax.driver.core.querybuilder.Delete.Where;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Update;

import static org.springframework.data.cassandra.repository.support.BasicMapId.id;

/**
 * {@link CassandraConverter} that uses a {@link MappingContext} to do sophisticated mapping of domain objects to
 * {@link Row}.
 * 
 * @author Alex Shvid
 * @author Matthew T. Adams
 * @author Oliver Gierke
 */
public class MappingCassandraConverter extends AbstractCassandraConverter implements CassandraConverter,
		ApplicationContextAware, BeanClassLoaderAware {

	protected final Logger log = LoggerFactory.getLogger(getClass());

	protected final CassandraMappingContext mappingContext;
	protected ApplicationContext applicationContext;
	protected SpELContext spELContext;

	protected ClassLoader beanClassLoader;

	/**
	 * Creates a new {@link MappingCassandraConverter} with a {@link BasicCassandraMappingContext}.
	 */
	public MappingCassandraConverter() {
		this(new BasicCassandraMappingContext());
	}

	/**
	 * Creates a new {@link MappingCassandraConverter} with the given {@link CassandraMappingContext}.
	 * 
	 * @param mappingContext must not be {@literal null}.
	 */
	public MappingCassandraConverter(CassandraMappingContext mappingContext) {

		super(new DefaultConversionService());

		Assert.notNull(mappingContext);

		this.mappingContext = mappingContext;
		this.spELContext = new SpELContext(RowReaderPropertyAccessor.INSTANCE);
	}

	@SuppressWarnings("unchecked")
	public  R readRow(Class clazz, Row row) {

		Class beanClassLoaderClass = transformClassToBeanClassLoaderClass(clazz);

		TypeInformation type = ClassTypeInformation.from(beanClassLoaderClass);
		TypeInformation typeToUse = type;
		Class rawType = typeToUse.getType();

		if (Row.class.isAssignableFrom(rawType)) {
			return (R) row;
		}

		CassandraPersistentEntity persistentEntity = (CassandraPersistentEntity) mappingContext
				.getPersistentEntity(typeToUse);
		if (persistentEntity == null) {
			throw new MappingException("No mapping metadata found for " + rawType.getName());
		}

		return readEntityFromRow(persistentEntity, row);
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
		this.spELContext = new SpELContext(this.spELContext, applicationContext);
	}

	protected  S readEntityFromRow(final CassandraPersistentEntity entity, final Row row) {

		DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(row, spELContext);

		BasicCassandraRowValueProvider rowValueProvider = new BasicCassandraRowValueProvider(row, evaluator);

		CassandraPersistentEntityParameterValueProvider parameterProvider = new CassandraPersistentEntityParameterValueProvider(
				entity, rowValueProvider, null);

		EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
		S instance = instantiator.createInstance(entity, parameterProvider);

		BeanWrapper wrapper = BeanWrapper.create(instance, conversionService);

		readPropertiesFromRow(entity, rowValueProvider, wrapper);

		return wrapper.getBean();
	}

	protected void readPropertiesFromRow(final CassandraPersistentEntity entity,
			final BasicCassandraRowValueProvider row, final BeanWrapper wrapper) {

		entity.doWithProperties(new PropertyHandler() {

			@Override
			public void doWithPersistentProperty(CassandraPersistentProperty prop) {

				MappingCassandraConverter.this.readPropertyFromRow(entity, prop, row, wrapper);
			}
		});
	}

	protected void readPropertyFromRow(final CassandraPersistentEntity entity, final CassandraPersistentProperty prop,
			final BasicCassandraRowValueProvider row, final BeanWrapper wrapper) {

		if (entity.isConstructorArgument(prop)) { // skip 'cause prop was set in ctor
			return;
		}

		if (prop.isCompositePrimaryKey()) {

			// get the key
			CassandraPersistentProperty keyProperty = entity.getIdProperty();
			Object key = wrapper.getProperty(keyProperty);
			if (key == null) {
				key = instantiatePrimaryKey(keyProperty.getCompositePrimaryKeyEntity(), keyProperty, row);
			}

			// wrap the key
			BeanWrapper keyWrapper = BeanWrapper.create(key, conversionService);

			// now recurse on using the key this time
			readPropertiesFromRow(prop.getCompositePrimaryKeyEntity(), row, keyWrapper);

			// now that the key's properties have been populated, set the key property on the entity
			wrapper.setProperty(keyProperty, keyWrapper.getBean());
			return;
		}

		if (!row.getRow().getColumnDefinitions().contains(prop.getColumnName().toCql())) {
			return;
		}

		Object obj = row.getPropertyValue(prop);
		wrapper.setProperty(prop, obj);
	}

	protected Object instantiatePrimaryKey(CassandraPersistentEntity entity, CassandraPersistentProperty keyProperty,
			BasicCassandraRowValueProvider propertyProvider) {

		EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);

		return instantiator.createInstance(entity, new CassandraPersistentEntityParameterValueProvider(entity,
				propertyProvider, null));
	}

	@Override
	public  R read(Class type, Object row) {
		if (row instanceof Row) {
			return readRow(type, (Row) row);
		}
		throw new MappingException("Unknown row object " + row.getClass().getName());
	}

	@Override
	public void write(Object source, Object sink) {

		if (source == null) {
			return;
		}

		Class beanClassLoaderClass = transformClassToBeanClassLoaderClass(source.getClass());
		CassandraPersistentEntity entity = mappingContext.getPersistentEntity(beanClassLoaderClass);

		if (entity == null) {
			throw new MappingException("No mapping metadata found for " + source.getClass());
		}

		if (sink instanceof Insert) {
			writeInsertFromObject(source, (Insert) sink, entity);
		} else if (sink instanceof Update) {
			writeUpdateFromObject(source, (Update) sink, entity);
		} else if (sink instanceof Where) {
			writeDeleteWhereFromObject(source, (Where) sink, entity);
		} else {
			throw new MappingException("Unknown buildStatement " + sink.getClass().getName());
		}
	}

	protected void writeInsertFromObject(final Object object, final Insert insert, CassandraPersistentEntity entity) {
		writeInsertFromWrapper(BeanWrapper.create(object, conversionService), insert, entity);
	}

	protected void writeInsertFromWrapper(final BeanWrapper wrapper, final Insert insert,
			CassandraPersistentEntity entity) {

		entity.doWithProperties(new PropertyHandler() {

			@Override
			public void doWithPersistentProperty(CassandraPersistentProperty prop) {

				Object value = wrapper.getProperty(prop, prop.getType());

				log.debug("prop.type -> " + prop.getType().getName());
				log.debug("prop.value -> " + value);

				if (prop.isCompositePrimaryKey()) {
					log.debug("prop is a compositeKey");
					writeInsertFromWrapper(BeanWrapper.create(value, conversionService), insert,
							prop.getCompositePrimaryKeyEntity());
					return;
				}

				if (value != null) {
					log.debug(String.format("Adding insert.value [%s] - [%s]", prop.getColumnName().toCql(), value));
					insert.value(prop.getColumnName().toCql(), value);
				}
			}
		});
	}

	protected void writeUpdateFromObject(final Object object, final Update update, CassandraPersistentEntity entity) {
		writeUpdateFromWrapper(BeanWrapper.create(object, conversionService), update, entity);
	}

	protected void writeUpdateFromWrapper(final BeanWrapper wrapper, final Update update,
			final CassandraPersistentEntity entity) {

		entity.doWithProperties(new PropertyHandler() {

			@Override
			public void doWithPersistentProperty(CassandraPersistentProperty prop) {

				Object value = wrapper.getProperty(prop, prop.getType());

				if (prop.isCompositePrimaryKey()) {
					writeUpdateFromWrapper(BeanWrapper.create(value, conversionService), update,
							prop.getCompositePrimaryKeyEntity());
					return;
				}

				if (value != null) {
					if (prop.isIdProperty() || entity.isCompositePrimaryKey() || prop.isPrimaryKeyColumn()) {
						update.where(QueryBuilder.eq(prop.getColumnName().toCql(), value));
					} else {
						update.with(QueryBuilder.set(prop.getColumnName().toCql(), value));
					}
				}
			}
		});
	}

	protected void writeDeleteWhereFromObject(final Object object, final Where where, CassandraPersistentEntity entity) {
		writeDeleteWhereFromWrapper(BeanWrapper.create(object, conversionService), where, entity);
	}

	protected void writeDeleteWhereFromWrapper(final BeanWrapper wrapper, final Where where,
			CassandraPersistentEntity entity) {

		// if the entity itself if a composite primary key, then we've recursed, so just add columns & return
		if (entity.isCompositePrimaryKey()) {
			entity.doWithProperties(new PropertyHandler() {
				@Override
				public void doWithPersistentProperty(CassandraPersistentProperty p) {
					where.and(QueryBuilder.eq(p.getColumnName().toCql(), wrapper.getProperty(p)));
				}
			});
			return;
		}

		// else, wrapper is an entity with an id
		Object id = getId(wrapper, entity);
		if (id == null) {
			String msg = String.format("no id value found in object {}", wrapper.getBean());
			log.error(msg);
			throw new IllegalArgumentException(msg);
		}

		if (id instanceof MapId) {

			for (Map.Entry entry : ((MapId) id).entrySet()) {
				where.and(QueryBuilder.eq(entry.getKey(), entry.getValue()));
			}
			return;
		}

		CassandraPersistentProperty idProperty = entity.getIdProperty();
		if (idProperty != null) {

			if (idProperty.isCompositePrimaryKey()) {
				writeDeleteWhereFromWrapper(BeanWrapper.create(id, conversionService), where,
						idProperty.getCompositePrimaryKeyEntity());
				return;
			}

			where.and(QueryBuilder.eq(idProperty.getColumnName().toCql(), id));
			return;
		}
	}

	@Override
	public Object getId(Object object, CassandraPersistentEntity entity) {

		Assert.notNull(object);

		final BeanWrapper wrapper = object instanceof BeanWrapper ? (BeanWrapper) object : BeanWrapper.create(object,
				conversionService);
		object = wrapper.getBean();

		if (!entity.getType().isAssignableFrom(object.getClass())) {
			throw new IllegalArgumentException(String.format(
					"given instance of type [%s] is not of compatible expected type [%s]", object.getClass().getName(), entity
							.getType().getName()));
		}

		if (object instanceof MapIdentifiable) {
			return ((MapIdentifiable) object).getMapId();
		}

		CassandraPersistentProperty idProperty = entity.getIdProperty();
		if (idProperty != null) {
			return wrapper.getProperty(entity.getIdProperty(), idProperty.getType());
		}

		// if the class doesn't have an id property, then it's using MapId
		final MapId id = id();
		entity.doWithProperties(new PropertyHandler() {

			@Override
			public void doWithPersistentProperty(CassandraPersistentProperty p) {
				if (p.isPrimaryKeyColumn()) {
					id.with(p.getName(), (Serializable) wrapper.getProperty(p, p.getType()));
				}
			}
		});

		return id;
	}

	@SuppressWarnings("unchecked")
	protected  Class transformClassToBeanClassLoaderClass(Class entity) {
		try {
			return (Class) ClassUtils.forName(entity.getName(), beanClassLoader);
		} catch (ClassNotFoundException e) {
			return entity;
		} catch (LinkageError e) {
			return entity;
		}
	}

	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.beanClassLoader = classLoader;

	}

	@Override
	public CassandraMappingContext getMappingContext() {
		return mappingContext;
	}
}