/*
* Copyright 2016-2017 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.repository.query;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.DataType.CollectionType;
import com.datastax.driver.core.TypeCodec;
/**
* Custom {@link org.springframework.data.repository.query.ParameterAccessor} that uses a {@link CassandraConverter} to
* convert parameters.
*
* @author Mark Paluch
* @since 1.5
*/
class ConvertingParameterAccessor implements CassandraParameterAccessor {
private static final TypeInformation<Set> SET = ClassTypeInformation.from(Set.class);
private final CassandraConverter converter;
private final CassandraParameterAccessor delegate;
ConvertingParameterAccessor(CassandraConverter converter, CassandraParameterAccessor delegate) {
this.converter = converter;
this.delegate = delegate;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#getPageable()
*/
@Override
public Pageable getPageable() {
return delegate.getPageable();
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#getSort()
*/
@Override
public Sort getSort() {
return delegate.getSort();
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#getDynamicProjection()
*/
@Override
public Optional<Class<?>> getDynamicProjection() {
return delegate.getDynamicProjection();
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#getBindableValue(int)
*/
@Override
public Object getBindableValue(int index) {
return potentiallyConvert(index, Optional.ofNullable(delegate.getBindableValue(index)));
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.CassandraParameterAccessor#findCassandraType(int)
*/
@Override
public CassandraType findCassandraType(int index) {
return delegate.findCassandraType(index);
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.CassandraParameterAccessor#getDataType(int)
*/
@Override
public DataType getDataType(int index) {
DataType dataType = delegate.getDataType(index);
return (dataType != null ? dataType : converter.getMappingContext().getDataType(getParameterType(index)));
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.CassandraParameterAccessor#getParameterType(int)
*/
@Override
public Class<?> getParameterType(int index) {
return delegate.getParameterType(index);
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#hasBindableNullValue()
*/
@Override
public boolean hasBindableNullValue() {
return delegate.hasBindableNullValue();
}
/* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#iterator()
*/
public Iterator<Object> iterator() {
return new ConvertingIterator(delegate.iterator());
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.CassandraParameterAccessor#getValues()
*/
@Override
public Object[] getValues() {
return delegate.getValues();
}
@SuppressWarnings("unchecked")
private Object potentiallyConvert(int index, Optional<Object> bindableValue) {
return bindableValue
.flatMap(
v -> converter.convertToColumnType(bindableValue, findTypeInformation(index, v, Optional.empty())))
.orElse(null);
}
@SuppressWarnings("unchecked")
private Object potentiallyConvert(int index, Optional<Object> bindableValue, CassandraPersistentProperty property) {
return bindableValue.flatMap(
v -> converter.convertToColumnType(bindableValue, findTypeInformation(index, v, Optional.of(property))))
.orElse(null);
}
private TypeInformation<?> findTypeInformation(int index, Object bindableValue,
Optional<CassandraPersistentProperty> property) {
if (delegate.findCassandraType(index) != null) {
TypeCodec<?> typeCodec = CodecRegistry.DEFAULT_INSTANCE.codecFor(getDataType(index, property));
if (typeCodec.getJavaType().getType() instanceof Class<?>) {
return ClassTypeInformation.from((Class<?>) typeCodec.getJavaType().getType());
}
return ClassTypeInformation.from(typeCodec.getJavaType().getRawType());
}
return property.map(PersistentProperty::getTypeInformation)
.orElseGet(() -> (TypeInformation) ClassTypeInformation.from(bindableValue.getClass()));
}
/**
* Return the {@link DataType} based on annotated parameters with {@link CassandraType}, the
* {@link CassandraPersistentProperty} type or the declared parameter type.
*
* @param index index of parameter.
* @param property {@link CassandraPersistentProperty}.
* @return the {@link DataType}
*/
DataType getDataType(int index, Optional<CassandraPersistentProperty> optionalProperty) {
CassandraType cassandraType = delegate.findCassandraType(index);
if (cassandraType != null) {
return CassandraSimpleTypeHolder.getDataTypeFor(cassandraType.type());
}
CassandraMappingContext mappingContext = converter.getMappingContext();
TypeInformation<?> typeInformation = ClassTypeInformation.from(getParameterType(index));
return optionalProperty.map(property -> getDataType(mappingContext, typeInformation, property))
.orElseGet(() -> mappingContext.getDataType(typeInformation.getType()));
}
private DataType getDataType(CassandraMappingContext mappingContext, TypeInformation<?> typeInformation,
CassandraPersistentProperty property) {
DataType dataType = mappingContext.getDataType(property);
if (property.isCollectionLike() && !typeInformation.isCollectionLike()) {
if (dataType instanceof CollectionType) {
CollectionType collectionType = (CollectionType) dataType;
if (collectionType.getTypeArguments().size() == 1) {
return collectionType.getTypeArguments().get(0);
}
}
}
if (!property.isCollectionLike() && typeInformation.isCollectionLike()) {
if (typeInformation.isAssignableFrom(SET)) {
return DataType.set(dataType);
}
return DataType.list(dataType);
}
if (property.isMap()) {
if (dataType instanceof CollectionType) {
CollectionType collectionType = (CollectionType) dataType;
if (collectionType.getTypeArguments().size() == 2) {
return collectionType.getTypeArguments().get(0);
}
}
}
return mappingContext.getDataType(property);
}
/**
* Custom {@link Iterator} to convert items before returning them.
*
* @author Mark Paluch
*/
private class ConvertingIterator implements PotentiallyConvertingIterator {
private final Iterator<Object> delegate;
private int index = 0;
/**
* Create a new {@link ConvertingIterator} for the given delegate.
*
* @param delegate must not be {@literal null}.
*/
ConvertingIterator(Iterator<Object> delegate) {
this.delegate = delegate;
}
/*
* (non-Javadoc)
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return delegate.hasNext();
}
/*
* (non-Javadoc)
* @see java.util.Iterator#next()
*/
public Object next() {
return potentiallyConvert(index++, Optional.ofNullable(delegate.next()));
}
/*
* (non-Javadoc)
* @see java.util.Iterator#remove()
*/
public void remove() {
delegate.remove();
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator#nextConverted(org.springframework.data.cassandra.mapping.CassandraPersistentProperty)
*/
@Override
public Object nextConverted(CassandraPersistentProperty property) {
return potentiallyConvert(index++, Optional.ofNullable(delegate.next()), property);
}
}
/**
* Custom {@link Iterator} that adds a method to access elements in a converted manner.
*
* @author Mark Paluch
*/
interface PotentiallyConvertingIterator extends Iterator<Object> {
/**
* Returns the next element and pass in type information for potential conversion.
*
* @return the converted object, may be {@literal null}.
*/
Object nextConverted(CassandraPersistentProperty property);
}
}