diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java
index 296b33055..eb3dbb7ad 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/AbstractCassandraConverter.java
@@ -62,6 +62,14 @@ public abstract class AbstractCassandraConverter implements CassandraConverter,
this.conversions = conversions;
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.cassandra.convert.CassandraConverter#getCustomConversions()
+ */
+ @Override
+ public CustomConversions getCustomConversions() {
+ return conversions;
+ }
+
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java
index 9c73cb7c1..ec2e9a13c 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/convert/CassandraConverter.java
@@ -46,7 +46,7 @@ public interface CassandraConverter
*
A the composite primary key for {@link org.springframework.data.cassandra.mapping.PrimaryKey} using a
* {@link org.springframework.data.cassandra.mapping.PrimaryKeyClass}
*
- *
+ *
* @param object must not be {@literal null}.
* @param entity must not be {@literal null}.
* @return
@@ -55,10 +55,17 @@ public interface CassandraConverter
/**
* Converts and writes a {@code source} object into a {@code sink} using the given {@link CassandraPersistentEntity}.
- *
+ *
* @param source the source, may be {@literal null}.
* @param sink must not be {@literal null}.
* @param entity must not be {@literal null}.
*/
void write(Object source, Object sink, CassandraPersistentEntity> entity);
+
+ /**
+ * Returns the {@link CustomConversions} registered in the {@link CassandraConverter}.
+ *
+ * @return the {@link CustomConversions}.
+ */
+ CustomConversions getCustomConversions();
}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntityMetadataVerifier.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntityMetadataVerifier.java
index 9f129a84b..204ca8ba9 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntityMetadataVerifier.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntityMetadataVerifier.java
@@ -42,6 +42,10 @@ public class BasicCassandraPersistentEntityMetadataVerifier implements Cassandra
@Override
public void verify(CassandraPersistentEntity> entity) throws MappingException {
+ if(entity.getType().isInterface()){
+ return;
+ }
+
VerifierMappingExceptions exceptions = new VerifierMappingExceptions(entity,
String.format("Mapping Exceptions from BasicCassandraPersistentEntityMetadataVerifier for %s", entity.getName()));
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java
index 387b4bead..274d1ab03 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentProperty.java
@@ -51,6 +51,7 @@ import com.datastax.driver.core.DataType;
* @author Alex Shvid
* @author Matthew T. Adams
* @author Antoine Toulme
+ * @author Mark Paluch
*/
public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentProperty
implements CassandraPersistentProperty, ApplicationContextAware {
@@ -129,7 +130,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP
List columnNames = getColumnNames();
if (columnNames.size() != 1) {
- throw new IllegalStateException("property does not have a single column mapping");
+ throw new IllegalStateException(String.format("Property [%s] has no single column mapping", getName()));
}
return columnNames.get(0);
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/AbstractCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/AbstractCassandraQuery.java
index 5bcb81e85..4d251d232 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/AbstractCassandraQuery.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/AbstractCassandraQuery.java
@@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -28,6 +29,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.convert.CassandraConverter;
+import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.CollectionExecution;
import org.springframework.data.cassandra.repository.query.CassandraQueryExecution.ResultProcessingConverter;
@@ -38,6 +40,7 @@ import org.springframework.data.cassandra.repository.query.CassandraQueryExecuti
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
+import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -65,22 +68,29 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
*/
public AbstractCassandraQuery(CassandraQueryMethod method, CassandraOperations operations) {
- Assert.notNull(operations);
- Assert.notNull(method);
+ Assert.notNull(method, "CassandraQueryMethod must not be null");
+ Assert.notNull(operations, "CassandraOperations must not be null");
this.method = method;
this.template = operations;
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
+ */
@Override
public CassandraQueryMethod getQueryMethod() {
return method;
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
+ */
@Override
public Object execute(Object[] parameters) {
- CassandraParameterAccessor accessor = new ConvertingParameterAccessor(template.getConverter(), new CassandraParametersParameterAccessor(method, parameters));
+ CassandraParameterAccessor accessor = new ConvertingParameterAccessor(template.getConverter(),
+ new CassandraParametersParameterAccessor(method, parameters));
String query = createQuery(accessor);
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
@@ -88,7 +98,13 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
CassandraQueryExecution cassandraQueryExecution = getExecution(query, accessor,
new ResultProcessingConverter(processor));
- return cassandraQueryExecution.execute(query, processor.getReturnedType().getReturnedType());
+ CassandraReturnedType returnedType = new CassandraReturnedType(processor.getReturnedType(), template.getConverter().getCustomConversions());
+
+ if (returnedType.isProjecting()) {
+ return cassandraQueryExecution.execute(query, returnedType.getDomainType());
+ }
+
+ return cassandraQueryExecution.execute(query, returnedType.getReturnedType());
}
/**
@@ -167,6 +183,7 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
return object;
}
+ @Deprecated
protected void warnIfMoreResults(Iterator iterator) {
if (log.isWarnEnabled() && iterator.hasNext()) {
@@ -180,6 +197,7 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
}
}
+ @Deprecated
public ConversionService getConversionService() {
return template.getConverter().getConversionService();
}
@@ -200,4 +218,48 @@ public abstract class AbstractCassandraQuery implements RepositoryQuery {
* @param accessor must not be {@literal null}.
*/
protected abstract String createQuery(CassandraParameterAccessor accessor);
+
+ private class CassandraReturnedType {
+
+ private final ReturnedType returnedType;
+ private final CustomConversions customConversions;
+
+ CassandraReturnedType(ReturnedType returnedType, CustomConversions customConversions) {
+ this.returnedType = returnedType;
+ this.customConversions = customConversions;
+ }
+
+ boolean isProjecting(){
+
+ if(!returnedType.isProjecting()){
+ return false;
+ }
+
+ // Spring Data Cassandra allows List and Map declarations on query methods
+ // so we don't want to let projection kick in
+ if(ClassUtils.isAssignable(Map.class, returnedType.getReturnedType())){
+ return false;
+ }
+
+ // Type conversion using registered conversions is handled on template level
+ if(customConversions.hasCustomWriteTarget(returnedType.getReturnedType())){
+ return false;
+ }
+
+ // Don't apply projection on Cassandra simple types
+ if(customConversions.isSimpleType(returnedType.getReturnedType())){
+ return false;
+ }
+
+ return true;
+ }
+
+ Class> getReturnedType() {
+ return returnedType.getReturnedType();
+ }
+
+ Class> getDomainType() {
+ return returnedType.getDomainType();
+ }
+ }
}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraParameterAccessor.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraParameterAccessor.java
index 1575e2858..745c4106c 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraParameterAccessor.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraParameterAccessor.java
@@ -15,6 +15,7 @@
*/
package org.springframework.data.cassandra.repository.query;
+import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.repository.query.ParameterAccessor;
import com.datastax.driver.core.DataType;
@@ -32,7 +33,7 @@ public interface CassandraParameterAccessor extends ParameterAccessor {
* Returns the Cassandra {@link DataType} for the declared parameter if the type is a
* {@link org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder simple type}. Parameter types may be
* specified using {@link org.springframework.data.cassandra.mapping.CassandraType}.
- *
+ *
* @param index the parameter index
* @return the Cassandra {@link DataType} or {@literal null} if the parameter type cannot be determined from
* {@link org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder}
@@ -41,6 +42,16 @@ public interface CassandraParameterAccessor extends ParameterAccessor {
*/
DataType getDataType(int index);
+ /**
+ * Returns the {@link CassandraType} for the declared method parameter.
+ *
+ * @param index the parameter index
+ * @return the Cassandra {@link CassandraType} or {@literal null}.
+ * @see org.springframework.data.cassandra.mapping.CassandraSimpleTypeHolder
+ * @see org.springframework.data.cassandra.mapping.CassandraType
+ */
+ CassandraType findCassandraType(int index);
+
/**
* The actual parameter type (after unwrapping).
*
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraParameters.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraParameters.java
index e08dfedb1..dc9048711 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraParameters.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraParameters.java
@@ -71,7 +71,7 @@ public class CassandraParameters extends Parameters {
- private static final Logger LOG = LoggerFactory.getLogger(CassandraQueryCreator.class);
- private final CassandraParameterAccessor accessor;
+ private static final Pattern PUNCTATION_PATTERN = Pattern.compile("\\p{Punct}");
+ private static final Logger LOG = LoggerFactory.getLogger(CassandraQueryCreator.class);
private final CassandraMappingContext context;
+ private final WhereBuilder whereBuilder = new WhereBuilder();
+ private final CassandraPersistentEntity> entity;
/**
* Creates a new {@link CassandraQueryCreator} from the given {@link PartTree}, {@link ConvertingParameterAccessor}
* and {@link MappingContext}.
*
- * @param tree
- * @param accessor
- * @param context
+ * @param tree must not be {@literal null}.
+ * @param accessor must not be {@literal null}.
+ * @param context must not be {@literal null}.
+ * @param entityMetadata must not be {@literal null}.
*/
- public CassandraQueryCreator(PartTree tree, CassandraParameterAccessor accessor, CassandraMappingContext context) {
+ public CassandraQueryCreator(PartTree tree, CassandraParameterAccessor accessor, CassandraMappingContext context,
+ EntityMetadata> entityMetadata) {
super(tree, accessor);
- Assert.notNull(context);
+ Assert.notNull(context, "CassandraMappingContext must not be null");
+ Assert.notNull(entityMetadata, "EntityInformation must not be null");
- this.accessor = accessor;
this.context = context;
+ this.entity = context.getPersistentEntity(entityMetadata.getJavaType());
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator)
+ */
@Override
protected Clause create(Part part, Iterator iterator) {
PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty());
CassandraPersistentProperty property = path.getLeafProperty();
- Clause criteria = from(part, property,
- null /* TODO where(path.toDotPath(CassandraPersistentProperty.PropertyToFieldNameConverter.INSTANCE))*/,
- iterator);
-
- return criteria;
+ return from(part, property, (PotentiallyConvertingIterator) iterator);
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator)
+ */
@Override
protected Clause and(Part part, Clause base, Iterator iterator) {
if (base == null) {
- return create(part, iterator);
+ return whereBuilder.and(create(part, iterator));
}
- PersistentPropertyPath path = context.getPersistentPropertyPath(part.getProperty());
- CassandraPersistentProperty property = path.getLeafProperty();
-
- return from(part, property,
- null /* TODO base.and(path.toDotPath(CassandraPersistentProperty.PropertyToFieldNameConverter.INSTANCE))*/,
- iterator);
+ whereBuilder.and(base);
+ return create(part, iterator);
}
+ /*
+ * Cassandra does not support OR queries.
+ *
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object)
+ */
@Override
protected Clause or(Clause base, Clause criteria) {
throw new InvalidDataAccessApiUsageException(String.format("Cassandra does not support an OR operator!"));
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.query.parser.AbstractQueryCreator#complete(java.lang.Object, org.springframework.data.domain.Sort)
+ */
@Override
protected Select complete(Clause criteria, Sort sort) {
- if (criteria == null) {
- return null;
+ if (criteria != null) {
+ whereBuilder.and(criteria);
}
- Select select = QueryBuilder.select().all().from("TODO");
- select.where(criteria);
+ Select select = StatementBuilder.select(entity, whereBuilder, sort);
if (LOG.isDebugEnabled()) {
- LOG.debug("Created query " + select.getQueryString());
+ LOG.debug("Created query {}", select);
}
return select;
}
- private Clause from(Part part, CassandraPersistentProperty property, Clause criteria, Iterator parameters) {
+ private Clause from(Part part, CassandraPersistentProperty property, PotentiallyConvertingIterator parameters) {
Type type = part.getType();
switch (type) {
- // TODO
- // case AFTER:
- // case GREATER_THAN:
- // return criteria.gt(parameters.nextConverted(property));
- // case GREATER_THAN_EQUAL:
- // return criteria.gte(parameters.nextConverted(property));
- // case BEFORE:
- // case LESS_THAN:
- // return criteria.lt(parameters.nextConverted(property));
- // case LESS_THAN_EQUAL:
- // return criteria.lte(parameters.nextConverted(property));
- // case BETWEEN:
- // return criteria.gt(parameters.nextConverted(property)).lt(parameters.nextConverted(property));
- // case IS_NOT_NULL:
- // return criteria.ne(null);
- // case IS_NULL:
- // return criteria.is(null);
- // case NOT_IN:
- // return criteria.nin(nextAsArray(parameters, property));
- // case IN:
- // return criteria.in(nextAsArray(parameters, property));
- // case LIKE:
- // case STARTING_WITH:
- // case ENDING_WITH:
- // case CONTAINING:
- // return addAppropriateLikeRegexTo(criteria, part, parameters.next().toString());
- // case REGEX:
- // return criteria.regex(parameters.next().toString());
- // case EXISTS:
- // return criteria.exists((Boolean) parameters.next());
- // case TRUE:
- // return criteria.is(true);
- // case FALSE:
- // return criteria.is(false);
- // case WITHIN:
- //
- // Object parameter = parameters.next();
- // return criteria.within((Shape) parameter);
- // case SIMPLE_PROPERTY:
- //
- // return isSimpleComparisionPossible(part) ? criteria.is(parameters.nextConverted(property))
- // : createLikeRegexCriteriaOrThrow(part, property, criteria, parameters, false);
- //
- // case NEGATING_SIMPLE_PROPERTY:
- //
- // return isSimpleComparisionPossible(part) ? criteria.ne(parameters.nextConverted(property))
- // : createLikeRegexCriteriaOrThrow(part, property, criteria, parameters, true);
+ case AFTER:
+ case GREATER_THAN:
+ return QueryBuilder.gt(columnName(property), parameters.nextConverted(property));
+ case GREATER_THAN_EQUAL:
+ return QueryBuilder.gte(columnName(property), parameters.nextConverted(property));
+ case BEFORE:
+ case LESS_THAN:
+ return QueryBuilder.lt(columnName(property), parameters.nextConverted(property));
+ case LESS_THAN_EQUAL:
+ return QueryBuilder.lte(columnName(property), parameters.nextConverted(property));
+ case IN:
+ return QueryBuilder.in(columnName(property), nextAsArray(property, parameters));
+ case LIKE:
+ case STARTING_WITH:
+ case ENDING_WITH:
+ return QueryBuilder.like(columnName(property), like(type, parameters.nextConverted(property)));
+ case CONTAINING:
+ return containing(property, parameters.nextConverted(property));
+ case TRUE:
+ return QueryBuilder.eq(columnName(property), true);
+ case FALSE:
+ return QueryBuilder.eq(columnName(property), false);
+ case SIMPLE_PROPERTY:
+ return QueryBuilder.eq(columnName(property), parameters.nextConverted(property));
default:
- throw new UnsupportedCassandraQueryOperatorException(String.format(""));
+ throw new InvalidDataAccessApiUsageException(
+ String.format("Unsupported Keyword: [%s] in part [%s]", type, part));
}
}
- private boolean isSimpleComparisionPossible(Part part) {
+ private Clause containing(CassandraPersistentProperty property, Object bindableValue) {
- switch (part.shouldIgnoreCase()) {
- case NEVER:
- return true;
- case WHEN_POSSIBLE:
- return part.getProperty().getType() != String.class;
- case ALWAYS:
- return false;
- default:
- return true;
- }
- }
-
- /**
- * Returns the next element from the given {@link Iterator} expecting it to be of a certain type.
- *
- * @param
- * @param iterator
- * @param type
- * @throws IllegalArgumentException in case the next element in the iterator is not of the given type.
- * @return
- */
- @SuppressWarnings("unchecked")
- private T nextAs(Iterator iterator, Class type) {
- Object parameter = iterator.next();
- if (parameter.getClass().isAssignableFrom(type)) {
- return (T) parameter;
+ if (property.isCollectionLike() || ClassUtils.isAssignable(Map.class, property.getType())) {
+ return QueryBuilder.contains(columnName(property), bindableValue);
}
- throw new IllegalArgumentException(String.format("Expected parameter type of %s but got %s!", type,
- parameter.getClass()));
+ return QueryBuilder.like(columnName(property), like(Type.CONTAINING, bindableValue));
}
- private Object[] nextAsArray(Iterator iterator, CassandraPersistentProperty property) {
- Object next = iterator.next(); // TODO nextConverted(property);
+ private Object like(Type type, Object value) {
+
+ if (value == null) {
+ return null;
+ }
+
+ if (type == Type.LIKE) {
+ return value;
+ }
+
+ if (type == Type.CONTAINING) {
+ return "%" + value + "%";
+ }
+
+ if (type == Type.STARTING_WITH) {
+ return value + "%";
+ }
+
+ if (type == Type.ENDING_WITH) {
+ return "%" + value;
+ }
+
+ throw new IllegalArgumentException(String.format("Part Type [%s] not supported with like queries", type));
+
+ }
+
+ private static String columnName(CassandraPersistentProperty property) {
+ return property.getColumnName().toCql();
+ }
+
+ private Object[] nextAsArray(CassandraPersistentProperty property, PotentiallyConvertingIterator iterator) {
+
+ Object next = iterator.nextConverted(property);
if (next instanceof Collection) {
return ((Collection>) next).toArray();
@@ -217,4 +222,91 @@ class CassandraQueryCreator extends AbstractQueryCreator {
return new Object[] { next };
}
+
+ /**
+ * Where clause builder. Collects {@link Clause clauses} and builds the where-clause depending on the WHERE type.
+ *
+ * @author Mark Paluch
+ */
+ static class WhereBuilder {
+
+ private List clauses = new ArrayList();
+
+ Clause and(Clause clause) {
+
+ clauses.add(clause);
+ return clause;
+ }
+
+ Select.Where build(Select.Where where) {
+
+ for (Clause clause : clauses) {
+ where = where.and(clause);
+ }
+ return where;
+ }
+ }
+
+ /**
+ * @author Mark Paluch
+ */
+ static class StatementBuilder {
+
+ /**
+ * Build a {@link Select} statement from the given {@link WhereBuilder} and {@link Sort}. Resolves property names
+ * for {@link Sort} using the {@link CassandraPersistentEntity}.
+ *
+ * @param whereBuilder
+ * @param entity
+ * @param sort
+ * @return
+ */
+ static Select select(CassandraPersistentEntity> entity, WhereBuilder whereBuilder, Sort sort) {
+
+ Select select = QueryBuilder.select().from(entity.getTableName().toCql());
+ whereBuilder.build(select.where());
+
+ if (sort != null) {
+
+ for (Order order : sort) {
+
+ String dotPath = order.getProperty();
+ CassandraPersistentProperty property = getPersistentProperty(entity, dotPath);
+
+ if (order.isAscending()) {
+ select.orderBy(QueryBuilder.asc(columnName(property)));
+ } else {
+ select.orderBy(QueryBuilder.desc(columnName(property)));
+ }
+ }
+ }
+
+ return select;
+ }
+
+ private static CassandraPersistentProperty getPersistentProperty(CassandraPersistentEntity> entity,
+ String dotPath) {
+
+ String[] segments = PUNCTATION_PATTERN.split(dotPath);
+
+ CassandraPersistentProperty property = null;
+ CassandraPersistentEntity> currentEntity = entity;
+
+ for (String segment : segments) {
+ property = currentEntity.getPersistentProperty(segment);
+
+ if (property != null && property.isCompositePrimaryKey()) {
+ currentEntity = property.getCompositePrimaryKeyEntity();
+ }
+ }
+
+ if (property != null) {
+
+ return property;
+ }
+
+ throw new IllegalArgumentException(
+ String.format("Cannot resolve path [%s] to a property of [%s]", dotPath, entity.getName()));
+ }
+ }
}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java
index be295f771..b38f07a57 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethod.java
@@ -21,6 +21,7 @@ import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
+import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -28,6 +29,7 @@ import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import com.datastax.driver.core.ResultSet;
@@ -41,16 +43,9 @@ import com.datastax.driver.core.ResultSet;
*/
public class CassandraQueryMethod extends QueryMethod {
- private boolean queryCached = false;
-
- @SuppressWarnings("all")
- private final CassandraMappingContext mappingContext;
-
private final Method method;
-
- private Query query;
-
- private String queryString;
+ private final CassandraMappingContext mappingContext;
+ private CassandraEntityMetadata> metadata;
/**
* Creates a new {@link CassandraQueryMethod} from the given {@link Method}.
@@ -85,6 +80,36 @@ public class CassandraQueryMethod extends QueryMethod {
}
}
+ @Override
+ public CassandraEntityMetadata> getEntityInformation() {
+
+ if (metadata == null) {
+
+ Class> returnedObjectType = getReturnedObjectType();
+ Class> domainClass = getDomainClass();
+
+ if (ClassUtils.isPrimitiveOrWrapper(returnedObjectType)) {
+
+ this.metadata = new SimpleCassandraEntityMetadata((Class) domainClass,
+ mappingContext.getPersistentEntity(domainClass));
+
+ } else {
+
+ CassandraPersistentEntity> returnedEntity = mappingContext.getPersistentEntity(returnedObjectType);
+ CassandraPersistentEntity> managedEntity = mappingContext.getPersistentEntity(domainClass);
+ returnedEntity = returnedEntity == null || returnedEntity.getType().isInterface() ? managedEntity
+ : returnedEntity;
+ CassandraPersistentEntity> collectionEntity = domainClass.isAssignableFrom(returnedObjectType)
+ ? returnedEntity : managedEntity;
+
+ this.metadata = new SimpleCassandraEntityMetadata((Class) returnedEntity.getType(),
+ collectionEntity);
+ }
+ }
+
+ return this.metadata;
+ }
+
/* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryMethod#createParameters(java.lang.reflect.Method)
*/
@@ -93,18 +118,6 @@ public class CassandraQueryMethod extends QueryMethod {
return new CassandraParameters(method);
}
- /**
- * Returns the {@link Query} annotation that is applied to the method or {@code null} if none available.
- */
- Query getQueryAnnotation() {
- if (query == null) {
- query = AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
- queryCached = true;
- }
-
- return query;
- }
-
/**
* Returns whether the method has an annotated query.
*/
@@ -115,15 +128,22 @@ public class CassandraQueryMethod extends QueryMethod {
/**
* Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation found
* nor the attribute was specified.
+ *
+ * @return
*/
public String getAnnotatedQuery() {
- if (!queryCached) {
- queryString = (String) AnnotationUtils.getValue(getQueryAnnotation());
- queryString = (StringUtils.hasText(queryString) ? queryString : null);
- }
+ String query = (String) AnnotationUtils.getValue(getQueryAnnotation());
+ return StringUtils.hasText(query) ? query : null;
+ }
- return queryString;
+ /**
+ * Returns the {@link Query} annotation that is applied to the method or {@code null} if none available.
+ *
+ * @return
+ */
+ Query getQueryAnnotation() {
+ return AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
}
/**
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessor.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessor.java
index 17c0968b6..a288019e5 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessor.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessor.java
@@ -15,14 +15,26 @@
*/
package org.springframework.data.cassandra.repository.query;
+import java.util.Collection;
import java.util.Iterator;
+import java.util.Set;
+import org.springframework.core.CollectionFactory;
+import org.springframework.core.convert.ConversionService;
import org.springframework.data.cassandra.convert.CassandraConverter;
+import org.springframework.data.cassandra.convert.CustomConversions;
+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.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;
/**
@@ -34,10 +46,12 @@ import com.datastax.driver.core.TypeCodec;
*/
class ConvertingParameterAccessor implements CassandraParameterAccessor {
+ private final static TypeInformation SET = ClassTypeInformation.from(Set.class);
+
private final CassandraConverter cassandraConverter;
private final CassandraParameterAccessor delegate;
- public ConvertingParameterAccessor(CassandraConverter cassandraConverter, CassandraParameterAccessor delegate) {
+ ConvertingParameterAccessor(CassandraConverter cassandraConverter, CassandraParameterAccessor delegate) {
this.cassandraConverter = cassandraConverter;
this.delegate = delegate;
@@ -72,7 +86,12 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
*/
@Override
public Object getBindableValue(int index) {
- return potentiallyConvert(index, delegate.getBindableValue(index));
+ return potentiallyConvert(index, delegate.getBindableValue(index), null);
+ }
+
+ @Override
+ public CassandraType findCassandraType(int index) {
+ return delegate.findCassandraType(index);
}
/* (non-Javadoc)
@@ -80,7 +99,14 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
*/
@Override
public DataType getDataType(int index) {
- return delegate.getDataType(index);
+
+ DataType dataType = delegate.getDataType(index);
+
+ if (dataType != null) {
+ return dataType;
+ }
+
+ return cassandraConverter.getMappingContext().getDataType(getParameterType(index));
}
/* (non-Javadoc)
@@ -99,11 +125,6 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
return delegate.hasBindableNullValue();
}
- /*
- * (non-Javadoc)
- *
- * @see java.lang.Iterable#iterator()
- */
/* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#iterator()
*/
@@ -111,20 +132,37 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
return new ConvertingIterator(delegate.iterator());
}
- private Object potentiallyConvert(int index, Object bindableValue) {
+ @SuppressWarnings("unchecked")
+ private Object potentiallyConvert(int index, Object bindableValue, CassandraPersistentProperty property) {
if (bindableValue == null) {
return null;
}
- DataType parameterType = getDataType(index);
-
- if (parameterType == null) {
- parameterType = cassandraConverter.getMappingContext().getDataType(getParameterType(index));
+ if (bindableValue.getClass().isArray()) {
+ return bindableValue;
}
+ DataType parameterType = getDataType(index, property);
TypeCodec> cassandraType = CodecRegistry.DEFAULT_INSTANCE.codecFor(parameterType);
+ if (property != null && getCustomConversions().hasCustomWriteTarget(property.getActualType())
+ && property.isCollectionLike()) {
+
+ Class> customWriteTarget = getCustomConversions().getCustomWriteTarget(property.getActualType());
+ if (Collection.class.isAssignableFrom(property.getType()) && bindableValue instanceof Collection) {
+
+ Collection original = (Collection) bindableValue;
+ Collection converted = CollectionFactory.createCollection(property.getType(), original.size());
+
+ for (Object o : original) {
+ converted.add(getConversionService().convert(o, customWriteTarget));
+ }
+
+ return converted;
+ }
+ }
+
if (cassandraType.getJavaType().getRawType().isAssignableFrom(bindableValue.getClass())) {
return bindableValue;
}
@@ -132,12 +170,79 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
return cassandraConverter.getConversionService().convert(bindableValue, cassandraType.getJavaType().getRawType());
}
+ private CustomConversions getCustomConversions() {
+ return cassandraConverter.getCustomConversions();
+ }
+
+ private ConversionService getConversionService() {
+ return cassandraConverter.getConversionService();
+ }
+
+ /**
+ * Return the {@link DataType} based on annotated parameters with {@link CassandraType}, the
+ * {@link CassandraPersistentProperty} type or the declared parameter type.
+ *
+ * @param index
+ * @param cassandraPersistentProperty
+ * @return the {@link DataType}
+ */
+ DataType getDataType(int index, CassandraPersistentProperty cassandraPersistentProperty) {
+
+ CassandraType cassandraType = delegate.findCassandraType(index);
+
+ if (cassandraType != null) {
+ return CassandraSimpleTypeHolder.getDataTypeFor(cassandraType.type());
+ }
+
+ CassandraMappingContext mappingContext = cassandraConverter.getMappingContext();
+ TypeInformation> typeInformation = ClassTypeInformation.from(getParameterType(index));
+
+ if (cassandraPersistentProperty == null) {
+ return mappingContext.getDataType(typeInformation.getType());
+ }
+
+ DataType dataType = mappingContext.getDataType(cassandraPersistentProperty);
+
+ if (cassandraPersistentProperty.isCollectionLike() && !typeInformation.isCollectionLike()) {
+
+ if (dataType instanceof CollectionType) {
+
+ CollectionType collectionType = (CollectionType) dataType;
+ if (collectionType.getTypeArguments().size() == 1) {
+ return collectionType.getTypeArguments().get(0);
+ }
+ }
+ }
+
+ if (!cassandraPersistentProperty.isCollectionLike() && typeInformation.isCollectionLike()) {
+
+ if (typeInformation.isAssignableFrom(SET)) {
+ return DataType.set(dataType);
+ }
+
+ return DataType.list(dataType);
+ }
+
+ if (cassandraPersistentProperty.isMap()) {
+
+ if (dataType instanceof CollectionType) {
+
+ CollectionType collectionType = (CollectionType) dataType;
+ if (collectionType.getTypeArguments().size() == 2) {
+ return collectionType.getTypeArguments().get(0);
+ }
+ }
+ }
+
+ return mappingContext.getDataType(cassandraPersistentProperty);
+ }
+
/**
* Custom {@link Iterator} to convert items before returning them.
*
* @author Mark Paluch
*/
- private class ConvertingIterator implements Iterator {
+ private class ConvertingIterator implements PotentiallyConvertingIterator {
private final Iterator delegate;
private int index = 0;
@@ -145,9 +250,9 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
/**
* Creates a new {@link ConvertingIterator} for the given delegate.
*
- * @param delegate
+ * @param delegate must not be {@literal null}.
*/
- public ConvertingIterator(Iterator delegate) {
+ ConvertingIterator(Iterator delegate) {
this.delegate = delegate;
}
@@ -164,7 +269,7 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
* @see java.util.Iterator#next()
*/
public Object next() {
- return potentiallyConvert(index++, next());
+ return potentiallyConvert(index++, delegate.next(), null);
}
/*
@@ -174,5 +279,25 @@ class ConvertingParameterAccessor implements CassandraParameterAccessor {
public void remove() {
delegate.remove();
}
+
+ @Override
+ public Object nextConverted(CassandraPersistentProperty property) {
+ return potentiallyConvert(index++, delegate.next(), property);
+ }
+ }
+
+ /**
+ * Custom {@link Iterator} that adds a method to access elements in a converted manner.
+ *
+ * @author Mark Paluch
+ */
+ interface PotentiallyConvertingIterator extends Iterator {
+
+ /**
+ * Returns the next element and pass in type information for potential conversion.
+ *
+ * @return the converted object, may be {@literal null}.
+ */
+ Object nextConverted(CassandraPersistentProperty property);
}
}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQuery.java
index 9b4439951..f1106a52d 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQuery.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQuery.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -24,6 +24,9 @@ import org.springframework.data.repository.query.parser.PartTree;
/**
* {@link RepositoryQuery} implementation for Cassandra.
+ *
+ * @author Matthew Adams
+ * @author Mark Paluch
*/
public class PartTreeCassandraQuery extends AbstractCassandraQuery {
@@ -34,13 +37,14 @@ public class PartTreeCassandraQuery extends AbstractCassandraQuery {
* Creates a new {@link PartTreeCassandraQuery} from the given {@link QueryMethod} and {@link CassandraTemplate}.
*
* @param method must not be {@literal null}.
- * @param template must not be {@literal null}.
+ * @param operations must not be {@literal null}.
*/
- public PartTreeCassandraQuery(CassandraQueryMethod method, CassandraOperations cassandraOperations) {
+ public PartTreeCassandraQuery(CassandraQueryMethod method, CassandraOperations operations) {
+
+ super(method, operations);
- super(method, cassandraOperations);
this.tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType());
- this.context = cassandraOperations.getConverter().getMappingContext();
+ this.context = operations.getConverter().getMappingContext();
}
/**
@@ -52,10 +56,15 @@ public class PartTreeCassandraQuery extends AbstractCassandraQuery {
return tree;
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.repository.query.AbstractCassandraQuery#createQuery(org.springframework.data.cassandra.repository.query.CassandraParameterAccessor, boolean)
+ */
@Override
protected String createQuery(CassandraParameterAccessor accessor) {
- CassandraQueryCreator creator = new CassandraQueryCreator(tree, accessor, context);
- return creator.createQuery().getQueryString();
+ CassandraQueryCreator creator = new CassandraQueryCreator(tree, accessor, context,
+ getQueryMethod().getEntityInformation());
+ return creator.createQuery().toString();
}
}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/SimpleCassandraEntityMetadata.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/SimpleCassandraEntityMetadata.java
new file mode 100644
index 000000000..906714590
--- /dev/null
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/SimpleCassandraEntityMetadata.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 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 org.springframework.cassandra.core.cql.CqlIdentifier;
+import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of {@link CassandraEntityMetadata} based on the type and {@link CassandraPersistentEntity}.
+ *
+ * @author Mark Paluch
+ * @since 1.5
+ */
+class SimpleCassandraEntityMetadata implements CassandraEntityMetadata {
+
+ private final Class type;
+ private final CassandraPersistentEntity> tableEntity;
+
+ /**
+ * Creates a new {@link SimpleCassandraEntityMetadata} using the given type and {@link CassandraPersistentEntity} to
+ * use for table lookups.
+ *
+ * @param type must not be {@literal null}.
+ * @param tableEntity must not be {@literal null} or empty.
+ */
+ public SimpleCassandraEntityMetadata(Class type, CassandraPersistentEntity> tableEntity) {
+
+ Assert.notNull(type, "Type must not be null!");
+ Assert.notNull(tableEntity, "Collection entity must not be null or empty!");
+
+ this.type = type;
+ this.tableEntity = tableEntity;
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.cassandra.repository.query.CassandraEntityMetadata#getTableName()
+ */
+ @Override
+ public CqlIdentifier getTableName() {
+ return tableEntity.getTableName();
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.core.EntityMetadata#getJavaType()
+ */
+ @Override
+ public Class getJavaType() {
+ return type;
+ }
+}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java
index 15959297c..bf9a7e538 100644
--- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2015 the original author or authors
+ * Copyright 2013-2016 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.
@@ -18,13 +18,13 @@ package org.springframework.data.cassandra.repository.support;
import java.io.Serializable;
import java.lang.reflect.Method;
-import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.repository.TypedIdCassandraRepository;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
import org.springframework.data.cassandra.repository.query.CassandraQueryMethod;
+import org.springframework.data.cassandra.repository.query.PartTreeCassandraQuery;
import org.springframework.data.cassandra.repository.query.StringBasedCassandraQuery;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.projection.ProjectionFactory;
@@ -43,8 +43,8 @@ import org.springframework.util.Assert;
* @author Alex Shvid
* @author Matthew T. Adams
* @author Thomas Darimont
+ * @author Mark Paluch
*/
-
public class CassandraRepositoryFactory extends RepositoryFactorySupport {
private final CassandraOperations cassandraOperations;
@@ -61,16 +61,19 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
this.cassandraOperations = cassandraOperations;
this.mappingContext = cassandraOperations.getConverter().getMappingContext();
-
- // TODO: remove when supporting declarative query methods
- setQueryLookupStrategyKey(QueryLookupStrategy.Key.USE_DECLARED_QUERY);
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
+ */
@Override
protected Class> getRepositoryBaseClass(RepositoryMetadata metadata) {
return SimpleCassandraRepository.class;
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryInformation)
+ */
@Override
protected Object getTargetRepository(RepositoryInformation information) {
@@ -78,6 +81,9 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
return getTargetRepositoryViaReflection(information, entityInformation, cassandraOperations);
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class)
+ */
@Override
@SuppressWarnings("unchecked")
public CassandraEntityInformation getEntityInformation(Class domainClass) {
@@ -85,14 +91,17 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
CassandraPersistentEntity> entity = mappingContext.getPersistentEntity(domainClass);
if (entity == null) {
- throw new MappingException(String.format("Could not lookup mapping metadata for domain class %s!",
- domainClass.getName()));
+ throw new MappingException(
+ String.format("Could not lookup mapping metadata for domain class %s!", domainClass.getName()));
}
return new MappingCassandraEntityInformation((CassandraPersistentEntity) entity,
cassandraOperations.getConverter());
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key)
+ */
@Override
protected QueryLookupStrategy getQueryLookupStrategy(Key key) {
return new CassandraQueryLookupStrategy();
@@ -117,7 +126,7 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
} else if (queryMethod.hasAnnotatedQuery()) {
return new StringBasedCassandraQuery(queryMethod, cassandraOperations);
} else {
- throw new InvalidDataAccessApiUsageException("declarative query methods are a todo");
+ return new PartTreeCassandraQuery(queryMethod, cassandraOperations);
}
}
}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/Person.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/Person.java
index 92eccf73f..706ec31f5 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/Person.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/Person.java
@@ -29,4 +29,5 @@ public class Person {
@Id String id;
String firstname;
+ String lastname;
}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/CassandraParametersUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/CassandraParametersUnitTests.java
index 965eeed6a..aea5da220 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/CassandraParametersUnitTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/CassandraParametersUnitTests.java
@@ -44,12 +44,12 @@ public class CassandraParametersUnitTests {
* @see DATACASS-296
*/
@Test
- public void shouldReturnDataTypeForSimpleType() throws Exception {
+ public void shouldUnknownDataTypeForSimpleType() throws Exception {
Method method = PersonRepository.class.getMethod("findByFirstname", String.class);
CassandraParameters cassandraParameters = new CassandraParameters(method);
- assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(DataType.varchar()));
+ assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(nullValue()));
}
/**
@@ -61,7 +61,8 @@ public class CassandraParametersUnitTests {
Method method = PersonRepository.class.getMethod("findByFirstTime", String.class);
CassandraParameters cassandraParameters = new CassandraParameters(method);
- assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(DataType.time()));
+ assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(notNullValue()));
+ assertThat(cassandraParameters.getParameter(0).getCassandraType().type(), is(Name.TIME));
}
/**
@@ -85,7 +86,8 @@ public class CassandraParametersUnitTests {
Method method = PersonRepository.class.getMethod("findByAnnotatedObject", Object.class);
CassandraParameters cassandraParameters = new CassandraParameters(method);
- assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(DataType.time()));
+ assertThat(cassandraParameters.getParameter(0).getCassandraType(), is(notNullValue()));
+ assertThat(cassandraParameters.getParameter(0).getCassandraType().type(), is(Name.TIME));
}
interface PersonRepository {
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/CassandraQueryCreatorUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/CassandraQueryCreatorUnitTests.java
new file mode 100644
index 000000000..906f7a4ce
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/CassandraQueryCreatorUnitTests.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2016 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 static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.springframework.data.cassandra.repository.query.StubParameterAccessor.*;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.cassandra.core.PrimaryKeyType;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.cassandra.convert.CassandraConverter;
+import org.springframework.data.cassandra.convert.MappingCassandraConverter;
+import org.springframework.data.cassandra.domain.Person;
+import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
+import org.springframework.data.cassandra.mapping.CassandraMappingContext;
+import org.springframework.data.cassandra.mapping.Column;
+import org.springframework.data.cassandra.mapping.PrimaryKey;
+import org.springframework.data.cassandra.mapping.PrimaryKeyClass;
+import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
+import org.springframework.data.cassandra.mapping.Table;
+import org.springframework.data.repository.core.EntityMetadata;
+import org.springframework.data.repository.query.parser.PartTree;
+
+/**
+ * Unit tests for {@link CassandraQueryCreator}.
+ *
+ * @author Mark Paluch
+ * @soundtrack Odyssey - Everybody Move 9Club Mix
+ */
+public class CassandraQueryCreatorUnitTests {
+
+ CassandraMappingContext context;
+ CassandraConverter converter;
+
+ @Rule public ExpectedException expection = ExpectedException.none();
+
+ @Before
+ public void setUp() throws SecurityException, NoSuchMethodException {
+
+ context = new BasicCassandraMappingContext();
+ converter = new MappingCassandraConverter(context);
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsQueryCorrectly() {
+
+ String query = createQuery("findByFirstname", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname='Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsQueryWithSortCorrectly() {
+
+ String query = createQuery("findByFirstnameOrderByLastname", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname='Walter' ORDER BY lastname ASC;")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsAndQueryCorrectly() {
+
+ String query = createQuery("findByFirstnameAndLastname", Person.class, "Walter", "White");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname='Walter' AND lastname='White';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test(expected = InvalidDataAccessApiUsageException.class)
+ public void rejectsNegatingQueryQuery() {
+ createQuery("findByFirstnameNot", Person.class, "Walter");
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test(expected = InvalidDataAccessApiUsageException.class)
+ public void rejectsOrQuery() {
+ createQuery("findByFirstnameOrLastname", Person.class, "Walter", "White");
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsGreaterThanQueryCorrectly() {
+
+ String query = createQuery("findByFirstnameGreaterThan", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname>'Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsGreaterThanEqualQueryCorrectly() {
+
+ String query = createQuery("findByFirstnameGreaterThanEqual", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname>='Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsLessThanQueryCorrectly() {
+
+ String query = createQuery("findByFirstnameLessThan", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname<'Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsLessThanEqualQueryCorrectly() {
+
+ String query = createQuery("findByFirstnameLessThanEqual", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname<='Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsInQueryCorrectly() {
+
+ String query = createQuery("findByFirstnameIn", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname IN ('Walter');")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsInQueryWithListCorrectly() {
+
+ String query = createQuery("findByFirstnameIn", Person.class, Arrays.asList("Walter", "Gus"));
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname IN ('Walter','Gus');")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsInQueryWithArrayCorrectly() {
+
+ String query = createQuery("findByFirstnameInAndLastname", Person.class, new String[] { "Walter", "Gus" }, "Fring");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname IN ('Walter','Gus') AND lastname='Fring';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsLikeQueryCorrectly() {
+
+ assertThat(createQuery("findByFirstnameLike", Person.class, "Wal%ter"),
+ is(equalTo("SELECT * FROM person WHERE firstname LIKE 'Wal%ter';")));
+ assertThat(createQuery("findByFirstnameLike", Person.class, "Walter"),
+ is(equalTo("SELECT * FROM person WHERE firstname LIKE 'Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsStartsWithQueryCorrectly() {
+
+ String query = createQuery("findByFirstnameStartsWith", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname LIKE 'Walter%';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsEndsWithQueryCorrectly() {
+
+ String query = createQuery("findByFirstnameEndsWith", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname LIKE '%Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsContainsQueryOnSimplePropertyCorrectly() {
+
+ String query = createQuery("findByFirstnameContains", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname LIKE '%Walter%';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsContainsQueryOnSetPropertyCorrectly() {
+
+ String query = createQuery("findByMysetContains", TypeWithSet.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM typewithset WHERE myset CONTAINS 'Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsContainsQueryOnListPropertyCorrectly() {
+
+ String query = createQuery("findByMylistContains", TypeWithList.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM typewithlist WHERE mylist CONTAINS 'Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsContainsQueryOnMapPropertyCorrectly() {
+
+ String query = createQuery("findByMymapContains", TypeWithMap.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM typewithmap WHERE mymap CONTAINS 'Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsIsTrueQueryCorrectly() {
+
+ String query = createQuery("findByFirstnameIsTrue", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname=true;")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsIsFalseQueryCorrectly() {
+
+ String query = createQuery("findByFirstnameIsFalse", Person.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname=false;")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsQueryUsingQuotingCorrectly() {
+
+ String query = createQuery("findByIdAndSet", QuotedType.class, "Walter", "White");
+
+ assertThat(query, is(equalTo("SELECT * FROM \"myTable\" WHERE \"my_id\"='Walter' AND \"set\"='White';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsFindByPrimaryKeyPartCorrectly() {
+
+ String query = createQuery("findByKeyFirstname", TypeWithCompositeId.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM typewithcompositeid WHERE firstname='Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsFindByPrimaryKeyPartWithSortCorrectly() {
+
+ String query = createQuery("findByKeyFirstnameOrderByKeyLastnameAsc", TypeWithCompositeId.class, "Walter");
+
+ assertThat(query, is(equalTo("SELECT * FROM typewithcompositeid WHERE firstname='Walter' ORDER BY lastname ASC;")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsFindByPrimaryKeyPartOfPrimaryKeyClassCorrectly() {
+
+ String query = createQuery("findByFirstname", Key.class, "Walter");
+
+ // ⊙_ʘ rly? ヾ( •́д•̀ ;)ノ
+ assertThat(query, is(equalTo("SELECT * FROM key WHERE firstname='Walter';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test(expected = IllegalStateException.class)
+ public void createsFindByPrimaryKey2PartCorrectly() {
+ createQuery("findByKey", TypeWithCompositeId.class, new Key());
+ }
+
+ private String createQuery(String source, Class> entityClass, Object... values) {
+
+ PartTree tree = new PartTree(source, entityClass);
+ CassandraQueryCreator creator = new CassandraQueryCreator(tree, getAccessor(converter, values), context,
+ getEntityInformation(entityClass));
+ return creator.createQuery().toString();
+ }
+
+ private EntityMetadata getEntityInformation(final Class entityClass) {
+ return new EntityMetadata() {
+ @Override
+ public Class getJavaType() {
+ return entityClass;
+ }
+ };
+ }
+
+ @Table
+ private static class TypeWithSet {
+
+ @Id String id;
+ Set myset;
+ }
+
+ @Table
+ private static class TypeWithList {
+
+ @Id String id;
+ List mylist;
+ }
+
+ @Table
+ private static class TypeWithMap {
+
+ @Id String id;
+ Map mymap;
+ }
+
+ @Table(value = "myTable", forceQuote = true)
+ private static class QuotedType {
+
+ @PrimaryKey(value = "my_id", forceQuote = true) String id;
+
+ @Column(value = "set") Set set;
+ }
+
+ @PrimaryKeyClass
+ private static class Key implements Serializable {
+
+ @PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 1) String firstname;
+
+ @PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 1) String lastname;
+ }
+
+ @Table
+ private static class TypeWithCompositeId {
+
+ @PrimaryKey Key key;
+
+ String city;
+ }
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethodUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethodUnitTests.java
new file mode 100644
index 000000000..1378bbc26
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/CassandraQueryMethodUnitTests.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2016 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 static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.data.cassandra.domain.Person;
+import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
+import org.springframework.data.cassandra.mapping.CassandraMappingContext;
+import org.springframework.data.projection.ProjectionFactory;
+import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
+import org.springframework.data.repository.Repository;
+import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
+
+/**
+ * Unit tests for {@link CassandraQueryMethod}.
+ *
+ * @author Mark Paluch
+ */
+public class CassandraQueryMethodUnitTests {
+
+ CassandraMappingContext context;
+
+ @Before
+ public void setUp() {
+ context = new BasicCassandraMappingContext();
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void detectsCollectionFromRepoTypeIfReturnTypeNotAssignable() throws Exception {
+
+ CassandraQueryMethod queryMethod = queryMethod(SampleRepository.class, "method");
+ CassandraEntityMetadata> metadata = queryMethod.getEntityInformation();
+
+ assertThat(metadata.getJavaType(), is(typeCompatibleWith(Person.class)));
+ assertThat(metadata.getTableName().toCql(), is("person"));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void rejectsNullMappingContext() throws Exception {
+
+ Method method = SampleRepository.class.getMethod("method");
+
+ new CassandraQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
+ new SpelAwareProxyProjectionFactory(), null);
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void considersMethodAsCollectionQuery() throws Exception {
+
+ CassandraQueryMethod queryMethod = queryMethod(SampleRepository.class, "method");
+
+ assertThat(queryMethod.isCollectionQuery(), is(true));
+ }
+
+ private CassandraQueryMethod queryMethod(Class> repository, String name, Class>... parameters) throws Exception {
+
+ Method method = repository.getMethod(name, parameters);
+ ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
+ return new CassandraQueryMethod(method, new DefaultRepositoryMetadata(repository), factory, context);
+ }
+
+ interface SampleRepository extends Repository {
+
+ List method();
+ }
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessorUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessorUnitTests.java
index 649cddf53..d4c0c1940 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessorUnitTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ConvertingParameterAccessorUnitTests.java
@@ -20,6 +20,10 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.time.LocalDate;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
import org.junit.Before;
import org.junit.Test;
@@ -28,6 +32,9 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
+import org.springframework.data.cassandra.mapping.CassandraPersistentProperty;
+import org.springframework.data.cassandra.mapping.CassandraType;
+import org.springframework.data.cassandra.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator;
import com.datastax.driver.core.DataType;
@@ -41,14 +48,16 @@ import com.datastax.driver.core.DataType;
public class ConvertingParameterAccessorUnitTests {
@Mock CassandraParameterAccessor delegateMock;
-
+ @Mock CassandraPersistentProperty propertyMock;
MappingCassandraConverter converter;
+ ConvertingParameterAccessor accessor;
@Before
public void setUp() {
this.converter = new MappingCassandraConverter(new BasicCassandraMappingContext());
this.converter.afterPropertiesSet();
+ this.accessor = new ConvertingParameterAccessor(converter, delegateMock);
}
/**
@@ -56,9 +65,6 @@ public class ConvertingParameterAccessorUnitTests {
*/
@Test
public void shouldReturnNullBindableValue() {
-
- ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter, delegateMock);
-
assertThat(accessor.getBindableValue(0), is(nullValue()));
}
@@ -66,12 +72,12 @@ public class ConvertingParameterAccessorUnitTests {
* @see DATACASS-296
*/
@Test
+ @SuppressWarnings({"rawtypes", "unchecked"})
public void shouldReturnNativeBindableValue() {
- ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter, delegateMock);
-
when(delegateMock.getBindableValue(0)).thenReturn("hello");
when(delegateMock.getDataType(0)).thenReturn(DataType.varchar());
+ when(delegateMock.getParameterType(0)).thenReturn((Class) String.class);
assertThat(accessor.getBindableValue(0), is(equalTo((Object) "hello")));
}
@@ -80,11 +86,9 @@ public class ConvertingParameterAccessorUnitTests {
* @see DATACASS-296
*/
@Test
- @SuppressWarnings("rawtypes")
+ @SuppressWarnings({"rawtypes", "unchecked"})
public void shouldReturnConvertedBindableValue() {
- ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter, delegateMock);
-
LocalDate localDate = LocalDate.of(2010, 7, 4);
when(delegateMock.getBindableValue(0)).thenReturn(localDate);
@@ -93,4 +97,70 @@ public class ConvertingParameterAccessorUnitTests {
assertThat(accessor.getBindableValue(0),
is(equalTo((Object) com.datastax.driver.core.LocalDate.fromYearMonthDay(2010, 7, 4))));
}
+
+ /**
+ * @see DATACASS-296
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldReturnDataTypeProvidedByDelegate() {
+
+ when(delegateMock.getDataType(0)).thenReturn(DataType.varchar());
+
+ assertThat(accessor.getDataType(0), is(equalTo(DataType.varchar())));
+ }
+
+ /**
+ * @see DATACASS-296
+ * @see DATACASS-7
+ */
+ @Test
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public void shouldConvertCollections() {
+
+ LocalDate localDate = LocalDate.of(2010, 7, 4);
+
+ when(delegateMock.iterator()).thenReturn((Iterator) Arrays.asList(Collections.singletonList(localDate)).iterator());
+ when(delegateMock.getDataType(0)).thenReturn(DataType.list(DataType.date()));
+ when(delegateMock.getParameterType(0)).thenReturn((Class) List.class);
+ when(propertyMock.getType()).thenReturn((Class) List.class);
+ when(propertyMock.getActualType()).thenReturn((Class) LocalDate.class);
+ when(propertyMock.isCollectionLike()).thenReturn(true);
+
+ PotentiallyConvertingIterator iterator = (PotentiallyConvertingIterator) accessor.iterator();
+ Object converted = iterator.nextConverted(propertyMock);
+
+ assertThat(converted, is(instanceOf(List.class)));
+
+ List> list = (List>) converted;
+ assertThat(list.get(0), is(instanceOf(com.datastax.driver.core.LocalDate.class)));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public void shouldProvideTypeBasedOnValue() {
+
+ when(delegateMock.getDataType(0)).thenReturn(null);
+ when(delegateMock.getParameterType(0)).thenReturn((Class) LocalDate.class);
+
+ assertThat(accessor.getDataType(0), is(equalTo(DataType.date())));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ @SuppressWarnings("rawtypes")
+ public void shouldProvideTypeBasedOnPropertyType() {
+
+ when(propertyMock.getDataType()).thenReturn(DataType.varchar());
+ when(propertyMock.findAnnotation(CassandraType.class)).thenReturn(mock(CassandraType.class));
+ when(delegateMock.getParameterType(0)).thenReturn((Class) String.class);
+ when(delegateMock.getDataType(0)).thenReturn(null);
+
+ assertThat(accessor.getDataType(0, propertyMock), is(equalTo(DataType.varchar())));
+ }
}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java
new file mode 100644
index 000000000..2bd5fc542
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2016 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 static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Method;
+
+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.runners.MockitoJUnitRunner;
+import org.springframework.data.cassandra.convert.CassandraConverter;
+import org.springframework.data.cassandra.convert.MappingCassandraConverter;
+import org.springframework.data.cassandra.core.CassandraOperations;
+import org.springframework.data.cassandra.domain.Person;
+import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
+import org.springframework.data.cassandra.mapping.CassandraMappingContext;
+import org.springframework.data.cassandra.repository.CassandraRepository;
+import org.springframework.data.cassandra.repository.Query;
+import org.springframework.data.projection.ProjectionFactory;
+import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
+import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
+
+/**
+ * Unit tests for {@link PartTreeCassandraQuery}.
+ *
+ * @author Mark Paluch
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class PartTreeCassandraQueryUnitTests {
+
+ public @Rule ExpectedException exception = ExpectedException.none();
+
+ @Mock CassandraOperations cassandraOperationsMock;
+
+ CassandraMappingContext mappingContext;
+ CassandraConverter converter;
+
+ @Before
+ public void setUp() {
+
+ mappingContext = new BasicCassandraMappingContext();
+ converter = new MappingCassandraConverter(mappingContext);
+ when(cassandraOperationsMock.getConverter()).thenReturn(converter);
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldDeriveSimpleQuery() {
+
+ String query = deriveQueryFromMethod("findByLastname", "foo");
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE lastname='foo';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldDeriveSimpleQueryWithoutNames() {
+
+ String query = deriveQueryFromMethod("findPersonBy");
+
+ assertThat(query, is(equalTo("SELECT * FROM person;")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldDeriveAndQuery() {
+
+ String query = deriveQueryFromMethod("findByFirstnameAndLastname", "foo", "bar" );
+
+ assertThat(query, is(equalTo("SELECT * FROM person WHERE firstname='foo' AND lastname='bar';")));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void usesDynamicProjection() {
+
+ String query = deriveQueryFromMethod("findDynamicallyProjectedBy", PersonProjection.class);
+
+ assertThat(query, is(equalTo("SELECT * FROM person;")));
+ }
+
+
+ private String deriveQueryFromMethod(String method, Object... args) {
+
+ Class>[] types = new Class>[args.length];
+
+ for (int i = 0; i < args.length; i++) {
+ types[i] = args[i].getClass();
+ }
+
+ PartTreeCassandraQuery partTreeQuery = createQueryForMethod(method, types);
+
+ CassandraParameterAccessor accessor = new CassandraParametersParameterAccessor(partTreeQuery.getQueryMethod(), args);
+ return partTreeQuery.createQuery(new ConvertingParameterAccessor(cassandraOperationsMock.getConverter(), accessor));
+ }
+
+ private PartTreeCassandraQuery createQueryForMethod(String methodName, Class>... paramTypes) {
+
+ try {
+
+ Method method = Repo.class.getMethod(methodName, paramTypes);
+ ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
+ CassandraQueryMethod queryMethod = new CassandraQueryMethod(method, new DefaultRepositoryMetadata(Repo.class), factory,
+ mappingContext);
+
+ return new PartTreeCassandraQuery(queryMethod, cassandraOperationsMock);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException(e.getMessage(), e);
+ } catch (SecurityException e) {
+ throw new IllegalArgumentException(e.getMessage(), e);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ interface Repo extends CassandraRepository {
+
+ @Query()
+ Person findByLastname(String lastname);
+
+ Person findByFirstnameAndLastname(String firstname, String lastname);
+
+ Person findPersonByFirstnameAndLastname(String firstname, String lastname);
+
+ Person findByAge(Integer age);
+
+ Person findPersonBy();
+
+ PersonProjection findPersonProjectedBy();
+
+ T findDynamicallyProjectedBy(Class type);
+
+ }
+
+ interface PersonProjection {
+
+ String getFirstname();
+
+ String getLastname();
+ }
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StubParameterAccessor.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StubParameterAccessor.java
new file mode 100644
index 000000000..75abb515d
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StubParameterAccessor.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016 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.Arrays;
+import java.util.Iterator;
+
+import org.springframework.data.cassandra.convert.CassandraConverter;
+import org.springframework.data.cassandra.mapping.CassandraType;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Range;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.geo.Distance;
+import org.springframework.data.repository.query.ParameterAccessor;
+
+import com.datastax.driver.core.CodecRegistry;
+import com.datastax.driver.core.DataType;
+
+/**
+ * Simple {@link ParameterAccessor} that returns the given parameters unfiltered.
+ *
+ * @author Mark Paluch
+ */
+class StubParameterAccessor implements CassandraParameterAccessor {
+
+ private final Object[] values;
+
+ /**
+ * Creates a new {@link ConvertingParameterAccessor} backed by a {@link StubParameterAccessor} simply returning the
+ * given parameters converted but unfiltered.
+ *
+ * @param converter
+ * @param parameters
+ * @return
+ */
+ public static ConvertingParameterAccessor getAccessor(CassandraConverter converter, Object... parameters) {
+ return new ConvertingParameterAccessor(converter, new StubParameterAccessor(parameters));
+ }
+
+ @SuppressWarnings("unchecked")
+ public StubParameterAccessor(Object... values) {
+ this.values = values;
+ }
+
+ @Override
+ public DataType getDataType(int index) {
+ return CodecRegistry.DEFAULT_INSTANCE.codecFor(values[index]).getCqlType();
+ }
+
+ @Override
+ public Class> getParameterType(int index) {
+ return values[index].getClass();
+ }
+
+ @Override
+ public Pageable getPageable() {
+ return null;
+ }
+
+ @Override
+ public Sort getSort() {
+ return null;
+ }
+
+ @Override
+ public Class> getDynamicProjection() {
+ return null;
+ }
+
+ @Override
+ public Object getBindableValue(int index) {
+ return values[index];
+ }
+
+ @Override
+ public boolean hasBindableNullValue() {
+ return false;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return Arrays.asList(values).iterator();
+ }
+
+ @Override
+ public CassandraType findCassandraType(int index) {
+ return null;
+ }
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryUnitTests.java
new file mode 100644
index 000000000..29842dc2e
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactoryUnitTests.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 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.support;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import java.io.Serializable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.data.cassandra.convert.CassandraConverter;
+import org.springframework.data.cassandra.core.CassandraTemplate;
+import org.springframework.data.cassandra.domain.Person;
+import org.springframework.data.cassandra.mapping.CassandraMappingContext;
+import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
+import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
+import org.springframework.data.repository.Repository;
+
+/**
+ * Unit tests for {@link CassandraRepositoryFactory}.
+ *
+ * @author Mark Paluch
+ */
+@RunWith(MockitoJUnitRunner.class)
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class CassandraRepositoryFactoryUnitTests {
+
+ @Mock CassandraTemplate template;
+ @Mock CassandraConverter converter;
+ @Mock CassandraMappingContext mappingContext;
+ @Mock CassandraPersistentEntity entity;
+
+ @Before
+ public void setUp() {
+
+ when(template.getConverter()).thenReturn(converter);
+ when(converter.getMappingContext()).thenReturn(mappingContext);
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void usesMappingCassandraEntityInformationIfMappingContextSet() {
+
+ when(mappingContext.getPersistentEntity(Person.class)).thenReturn(entity);
+ when(entity.getType()).thenReturn(Person.class);
+
+ CassandraRepositoryFactory factory = new CassandraRepositoryFactory(template);
+ CassandraEntityInformation entityInformation = factory.getEntityInformation(Person.class);
+ assertTrue(entityInformation instanceof MappingCassandraEntityInformation);
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void createsRepositoryWithIdTypeLong() {
+
+ when(mappingContext.getPersistentEntity(Person.class)).thenReturn(entity);
+ when(entity.getType()).thenReturn(Person.class);
+
+ CassandraRepositoryFactory factory = new CassandraRepositoryFactory(template);
+ MyPersonRepository repository = factory.getRepository(MyPersonRepository.class);
+ assertThat(repository, is(notNullValue()));
+ }
+
+ interface MyPersonRepository extends Repository {
+
+ }
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/AsynchronousCassandraTemplateIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/AsynchronousCassandraTemplateIntegrationTests.java
index 3057a97e4..5dda280cc 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/AsynchronousCassandraTemplateIntegrationTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/AsynchronousCassandraTemplateIntegrationTests.java
@@ -246,7 +246,7 @@ public class AsynchronousCassandraTemplateIntegrationTests extends AbstractSprin
@AllArgsConstructor
@NoArgsConstructor
@SuppressWarnings("unused")
- public static class Person {
+ static class Person {
@PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) String id;
@Column String firstname;
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java
index be1f9170f..aba473d43 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/core/CassandraOperationsIntegrationTests.java
@@ -734,7 +734,11 @@ public class CassandraOperationsIntegrationTests extends AbstractSpringDataEmbed
* @see DATACASS-182
*/
@Test
- public void stream() {
+ public void stream() throws InterruptedException {
+
+ while(template.select("SELECT * FROM book", Book.class).size() != 0){
+ Thread.sleep(10);
+ }
template.insert(getBookList(20));
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/conversion/Address.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/conversion/Address.java
new file mode 100644
index 000000000..933d97db5
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/conversion/Address.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 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.test.integration.repository.querymethods.conversion;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author Mark Paluch
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+class Address {
+
+ String city;
+ String country;
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/conversion/Contact.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/conversion/Contact.java
new file mode 100644
index 000000000..a646a8c0f
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/conversion/Contact.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016 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.test.integration.repository.querymethods.conversion;
+
+import java.util.List;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.cassandra.mapping.Table;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author Mark Paluch
+ */
+@Table
+@Data
+@NoArgsConstructor
+class Contact {
+
+ @Id String id;
+
+ Address address;
+ List addresses;
+
+ public Contact(String id) {
+ this.id = id;
+ }
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/conversion/ParameterConversionIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/conversion/ParameterConversionIntegrationTests.java
new file mode 100644
index 000000000..2567ff361
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/conversion/ParameterConversionIntegrationTests.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2016 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.test.integration.repository.querymethods.conversion;
+
+import static org.hamcrest.MatcherAssert.*;
+import static org.hamcrest.Matchers.*;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.cassandra.config.SchemaAction;
+import org.springframework.data.cassandra.convert.CustomConversions;
+import org.springframework.data.cassandra.core.CassandraOperations;
+import org.springframework.data.cassandra.repository.CassandraRepository;
+import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
+import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.base.PersonRepository;
+import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
+import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.util.StringUtils;
+
+/**
+ * Integration tests for query derivation through {@link PersonRepository}.
+ *
+ * @author Mark Paluch
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class ParameterConversionIntegrationTests extends AbstractSpringDataEmbeddedCassandraIntegrationTest {
+
+ @Configuration
+ @EnableCassandraRepositories(considerNestedRepositories = true)
+ public static class Config extends IntegrationTestConfig {
+
+ @Override
+ public String[] getEntityBasePackages() {
+ return new String[] { Contact.class.getPackage().getName() };
+ }
+
+ @Override
+ public SchemaAction getSchemaAction() {
+ return SchemaAction.RECREATE_DROP_UNUSED;
+ }
+
+ @Override
+ public CustomConversions customConversions() {
+ return new CustomConversions(Arrays.asList(AddressReadConverter.INSTANCE, AddressWriteConverter.INSTANCE));
+ }
+ }
+
+ @Autowired CassandraOperations template;
+ @Autowired ContactRepository contactRepository;
+
+ Contact walter, flynn;
+
+ @Before
+ public void before() {
+
+ deleteAllEntities();
+
+ template.execute("CREATE INDEX IF NOT EXISTS contact_address ON contact (address);");
+ template.execute("CREATE INDEX IF NOT EXISTS contact_addresses ON contact (addresses);");
+
+ walter = new Contact("Walter");
+ walter.setAddress(new Address("Albuquerque", "USA"));
+ walter.setAddresses(Arrays.asList(new Address("Albuquerque", "USA"), new Address("New Hampshire", "USA"),
+ new Address("Grocery Store", "Mexico")));
+
+ flynn = new Contact("Flynn");
+ flynn.setAddress(new Address("Albuquerque", "USA"));
+ flynn.setAddresses(Collections.singletonList(new Address("Albuquerque", "USA")));
+
+ walter = contactRepository.save(walter);
+ flynn = contactRepository.save(flynn);
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldFindByConvertedParameter() {
+
+ List contacts = contactRepository.findByAddress(walter.getAddress());
+
+ assertThat(contacts, hasItems(walter, flynn));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldFindByStringParameter() {
+
+ String parameter = AddressWriteConverter.INSTANCE.convert(walter.getAddress());
+ List contacts = contactRepository.findByAddress(parameter);
+
+ assertThat(contacts, hasItems(walter, flynn));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void findByAddressesIn() {
+
+ assertThat(contactRepository.findByAddressesContains(flynn.address), containsInAnyOrder(flynn, walter));
+ assertThat(contactRepository.findByAddressesContains(walter.addresses.get(1)), contains(walter));
+ }
+
+ interface ContactRepository extends CassandraRepository {
+
+ List findByAddress(Address address);
+
+ List findByAddress(String address);
+
+ List findByAddressesContains(Address address);
+ }
+
+ /**
+ * @author Mark Paluch
+ */
+ static enum AddressReadConverter implements Converter {
+
+ INSTANCE;
+
+ public Address convert(String source) {
+
+ if (StringUtils.hasText(source)) {
+ try {
+ return new ObjectMapper().readValue(source, Address.class);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * @author Mark Paluch
+ */
+ static enum AddressWriteConverter implements Converter {
+ INSTANCE;
+
+ public String convert(Address source) {
+
+ try {
+ return new ObjectMapper().writeValueAsString(source);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/declared/Person.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/declared/Person.java
index d8c38eae8..73fba9e21 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/declared/Person.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/declared/Person.java
@@ -20,17 +20,18 @@ import java.time.ZoneId;
import java.util.Date;
import org.springframework.cassandra.core.PrimaryKeyType;
-import org.springframework.data.cassandra.mapping.Indexed;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
import lombok.Data;
+import lombok.NoArgsConstructor;
/**
* Sample domain class.
*/
@Table
@Data
+@NoArgsConstructor
public class Person {
@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED, ordinal = 0) private String lastname;
@@ -44,4 +45,10 @@ public class Person {
private LocalDate createdDate;
private ZoneId zoneId;
+
+ public Person(String firstname, String lastname) {
+
+ this.firstname = firstname;
+ this.lastname = lastname;
+ }
}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/derived/PersonRepository.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/derived/PersonRepository.java
new file mode 100644
index 000000000..b7603fcc1
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/derived/PersonRepository.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016 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.test.integration.repository.querymethods.derived;
+
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.data.cassandra.repository.CassandraRepository;
+import org.springframework.data.cassandra.repository.Query;
+import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
+import org.springframework.data.domain.Sort;
+
+/**
+ * @author Mark Paluch
+ */
+interface PersonRepository extends CassandraRepository {
+
+ List findByLastname(String lastname);
+
+ List findByLastname(String lastname, Sort sort);
+
+ List findByLastnameOrderByFirstnameAsc(String lastname);
+
+ Person findByFirstnameAndLastname(String firstname, String lastname);
+
+ Person findByCreatedDate(LocalDate createdDate);
+
+ Person findByNicknameStartsWith(String prefix);
+
+ Person findByNicknameContains(String contains);
+
+ Person findByNumberOfChildren(NumberOfChildren numberOfChildren);
+
+ Collection findPersonProjectedBy();
+
+ @Query("select * from person where firstname = ?0 and lastname = 'White'")
+ List findByFirstname(String firstname);
+
+ enum NumberOfChildren {
+ ZERO, ONE, TWO,
+ }
+
+ interface PersonProjection {
+
+ String getFirstname();
+
+ String getLastname();
+ }
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/derived/QueryDerivationIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/derived/QueryDerivationIntegrationTests.java
new file mode 100644
index 000000000..d7ad2bca6
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/repository/querymethods/derived/QueryDerivationIntegrationTests.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2016 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.test.integration.repository.querymethods.derived;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.SpringVersion;
+import org.springframework.data.cassandra.config.SchemaAction;
+import org.springframework.data.cassandra.core.CassandraOperations;
+import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
+import org.springframework.data.cassandra.test.integration.repository.querymethods.declared.Person;
+import org.springframework.data.cassandra.test.integration.repository.querymethods.derived.PersonRepository.NumberOfChildren;
+import org.springframework.data.cassandra.test.integration.repository.querymethods.derived.PersonRepository.PersonProjection;
+import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest;
+import org.springframework.data.cassandra.test.integration.support.CassandraVersion;
+import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.util.Version;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * Integration tests for query derivation through {@link PersonRepository}.
+ *
+ * @author Mark Paluch
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedCassandraIntegrationTest {
+
+ @Configuration
+ @EnableCassandraRepositories
+ public static class Config extends IntegrationTestConfig {
+
+ @Override
+ public String[] getEntityBasePackages() {
+ return new String[] { Person.class.getPackage().getName() };
+ }
+
+ @Override
+ public SchemaAction getSchemaAction() {
+ return SchemaAction.RECREATE_DROP_UNUSED;
+ }
+
+ }
+
+ @Autowired CassandraOperations template;
+ @Autowired PersonRepository personRepository;
+
+ Person walter, skyler, flynn;
+
+ @Before
+ public void before() {
+
+ deleteAllEntities();
+
+ Person person = new Person("Walter", "White");
+ person.setNumberOfChildren(2);
+
+ walter = personRepository.save(person);
+ skyler = personRepository.save(new Person("Skyler", "White"));
+ flynn = personRepository.save(new Person("Flynn (Walter Jr.)", "White"));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldFindByLastname() {
+
+ List result = personRepository.findByLastname("White");
+
+ assertThat(result, hasItems(walter, skyler, flynn));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldFindByLastnameAndDynamicSort() {
+
+ List result = personRepository.findByLastname("White", new Sort("firstname"));
+
+ assertThat(result, contains(flynn, skyler, walter));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldFindByLastnameWithOrdering() {
+
+ List result = personRepository.findByLastnameOrderByFirstnameAsc("White");
+
+ assertThat(result, contains(flynn, skyler, walter));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldFindByFirstnameAndLastname() {
+
+ Person result = personRepository.findByFirstnameAndLastname("Walter", "White");
+
+ assertThat(result, is(walter));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void executesCollectionQueryWithProjectionCorrectly() {
+
+ Collection collection = personRepository.findPersonProjectedBy();
+
+ assertThat(collection, hasSize(3));
+
+ for (PersonProjection personProjection : collection) {
+ assertThat(personProjection.getLastname(), is(equalTo("White")));
+ }
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldFindByNumberOfChildren() throws Exception {
+
+ assumeThat(SpringVersion.getVersion(), startsWith("4.3"));
+
+ template.execute("CREATE INDEX IF NOT EXISTS person_number_of_children ON person (numberofchildren);");
+ // Give Cassandra some time to build the index
+ Thread.sleep(500);
+
+ Person result = personRepository.findByNumberOfChildren(NumberOfChildren.TWO);
+
+ assertThat(result, is(walter));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldFindByLocalDate() throws InterruptedException {
+
+ template.execute("CREATE INDEX IF NOT EXISTS person_created_date ON person (createddate);");
+ // Give Cassandra some time to build the index
+ Thread.sleep(500);
+
+ walter.setCreatedDate(LocalDate.now());
+ personRepository.save(walter);
+
+ Person result = personRepository.findByCreatedDate(walter.getCreatedDate());
+
+ assertThat(result, is(walter));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldUseQueryOverride() {
+
+ Person otherWalter = new Person("Walter", "Black");
+ personRepository.save(otherWalter);
+
+ List result = personRepository.findByFirstname("Walter");
+
+ assertThat(result, hasSize(1));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldUseStartsWithQuery() throws InterruptedException {
+
+ Version version = CassandraVersion.get(template.getSession());
+ assumeTrue(version.isGreaterThanOrEqualTo(Version.parse("3.4")));
+
+ template.execute(
+ "CREATE CUSTOM INDEX IF NOT EXISTS fn_starts_with ON person (nickname) USING 'org.apache.cassandra.index.sasi.SASIIndex';");
+ // Give Cassandra some time to build the index
+ Thread.sleep(500);
+
+ walter.setNickname("Heisenberg");
+ personRepository.save(walter);
+
+ assertThat(personRepository.findByNicknameStartsWith("Heis"), is(walter));
+ }
+
+ /**
+ * @see DATACASS-7
+ */
+ @Test
+ public void shouldUseContainsQuery() throws InterruptedException {
+
+ Version version = CassandraVersion.get(template.getSession());
+ assumeTrue(version.isGreaterThanOrEqualTo(Version.parse("3.4")));
+
+ template.execute(
+ "CREATE CUSTOM INDEX IF NOT EXISTS fn_contains ON person (nickname) USING 'org.apache.cassandra.index.sasi.SASIIndex'\n"
+ + "WITH OPTIONS = { 'mode': 'CONTAINS' };");
+ // Give Cassandra some time to build the index
+ Thread.sleep(500);
+
+ walter.setNickname("Heisenberg");
+ personRepository.save(walter);
+
+ assertThat(personRepository.findByNicknameContains("eisenber"), is(walter));
+ }
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/AbstractSpringDataEmbeddedCassandraIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/AbstractSpringDataEmbeddedCassandraIntegrationTest.java
index 0503034eb..927f1ae2b 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/AbstractSpringDataEmbeddedCassandraIntegrationTest.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/AbstractSpringDataEmbeddedCassandraIntegrationTest.java
@@ -39,6 +39,11 @@ public abstract class AbstractSpringDataEmbeddedCassandraIntegrationTest
*/
public void deleteAllEntities() {
for (CassandraPersistentEntity> entity : template.getConverter().getMappingContext().getPersistentEntities()) {
+
+ if(entity.getType().isInterface()){
+ continue;
+ }
+
template.truncate(entity.getTableName());
}
}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/CassandraVersion.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/CassandraVersion.java
new file mode 100644
index 000000000..1b9d73547
--- /dev/null
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/test/integration/support/CassandraVersion.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 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.test.integration.support;
+
+import lombok.experimental.UtilityClass;
+
+import org.springframework.data.util.Version;
+import org.springframework.util.Assert;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Row;
+import com.datastax.driver.core.Session;
+
+/**
+ * Utility to retrieve the Cassandra release version.
+ *
+ * @author Mark Paluch
+ */
+@UtilityClass
+public class CassandraVersion {
+
+ /**
+ * Retrieve the Cassandra release version.
+ *
+ * @param session must not be {@literal null}.
+ * @return the release {@link Version}.
+ */
+ public static Version get(Session session) {
+
+ Assert.notNull(session, "Session must not be null");
+
+ ResultSet resultSet = session.execute("SELECT release_version FROM system.local;");
+ Row row = resultSet.one();
+ return Version.parse(row.getString(0));
+ }
+}
diff --git a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/repository/simple/UserRepositoryXmlConfigIntegrationTests-context.xml b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/repository/simple/UserRepositoryXmlConfigIntegrationTests-context.xml
index ec5fbec6b..3a78b36da 100644
--- a/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/repository/simple/UserRepositoryXmlConfigIntegrationTests-context.xml
+++ b/spring-data-cassandra/src/test/resources/org/springframework/data/cassandra/test/integration/repository/simple/UserRepositoryXmlConfigIntegrationTests-context.xml
@@ -1,34 +1,15 @@
-
+ entity-base-packages="org.springframework.data.cassandra.test.integration.repository.simple">