From 120550614768a036655ae211f3326e1e014a4f4a Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 2 Feb 2018 12:53:57 -0800 Subject: [PATCH] DATACASS-284 - Polish. --- .../core/mapping/CassandraMappingContext.java | 312 +++++++++--------- .../cassandra/core/mapping/CassandraType.java | 29 +- .../cassandra/core/mapping/EntityMapping.java | 20 +- .../core/mapping/PropertyMapping.java | 13 +- .../core/mapping/UserTypeResolver.java | 13 +- ...CassandraTypeMappingIntegrationTests.java} | 23 +- .../CassandraMappingContextUnitTests.java | 34 +- .../cassandra/domain/AllPossibleTypes.java | 2 +- ...RepositoryReturnTypesIntegrationTests.java | 6 +- src/main/asciidoc/reference/cassandra.adoc | 7 +- 10 files changed, 257 insertions(+), 202 deletions(-) rename spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/{CassandraTypeMappingIntegrationTest.java => CassandraTypeMappingIntegrationTests.java} (98%) diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContext.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContext.java index 8c5315ce2..b8f031985 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContext.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContext.java @@ -15,11 +15,20 @@ */ package org.springframework.data.cassandra.core.mapping; -import static org.springframework.data.cassandra.core.cql.CqlIdentifier.*; -import static org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification.*; -import static org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder.*; +import static org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification.createTable; +import static org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder.getDataTypeFor; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.stream.StreamSupport; import org.springframework.beans.BeansException; @@ -42,6 +51,7 @@ import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.Optionals; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -76,7 +86,7 @@ public class CassandraMappingContext private @Nullable UserTypeResolver userTypeResolver; - private @Nullable ApplicationContext context; + private @Nullable ApplicationContext applicationContext; private @Nullable ClassLoader beanClassLoader; @@ -90,8 +100,9 @@ public class CassandraMappingContext */ public CassandraMappingContext() { - setCustomConversions( - new CustomConversions(StoreConversions.of(CassandraSimpleTypeHolder.HOLDER), Collections.emptyList())); + setCustomConversions(new CustomConversions( + StoreConversions.of(CassandraSimpleTypeHolder.HOLDER), Collections.emptyList())); + setSimpleTypeHolder(CassandraSimpleTypeHolder.HOLDER); } @@ -104,34 +115,38 @@ public class CassandraMappingContext processMappingOverrides(); } + @SuppressWarnings("all") private void processMappingOverrides() { this.mapping.getEntityMappings().stream().filter(Objects::nonNull).forEach(entityMapping -> { Class entityClass = getEntityClass(entityMapping.getEntityClassName()); + CassandraPersistentEntity entity = getRequiredPersistentEntity(entityClass); String entityTableName = entityMapping.getTableName(); if (StringUtils.hasText(entityTableName)) { - entity.setTableName(of(entityTableName, Boolean.valueOf(entityMapping.getForceQuote()))); + entity.setTableName(CqlIdentifier.of(entityTableName, Boolean.valueOf(entityMapping.getForceQuote()))); } processMappingOverrides(entity, entityMapping); + }); } private Class getEntityClass(String entityClassName) { try { - return ClassUtils.forName(entityClassName, beanClassLoader); - } catch (ClassNotFoundException e) { - throw new IllegalStateException(String.format("Unknown persistent entity name [%s]", entityClassName), e); + return ClassUtils.forName(entityClassName, this.beanClassLoader); + } + catch (ClassNotFoundException cause) { + throw new IllegalStateException( + String.format("Unknown persistent entity name [%s]", entityClassName), cause); } } private static void processMappingOverrides(CassandraPersistentEntity entity, EntityMapping entityMapping) { - entityMapping.getPropertyMappings() .forEach((key, propertyMapping) -> processMappingOverride(entity, propertyMapping)); } @@ -145,7 +160,7 @@ public class CassandraMappingContext property.setForceQuote(forceQuote); if (StringUtils.hasText(mapping.getColumnName())) { - property.setColumnName(of(mapping.getColumnName(), forceQuote)); + property.setColumnName(CqlIdentifier.of(mapping.getColumnName(), forceQuote)); } } @@ -154,7 +169,7 @@ public class CassandraMappingContext */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.context = applicationContext; + this.applicationContext = applicationContext; } /* (non-Javadoc) @@ -189,6 +204,24 @@ public class CassandraMappingContext this.mapping = mapping; } + /** + * Returns only {@link Table} entities. + * + * @since 1.5 + */ + public Collection> getTableEntities() { + return Collections.unmodifiableCollection(this.tableEntities); + } + + /** + * Returns only those entities representing a user defined type. + * + * @since 1.5 + */ + public Collection> getUserDefinedTypeEntities() { + return Collections.unmodifiableSet(this.userDefinedTypes); + } + /** * Sets the {@link UserTypeResolver}. * @@ -213,25 +246,7 @@ public class CassandraMappingContext * @return Returns the verifier. */ public CassandraPersistentEntityMetadataVerifier getVerifier() { - return verifier; - } - - /** - * Returns only {@link Table} entities. - * - * @since 1.5 - */ - public Collection> getTableEntities() { - return Collections.unmodifiableCollection(this.tableEntities); - } - - /** - * Returns only those entities representing a user defined type. - * - * @since 1.5 - */ - public Collection> getUserDefinedTypeEntities() { - return Collections.unmodifiableSet(this.userDefinedTypes); + return this.verifier; } /* (non-Javadoc) @@ -252,14 +267,15 @@ public class CassandraMappingContext } // now do some caching of the entity - Set> entities = this.entitySetsByTableName.computeIfAbsent(entity.getTableName(), - cqlIdentifier -> new HashSet<>()); + Set> entities = this.entitySetsByTableName + .computeIfAbsent(entity.getTableName(), cqlIdentifier -> new HashSet<>()); entities.add(entity); if (!entity.isUserDefinedType() && entity.isAnnotationPresent(Table.class)) { this.tableEntities.add(entity); } + }); return optional; @@ -270,8 +286,8 @@ public class CassandraMappingContext */ @Override protected boolean shouldCreatePersistentEntityFor(TypeInformation typeInfo) { - return (!this.customConversions.hasCustomWriteTarget(typeInfo.getType()) - && super.shouldCreatePersistentEntityFor(typeInfo)); + return !this.customConversions.hasCustomWriteTarget(typeInfo.getType()) + && super.shouldCreatePersistentEntityFor(typeInfo); } /* (non-Javadoc) @@ -280,27 +296,32 @@ public class CassandraMappingContext @Override protected BasicCassandraPersistentEntity createPersistentEntity(TypeInformation typeInformation) { - UserDefinedType userDefinedType = AnnotatedElementUtils.findMergedAnnotation(typeInformation.getType(), - UserDefinedType.class); + BasicCassandraPersistentEntity entity = Optional.ofNullable(resolveUserDefinedType(typeInformation)) + .>map(resolvedUserDefinedType -> + new CassandraUserTypePersistentEntity<>(typeInformation, getVerifier(), resolveUserTypeResolver())) + .orElseGet(() -> + new BasicCassandraPersistentEntity<>(typeInformation, getVerifier())); - BasicCassandraPersistentEntity entity; - - if (userDefinedType != null) { - Assert.state(this.userTypeResolver != null, "UserTypeResolver must not be null"); - - entity = new CassandraUserTypePersistentEntity<>(typeInformation, this.verifier, this.userTypeResolver); - - } else { - entity = new BasicCassandraPersistentEntity<>(typeInformation, this.verifier); - } - - if (this.context != null) { - entity.setApplicationContext(this.context); - } + Optional.ofNullable(this.applicationContext).ifPresent(entity::setApplicationContext); return entity; } + @Nullable + private UserDefinedType resolveUserDefinedType(TypeInformation typeInformation) { + return AnnotatedElementUtils.findMergedAnnotation(typeInformation.getType(), UserDefinedType.class); + } + + @NonNull + private UserTypeResolver resolveUserTypeResolver() { + + UserTypeResolver resolvedUserTypeResolver = this.userTypeResolver; + + Assert.state(resolvedUserTypeResolver != null, "UserTypeResolver must not be null"); + + return resolvedUserTypeResolver; + } + /* (non-Javadoc) * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(org.springframework.data.mapping.model.Property, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder) */ @@ -308,14 +329,12 @@ public class CassandraMappingContext protected CassandraPersistentProperty createPersistentProperty(Property property, BasicCassandraPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { - BasicCassandraPersistentProperty cassandraProperty = new BasicCassandraPersistentProperty(property, owner, - simpleTypeHolder, this.userTypeResolver); + BasicCassandraPersistentProperty persistentProperty = + new BasicCassandraPersistentProperty(property, owner, simpleTypeHolder, this.userTypeResolver); - if (this.context != null) { - cassandraProperty.setApplicationContext(this.context); - } + Optional.ofNullable(this.applicationContext).ifPresent(persistentProperty::setApplicationContext); - return cassandraProperty; + return persistentProperty; } /** @@ -326,7 +345,7 @@ public class CassandraMappingContext */ public boolean usesTable(CqlIdentifier name) { - Assert.notNull(name, "Table name must not be null!"); + Assert.notNull(name, "Table name must not be null"); return this.entitySetsByTableName.containsKey(name); } @@ -340,23 +359,26 @@ public class CassandraMappingContext */ public boolean usesUserType(CqlIdentifier name) { - Assert.notNull(name, "User type name must not be null!"); + Assert.notNull(name, "User type name must not be null"); return hasMappedUserType(name) || hasReferencedUserType(name); } - private boolean hasReferencedUserType(CqlIdentifier identifier) { - - return getPersistentEntities().stream().flatMap(entity -> StreamSupport.stream(entity.spliterator(), false)) - .flatMap(it -> Optionals.toStream(Optional.ofNullable(it.findAnnotation(CassandraType.class)))) - .map(CassandraType::userTypeName).filter(StringUtils::hasText).map(CqlIdentifier::of) - .anyMatch(identifier::equals); - } - private boolean hasMappedUserType(CqlIdentifier identifier) { return this.userDefinedTypes.stream().map(CassandraPersistentEntity::getTableName).anyMatch(identifier::equals); } + private boolean hasReferencedUserType(CqlIdentifier identifier) { + + return getPersistentEntities().stream() + .flatMap(entity -> StreamSupport.stream(entity.spliterator(), false)) + .flatMap(it -> Optionals.toStream(Optional.ofNullable(it.findAnnotation(CassandraType.class)))) + .map(CassandraType::userTypeName) + .filter(StringUtils::hasText) + .map(CqlIdentifier::of) + .anyMatch(identifier::equals); + } + /** * Returns a {@link CreateTableSpecification} for the given entity, including all mapping information. * @@ -370,36 +392,31 @@ public class CassandraMappingContext for (CassandraPersistentProperty property : entity) { - if (!property.isCompositePrimaryKey()) { - continue; - } + if (property.isCompositePrimaryKey()) { - CassandraPersistentEntity primaryKeyEntity = getRequiredPersistentEntity(property.getRawType()); + CassandraPersistentEntity primaryKeyEntity = getRequiredPersistentEntity(property.getRawType()); - for (CassandraPersistentProperty primaryKeyProperty : primaryKeyEntity) { - if (primaryKeyProperty.isPartitionKeyColumn()) { - specification.partitionKeyColumn(primaryKeyProperty.getColumnName(), getDataType(primaryKeyProperty)); - } else { // it's a cluster column - specification.clusteredKeyColumn(primaryKeyProperty.getColumnName(), getDataType(primaryKeyProperty), - primaryKeyProperty.getPrimaryKeyOrdering()); + for (CassandraPersistentProperty primaryKeyProperty : primaryKeyEntity) { + if (primaryKeyProperty.isPartitionKeyColumn()) { + specification.partitionKeyColumn(primaryKeyProperty.getColumnName(), + getDataType(primaryKeyProperty)); + } else { // cluster column + specification.clusteredKeyColumn(primaryKeyProperty.getColumnName(), + getDataType(primaryKeyProperty), primaryKeyProperty.getPrimaryKeyOrdering()); + } } } - } - - for (CassandraPersistentProperty property : entity) { - - if (property.isCompositePrimaryKey()) { - continue; - } - - if (property.isIdProperty() || property.isPartitionKeyColumn()) { - specification.partitionKeyColumn(property.getColumnName(), - UserTypeUtil.potentiallyFreeze(getDataType(property))); - } else if (property.isClusterKeyColumn()) { - specification.clusteredKeyColumn(property.getColumnName(), - UserTypeUtil.potentiallyFreeze(getDataType(property)), property.getPrimaryKeyOrdering()); - } else { - specification.column(property.getColumnName(), UserTypeUtil.potentiallyFreeze(getDataType(property))); + else { + if (property.isIdProperty() || property.isPartitionKeyColumn()) { + specification.partitionKeyColumn(property.getColumnName(), + UserTypeUtil.potentiallyFreeze(getDataType(property))); + } else if (property.isClusterKeyColumn()) { + specification.clusteredKeyColumn(property.getColumnName(), + UserTypeUtil.potentiallyFreeze(getDataType(property)), property.getPrimaryKeyOrdering()); + } else { + specification.column(property.getColumnName(), + UserTypeUtil.potentiallyFreeze(getDataType(property))); + } } } @@ -419,17 +436,11 @@ public class CassandraMappingContext Assert.notNull(entity, "CassandraPersistentEntity must not be null"); - return getCreateIndexSpecifications(entity.getTableName(), entity); - } - - private List getCreateIndexSpecifications(CqlIdentifier tableName, - CassandraPersistentEntity entity) { - List indexes = new ArrayList<>(); for (CassandraPersistentProperty property : entity) { if (property.isCompositePrimaryKey()) { - indexes.addAll(getCreateIndexSpecifications(tableName, getRequiredPersistentEntity(property))); + indexes.addAll(getCreateIndexSpecificationsFor(getRequiredPersistentEntity(property))); } else { indexes.addAll(IndexSpecificationFactory.createIndexSpecifications(property)); } @@ -466,6 +477,31 @@ public class CassandraMappingContext return specification; } + /** + * Retrieve the data type based on the given {@code type}. Cassandra {@link DataType types} are determined using + * simple types and configured {@link org.springframework.data.convert.CustomConversions}. + * + * @param type must not be {@literal null}. + * @return the Cassandra {@link DataType type}. + * @see org.springframework.data.convert.CustomConversions + * @see CassandraSimpleTypeHolder + * @since 1.5 + */ + public DataType getDataType(Class type) { + + return this.customConversions.getCustomWriteTarget(type) + .map(CassandraSimpleTypeHolder::getDataTypeFor) + .orElseGet(() -> getDataTypeFor(type)); + } + + @Nullable + private DataType getDataType(@NonNull Class type, DataTypeProvider dataTypeProvider) { + + BasicCassandraPersistentEntity entity = getPersistentEntity(type); + + return entity != null && entity.isUserDefinedType() ? dataTypeProvider.getDataType(entity) : getDataType(type); + } + /** * Retrieve the data type of the property. Cassandra {@link DataType types} are determined using simple types and * configured {@link org.springframework.data.convert.CustomConversions}. @@ -487,10 +523,20 @@ public class CassandraMappingContext CassandraType annotation = property.getRequiredAnnotation(CassandraType.class); + if (annotation.type() == Name.TUPLE) { + + DataType[] dataTypes = Arrays.stream(annotation.typeArguments()) + .map(CassandraSimpleTypeHolder::getDataTypeFor) + .toArray(DataType[]::new); + + return TupleType.of(ProtocolVersion.NEWEST_SUPPORTED, CodecRegistry.DEFAULT_INSTANCE, dataTypes); + } + if (annotation.type() == Name.UDT) { CqlIdentifier userTypeName = CqlIdentifier.of(annotation.userTypeName()); - DataType userType = dataTypeProvider.getUserType(userTypeName, userTypeResolver); + + DataType userType = dataTypeProvider.getUserType(userTypeName, resolveUserTypeResolver()); if (userType == null) { throw new MappingException(String.format("User type [%s] not found", userTypeName)); @@ -503,14 +549,6 @@ public class CassandraMappingContext } } - if (annotation.type() == Name.TUPLE) { - - DataType[] dataTypes = Arrays.stream(annotation.typeArguments()) // - .map(CassandraSimpleTypeHolder::getDataTypeFor) // - .toArray(DataType[]::new); - return TupleType.of(ProtocolVersion.NEWEST_SUPPORTED, CodecRegistry.DEFAULT_INSTANCE, dataTypes); - } - return property.getDataType(); } @@ -526,11 +564,13 @@ public class CassandraMappingContext } return this.customConversions.getCustomWriteTarget(property.getType()) - .map(CassandraSimpleTypeHolder::getDataTypeFor).orElseGet(() -> this.customConversions - .getCustomWriteTarget(property.getActualType()).filter(it -> !property.isMapLike()).map(it -> { + .map(CassandraSimpleTypeHolder::getDataTypeFor) + .orElseGet(() -> this.customConversions + .getCustomWriteTarget(property.getActualType()) + .filter(it -> !property.isMapLike()) + .map(it -> { if (property.isCollectionLike()) { - if (List.class.isAssignableFrom(property.getType())) { return DataType.list(getDataTypeFor(it)); } @@ -541,6 +581,7 @@ public class CassandraMappingContext } return getDataTypeFor(it); + }).orElseGet(() -> { if (property.isMapLike()) { @@ -548,19 +589,13 @@ public class CassandraMappingContext Class keyType = property.getComponentType(); Class valueType = property.getMapValueType(); - return DataType.map(getDataType(keyType, dataTypeProvider), getDataType(valueType, dataTypeProvider)); + return DataType.map(getDataType(keyType, dataTypeProvider), + getDataType(valueType, dataTypeProvider)); } return property.getDataType(); + })); - - } - - private DataType getDataType(Class type, DataTypeProvider dataTypeProvider) { - - BasicCassandraPersistentEntity entity = getPersistentEntity(type); - - return entity != null && entity.isUserDefinedType() ? dataTypeProvider.getDataType(entity) : getDataType(type); } @Nullable @@ -568,36 +603,16 @@ public class CassandraMappingContext if (property.isCollectionLike()) { - if (Set.class.isAssignableFrom(property.getType())) { - return DataType.set(elementType); - } - if (List.class.isAssignableFrom(property.getType())) { return DataType.list(elementType); } + + if (Set.class.isAssignableFrom(property.getType())) { + return DataType.set(elementType); + } } - if (!property.isCollectionLike() && !property.isMapLike()) { - return elementType; - } - - return null; - } - - /** - * Retrieve the data type based on the given {@code type}. Cassandra {@link DataType types} are determined using - * simple types and configured {@link org.springframework.data.convert.CustomConversions}. - * - * @param type must not be {@literal null}. - * @return the Cassandra {@link DataType type}. - * @see org.springframework.data.convert.CustomConversions - * @see CassandraSimpleTypeHolder - * @since 1.5 - */ - public DataType getDataType(Class type) { - - return this.customConversions.getCustomWriteTarget(type).map(CassandraSimpleTypeHolder::getDataTypeFor) - .orElseGet(() -> getDataTypeFor(type)); + return !(property.isCollectionLike() || property.isMapLike()) ? elementType : null; } /** @@ -652,5 +667,6 @@ public class CassandraMappingContext */ @Nullable abstract DataType getUserType(CqlIdentifier userTypeName, UserTypeResolver userTypeResolver); + } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraType.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraType.java index dfd1eb8ed..725dd3199 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraType.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraType.java @@ -22,39 +22,46 @@ import java.lang.annotation.RetentionPolicy; import com.datastax.driver.core.DataType; /** - * Specifies the Cassandra type of the annotated property or parameter if used in query methods. + * Specifies the Cassandra type of the annotated property or parameter when used in query methods. * * @author Alex Shvid * @author Matthew T. Adams * @author Mark Paluch + * @see com.datastax.driver.core.DataType */ @Documented @Retention(RetentionPolicy.RUNTIME) public @interface CassandraType { /** - * The {@link DataType}.{@link Name} of the property. + * The {@link DataType.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 {@link java.util.Collection Collection-like}, then this attribute holds + * a single {@link DataType.Name DataType Name} representing the element type of the {@link java.util.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 a {@link java.util.Map}, then this attribute holds exactly + * two {@link DataType.Name DataType Names}; 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. + * If the property is neither {@link java.util.Collection Collection-like} nor a {@link java.util.Map}, + * then this attribute is ignored. + * + * @return an array of {@link DataType.Name} objects. + * @see com.datastax.driver.core.DataType.Name */ DataType.Name[] typeArguments() default {}; /** - * If the property maps to a user-defined type then this attribute holds the user type name. For collection-like - * properties the user type name applies to the component type. The user type name is only required if the UDT does - * not map to a class annotated with {@link UserDefinedType}. + * If the property maps to a User-Defined Type (UDT) then this attribute holds the user type name. * - * @return name of the user type + * For {@link java.util.Collection Collection-like} properties the user type name applies to the component type. + * The user type name is only required if the UDT does not map to a class annotated with {@link UserDefinedType}. + * + * @return {@link String name} of the user type * @since 1.5 */ String userTypeName() default ""; + } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/EntityMapping.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/EntityMapping.java index f97503c4f..d02ef767e 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/EntityMapping.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/EntityMapping.java @@ -63,40 +63,43 @@ public class EntityMapping { @Nullable public String getEntityClassName() { - return entityClassName; + return this.entityClassName; } public void setEntityClassName(String entityClassName) { Assert.hasText(entityClassName, "Entity class name must not be null or empty"); + this.entityClassName = entityClassName; } public String getForceQuote() { - return forceQuote; + return this.forceQuote; } public void setForceQuote(String forceQuote) { Assert.notNull(forceQuote, "Force quote must not be null or empty"); + this.forceQuote = forceQuote; } public Map getPropertyMappings() { - return Collections.unmodifiableMap(propertyMappings); + return Collections.unmodifiableMap(this.propertyMappings); } public void setPropertyMappings(@Nullable Map propertyMappings) { - this.propertyMappings = (propertyMappings != null ? new HashMap<>(propertyMappings) : Collections.emptyMap()); + this.propertyMappings = propertyMappings != null ? new HashMap<>(propertyMappings) : Collections.emptyMap(); } public String getTableName() { - return tableName; + return this.tableName; } public void setTableName(String tableName) { Assert.notNull(tableName, "Table name must not be null or empty"); + this.tableName = tableName; } @@ -105,6 +108,7 @@ public class EntityMapping { */ @Override public boolean equals(Object obj) { + if (this == obj) { return true; } @@ -127,9 +131,11 @@ public class EntityMapping { public int hashCode() { int hashValue = 17; + hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(this.getEntityClassName()); hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(this.getForceQuote()); hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(this.getTableName()); + return hashValue; } @@ -139,12 +145,14 @@ public class EntityMapping { @Override public String toString() { return String.format( - "{ @type = %1$s, entityClassName = %2$s, tableName = %3$s, forceQuote = %4$s, propertyMappings = %5$s }", + "{ @type = %1$s, entityClassName = %2$s, tableName = %3$s, forceQuote = %4$s, propertyMappings = %5$s }", getClass().getName(), getEntityClassName(), getTableName(), getForceQuote(), toString(getPropertyMappings())); } private String toString(Map map) { + StringBuilder builder = new StringBuilder("["); + int count = 0; for (Map.Entry entry : map.entrySet()) { diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/PropertyMapping.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/PropertyMapping.java index dc2dab071..87fc56bed 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/PropertyMapping.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/PropertyMapping.java @@ -17,6 +17,7 @@ package org.springframework.data.cassandra.core.mapping; import lombok.EqualsAndHashCode; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -46,9 +47,10 @@ public class PropertyMapping { this(propertyName, columnName, "false"); } - public PropertyMapping(String propertyName, String columnName, String forceQuote) { + public PropertyMapping(@NonNull String propertyName, @NonNull String columnName, String forceQuote) { Assert.notNull(propertyName, "Property name must not be null"); + this.propertyName = propertyName; setColumnName(columnName); @@ -57,18 +59,19 @@ public class PropertyMapping { @Nullable public String getColumnName() { - return columnName; + return this.columnName; } - public void setColumnName(String columnName) { + public void setColumnName(@NonNull String columnName) { Assert.notNull(columnName, "Column name must not be null"); + this.columnName = columnName; } @Nullable public String getForceQuote() { - return forceQuote; + return this.forceQuote; } public void setForceQuote(String forceQuote) { @@ -76,7 +79,7 @@ public class PropertyMapping { } public String getPropertyName() { - return propertyName; + return this.propertyName; } /* (non-Javadoc) diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/UserTypeResolver.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/UserTypeResolver.java index ce6ac1aae..e32343ebc 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/UserTypeResolver.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/UserTypeResolver.java @@ -21,20 +21,25 @@ import org.springframework.lang.Nullable; import com.datastax.driver.core.UserType; /** - * Strategy interface to resolve {@link UserType} by its name. + * Strategy interface to resolve {@link UserType} by {@link String name}. * * @author Mark Paluch + * @see com.datastax.driver.core.DataType + * @see org.springframework.data.cassandra.core.cql.CqlIdentifier * @since 1.5 */ @FunctionalInterface public interface UserTypeResolver { /** - * Resolve a {@link UserType} by its name. + * Resolve a {@link UserType} by {@link String name}. * - * @param typeName must not be {@literal null}. - * @return the type or {@literal null}, if not found. + * @param typeName {@link String name} of the {@link UserType} to resolve; must not be {@literal null}. + * @return the resolved {@link UserType} or {@literal null} if not found. + * @see org.springframework.data.cassandra.core.cql.CqlIdentifier + * @see com.datastax.driver.core.DataType */ @Nullable UserType resolveType(CqlIdentifier typeName); + } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/CassandraTypeMappingIntegrationTest.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/CassandraTypeMappingIntegrationTests.java similarity index 98% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/CassandraTypeMappingIntegrationTest.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/CassandraTypeMappingIntegrationTests.java index a9c3833f5..3b55f2d75 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/CassandraTypeMappingIntegrationTest.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/CassandraTypeMappingIntegrationTests.java @@ -15,12 +15,8 @@ */ package org.springframework.data.cassandra.core.convert; -import static org.assertj.core.api.Assertions.*; -import static org.junit.Assume.*; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assume.assumeTrue; import java.math.BigDecimal; import java.math.BigInteger; @@ -35,10 +31,14 @@ import java.util.HashSet; import java.util.List; import java.util.UUID; -import org.assertj.core.api.Assertions; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; + import org.springframework.data.annotation.Id; import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.cassandra.core.CassandraTemplate; @@ -48,6 +48,8 @@ import org.springframework.data.cassandra.support.CassandraVersion; import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTest; import org.springframework.data.util.Version; +import org.assertj.core.api.Assertions; + import com.datastax.driver.core.DataType; import com.datastax.driver.core.Duration; import com.datastax.driver.core.LocalDate; @@ -62,7 +64,7 @@ import com.datastax.driver.core.TupleValue; * @soundtrack DJ THT meets Scarlet - Live 2 Dance (Extended Mix) (Zgin Remix) */ @SuppressWarnings("Since15") -public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatingIntegrationTest { +public class CassandraTypeMappingIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest { static final Version VERSION_3_10 = Version.parse("3.10"); @@ -460,11 +462,13 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin public void shouldReadAndWriteTupleType() { TupleType tupleType = cluster.getMetadata().newTupleType(DataType.varchar(), DataType.bigint()); + AllPossibleTypes entity = new AllPossibleTypes("1"); entity.setTupleValue(tupleType.newValue("foo", 23L)); operations.insert(entity); + AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class); assertThat(loaded.getTupleValue().getObject(0)).isEqualTo("foo"); @@ -477,11 +481,12 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin TupleType tupleType = cluster.getMetadata().newTupleType(DataType.varchar(), DataType.bigint()); ListOfTuples entity = new ListOfTuples(); - entity.setId("foo"); + entity.setId("foo"); entity.setTuples(Arrays.asList(tupleType.newValue("foo", 23L), tupleType.newValue("bar", 42L))); operations.insert(entity); + ListOfTuples loaded = operations.selectOneById(entity.getId(), ListOfTuples.class); assertThat(loaded.getTuples().get(0).getObject(0)).isEqualTo("foo"); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContextUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContextUnitTests.java index db9aefd43..229fa569d 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContextUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraMappingContextUnitTests.java @@ -15,8 +15,10 @@ */ package org.springframework.data.cassandra.core.mapping; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.Serializable; import java.util.Collection; @@ -27,6 +29,7 @@ import java.util.NoSuchElementException; import org.junit.Before; import org.junit.Test; + import org.springframework.core.convert.converter.Converter; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.annotation.Id; @@ -64,12 +67,12 @@ public class CassandraMappingContextUnitTests { @Before public void before() { - mappingContext.setUserTypeResolver(typeName -> null); + this.mappingContext.setUserTypeResolver(typeName -> null); } @Test public void testgetRequiredPersistentEntityOfTransientType() { - mappingContext.getRequiredPersistentEntity(Transient.class); + this.mappingContext.getRequiredPersistentEntity(Transient.class); } private static class Transient {} @@ -342,15 +345,16 @@ public class CassandraMappingContextUnitTests { @Test(expected = InvalidDataAccessApiUsageException.class) // DATACASS-284 public void shouldRejectUntypedTuples() { - - mappingContext.getCreateTableSpecificationFor(mappingContext.getRequiredPersistentEntity(UntypedTupleEntity.class)); + this.mappingContext.getCreateTableSpecificationFor( + this.mappingContext.getRequiredPersistentEntity(UntypedTupleEntity.class)); } @Test // DATACASS-284 public void shouldCreateTableForTypedTupleType() { - CreateTableSpecification tableSpecification = mappingContext - .getCreateTableSpecificationFor(mappingContext.getRequiredPersistentEntity(TypedTupleEntity.class)); + CreateTableSpecification tableSpecification = + this.mappingContext.getCreateTableSpecificationFor( + this.mappingContext.getRequiredPersistentEntity(TypedTupleEntity.class)); assertThat(tableSpecification.getColumns()).hasSize(2); @@ -662,17 +666,15 @@ public class CassandraMappingContextUnitTests { String str; } - @Table - static class UntypedTupleEntity { - - @Id String id; - TupleType untyped; - } - @Table static class TypedTupleEntity { - @Id String id; @CassandraType(type = Name.TUPLE, typeArguments = { Name.VARCHAR, Name.BIGINT }) TupleValue typed; } + + @Table + static class UntypedTupleEntity { + @Id String id; + TupleType untyped; + } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/AllPossibleTypes.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/AllPossibleTypes.java index c67541481..7cb620d11 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/AllPossibleTypes.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/domain/AllPossibleTypes.java @@ -30,7 +30,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import org.springframework.data.cassandra.core.convert.CassandraTypeMappingIntegrationTest.Condition; +import org.springframework.data.cassandra.core.convert.CassandraTypeMappingIntegrationTests.Condition; import org.springframework.data.cassandra.core.mapping.CassandraType; import org.springframework.data.cassandra.core.mapping.PrimaryKey; import org.springframework.data.cassandra.core.mapping.Table; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/isolated/RepositoryReturnTypesIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/isolated/RepositoryReturnTypesIntegrationTests.java index 2bb5043e1..3ac78cf13 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/isolated/RepositoryReturnTypesIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/isolated/RepositoryReturnTypesIntegrationTests.java @@ -15,7 +15,8 @@ */ package org.springframework.data.cassandra.repository.isolated; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; import java.math.BigDecimal; import java.math.BigInteger; @@ -31,6 +32,7 @@ import java.util.Set; 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.data.cassandra.config.SchemaAction; @@ -261,11 +263,13 @@ public class RepositoryReturnTypesIntegrationTests extends AbstractSpringDataEmb public void shouldReturnEntityAsMap() { AllPossibleTypes entity = new AllPossibleTypes("123"); + entity.setPrimitiveInteger(123); entity.setBigInteger(BigInteger.ONE); allPossibleTypesRepository.save(entity); Map result = allPossibleTypesRepository.findEntityAsMapById(entity.getId()); + assertThat(result.size()).isGreaterThan(30); assertThat(result.get("primitiveinteger")).isEqualTo((Object) Integer.valueOf(123)); assertThat(result.get("biginteger")).isEqualTo((Object) BigInteger.ONE); diff --git a/src/main/asciidoc/reference/cassandra.adoc b/src/main/asciidoc/reference/cassandra.adoc index 00d3aef52..61ce76e3d 100644 --- a/src/main/asciidoc/reference/cassandra.adoc +++ b/src/main/asciidoc/reference/cassandra.adoc @@ -613,7 +613,12 @@ NOTE: `SchemaAction.RECREATE`/`SchemaAction.RECREATE_DROP_UNUSED` will drop your ==== Enabling Tables and User-Defined Types for Schema Management -<> explains object mapping using conventions and annotations. Schema management is only active for entities annotated with `@Table` and user-defined types annotated with `@UserDefinedType` to prevent unwanted classes from being created as table/type. Entities are discovered by scanning the class path. Entity scanning requires one or more base packages. Tuple typed columns using `TupleValue` do not provide any typing details hences you must annotate such column properties with `@CassandraType(type = TUPLE, typeArguments = …)` to specify the desired column type. +<> explains object mapping using conventions and annotations. Schema management is only active +for entities annotated with `@Table` and user-defined types annotated with `@UserDefinedType` to prevent +unwanted classes from being created as table/type. Entities are discovered by scanning the classpath. +Entity scanning requires one or more base packages. Tuple typed columns using `TupleValue` do not provide +any typing details hences you must annotate such column properties with `@CassandraType(type = TUPLE, typeArguments = …)` +to specify the desired column type. .Specifying Entity Base Packages via XML ====