diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java index 5bac36bcb..e649c7bcf 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/BasicCassandraPersistentEntity.java @@ -34,6 +34,7 @@ import org.springframework.util.StringUtils; * table name. * * @author Alex Shvid + * @author Matthew T. Adams */ public class BasicCassandraPersistentEntity extends BasicPersistentEntity implements CassandraPersistentEntity, ApplicationContextAware { @@ -44,7 +45,7 @@ public class BasicCassandraPersistentEntity extends BasicPersistentEntity extends BasicPersistentEntity rawType = typeInformation.getType(); - String fallback = CassandraNamingUtils.getPreferredTableName(rawType); + Table anno = rawType.getAnnotation(Table.class); - if (rawType.isAnnotationPresent(Table.class)) { - Table d = rawType.getAnnotation(Table.class); - this.table = StringUtils.hasText(d.name()) ? d.name() : fallback; - } else { - this.table = fallback; - } + this.table = anno != null && StringUtils.hasText(anno.name()) ? anno.name() : CassandraNamingUtils + .getPreferredTableName(rawType); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 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 14fbb2ebf..1617353fb 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 @@ -59,7 +59,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP return true; } - return getField().isAnnotationPresent(PrimaryKey.class); + return isAnnotationPresent(PrimaryKey.class); } @Override @@ -70,85 +70,105 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP public String getColumnName() { // first check @Column annotation - Column annotation = getField().getAnnotation(Column.class); - if (annotation != null && StringUtils.hasText(annotation.value())) { - return annotation.value(); + Column column = findAnnotation(Column.class); + if (column != null && StringUtils.hasText(column.value())) { + return column.value(); } // else check @KeyColumn annotation - PrimaryKeyColumn anno = getField().getAnnotation(PrimaryKeyColumn.class); - if (anno == null || !StringUtils.hasText(anno.value())) { - return field.getName(); + PrimaryKeyColumn pk = findAnnotation(PrimaryKeyColumn.class); + if (pk != null && StringUtils.hasText(pk.value())) { + return pk.value(); } - return anno.value(); + + // else default + return field.getName().toLowerCase(); } public Ordering getOrdering() { - PrimaryKeyColumn anno = getField().getAnnotation(PrimaryKeyColumn.class); + PrimaryKeyColumn anno = findAnnotation(PrimaryKeyColumn.class); return anno == null ? null : anno.ordering(); } public DataType getDataType() { - CassandraType annotation = getField().getAnnotation(CassandraType.class); + + CassandraType annotation = findAnnotation(CassandraType.class); if (annotation != null) { - return qualifyAnnotatedType(annotation); + return getDataTypeFor(annotation); } + if (isMap()) { + List> args = getTypeInformation().getTypeArguments(); ensureTypeArguments(args.size(), 2); - return DataType.map(autodetectPrimitiveType(args.get(0).getType()), - autodetectPrimitiveType(args.get(1).getType())); + + return DataType.map(getDataTypeFor(args.get(0).getType()), getDataTypeFor(args.get(1).getType())); } + if (isCollectionLike()) { + List> args = getTypeInformation().getTypeArguments(); ensureTypeArguments(args.size(), 1); + if (Set.class.isAssignableFrom(getType())) { - return DataType.set(autodetectPrimitiveType(args.get(0).getType())); - } else if (List.class.isAssignableFrom(getType())) { - return DataType.list(autodetectPrimitiveType(args.get(0).getType())); + return DataType.set(getDataTypeFor(args.get(0).getType())); + } + if (List.class.isAssignableFrom(getType())) { + return DataType.list(getDataTypeFor(args.get(0).getType())); } } - DataType dataType = CassandraSimpleTypeHolder.getDataTypeFor(this.getType()); + + DataType dataType = CassandraSimpleTypeHolder.getDataTypeFor(getType()); if (dataType == null) { throw new InvalidDataAccessApiUsageException( - "only primitive types and Set,List,Map collections are allowed, unknown type for property '" + this.getName() - + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); + String + .format( + "unknown type for property [%s], type [%s] in entity [%s]; only primitive types and collections or maps of primitive types are allowed", + getName(), getType(), getOwner().getName())); } return dataType; } - private DataType qualifyAnnotatedType(CassandraType annotation) { + private DataType getDataTypeFor(CassandraType annotation) { + DataType.Name type = annotation.type(); + if (type.isCollection()) { switch (type) { + case MAP: ensureTypeArguments(annotation.typeArguments().length, 2); - return DataType.map(resolvePrimitiveType(annotation.typeArguments()[0]), - resolvePrimitiveType(annotation.typeArguments()[1])); + return DataType.map(getDataTypeFor(annotation.typeArguments()[0]), + getDataTypeFor(annotation.typeArguments()[1])); + case LIST: ensureTypeArguments(annotation.typeArguments().length, 1); - return DataType.list(resolvePrimitiveType(annotation.typeArguments()[0])); + return DataType.list(getDataTypeFor(annotation.typeArguments()[0])); + case SET: ensureTypeArguments(annotation.typeArguments().length, 1); - return DataType.set(resolvePrimitiveType(annotation.typeArguments()[0])); + return DataType.set(getDataTypeFor(annotation.typeArguments()[0])); + default: - throw new InvalidDataAccessApiUsageException("unknown collection DataType for property '" + this.getName() - + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); + throw new InvalidDataAccessApiUsageException( + String.format("unknown multivalued DataType [%s] for property [%s] in entity [%s]", type, getType(), + getOwner().getName())); } } else { + return CassandraSimpleTypeHolder.getDataTypeFor(type); } } public boolean isIndexed() { - return getField().isAnnotationPresent(Indexed.class); + return isAnnotationPresent(Indexed.class); } public boolean isPartitionKeyColumn() { - PrimaryKeyColumn anno = getField().getAnnotation(PrimaryKeyColumn.class); + PrimaryKeyColumn anno = findAnnotation(PrimaryKeyColumn.class); return anno != null && anno.type() == PrimaryKeyType.PARTITIONED; } @@ -156,14 +176,14 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP @Override public boolean isClusterKeyColumn() { - PrimaryKeyColumn anno = getField().getAnnotation(PrimaryKeyColumn.class); + PrimaryKeyColumn anno = findAnnotation(PrimaryKeyColumn.class); return anno != null && anno.type() == PrimaryKeyType.CLUSTERED; } @Override public boolean isPrimaryKeyColumn() { - return getField().isAnnotationPresent(PrimaryKeyColumn.class); + return isAnnotationPresent(PrimaryKeyColumn.class); } @Override @@ -171,7 +191,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP return new Association(this, null); } - DataType resolvePrimitiveType(DataType.Name typeName) { + protected DataType getDataTypeFor(DataType.Name typeName) { DataType dataType = CassandraSimpleTypeHolder.getDataTypeFor(typeName); if (dataType == null) { throw new InvalidDataAccessApiUsageException( @@ -181,7 +201,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP return dataType; } - DataType autodetectPrimitiveType(Class javaType) { + protected DataType getDataTypeFor(Class javaType) { DataType dataType = CassandraSimpleTypeHolder.getDataTypeFor(javaType); if (dataType == null) { throw new InvalidDataAccessApiUsageException( @@ -191,7 +211,7 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP return dataType; } - void ensureTypeArguments(int args, int expected) { + protected void ensureTypeArguments(int args, int expected) { if (args != expected) { throw new InvalidDataAccessApiUsageException("expected " + expected + " of typed arguments for the property '" + this.getName() + "' type is '" + this.getType() + "' in the entity " + this.getOwner().getName()); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java index d2a054213..31029b5b4 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraMappingContext.java @@ -45,20 +45,12 @@ public class CassandraMappingContext extends setSimpleTypeHolder(new CassandraSimpleTypeHolder()); } - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.mapping.MutablePersistentEntity, org.springframework.data.mapping.SimpleTypeHolder) - */ @Override public CassandraPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, BasicCassandraPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { return new CachingCassandraPersistentProperty(field, descriptor, owner, simpleTypeHolder); } - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.BasicMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation, org.springframework.data.mapping.model.MappingContext) - */ @Override protected BasicCassandraPersistentEntity createPersistentEntity(TypeInformation typeInformation) { @@ -71,10 +63,6 @@ public class CassandraMappingContext extends return entity; } - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) - */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraType.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraType.java index 1b94c3e70..e1505e6a7 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraType.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CassandraType.java @@ -31,8 +31,19 @@ import com.datastax.driver.core.DataType; @Retention(RetentionPolicy.RUNTIME) public @interface CassandraType { + /** + * The {@link DataType}.{@link Name} of the property. + */ DataType.Name type(); + /** + * If the property is collection-like, then this attribute holds a single {@link DataType}.{@link Name}, representing + * the element type of the collection. + *

+ * If the property is map, then this attribute holds exactly two {@link DataType}.{@link Name}s: the first is the key + * type, and the second is the value type. + *

+ * If the property is neither collection-like or a map, then this attribute is ignored. + */ DataType.Name[] typeArguments() default {}; - } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Column.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Column.java index 64ae3a1cd..5fc1c6051 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Column.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Column.java @@ -31,8 +31,10 @@ package org.springframework.data.cassandra.mapping; import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * Annotation to define custom metadata for document fields. @@ -42,6 +44,7 @@ import java.lang.annotation.RetentionPolicy; */ @Documented @Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) public @interface Column { /** diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CompositePrimaryKey.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CompositePrimaryKey.java index 300b426a8..8288da4ea 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CompositePrimaryKey.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/CompositePrimaryKey.java @@ -22,21 +22,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Defines composite primary key class in the Cassandra table that contains several fields. Example: - * - * @CompositePrimaryKey class AccountPK { String account; String region; } - * - * @Table class Account { - * @Id AccountPK pk; } - * + * Defines composite primary key class in the Cassandra table that contains several fields. * * @author Alex Shvid - * @deprecated This class is actually unnecessary and can be inferred by the existence of field(s) with - * {@link PrimaryKeyColumn} annotations on them. + * @author Matthew T. Adams */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) -@Deprecated public @interface CompositePrimaryKey { } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Indexed.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Indexed.java index 5a1f666d8..f98e348ba 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Indexed.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/Indexed.java @@ -21,15 +21,14 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Identifies a secondary index in the table. Usually it is a field with common dublicate values for the hole table. - * such as city, place, educationType, state flags ant etc. + * Identifies a secondary index in the table. Usually it is a field with common duplicate values within the table. * - * Using unique fields is not common and has overhead, such as email, username and etc. + * Using unique fields is not recommended. * * @author Alex Shvid + * @author Matthew T. Adams */ @Retention(value = RetentionPolicy.RUNTIME) @Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) public @interface Indexed { - } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKey.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKey.java index 3cd4415c3..a738af9f9 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKey.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKey.java @@ -20,6 +20,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.data.annotation.Id; + /** * Identifies the primary key field of the entity, which may be of a basic type or of a type that represents a composite * primary key class. This field corresponds to the PRIMARY KEY of the corresponding Cassandra table. @@ -29,6 +31,6 @@ import java.lang.annotation.Target; */ @Retention(value = RetentionPolicy.RUNTIME) @Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) -@org.springframework.data.annotation.Id +@Id public @interface PrimaryKey { } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKeyColumn.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKeyColumn.java index b882d35ae..ce6861a53 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKeyColumn.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/mapping/PrimaryKeyColumn.java @@ -28,7 +28,7 @@ import org.springframework.cassandra.core.PrimaryKeyType; * cluster key field. */ @Retention(value = RetentionPolicy.RUNTIME) -@Target(value = { ElementType.FIELD, ElementType.METHOD }) +@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) public @interface PrimaryKeyColumn { /**