diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java index 15fc091fa..70e5b015f 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/config/AbstractCassandraConfiguration.java @@ -68,12 +68,17 @@ public abstract class AbstractCassandraConfiguration extends AbstractSessionConf @Bean public CassandraConverter cassandraConverter() { - MappingCassandraConverter mappingCassandraConverter = - new MappingCassandraConverter(requireBeanOfType(CassandraMappingContext.class)); + UserTypeResolver userTypeResolver = new SimpleUserTypeResolver(getRequiredSession(), + CqlIdentifier.fromCql(getKeyspaceName())); - mappingCassandraConverter.setCustomConversions(requireBeanOfType(CassandraCustomConversions.class)); + MappingCassandraConverter converter = new MappingCassandraConverter( + requireBeanOfType(CassandraMappingContext.class)); - return mappingCassandraConverter; + converter.setCodecRegistry(getRequiredSession().getContext().getCodecRegistry()); + converter.setUserTypeResolver(userTypeResolver); + converter.setCustomConversions(requireBeanOfType(CassandraCustomConversions.class)); + + return converter; } /** @@ -86,11 +91,11 @@ public abstract class AbstractCassandraConfiguration extends AbstractSessionConf @Bean public CassandraMappingContext cassandraMapping() throws ClassNotFoundException { - UserTypeResolver userTypeResolver = - new SimpleUserTypeResolver(getRequiredSession(), CqlIdentifier.fromCql(getKeyspaceName())); + UserTypeResolver userTypeResolver = new SimpleUserTypeResolver(getRequiredSession(), + CqlIdentifier.fromCql(getKeyspaceName())); - CassandraMappingContext mappingContext = - new CassandraMappingContext(userTypeResolver, SimpleTupleTypeFactory.DEFAULT); + CassandraMappingContext mappingContext = new CassandraMappingContext(userTypeResolver, + SimpleTupleTypeFactory.DEFAULT); CustomConversions customConversions = requireBeanOfType(CustomConversions.class); @@ -156,8 +161,8 @@ public abstract class AbstractCassandraConfiguration extends AbstractSessionConf /** * Configures the Java {@link ClassLoader} used to resolve Cassandra application entity {@link Class types}. * - * @param classLoader Java {@link ClassLoader} used to resolve Cassandra application entity {@link Class types}; - * may be {@literal null}. + * @param classLoader Java {@link ClassLoader} used to resolve Cassandra application entity {@link Class types}; may + * be {@literal null}. * @see java.lang.ClassLoader */ @Override diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java index ca0c5d766..c9ede3ea4 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminOperations.java @@ -18,6 +18,7 @@ package org.springframework.data.cassandra.core; import java.util.Map; import java.util.Optional; +import org.springframework.data.cassandra.core.convert.SchemaFactory; import org.springframework.data.cassandra.core.mapping.Table; import com.datastax.oss.driver.api.core.CqlIdentifier; @@ -34,6 +35,12 @@ import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; */ public interface CassandraAdminOperations extends CassandraOperations { + /** + * @return the {@link SchemaFactory}. + * @since 3.0 + */ + SchemaFactory getSchemaFactory(); + /** * Create a table with the name given and fields corresponding to the given class. If the table already exists and * parameter {@code ifNotExists} is {@literal true}, this is a no-op and {@literal false} is returned. If the table diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java index 1048358e4..2ced5326d 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraAdminTemplate.java @@ -20,6 +20,7 @@ import java.util.Optional; import org.springframework.data.cassandra.SessionFactory; import org.springframework.data.cassandra.core.convert.CassandraConverter; +import org.springframework.data.cassandra.core.convert.SchemaFactory; import org.springframework.data.cassandra.core.cql.CqlOperations; import org.springframework.data.cassandra.core.cql.SessionCallback; import org.springframework.data.cassandra.core.cql.generator.CreateTableCqlGenerator; @@ -48,6 +49,8 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand protected static final boolean DEFAULT_DROP_TABLE_IF_EXISTS = false; + private final SchemaFactory schemaFactory; + /** * Constructor used for a basic template configuration. * @@ -56,6 +59,8 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand */ public CassandraAdminTemplate(CqlSession session) { super(session); + + this.schemaFactory = new SchemaFactory(getConverter()); } /** @@ -66,6 +71,7 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand */ public CassandraAdminTemplate(CqlSession session, CassandraConverter converter) { super(session, converter); + this.schemaFactory = new SchemaFactory(getConverter()); } /** @@ -76,6 +82,8 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand */ public CassandraAdminTemplate(SessionFactory sessionFactory, CassandraConverter converter) { super(sessionFactory, converter); + + this.schemaFactory = new SchemaFactory(getConverter()); } /** @@ -87,6 +95,13 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand */ public CassandraAdminTemplate(CqlOperations cqlOperations, CassandraConverter converter) { super(cqlOperations, converter); + + this.schemaFactory = new SchemaFactory(getConverter()); + } + + @Override + public SchemaFactory getSchemaFactory() { + return schemaFactory; } /* (non-Javadoc) @@ -98,8 +113,8 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand CassandraPersistentEntity entity = getConverter().getMappingContext().getRequiredPersistentEntity(entityClass); - CreateTableSpecification createTableSpecification = getConverter().getMappingContext() - .getCreateTableSpecificationFor(tableName, entity).ifNotExists(ifNotExists); + CreateTableSpecification createTableSpecification = this.schemaFactory + .getCreateTableSpecificationFor(entity, tableName).ifNotExists(ifNotExists); getCqlOperations().execute(CreateTableCqlGenerator.toCql(createTableSpecification)); } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java index 5519cad48..a19f36a25 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreator.java @@ -57,6 +57,22 @@ public class CassandraPersistentEntitySchemaCreator { private final CassandraMappingContext mappingContext; + /** + * Create a new {@link CassandraPersistentEntitySchemaCreator} for the given {@link CassandraMappingContext} and + * {@link CassandraAdminOperations}. + * + * @param mappingContext must not be {@literal null}. + * @param cassandraAdminOperations must not be {@literal null}. + * @since 3.0 + */ + public CassandraPersistentEntitySchemaCreator(CassandraAdminOperations cassandraAdminOperations) { + + Assert.notNull(cassandraAdminOperations, "CassandraAdminOperations must not be null"); + + this.cassandraAdminOperations = cassandraAdminOperations; + this.mappingContext = cassandraAdminOperations.getConverter().getMappingContext(); + } + /** * Create a new {@link CassandraPersistentEntitySchemaCreator} for the given {@link CassandraMappingContext} and * {@link CassandraAdminOperations}. @@ -96,7 +112,8 @@ public class CassandraPersistentEntitySchemaCreator { return this.mappingContext.getTableEntities() // .stream() // - .map(entity -> this.mappingContext.getCreateTableSpecificationFor(entity).ifNotExists(ifNotExists)) // + .map(entity -> cassandraAdminOperations.getSchemaFactory().getCreateTableSpecificationFor(entity) + .ifNotExists(ifNotExists)) // .collect(Collectors.toList()); } @@ -122,7 +139,7 @@ public class CassandraPersistentEntitySchemaCreator { return this.mappingContext.getTableEntities() // .stream() // - .flatMap(entity -> this.mappingContext.getCreateIndexSpecificationsFor(entity).stream()) // + .flatMap(entity -> cassandraAdminOperations.getSchemaFactory().getCreateIndexSpecificationsFor(entity).stream()) // .peek(it -> it.ifNotExists(ifNotExists)) // .collect(Collectors.toList()); } @@ -161,11 +178,10 @@ public class CassandraPersistentEntitySchemaCreator { visitUserTypes(entity, udts); }); - specifications - .addAll(udts - .stream().map(identifier -> this.mappingContext - .getCreateUserTypeSpecificationFor(byTableName.get(identifier)).ifNotExists(ifNotExists)) - .collect(Collectors.toList())); + specifications.addAll(udts.stream() + .map(identifier -> cassandraAdminOperations.getSchemaFactory() + .getCreateUserTypeSpecificationFor(byTableName.get(identifier)).ifNotExists(ifNotExists)) + .collect(Collectors.toList())); return specifications; } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraColumnType.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraColumnType.java index b5688d2fe..6715d2d95 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraColumnType.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraColumnType.java @@ -31,6 +31,7 @@ public interface CassandraColumnType extends ColumnType { * Returns the {@link DataType} associated with this column type. * * @return + * @throws org.springframework.data.mapping.MappingException if the column cannot be mapped onto a Cassandra type. */ DataType getDataType(); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraConverter.java index 30e037e22..014882aa1 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraConverter.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraConverter.java @@ -25,6 +25,8 @@ import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; + /** * Central Cassandra specific converter interface from Object to Row. * @@ -42,6 +44,14 @@ public interface CassandraConverter */ CustomConversions getCustomConversions(); + /** + * Returns the {@link CodecRegistry} registered in the {@link CassandraConverter}. + * + * @return the {@link CodecRegistry}. + * @since 3.0 + */ + CodecRegistry getCodecRegistry(); + /* (non-Javadoc) * @see org.springframework.data.convert.EntityConverter#getMappingContext() */ diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/ColumnType.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/ColumnType.java index 572d2e43e..83575e1cc 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/ColumnType.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/ColumnType.java @@ -88,8 +88,8 @@ public interface ColumnType { * @return */ static CassandraColumnType listOf(CassandraColumnType componentType) { - return new DefaultCassandraColumnType(ClassTypeInformation.LIST, DataTypes.listOf(componentType.getDataType()), - componentType); + return new DefaultCassandraColumnType(ClassTypeInformation.LIST, + () -> DataTypes.listOf(componentType.getDataType()), componentType); } /** @@ -114,7 +114,7 @@ public interface ColumnType { * @return */ static CassandraColumnType setOf(CassandraColumnType componentType) { - return new DefaultCassandraColumnType(ClassTypeInformation.SET, DataTypes.setOf(componentType.getDataType()), + return new DefaultCassandraColumnType(ClassTypeInformation.SET, () -> DataTypes.setOf(componentType.getDataType()), componentType); } @@ -138,7 +138,7 @@ public interface ColumnType { */ static CassandraColumnType mapOf(CassandraColumnType keyType, CassandraColumnType valueType) { return new DefaultCassandraColumnType(ClassTypeInformation.MAP, - DataTypes.mapOf(keyType.getDataType(), valueType.getDataType()), keyType, valueType); + () -> DataTypes.mapOf(keyType.getDataType(), valueType.getDataType()), keyType, valueType); } /** diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolver.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolver.java index 932a1d30a..8d0b89279 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolver.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolver.java @@ -37,6 +37,7 @@ public interface ColumnTypeResolver { * @return * @see CassandraType * @see CassandraPersistentProperty + * @throws org.springframework.dao.InvalidDataAccessApiUsageException */ default CassandraColumnType resolve(CassandraPersistentProperty property) { @@ -57,6 +58,7 @@ public interface ColumnTypeResolver { * @return * @see org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder * @see CassandraCustomConversions + * @throws org.springframework.dao.InvalidDataAccessApiUsageException */ CassandraColumnType resolve(TypeInformation typeInformation); @@ -67,6 +69,7 @@ public interface ColumnTypeResolver { * @return * @see CassandraType * @see CassandraPersistentProperty + * @throws org.springframework.dao.InvalidDataAccessApiUsageException */ CassandraColumnType resolve(CassandraType annotation); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultCassandraColumnType.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultCassandraColumnType.java index e9cbf1c7c..6789b8b4f 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultCassandraColumnType.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultCassandraColumnType.java @@ -15,9 +15,11 @@ */ package org.springframework.data.cassandra.core.convert; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; import com.datastax.oss.driver.api.core.type.DataType; @@ -31,16 +33,23 @@ import com.datastax.oss.driver.api.core.type.UserDefinedType; */ class DefaultCassandraColumnType extends DefaultColumnType implements CassandraColumnType { - private final DataType dataType; + private final Lazy dataType; DefaultCassandraColumnType(Class type, DataType dataType, ColumnType... parameters) { this(ClassTypeInformation.from(type), dataType, parameters); } + DefaultCassandraColumnType(TypeInformation typeInformation, Supplier dataType, + ColumnType... parameters) { + super(typeInformation, parameters); + + this.dataType = Lazy.of(dataType); + } + DefaultCassandraColumnType(TypeInformation typeInformation, DataType dataType, ColumnType... parameters) { super(typeInformation, parameters); - this.dataType = dataType; + this.dataType = Lazy.of(dataType); } /* @@ -48,7 +57,7 @@ class DefaultCassandraColumnType extends DefaultColumnType implements CassandraC * @see org.springframework.data.cassandra.core.convert.CassandraColumnType#getDataType() */ public DataType getDataType() { - return dataType; + return dataType.get(); } /* diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultColumnTypeResolver.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultColumnTypeResolver.java index 04fe301c8..774e3ed7e 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultColumnTypeResolver.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultColumnTypeResolver.java @@ -20,30 +20,39 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.StreamSupport; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity; -import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; import org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder; import org.springframework.data.cassandra.core.mapping.CassandraType; +import org.springframework.data.cassandra.core.mapping.CassandraType.Name; import org.springframework.data.cassandra.core.mapping.UserTypeResolver; import org.springframework.data.convert.CustomConversions; import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; -import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.ListType; import com.datastax.oss.driver.api.core.type.MapType; import com.datastax.oss.driver.api.core.type.SetType; import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException; import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; import com.datastax.oss.driver.api.core.type.reflect.GenericType; @@ -56,20 +65,64 @@ import com.datastax.oss.driver.api.core.type.reflect.GenericType; */ class DefaultColumnTypeResolver implements ColumnTypeResolver { - private final CassandraMappingContext mappingContext; - private final UserTypeResolver userTypeResolver; - private final CodecRegistry codecRegistry; - private CustomConversions customConversions; + private final Log log = LogFactory.getLog(getClass()); - public DefaultColumnTypeResolver(CassandraMappingContext mappingContext) { + private final MappingContext, ? extends CassandraPersistentProperty> mappingContext; + private final UserTypeResolver userTypeResolver; + private final Supplier codecRegistry; + private final Supplier customConversions; + + /** + * Creates a new {@link DefaultColumnTypeResolver}. + * + * @param mappingContext + * @param userTypeResolver + * @param codecRegistry + * @param customConversions + */ + public DefaultColumnTypeResolver( + MappingContext, ? extends CassandraPersistentProperty> mappingContext, + UserTypeResolver userTypeResolver, Supplier codecRegistry, + Supplier customConversions) { this.mappingContext = mappingContext; - this.userTypeResolver = mappingContext.getUserTypeResolver(); - this.codecRegistry = mappingContext.getCodecRegistry(); - this.customConversions = mappingContext.getCustomConversions(); + this.userTypeResolver = userTypeResolver; + this.codecRegistry = codecRegistry; + this.customConversions = customConversions; } - public void setCustomConversions(CustomConversions customConversions) { - this.customConversions = customConversions; + @Override + public CassandraColumnType resolve(CassandraPersistentProperty property) { + + Assert.notNull(property, "Property must not be null"); + + if (property.isAnnotationPresent(CassandraType.class)) { + + CassandraType annotation = property.getRequiredAnnotation(CassandraType.class); + + if (annotation.type() == Name.UDT && StringUtils.isEmpty(annotation.userTypeName())) { + throw new InvalidDataAccessApiUsageException( + String.format("Expected user type name in property ['%s'] of type ['%s'] in entity [%s]", + property.getName(), property.getType(), property.getOwner().getName())); + } + + if ((annotation.type() == Name.LIST || annotation.type() == Name.SET) && annotation.typeArguments().length != 1) { + + throw new InvalidDataAccessApiUsageException(String.format( + "Expected [%d] type arguments for property ['%s'] of type ['%s'] in entity [%s]; actual was [%d]", 1, + property.getName(), property.getType(), property.getOwner().getName(), annotation.typeArguments().length)); + } + + if (annotation.type() == Name.MAP && annotation.typeArguments().length != 2) { + + throw new InvalidDataAccessApiUsageException(String.format( + "Expected [%d] type arguments for property ['%s'] of type ['%s'] in entity [%s]; actual was [%d]", 2, + property.getName(), property.getType(), property.getOwner().getName(), annotation.typeArguments().length)); + } + + return resolve(annotation); + } + + return resolve(property.getTypeInformation()); } /* @@ -79,9 +132,7 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { @Override public CassandraColumnType resolve(TypeInformation typeInformation) { - Optional> writeTarget = customConversions.getCustomWriteTarget(typeInformation.getType()); - - return writeTarget.map(it -> { + return getCustomWriteTarget(typeInformation).map(it -> { return createCassandraTypeDescriptor(tryResolve(it), ClassTypeInformation.from(it)); }).orElseGet(() -> { @@ -93,17 +144,29 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { }); } + private Optional> getCustomWriteTarget(TypeInformation typeInformation) { + return customConversions.get().getCustomWriteTarget(typeInformation.getType()); + } + + @Nullable private DataType tryResolve(Class type) { if (TupleValue.class.isAssignableFrom(type)) { - return DataTypes.tupleOf(); + return null; } if (UdtValue.class.isAssignableFrom(type)) { - return UnknownUserDefinedType.INSTANCE; + return null; } - return codecRegistry.codecFor(type).getCqlType(); + try { + return getCodecRegistry().codecFor(type).getCqlType(); + } catch (CodecNotFoundException e) { + if (log.isDebugEnabled()) { + log.debug(String.format("Cannot resolve Codec for %s", type.getName()), e); + } + return null; + } } /* @@ -113,7 +176,7 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { @Override public CassandraColumnType resolve(CassandraType annotation) { - CassandraType.Name type = annotation.type(); + Name type = annotation.type(); switch (type) { case MAP: @@ -130,11 +193,10 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { case SET: assertTypeArguments(annotation.typeArguments().length, 1); - DataType componentType = annotation.typeArguments()[0] == CassandraType.Name.UDT - ? getUserType(annotation.userTypeName()) + DataType componentType = annotation.typeArguments()[0] == Name.UDT ? getUserType(annotation.userTypeName()) : CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[0]); - if (type == CassandraType.Name.SET) { + if (type == Name.SET) { return ColumnType.setOf(createCassandraTypeDescriptor(componentType)); } @@ -148,6 +210,11 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { return ColumnType.tupleOf(DataTypes.tupleOf(dataTypes)); case UDT: + if (StringUtils.isEmpty(annotation.userTypeName())) { + throw new InvalidDataAccessApiUsageException( + "Cannot resolve user type for @CassandraType(type=UDT) without userTypeName"); + } + return createCassandraTypeDescriptor(getUserType(annotation.userTypeName())); default: return createCassandraTypeDescriptor(CassandraSimpleTypeHolder.getDataTypeFor(type)); @@ -165,9 +232,7 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { ClassTypeInformation typeInformation = ClassTypeInformation.from(value.getClass()); - Optional> writeTarget = customConversions.getCustomWriteTarget(typeInformation.getType()); - - return writeTarget.map(it -> { + return getCustomWriteTarget(typeInformation).map(it -> { return (ColumnType) createCassandraTypeDescriptor(tryResolve(it), typeInformation); }).orElseGet(() -> { @@ -195,7 +260,7 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { return ColumnType.tupleOf(((TupleValue) value).getType()); } - BasicCassandraPersistentEntity persistentEntity = mappingContext.getPersistentEntity(typeInformation); + CassandraPersistentEntity persistentEntity = mappingContext.getPersistentEntity(typeInformation); if (persistentEntity != null) { @@ -213,11 +278,12 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { private CassandraColumnType createCassandraTypeDescriptor(DataType dataType) { - GenericType javaType = codecRegistry.codecFor(dataType).getJavaType(); + GenericType javaType = getCodecRegistry().codecFor(dataType).getJavaType(); return ColumnType.create(javaType.getRawType(), dataType); } - private CassandraColumnType createCassandraTypeDescriptor(DataType dataType, TypeInformation typeInformation) { + private CassandraColumnType createCassandraTypeDescriptor(@Nullable DataType dataType, + TypeInformation typeInformation) { if (typeInformation.isCollectionLike() || typeInformation.isMap()) { @@ -277,6 +343,10 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { } } + if (dataType == null) { + return new UnresolvableCassandraType(typeInformation); + } + return new DefaultCassandraColumnType(typeInformation, dataType); } @@ -295,12 +365,12 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { resolve(typeInformation.getRequiredMapValueType())); } - BasicCassandraPersistentEntity persistentEntity = mappingContext.getPersistentEntity(typeInformation); + CassandraPersistentEntity persistentEntity = mappingContext.getPersistentEntity(typeInformation); if (persistentEntity != null) { if (persistentEntity.isUserDefinedType()) { - return new DefaultCassandraColumnType(typeInformation, persistentEntity.getUserType()); + return new DefaultCassandraColumnType(typeInformation, getUserType(persistentEntity)); } if (persistentEntity.isTupleType()) { @@ -316,26 +386,47 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { return new UnresolvableCassandraType(typeInformation); } - return new DefaultCassandraColumnType(typeInformation, tryResolve(typeInformation.getType())); + DataType dataType = tryResolve(typeInformation.getType()); + if (dataType == null) { + return new UnresolvableCassandraType(typeInformation); + } + + return new DefaultCassandraColumnType(typeInformation, dataType); + } + + private DataType getUserType(CassandraPersistentEntity persistentEntity) { + + CqlIdentifier identifier = persistentEntity.getTableName(); + com.datastax.oss.driver.api.core.type.UserDefinedType userType = userTypeResolver.resolveType(identifier); + + if (userType == null) { + throw new MappingException(String.format("User type [%s] not found", identifier)); + } + + return userType; } private Class resolveToJavaType(DataType dataType) { - TypeCodec codec = codecRegistry.codecFor(dataType); + TypeCodec codec = getCodecRegistry().codecFor(dataType); return codec.getJavaType().getRawType(); } + private CodecRegistry getCodecRegistry() { + return codecRegistry.get(); + } + private DataType getUserType(String userTypeName) { UserDefinedType type = userTypeResolver.resolveType(CqlIdentifier.fromCql(userTypeName)); if (type == null) { - throw new IllegalArgumentException(String.format("Cannot resolve UserDefinedType for [%s]", userTypeName)); + throw new MappingException(String.format("Cannot resolve UserDefinedType for [%s]", userTypeName)); } return type; } - private void assertTypeArguments(int args, int expected) { + private static void assertTypeArguments(int args, int expected) { if (args != expected) { throw new InvalidDataAccessApiUsageException( @@ -343,147 +434,10 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { } } - enum UnknownUserDefinedType implements com.datastax.oss.driver.api.core.type.UserDefinedType { - INSTANCE; - - UnknownUserDefinedType() {} - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#getKeyspace() - */ - @Override - public CqlIdentifier getKeyspace() { - return null; - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#getName() - */ - @Override - public CqlIdentifier getName() { - return CqlIdentifier.fromCql("unknown"); - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#isFrozen() - */ - @Override - public boolean isFrozen() { - return false; - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#getFieldNames() - */ - @Override - public List getFieldNames() { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#firstIndexOf(com.datastax.oss.driver.api.core.CqlIdentifier) - */ - @Override - public int firstIndexOf(CqlIdentifier id) { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#firstIndexOf(java.lang.String) - */ - @Override - public int firstIndexOf(String name) { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#getFieldTypes() - */ - @Override - public List getFieldTypes() { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#copy(boolean) - */ - @Override - public com.datastax.oss.driver.api.core.type.UserDefinedType copy(boolean newFrozen) { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#newValue() - */ - @Override - public UdtValue newValue() { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#newValue(java.lang.Object[]) - */ - @Override - public UdtValue newValue(Object... fields) { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.type.UserDefinedType#getAttachmentPoint() - */ - @Override - public AttachmentPoint getAttachmentPoint() { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.detach.Detachable#isDetached() - */ - @Override - public boolean isDetached() { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - /* - * (non-Javadoc) - * @see com.datastax.oss.driver.api.core.detach.Detachable#attach(com.datastax.oss.driver.api.core.detach.AttachmentPoint) - */ - @Override - public void attach(AttachmentPoint attachmentPoint) { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - } - static class UnresolvableCassandraType extends DefaultCassandraColumnType { public UnresolvableCassandraType(TypeInformation type, ColumnType... parameters) { - super(type, null, parameters); - } - - public UnresolvableCassandraType(Class type, ColumnType... parameters) { - super(type, null, parameters); + super(type, Lazy.empty(), parameters); } /* @@ -495,4 +449,5 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver { throw new MappingException(String.format("Cannot resolve DataType for %s", getType().getName())); } } + } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/IndexSpecificationFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/IndexSpecificationFactory.java similarity index 96% rename from spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/IndexSpecificationFactory.java rename to spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/IndexSpecificationFactory.java index ed83df7c5..6df14377d 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/IndexSpecificationFactory.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/IndexSpecificationFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2020 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.core.mapping; +package org.springframework.data.cassandra.core.convert; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedParameterizedType; @@ -27,6 +27,9 @@ import java.util.function.BiConsumer; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification; +import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.core.mapping.Indexed; +import org.springframework.data.cassandra.core.mapping.SASI; import org.springframework.data.cassandra.core.mapping.SASI.NonTokenizingAnalyzed; import org.springframework.data.cassandra.core.mapping.SASI.Normalization; import org.springframework.data.cassandra.core.mapping.SASI.StandardAnalyzed; diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java index d8c84b0ec..6f7499c65 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java @@ -43,7 +43,7 @@ import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; import org.springframework.data.cassandra.core.mapping.MapId; import org.springframework.data.cassandra.core.mapping.MapIdentifiable; -import org.springframework.data.convert.CustomConversions; +import org.springframework.data.cassandra.core.mapping.UserTypeResolver; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PreferredConstructor; @@ -91,6 +91,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter private final CassandraMappingContext mappingContext; + private CodecRegistry codecRegistry; + + private UserTypeResolver userTypeResolver; + private @Nullable ClassLoader beanClassLoader; private SpELContext spELContext; @@ -107,8 +111,11 @@ public class MappingCassandraConverter extends AbstractCassandraConverter CassandraCustomConversions conversions = new CassandraCustomConversions(Collections.emptyList()); this.mappingContext = newDefaultMappingContext(conversions); + this.codecRegistry = mappingContext.getCodecRegistry(); this.spELContext = new SpELContext(RowReaderPropertyAccessor.INSTANCE); - this.cassandraTypeResolver = new DefaultColumnTypeResolver(mappingContext); + this.cassandraTypeResolver = new DefaultColumnTypeResolver(mappingContext, + userTypeName -> getUserTypeResolver().resolveType(userTypeName), this::getCodecRegistry, + this::getCustomConversions); this.setCustomConversions(conversions); } @@ -124,8 +131,11 @@ public class MappingCassandraConverter extends AbstractCassandraConverter Assert.notNull(mappingContext, "CassandraMappingContext must not be null"); this.mappingContext = mappingContext; + this.codecRegistry = mappingContext.getCodecRegistry(); this.spELContext = new SpELContext(RowReaderPropertyAccessor.INSTANCE); - this.cassandraTypeResolver = new DefaultColumnTypeResolver(mappingContext); + this.cassandraTypeResolver = new DefaultColumnTypeResolver(mappingContext, + userTypeName -> getUserTypeResolver().resolveType(userTypeName), this::getCodecRegistry, + this::getCustomConversions); this.setCustomConversions(mappingContext.getCustomConversions()); } @@ -143,15 +153,6 @@ public class MappingCassandraConverter extends AbstractCassandraConverter return mappingContext; } - @Override - public void setCustomConversions(CustomConversions conversions) { - super.setCustomConversions(conversions); - - if (this.cassandraTypeResolver != null) { - this.cassandraTypeResolver.setCustomConversions(conversions); - } - } - /* (non-Javadoc) * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) */ @@ -169,11 +170,63 @@ public class MappingCassandraConverter extends AbstractCassandraConverter } private TypeCodec getCodec(CassandraPersistentProperty property) { - return getCodecRegistry().codecFor(getMappingContext().getDataType(property)); + return getCodecRegistry().codecFor(cassandraTypeResolver.resolve(property).getDataType()); } - private CodecRegistry getCodecRegistry() { - return getMappingContext().getCodecRegistry(); + /** + * Sets the {@link CodecRegistry}. + * + * @param codecRegistry must not be {@literal null}. + * @since 3.0 + */ + public void setCodecRegistry(CodecRegistry codecRegistry) { + + Assert.notNull(codecRegistry, "CodecRegistry must not be null"); + + this.codecRegistry = codecRegistry; + } + + /** + * Returns the configured {@link CodecRegistry}. + * + * @return the configured {@link CodecRegistry}. + * @since 3.0 + */ + @Override + public CodecRegistry getCodecRegistry() { + + if (this.codecRegistry == null) { + return mappingContext.getCodecRegistry(); + } + + return this.codecRegistry; + } + + /** + * Sets the {@link UserTypeResolver}. + * + * @param userTypeResolver must not be {@literal null}. + */ + public void setUserTypeResolver(UserTypeResolver userTypeResolver) { + + Assert.notNull(userTypeResolver, "UserTypeResolver must not be null"); + + this.userTypeResolver = userTypeResolver; + } + + /** + * Returns the configured {@link UserTypeResolver}. + * + * @return the configured {@link UserTypeResolver}. + * @since 3.0 + */ + public UserTypeResolver getUserTypeResolver() { + + if (this.userTypeResolver == null) { + return this.mappingContext.getUserTypeResolver(); + } + + return userTypeResolver; } /* (non-Javadoc) diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/SchemaFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/SchemaFactory.java new file mode 100644 index 000000000..1f89d5945 --- /dev/null +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/SchemaFactory.java @@ -0,0 +1,383 @@ +/* + * Copyright 2020 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 + * + * https://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.core.convert; + +import static org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification; +import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification; +import org.springframework.data.cassandra.core.cql.keyspace.CreateUserTypeSpecification; +import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.core.mapping.UserTypeResolver; +import org.springframework.data.convert.CustomConversions; +import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.Assert; + +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.data.UdtValue; +import com.datastax.oss.driver.api.core.detach.AttachmentPoint; +import com.datastax.oss.driver.api.core.type.DataType; +import com.datastax.oss.driver.api.core.type.UserDefinedType; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; + +/** + * Factory for Cassandra Schema objects such as user-defined types, tables and indexes. + * + * @author Mark Paluch + * @since 3.0 + * @see CreateUserTypeSpecification + * @see CreateTableSpecification + * @see CreateIndexSpecification + * @see org.springframework.data.cassandra.core.mapping.CassandraMappingContext + */ +public class SchemaFactory { + + private final MappingContext, CassandraPersistentProperty> mappingContext; + + private final ColumnTypeResolver typeResolver; + + /** + * Creates a new {@link SchemaFactory} given {@link CassandraConverter}. + * + * @param converter must not be null. + */ + public SchemaFactory(CassandraConverter converter) { + + Assert.notNull(converter, "CassandraConverter must not be null"); + + this.mappingContext = converter.getMappingContext(); + this.typeResolver = new DefaultColumnTypeResolver(mappingContext, ShallowUserTypeResolver.INSTANCE, + converter::getCodecRegistry, converter::getCustomConversions); + } + + /** + * Creates a new {@link SchemaFactory} given {@link MappingContext}, {@link CustomConversions} and + * {@link CodecRegistry}. + * + * @param mappingContext must not be null. + * @param customConversions must not be null. + * @param codecRegistry must not be null. + */ + public SchemaFactory( + MappingContext, CassandraPersistentProperty> mappingContext, + CustomConversions customConversions, CodecRegistry codecRegistry) { + + Assert.notNull(mappingContext, "MappingContext must not be null"); + Assert.notNull(customConversions, "CustomConversions must not be null"); + Assert.notNull(codecRegistry, "CodecRegistry must not be null"); + + this.mappingContext = mappingContext; + this.typeResolver = new DefaultColumnTypeResolver(mappingContext, ShallowUserTypeResolver.INSTANCE, + () -> codecRegistry, () -> customConversions); + } + + /** + * Returns a {@link CreateTableSpecification} for the given entity, including all mapping information. + * + * @param entityType must not be {@literal null}. + * @return the {@link CreateTableSpecification} derived from {@code entityType}. + */ + public CreateTableSpecification getCreateTableSpecificationFor(Class entityType) { + + Assert.notNull(entityType, "Entity type must not be null"); + + return getCreateTableSpecificationFor(mappingContext.getRequiredPersistentEntity(entityType)); + } + + /** + * Returns a {@link CreateTableSpecification} for the given entity, including all mapping information. + * + * @param entity must not be {@literal null}. + * @return the {@link CreateTableSpecification} derived from {@link CassandraPersistentEntity}. + */ + public CreateTableSpecification getCreateTableSpecificationFor(CassandraPersistentEntity entity) { + + Assert.notNull(entity, "CassandraPersistentEntity must not be null"); + + return getCreateTableSpecificationFor(entity, entity.getTableName()); + } + + /** + * Returns a {@link CreateTableSpecification} for the given entity using {@link CqlIdentifier table name}, including + * all mapping information. + * + * @param entity must not be {@literal null}. + * @param tableName must not be {@literal null}. + * @return + * @since 2.2 + */ + public CreateTableSpecification getCreateTableSpecificationFor(CassandraPersistentEntity entity, + CqlIdentifier tableName) { + + Assert.notNull(tableName, "Table name must not be null"); + Assert.notNull(entity, "CassandraPersistentEntity must not be null"); + + CreateTableSpecification specification = createTable(tableName); + + for (CassandraPersistentProperty property : entity) { + + if (property.isCompositePrimaryKey()) { + + CassandraPersistentEntity primaryKeyEntity = mappingContext + .getRequiredPersistentEntity(property.getRawType()); + + for (CassandraPersistentProperty primaryKeyProperty : primaryKeyEntity) { + + DataType dataType = getDataType(primaryKeyProperty); + + if (primaryKeyProperty.isPartitionKeyColumn()) { + specification.partitionKeyColumn(primaryKeyProperty.getRequiredColumnName(), dataType); + } else { // cluster column + specification.clusteredKeyColumn(primaryKeyProperty.getRequiredColumnName(), dataType, + primaryKeyProperty.getPrimaryKeyOrdering()); + } + } + } else { + DataType type = UserTypeUtil.potentiallyFreeze(getDataType(property)); + + if (property.isIdProperty() || property.isPartitionKeyColumn()) { + specification.partitionKeyColumn(property.getRequiredColumnName(), type); + } else if (property.isClusterKeyColumn()) { + specification.clusteredKeyColumn(property.getRequiredColumnName(), type, property.getPrimaryKeyOrdering()); + } else { + specification.column(property.getRequiredColumnName(), type); + } + } + } + + if (specification.getPartitionKeyColumns().isEmpty()) { + throw new MappingException(String.format("No partition key columns found in entity [%s]", entity.getType())); + } + + return specification; + } + + private DataType getDataType(CassandraPersistentProperty property) { + + try { + return typeResolver.resolve(property).getDataType(); + } catch (MappingException e) { + + throw new MappingException(String.format( + "Cannot resolve DataType for type [%s] for property [%s] in entity [%s]; Consider registering a Converter or annotating the property with @CassandraType.", + property.getType(), property.getName(), property.getOwner().getName()), e); + } + } + + /** + * Returns {@link CreateIndexSpecification index specifications} derived from {@link CassandraPersistentEntity}. + * + * @param entityType must not be {@literal null}. + * @return the {@link CreateTableSpecification} derived from {@code entityType}. + */ + public List getCreateIndexSpecificationsFor(Class entityType) { + + Assert.notNull(entityType, "Entity type must not be null"); + + return getCreateIndexSpecificationsFor(mappingContext.getRequiredPersistentEntity(entityType)); + } + + /** + * Returns {@link CreateIndexSpecification index specifications} derived from {@link CassandraPersistentEntity}. + * + * @param entity must not be {@literal null}. + * @return + * @since 2.0 + */ + public List getCreateIndexSpecificationsFor(CassandraPersistentEntity entity) { + + Assert.notNull(entity, "CassandraPersistentEntity must not be null"); + + return getCreateIndexSpecificationsFor(entity, entity.getTableName()); + } + + /** + * Returns {@link CreateIndexSpecification index specifications} derived from {@link CassandraPersistentEntity} using + * {@link CqlIdentifier table name}. + * + * @param entity must not be {@literal null}. + * @param tableName must not be {@literal null}. + * @return + * @since 2.0 + */ + public List getCreateIndexSpecificationsFor(CassandraPersistentEntity entity, + CqlIdentifier tableName) { + + Assert.notNull(entity, "CassandraPersistentEntity must not be null"); + Assert.notNull(tableName, "Table name must not be null"); + + List indexes = new ArrayList<>(); + + for (CassandraPersistentProperty property : entity) { + if (property.isCompositePrimaryKey()) { + indexes.addAll(getCreateIndexSpecificationsFor(mappingContext.getRequiredPersistentEntity(property))); + } else { + indexes.addAll(IndexSpecificationFactory.createIndexSpecifications(property)); + } + } + + indexes.forEach(it -> it.tableName(entity.getTableName())); + + return indexes; + } + + /** + * Returns a {@link CreateUserTypeSpecification} for the given entity, including all mapping information. + * + * @param entity must not be {@literal null}. + */ + public CreateUserTypeSpecification getCreateUserTypeSpecificationFor(CassandraPersistentEntity entity) { + + Assert.notNull(entity, "CassandraPersistentEntity must not be null"); + + CreateUserTypeSpecification specification = CreateUserTypeSpecification.createType(entity.getTableName()); + + for (CassandraPersistentProperty property : entity) { + // Use frozen literal to not resolve types from Cassandra; At this stage, they might be not created yet. + specification.field(property.getRequiredColumnName(), UserTypeUtil.potentiallyFreeze(getDataType(property))); + } + + if (specification.getFields().isEmpty()) { + throw new MappingException(String.format("No fields in user type [%s]", entity.getType())); + } + + return specification; + } + + enum ShallowUserTypeResolver implements UserTypeResolver { + INSTANCE; + + @Override + public UserDefinedType resolveType(CqlIdentifier typeName) { + return new ShallowUserDefinedType(typeName, false); + } + } + + static class ShallowUserDefinedType implements com.datastax.oss.driver.api.core.type.UserDefinedType { + + private final CqlIdentifier name; + private final boolean frozen; + + public ShallowUserDefinedType(String name, boolean frozen) { + this(CqlIdentifier.fromInternal(name), frozen); + } + + public ShallowUserDefinedType(CqlIdentifier name, boolean frozen) { + this.name = name; + this.frozen = frozen; + } + + @Override + public CqlIdentifier getKeyspace() { + return null; + } + + @Override + public CqlIdentifier getName() { + return name; + } + + @Override + public boolean isFrozen() { + return frozen; + } + + @Override + public List getFieldNames() { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public int firstIndexOf(CqlIdentifier id) { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public int firstIndexOf(String name) { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public List getFieldTypes() { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public com.datastax.oss.driver.api.core.type.UserDefinedType copy(boolean newFrozen) { + return new ShallowUserDefinedType(this.name, newFrozen); + } + + @Override + public UdtValue newValue() { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public UdtValue newValue(@edu.umd.cs.findbugs.annotations.NonNull Object... fields) { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public AttachmentPoint getAttachmentPoint() { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public boolean isDetached() { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public void attach(@edu.umd.cs.findbugs.annotations.NonNull AttachmentPoint attachmentPoint) { + throw new UnsupportedOperationException( + "This implementation should only be used internally, this is likely a driver bug"); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof com.datastax.oss.driver.api.core.type.UserDefinedType)) + return false; + com.datastax.oss.driver.api.core.type.UserDefinedType that = (com.datastax.oss.driver.api.core.type.UserDefinedType) o; + return isFrozen() == that.isFrozen() && Objects.equals(getName(), that.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(name, frozen); + } + + @Override + public String toString() { + return "UDT(" + name.asCql(true) + ")"; + } + } +} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UpdateMapper.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UpdateMapper.java index 1ee03f3f5..4e3e0c870 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UpdateMapper.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UpdateMapper.java @@ -169,7 +169,8 @@ public class UpdateMapper extends QueryMapper { if (collection.isEmpty()) { - int protocolCode = field.getProperty().map(property -> getMappingContext().getDataType(property)) + int protocolCode = field.getProperty() + .map(property -> getConverter().getColumnTypeResolver().resolve(property).getDataType()) .map(DataType::getProtocolCode).orElse(ProtocolConstants.DataType.LIST); if (protocolCode == ProtocolConstants.DataType.SET) { @@ -203,7 +204,7 @@ public class UpdateMapper extends QueryMapper { if (field.getProperty().isPresent()) { - DataType dataType = getMappingContext().getDataType(field.getProperty().get()); + DataType dataType = getConverter().getColumnTypeResolver().resolve(field.getProperty().get()).getDataType(); if (dataType instanceof SetType && !(mappedValue instanceof Set)) { Collection collection = new HashSet<>(); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/UserTypeUtil.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UserTypeUtil.java similarity index 96% rename from spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/UserTypeUtil.java rename to spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UserTypeUtil.java index d1077257f..e38a80c57 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/UserTypeUtil.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UserTypeUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2020 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.core.mapping; +package org.springframework.data.cassandra.core.convert; import org.springframework.util.Assert; diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java index 60afd41ed..5f71519ea 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java @@ -35,7 +35,6 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.type.TupleType; /** * Cassandra specific {@link BasicPersistentEntity} implementation that adds Cassandra specific metadata. @@ -231,15 +230,6 @@ public class BasicCassandraPersistentEntity extends BasicPersistentEntity extends BasicPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { - this(property, owner, simpleTypeHolder, null); - } - - /** - * Create a new {@link BasicCassandraPersistentProperty}. - * - * @param property the actual {@link Property} in the domain entity corresponding to this persistent entity. - * @param owner the containing object or {@link CassandraPersistentEntity} of this persistent property. - * @param simpleTypeHolder mapping of Java [simple|wrapper] types to Cassandra data types. - * @param userTypeResolver resolver for user-defined types. - */ - public BasicCassandraPersistentProperty(Property property, CassandraPersistentEntity owner, - SimpleTypeHolder simpleTypeHolder, @Nullable UserTypeResolver userTypeResolver) { - super(property, owner, simpleTypeHolder); - - this.userTypeResolver = userTypeResolver; } /* (non-Javadoc) @@ -162,128 +135,6 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP return annotation != null ? annotation.ordering() : null; } - /* (non-Javadoc) - * @see org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty#getDataType() - */ - @Override - public DataType getDataType() { - - DataType dataType = findDataType(); - - if (dataType == null) { - throw new InvalidDataAccessApiUsageException(String.format( - "Unknown type [%s] for property [%s] in entity [%s]; only primitive types and Collections or Maps of primitive types are allowed", - getType(), getName(), getOwner().getName())); - } - - return dataType; - } - - @Nullable - private DataType findDataType() { - - CassandraType cassandraType = findAnnotation(CassandraType.class); - - if (cassandraType != null) { - return getDataTypeFor(cassandraType); - } - - if (isMap()) { - - List> args = getTypeInformation().getTypeArguments(); - - assertTypeArguments(args.size(), 2); - - return DataTypes.mapOf(getDataTypeFor(args.get(0).getType()), getDataTypeFor(args.get(1).getType())); - } - - if (isCollectionLike()) { - - List> args = getTypeInformation().getTypeArguments(); - - assertTypeArguments(args.size(), 1); - - if (Set.class.isAssignableFrom(getType())) { - return DataTypes.setOf(getDataTypeFor(args.get(0).getType())); - } - - if (List.class.isAssignableFrom(getType())) { - return DataTypes.listOf(getDataTypeFor(args.get(0).getType())); - } - } - - return CassandraSimpleTypeHolder.getDataTypeFor(getType()); - } - - private DataType getDataTypeFor(CassandraType annotation) { - - Name type = annotation.type(); - - switch (type) { - case MAP: - assertTypeArguments(annotation.typeArguments().length, 2); - return DataTypes.mapOf(CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[0]), - CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[1])); - case LIST: - assertTypeArguments(annotation.typeArguments().length, 1); - return annotation.typeArguments()[0] == Name.UDT - ? DataTypes.listOf(getUserType(annotation)) - : DataTypes.listOf(CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[0])); - case SET: - assertTypeArguments(annotation.typeArguments().length, 1); - return annotation.typeArguments()[0] == Name.UDT - ? DataTypes.setOf(getUserType(annotation)) - : DataTypes.setOf(CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[0])); - case UDT: - return getUserType(annotation); - default: - return CassandraSimpleTypeHolder.getDataTypeFor(type); - } - } - - private DataType getUserType(CassandraType annotation) { - - if (!StringUtils.hasText(annotation.userTypeName())) { - throw new InvalidDataAccessApiUsageException( - String.format("Expected user type name in property ['%s'] of type ['%s'] in entity [%s]", getName(), - getType(), getOwner().getName())); - } - - Assert.state(this.userTypeResolver != null, "UserTypeResolver is null"); - - CqlIdentifier identifier = CqlIdentifier.fromCql(annotation.userTypeName()); - - UserDefinedType userType = this.userTypeResolver.resolveType(identifier); - - if (userType == null) { - throw new MappingException(String.format("User type [%s] not found", identifier)); - } - - return userType; - } - - private DataType getDataTypeFor(Class javaType) { - - DataType dataType = CassandraSimpleTypeHolder.getDataTypeFor(javaType); - - if (dataType == null) { - throw new InvalidDataAccessApiUsageException(String.format( - "Only primitive types are allowed inside Collections for property [%1$s] of type ['%2$s'] in entity [%3$s]", - getName(), getType(), getOwner().getName())); - } - - return dataType; - } - - private void assertTypeArguments(int args, int expected) { - - if (args != expected) { - throw new InvalidDataAccessApiUsageException(String.format( - "Expected [%1$s] type arguments for property ['%2$s'] of type ['%3$s'] in entity [%4$s]; actual was [%5$d]", - expected, getName(), getType(), getOwner().getName(), args)); - } - } - /* (non-Javadoc) * @see org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty#isCompositePrimaryKey() */ diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleEntity.java index e8b151ce1..3fc1a6342 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleEntity.java @@ -16,17 +16,9 @@ package org.springframework.data.cassandra.core.mapping; import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; import org.springframework.data.mapping.MappingException; -import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; -import org.springframework.util.Assert; - -import com.datastax.oss.driver.api.core.type.DataType; -import com.datastax.oss.driver.api.core.type.TupleType; /** * Cassandra Tuple-specific {@link org.springframework.data.mapping.PersistentEntity} for a mapped tuples. Mapped tuples @@ -39,28 +31,15 @@ import com.datastax.oss.driver.api.core.type.TupleType; */ public class BasicCassandraPersistentTupleEntity extends BasicCassandraPersistentEntity { - private final Lazy tupleType; - /** - * Creates a new {@link BasicCassandraPersistentTupleEntity} given {@link TypeInformation} and - * {@link TupleTypeFactory}. + * Creates a new {@link BasicCassandraPersistentTupleEntity} given {@link TypeInformation}. * * @param information must not be {@literal null}. * @param tupleTypeFactory must not be {@literal null}. */ - public BasicCassandraPersistentTupleEntity(TypeInformation information, TupleTypeFactory tupleTypeFactory) { + public BasicCassandraPersistentTupleEntity(TypeInformation information) { super(information, CassandraPersistentTupleMetadataVerifier.INSTANCE, TuplePropertyComparator.INSTANCE); - - Assert.notNull(tupleTypeFactory, "TupleTypeFactory must not be null"); - - this.tupleType = Lazy.of(() -> tupleTypeFactory.create(getTupleFieldDataTypes())); - } - - private List getTupleFieldDataTypes() { - - return StreamSupport.stream(spliterator(), false).sorted(TuplePropertyComparator.INSTANCE) - .map(CassandraPersistentProperty::getDataType).collect(Collectors.toList()); } /* (non-Javadoc) @@ -82,14 +61,6 @@ public class BasicCassandraPersistentTupleEntity extends BasicCassandraPersis return true; } - /* (non-Javadoc) - * @see org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity#getTupleType() - */ - @Override - public TupleType getTupleType() { - return this.tupleType.get(); - } - /** * {@link CassandraPersistentProperty} comparator using to sort properties by their * {@link CassandraPersistentProperty#getRequiredOrdinal()}. diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleProperty.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleProperty.java index d0f262323..4f8270219 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleProperty.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleProperty.java @@ -44,21 +44,7 @@ public class BasicCassandraPersistentTupleProperty extends BasicCassandraPersist public BasicCassandraPersistentTupleProperty(Property property, CassandraPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { - this(property, owner, simpleTypeHolder, null); - } - - /** - * Create a new {@link BasicCassandraPersistentTupleProperty}. - * - * @param property the actual {@link Property} in the domain entity corresponding to this persistent entity. - * @param owner the containing object or {@link CassandraPersistentEntity} of this persistent property. - * @param simpleTypeHolder mapping of Java [simple|wrapper] types to Cassandra data types. - * @param userTypeResolver resolver for user-defined types. - */ - public BasicCassandraPersistentTupleProperty(Property property, CassandraPersistentEntity owner, - SimpleTypeHolder simpleTypeHolder, @Nullable UserTypeResolver userTypeResolver) { - - super(property, owner, simpleTypeHolder, userTypeResolver); + super(property, owner, simpleTypeHolder); this.ordinal = findOrdinal(); } 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 d6f3f77a4..9dabd1c26 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,14 @@ */ package org.springframework.data.cassandra.core.mapping; -import static org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification.*; -import static org.springframework.data.cassandra.core.mapping.CassandraType.*; - -import java.util.*; -import java.util.function.Supplier; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +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; @@ -27,32 +30,21 @@ import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.cassandra.core.convert.CassandraCustomConversions; -import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification; -import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification; -import org.springframework.data.cassandra.core.cql.keyspace.CreateUserTypeSpecification; import org.springframework.data.convert.CustomConversions; -import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.context.AbstractMappingContext; import org.springframework.data.mapping.context.MappingContext; 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; import org.springframework.util.StringUtils; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.data.TupleValue; -import com.datastax.oss.driver.api.core.data.UdtValue; -import com.datastax.oss.driver.api.core.detach.AttachmentPoint; import com.datastax.oss.driver.api.core.type.DataType; -import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.core.type.TupleType; import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** @@ -76,15 +68,13 @@ public class CassandraMappingContext private @Nullable ClassLoader beanClassLoader; - private CustomConversions customConversions = new CassandraCustomConversions(Collections.emptyList()); + private @Deprecated CustomConversions customConversions = new CassandraCustomConversions(Collections.emptyList()); private Mapping mapping = new Mapping(); - private TupleTypeFactory tupleTypeFactory = SimpleTupleTypeFactory.DEFAULT; + private @Deprecated @Nullable UserTypeResolver userTypeResolver; - private @Nullable UserTypeResolver userTypeResolver; - - private CodecRegistry codecRegistry = CodecRegistry.DEFAULT; + private @Deprecated CodecRegistry codecRegistry = CodecRegistry.DEFAULT; // caches private final Map>> entitySetsByTableName = new HashMap<>(); @@ -105,7 +95,10 @@ public class CassandraMappingContext * @param userTypeResolver must not be {@literal null}. * @param tupleTypeFactory must not be {@literal null}. * @since 2.1 + * @deprecated since 3.0, {@link UserTypeResolver} and {@link TupleTypeFactory} no longer required here as high-level + * type resolution went into {@link org.springframework.data.cassandra.core.convert.CassandraConverter}. */ + @Deprecated public CassandraMappingContext(UserTypeResolver userTypeResolver, TupleTypeFactory tupleTypeFactory) { setUserTypeResolver(userTypeResolver); @@ -192,7 +185,10 @@ public class CassandraMappingContext * * @param customConversions must not be {@literal null}. * @since 1.5 + * @deprecated since 3.0. Use custom conversion through + * {@link org.springframework.data.cassandra.core.convert.MappingCassandraConverter}. */ + @Deprecated public void setCustomConversions(CustomConversions customConversions) { Assert.notNull(customConversions, "CustomConversions must not be null"); @@ -200,6 +196,12 @@ public class CassandraMappingContext this.customConversions = customConversions; } + /** + * @return + * @deprecated since 3.0. Use custom conversion through + * {@link org.springframework.data.cassandra.core.convert.MappingCassandraConverter}. + */ + @Deprecated public CustomConversions getCustomConversions() { return customConversions; } @@ -239,7 +241,10 @@ public class CassandraMappingContext * * @param codecRegistry must not be {@literal null}. * @since 2.2 + * @deprecated since 3.0. Set {@link CodecRegistry} directly on + * {@link org.springframework.data.cassandra.core.convert.CassandraConverter}. */ + @Deprecated public void setCodecRegistry(CodecRegistry codecRegistry) { Assert.notNull(codecRegistry, "CodecRegistry must not be null"); @@ -247,7 +252,12 @@ public class CassandraMappingContext this.codecRegistry = codecRegistry; } - @NonNull + /** + * @return + * @deprecated since 3.0. Retrieve {@link CodecRegistry} directly from + * {@link org.springframework.data.cassandra.core.convert.CassandraConverter}. + */ + @Deprecated public CodecRegistry getCodecRegistry() { return this.codecRegistry; } @@ -257,25 +267,21 @@ public class CassandraMappingContext * * @param tupleTypeFactory must not be {@literal null}. * @since 2.1 + * @deprecated since 3.0. Tuple type creation uses + * {@link com.datastax.oss.driver.api.core.type.DataTypes#tupleOf(DataType...)} */ - public void setTupleTypeFactory(TupleTypeFactory tupleTypeFactory) { - - Assert.notNull(tupleTypeFactory, "TupleTypeFactory must not be null"); - - this.tupleTypeFactory = tupleTypeFactory; - } - - @NonNull - protected TupleTypeFactory getTupleTypeFactory() { - return this.tupleTypeFactory; - } + @Deprecated + public void setTupleTypeFactory(TupleTypeFactory tupleTypeFactory) {} /** * Sets the {@link UserTypeResolver}. * * @param userTypeResolver must not be {@literal null}. * @since 1.5 + * @deprecated since 3.0. Set {@link UserTypeResolver} directly on + * {@link org.springframework.data.cassandra.core.convert.CassandraConverter}. */ + @Deprecated public void setUserTypeResolver(UserTypeResolver userTypeResolver) { Assert.notNull(userTypeResolver, "UserTypeResolver must not be null"); @@ -283,7 +289,13 @@ public class CassandraMappingContext this.userTypeResolver = userTypeResolver; } + /** + * @return + * @deprecated since 3.0. Retrieve {@link UserTypeResolver} directly from + * {@link org.springframework.data.cassandra.core.convert.CassandraConverter}. + */ @Nullable + @Deprecated public UserTypeResolver getUserTypeResolver() { return this.userTypeResolver; } @@ -350,8 +362,8 @@ public class CassandraMappingContext protected BasicCassandraPersistentEntity createPersistentEntity(TypeInformation typeInformation) { BasicCassandraPersistentEntity entity = isUserDefinedType(typeInformation) - ? new CassandraUserTypePersistentEntity<>(typeInformation, getVerifier(), resolveUserTypeResolver()) - : isTuple(typeInformation) ? new BasicCassandraPersistentTupleEntity<>(typeInformation, getTupleTypeFactory()) + ? new CassandraUserTypePersistentEntity<>(typeInformation, getVerifier()) + : isTuple(typeInformation) ? new BasicCassandraPersistentTupleEntity<>(typeInformation) : new BasicCassandraPersistentEntity<>(typeInformation, getVerifier()); Optional.ofNullable(this.applicationContext).ifPresent(entity::setApplicationContext); @@ -367,16 +379,6 @@ public class CassandraMappingContext return AnnotatedElementUtils.hasAnnotation(typeInformation.getType(), UserDefinedType.class); } - @NonNull - private UserTypeResolver resolveUserTypeResolver() { - - UserTypeResolver resolvedUserTypeResolver = getUserTypeResolver(); - - 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) */ @@ -385,8 +387,8 @@ public class CassandraMappingContext BasicCassandraPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { BasicCassandraPersistentProperty persistentProperty = owner.isTupleType() - ? new BasicCassandraPersistentTupleProperty(property, owner, simpleTypeHolder, getUserTypeResolver()) - : new BasicCassandraPersistentProperty(property, owner, simpleTypeHolder, getUserTypeResolver()); + ? new BasicCassandraPersistentTupleProperty(property, owner, simpleTypeHolder) + : new BasicCassandraPersistentProperty(property, owner, simpleTypeHolder); Optional.ofNullable(this.applicationContext).ifPresent(persistentProperty::setApplicationContext); @@ -434,543 +436,4 @@ public class CassandraMappingContext .anyMatch(identifier::equals); } - /** - * Returns a {@link CreateTableSpecification} for the given entity, including all mapping information. - * - * @param entity must not be {@literal null}. - * @return - */ - public CreateTableSpecification getCreateTableSpecificationFor(CassandraPersistentEntity entity) { - - Assert.notNull(entity, "CassandraPersistentEntity must not be null"); - - return getCreateTableSpecificationFor(entity.getTableName(), entity); - } - - /** - * Returns a {@link CreateTableSpecification} for the given entity using {@code tableName}, including all mapping - * information. - * - * @param tableName must not be {@literal null}. - * @param entity must not be {@literal null}. - * @return - * @since 2.2 - */ - public CreateTableSpecification getCreateTableSpecificationFor( - com.datastax.oss.driver.api.core.CqlIdentifier tableName, CassandraPersistentEntity entity) { - - Assert.notNull(tableName, "Table name must not be null"); - Assert.notNull(entity, "CassandraPersistentEntity must not be null"); - - CreateTableSpecification specification = createTable(tableName); - - for (CassandraPersistentProperty property : entity) { - - if (property.isCompositePrimaryKey()) { - - CassandraPersistentEntity primaryKeyEntity = getRequiredPersistentEntity(property.getRawType()); - - for (CassandraPersistentProperty primaryKeyProperty : primaryKeyEntity) { - - DataType dataType = getDataTypeWithUserTypeFactory(primaryKeyProperty, DataTypeProvider.ShallowType); - - if (primaryKeyProperty.isPartitionKeyColumn()) { - specification.partitionKeyColumn(primaryKeyProperty.getRequiredColumnName(), dataType); - } else { // cluster column - specification.clusteredKeyColumn(primaryKeyProperty.getRequiredColumnName(), dataType, - primaryKeyProperty.getPrimaryKeyOrdering()); - } - } - } else { - DataType type = UserTypeUtil - .potentiallyFreeze(getDataTypeWithUserTypeFactory(property, DataTypeProvider.ShallowType)); - - if (property.isIdProperty() || property.isPartitionKeyColumn()) { - specification.partitionKeyColumn(property.getRequiredColumnName(), type); - } else if (property.isClusterKeyColumn()) { - specification.clusteredKeyColumn(property.getRequiredColumnName(), type, property.getPrimaryKeyOrdering()); - } else { - specification.column(property.getRequiredColumnName(), type); - } - } - } - - if (specification.getPartitionKeyColumns().isEmpty()) { - throw new MappingException(String.format("No partition key columns found in entity [%s]", entity.getType())); - } - - return specification; - } - - /** - * @param entity must not be {@literal null}. - * @return - * @since 2.0 - */ - public List getCreateIndexSpecificationsFor(CassandraPersistentEntity entity) { - - Assert.notNull(entity, "CassandraPersistentEntity must not be null"); - - List indexes = new ArrayList<>(); - - for (CassandraPersistentProperty property : entity) { - if (property.isCompositePrimaryKey()) { - indexes.addAll(getCreateIndexSpecificationsFor(getRequiredPersistentEntity(property))); - } else { - indexes.addAll(IndexSpecificationFactory.createIndexSpecifications(property)); - } - } - - indexes.forEach(it -> it.tableName(entity.getTableName())); - - return indexes; - } - - /** - * Returns a {@link CreateUserTypeSpecification} for the given entity, including all mapping information. - * - * @param entity must not be {@literal null}. - */ - public CreateUserTypeSpecification getCreateUserTypeSpecificationFor(CassandraPersistentEntity entity) { - - Assert.notNull(entity, "CassandraPersistentEntity must not be null"); - - CreateUserTypeSpecification specification = CreateUserTypeSpecification.createType(entity.getTableName()); - - for (CassandraPersistentProperty property : entity) { - // Use frozen literal to not resolve types from Cassandra; At this stage, they might be not created yet. - specification.field(property.getRequiredColumnName(), - getDataTypeWithUserTypeFactory(property, DataTypeProvider.FrozenLiteral)); - } - - if (specification.getFields().isEmpty()) { - throw new MappingException(String.format("No fields in user type [%s]", entity.getType())); - } - - 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 CassandraSimpleTypeHolder.getDataTypeFor(this.customConversions.getCustomWriteTarget(type).orElse(type)); - } - - public TupleType getTupleType(CassandraPersistentEntity persistentEntity) { - - Assert.notNull(persistentEntity, "CassandraPersistentEntity must not be null"); - Assert.isTrue(persistentEntity.isTupleType(), "CassandraPersistentEntity is not a mapped tuple type"); - - return getTupleType(DataTypeProvider.EntityUserType, persistentEntity); - } - - /** - * Retrieve the data type of the property. Cassandra {@link DataType types} are determined using simple types and - * configured {@link org.springframework.data.convert.CustomConversions}. - * - * @param property 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(CassandraPersistentProperty property) { - return getDataTypeWithUserTypeFactory(property, DataTypeProvider.EntityUserType); - } - - private DataType getDataTypeWithUserTypeFactory(CassandraPersistentProperty property, - DataTypeProvider dataTypeProvider) { - - if (property.isAnnotationPresent(CassandraType.class)) { - - CassandraType annotation = property.getRequiredAnnotation(CassandraType.class); - - if (annotation.type() == Name.TUPLE) { - - DataType[] dataTypes = Arrays.stream(annotation.typeArguments()).map(CassandraSimpleTypeHolder::getDataTypeFor) - .toArray(DataType[]::new); - - return getTupleTypeFactory().create(dataTypes); - } - - if (annotation.type() == Name.UDT) { - - com.datastax.oss.driver.api.core.CqlIdentifier userTypeName = com.datastax.oss.driver.api.core.CqlIdentifier - .fromCql(annotation.userTypeName()); - - DataType userType = dataTypeProvider.getUserType(userTypeName, resolveUserTypeResolver()); - - if (userType == null) { - throw new MappingException(String.format("User type [%s] not found", userTypeName)); - } - - DataType dataType = getUserDataType(property.getTypeInformation(), userType); - - if (dataType != null) { - return dataType; - } - } - - return property.getDataType(); - } - - if (TupleValue.class.isAssignableFrom(property.getType())) { - throw new MappingException(String.format( - "Unsupported raw TupleType to DataType for property [%s] in entity [%s]; Consider adding @CassandraType.", - property.getName(), property.getOwner().getName())); - } - - if (UdtValue.class.isAssignableFrom(property.getType())) { - throw new MappingException(String.format( - "Unsupported raw UdtValue to DataType for property [%s] in entity [%s]; Consider adding @CassandraType.", - property.getName(), property.getOwner().getName())); - } - - try { - DataType dataType = getDataTypeWithUserTypeFactory(property.getTypeInformation(), dataTypeProvider, - property::getDataType); - - if (dataType == null) { - throw new MappingException( - String.format("Cannot resolve DataType for property [%s] in entity [%s]; Consider adding @CassandraType.", - property.getName(), property.getOwner().getName())); - } - - return dataType; - } catch (InvalidDataAccessApiUsageException e) { - throw new MappingException(String.format("%s. Consider adding @CassandraType.", e.getMessage()), e); - } - } - - @Nullable - private DataType getDataTypeWithUserTypeFactory(TypeInformation typeInformation, DataTypeProvider dataTypeProvider, - Supplier fallback) { - - Optional customWriteTarget = this.customConversions.getCustomWriteTarget(typeInformation.getType()) - .map(it -> { - typeInformation.getType(); - return CassandraSimpleTypeHolder.getDataTypeFor(it); - }); - - DataType dataType = customWriteTarget.orElseGet(() -> { - - Class propertyType = typeInformation.getRequiredActualType().getType(); - - return this.customConversions.getCustomWriteTarget(propertyType).filter(it -> !typeInformation.isMap()) - .map(it -> { - - if (typeInformation.isCollectionLike()) { - if (List.class.isAssignableFrom(typeInformation.getType())) { - return DataTypes.listOf(CassandraSimpleTypeHolder.getDataTypeFor(it)); - } - - if (Set.class.isAssignableFrom(typeInformation.getType())) { - return DataTypes.setOf(CassandraSimpleTypeHolder.getDataTypeFor(it)); - } - } - - return CassandraSimpleTypeHolder.getDataTypeFor(it); - - }).orElse(null); - }); - - if (dataType != null) { - return dataType; - } - - if (typeInformation.isCollectionLike()) { - - TypeInformation componentType = typeInformation.getRequiredActualType(); - BasicCassandraPersistentEntity persistentEntity = getPersistentEntity(componentType); - TypeInformation typeToUse = persistentEntity != null ? persistentEntity.getTypeInformation() : componentType; - - if (List.class.isAssignableFrom(typeInformation.getType())) { - return DataTypes.listOf(getDataTypeWithUserTypeFactory(typeToUse, dataTypeProvider, fallback)); - } - - if (Set.class.isAssignableFrom(typeInformation.getType())) { - return DataTypes.setOf(getDataTypeWithUserTypeFactory(typeToUse, dataTypeProvider, fallback)); - } - - throw new IllegalArgumentException("Unsupported collection type: " + typeInformation); - } - - if (typeInformation.isMap()) { - return getMapDataType(typeInformation, dataTypeProvider); - } - - BasicCassandraPersistentEntity persistentEntity = getPersistentEntity(typeInformation); - - if (persistentEntity != null) { - - if (persistentEntity.isUserDefinedType()) { - - DataType udtType = dataTypeProvider.getDataType(persistentEntity); - - if (udtType != null) { - return udtType; - } - } else if (persistentEntity.isTupleType()) { - return getTupleType(dataTypeProvider, persistentEntity); - } - - return dataTypeProvider.getDataType(persistentEntity); - } - - DataType determinedType = CassandraSimpleTypeHolder.getDataTypeFor(typeInformation.getType()); - - if (determinedType != null) { - return determinedType; - } - - return fallback.get(); - } - - private TupleType getTupleType(DataTypeProvider dataTypeProvider, CassandraPersistentEntity persistentEntity) { - - List types = new ArrayList<>(); - - for (CassandraPersistentProperty persistentProperty : persistentEntity) { - types.add(getDataTypeWithUserTypeFactory(persistentProperty, dataTypeProvider)); - } - - return getTupleTypeFactory().create(types); - } - - @SuppressWarnings("all") - private DataType getMapDataType(TypeInformation typeInformation, DataTypeProvider dataTypeProvider) { - - TypeInformation keyTypeInformation = typeInformation.getComponentType(); - TypeInformation valueTypeInformation = typeInformation.getMapValueType(); - - DataType keyType = getDataTypeWithUserTypeFactory(keyTypeInformation, dataTypeProvider, () -> { - - DataType type = CassandraSimpleTypeHolder.getDataTypeFor(keyTypeInformation.getType()); - - if (type != null) { - return type; - } - - throw new MappingException(String.format("Cannot resolve key type for [%s]", typeInformation)); - }); - - DataType valueType = getDataTypeWithUserTypeFactory(valueTypeInformation, dataTypeProvider, () -> { - - DataType type = CassandraSimpleTypeHolder.getDataTypeFor(valueTypeInformation.getType()); - - if (type != null) { - return type; - } - - throw new MappingException("Cannot resolve value type for " + typeInformation + "."); - }); - - return DataTypes.mapOf(keyType, valueType); - } - - @Nullable - private DataType getUserDataType(TypeInformation property, @Nullable DataType elementType) { - - if (property.isCollectionLike()) { - - if (List.class.isAssignableFrom(property.getType())) { - return DataTypes.listOf(elementType); - } - - if (Set.class.isAssignableFrom(property.getType())) { - return DataTypes.setOf(elementType); - } - } - - return !(property.isCollectionLike() || property.isMap()) ? elementType : null; - } - - /** - * @author Jens Schauder - * @author Mark Paluch - * @since 1.5.1 - */ - enum DataTypeProvider { - - EntityUserType { - - @Override - public DataType getDataType(CassandraPersistentEntity entity) { - return entity.isTupleType() ? entity.getTupleType() : entity.getUserType(); - } - - @Override - DataType getUserType(com.datastax.oss.driver.api.core.CqlIdentifier userTypeName, - UserTypeResolver userTypeResolver) { - return userTypeResolver.resolveType(userTypeName); - } - }, - - ShallowType { - - @Override - public DataType getDataType(CassandraPersistentEntity entity) { - return entity.isTupleType() ? entity.getTupleType() : new ShallowUserDefinedType(entity.getTableName(), false); - } - - @Override - DataType getUserType(com.datastax.oss.driver.api.core.CqlIdentifier userTypeName, - UserTypeResolver userTypeResolver) { - return new ShallowUserDefinedType(userTypeName, false); - } - }, - - FrozenLiteral { - - @Override - public DataType getDataType(CassandraPersistentEntity entity) { - return new ShallowUserDefinedType(entity.getTableName(), true); - } - - @Override - DataType getUserType(com.datastax.oss.driver.api.core.CqlIdentifier userTypeName, - UserTypeResolver userTypeResolver) { - return new ShallowUserDefinedType(userTypeName, true); - } - }; - - /** - * Return the data type for the {@link CassandraPersistentEntity}. - * - * @param entity must not be {@literal null}. - * @return the {@link DataType}. - */ - @Nullable - abstract DataType getDataType(CassandraPersistentEntity entity); - - /** - * Return the user-defined type {@code userTypeName}. - * - * @param userTypeName must not be {@literal null}. - * @param userTypeResolver must not be {@literal null}. - * @return the {@link DataType}. - * @since 2.0.1 - */ - @Nullable - abstract DataType getUserType(com.datastax.oss.driver.api.core.CqlIdentifier userTypeName, - UserTypeResolver userTypeResolver); - - } - - static class ShallowUserDefinedType implements com.datastax.oss.driver.api.core.type.UserDefinedType { - - private final CqlIdentifier name; - private final boolean frozen; - - public ShallowUserDefinedType(String name, boolean frozen) { - this(CqlIdentifier.fromInternal(name), frozen); - } - - public ShallowUserDefinedType(CqlIdentifier name, boolean frozen) { - this.name = name; - this.frozen = frozen; - } - - @Override - public CqlIdentifier getKeyspace() { - return null; - } - - @Override - public CqlIdentifier getName() { - return name; - } - - @Override - public boolean isFrozen() { - return frozen; - } - - @Override - public List getFieldNames() { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - @Override - public int firstIndexOf(CqlIdentifier id) { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - @Override - public int firstIndexOf(String name) { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - @Override - public List getFieldTypes() { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - @Override - public com.datastax.oss.driver.api.core.type.UserDefinedType copy(boolean newFrozen) { - return new ShallowUserDefinedType(this.name, newFrozen); - } - - @Override - public UdtValue newValue() { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - @Override - public UdtValue newValue(@edu.umd.cs.findbugs.annotations.NonNull Object... fields) { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - @Override - public AttachmentPoint getAttachmentPoint() { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - @Override - public boolean isDetached() { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - @Override - public void attach(@edu.umd.cs.findbugs.annotations.NonNull AttachmentPoint attachmentPoint) { - throw new UnsupportedOperationException( - "This implementation should only be used internally, this is likely a driver bug"); - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof com.datastax.oss.driver.api.core.type.UserDefinedType)) - return false; - com.datastax.oss.driver.api.core.type.UserDefinedType that = (com.datastax.oss.driver.api.core.type.UserDefinedType) o; - return isFrozen() == that.isFrozen() && Objects.equals(getName(), that.getName()); - } - - @Override - public int hashCode() { - return Objects.hash(name, frozen); - } - - @Override - public String toString() { - return "ShallowUserDefinedType{" + "name=" + name + ", frozen=" + frozen + '}'; - } - } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraPersistentEntity.java index 862afd7a0..ada924be6 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraPersistentEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraPersistentEntity.java @@ -16,11 +16,9 @@ package org.springframework.data.cassandra.core.mapping; import org.springframework.data.mapping.PersistentEntity; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.type.TupleType; /** * Cassandra specific {@link PersistentEntity} abstraction. @@ -79,14 +77,6 @@ public interface CassandraPersistentEntity extends PersistentEntity extends PersistentEntity extends BasicCassandraPersistentEntity { - private final UserTypeResolver resolver; - - private final Object lock = new Object(); - - private volatile @Nullable com.datastax.oss.driver.api.core.type.UserDefinedType userType; - /** * Create a new {@link CassandraUserTypePersistentEntity}. * * @param typeInformation must not be {@literal null}. * @param verifier must not be {@literal null}. - * @param resolver must not be {@literal null}. */ public CassandraUserTypePersistentEntity(TypeInformation typeInformation, - CassandraPersistentEntityMetadataVerifier verifier, UserTypeResolver resolver) { + CassandraPersistentEntityMetadataVerifier verifier) { super(typeInformation, verifier); - - Assert.notNull(resolver, "UserTypeResolver must not be null"); - - this.resolver = resolver; } /* (non-Javadoc) @@ -77,29 +63,4 @@ public class CassandraUserTypePersistentEntity extends BasicCassandraPersiste public boolean isUserDefinedType() { return true; } - - /* (non-Javadoc) - * @see org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity#getUserType() - */ - @Override - public com.datastax.oss.driver.api.core.type.UserDefinedType getUserType() { - - if (userType == null) { - synchronized (lock) { - if (userType == null) { - - CqlIdentifier identifier = determineTableName(); - com.datastax.oss.driver.api.core.type.UserDefinedType userType = resolver.resolveType(identifier); - - if (userType == null) { - throw new MappingException(String.format("User type [%s] not found", identifier)); - } - - this.userType = userType; - } - } - } - - return userType; - } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/SimpleTupleTypeFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/SimpleTupleTypeFactory.java index a0f2b9f6f..f0f5f4547 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/SimpleTupleTypeFactory.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/SimpleTupleTypeFactory.java @@ -30,6 +30,7 @@ import com.datastax.oss.driver.internal.core.type.DefaultTupleType; * @author Mark Paluch * @since 3.0 */ + public enum SimpleTupleTypeFactory implements TupleTypeFactory { /** diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/TupleTypeFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/TupleTypeFactory.java index fcb175c36..f52822eae 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/TupleTypeFactory.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/TupleTypeFactory.java @@ -30,8 +30,10 @@ import com.datastax.oss.driver.api.core.type.TupleType; * @since 2.1 * @see SimpleTupleTypeFactory * @see CodecRegistryTupleTypeFactory + * @deprecated since 3.0 */ @FunctionalInterface +@Deprecated public interface TupleTypeFactory { /** 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 d642ac599..82540a67b 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 @@ -35,7 +35,6 @@ import org.springframework.util.Assert; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** * Base class for {@link RepositoryQuery} implementations for Cassandra. @@ -48,7 +47,6 @@ public abstract class AbstractCassandraQuery extends CassandraRepositoryQuerySup private final CassandraOperations operations; - private final CodecRegistry codecRegistry; private static CassandraConverter toConverter(CassandraOperations operations) { @@ -73,7 +71,6 @@ public abstract class AbstractCassandraQuery extends CassandraRepositoryQuerySup super(queryMethod, toMappingContext(operations)); this.operations = operations; - this.codecRegistry = operations.getConverter().getMappingContext().getCodecRegistry(); } /** @@ -94,7 +91,7 @@ public abstract class AbstractCassandraQuery extends CassandraRepositoryQuerySup public Object execute(Object[] parameters) { CassandraParameterAccessor parameterAccessor = new ConvertingParameterAccessor(toConverter(getOperations()), - new CassandraParametersParameterAccessor(getQueryMethod(), parameters), codecRegistry); + new CassandraParametersParameterAccessor(getQueryMethod(), parameters)); ResultProcessor resultProcessor = getQueryMethod().getResultProcessor().withDynamicProjection(parameterAccessor); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/AbstractReactiveCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/AbstractReactiveCassandraQuery.java index 0ea15d7f6..175888c2d 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/AbstractReactiveCassandraQuery.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/AbstractReactiveCassandraQuery.java @@ -39,7 +39,6 @@ import org.springframework.util.Assert; import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.cql.Statement; -import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** * Base class for reactive {@link RepositoryQuery} implementations for Cassandra. @@ -53,8 +52,6 @@ public abstract class AbstractReactiveCassandraQuery extends CassandraRepository private final ReactiveCassandraOperations operations; - private final CodecRegistry codecRegistry; - /** * Create a new {@link AbstractReactiveCassandraQuery} from the given {@link CassandraQueryMethod} and * {@link CassandraOperations}. @@ -67,7 +64,6 @@ public abstract class AbstractReactiveCassandraQuery extends CassandraRepository super(method, getRequiredMappingContext(operations)); this.operations = operations; - this.codecRegistry = operations.getConverter().getMappingContext().getCodecRegistry(); } /* @@ -102,7 +98,7 @@ public abstract class AbstractReactiveCassandraQuery extends CassandraRepository parameters); CassandraParameterAccessor convertingParameterAccessor = new ConvertingParameterAccessor( - getRequiredConverter(getReactiveCassandraOperations()), parameterAccessor, codecRegistry); + getRequiredConverter(getReactiveCassandraOperations()), parameterAccessor); Statement statement = createQuery(convertingParameterAccessor); 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 74f36d6cc..638c0711d 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 @@ -18,7 +18,6 @@ package org.springframework.data.cassandra.repository.query; import java.util.Collection; import java.util.Iterator; import java.util.Optional; -import java.util.Set; import org.springframework.data.cassandra.core.convert.CassandraConverter; import org.springframework.data.cassandra.core.cql.QueryOptions; @@ -26,12 +25,9 @@ import org.springframework.data.cassandra.core.mapping.CassandraPersistentProper import org.springframework.data.cassandra.core.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 org.springframework.lang.Nullable; import com.datastax.oss.driver.api.core.type.DataType; -import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** * Custom {@link org.springframework.data.repository.query.ParameterAccessor} that uses a {@link CassandraConverter} to @@ -43,14 +39,11 @@ import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; */ class ConvertingParameterAccessor implements CassandraParameterAccessor { - private static final TypeInformation SET = ClassTypeInformation.from(Set.class); - private final CassandraConverter converter; private final CassandraParameterAccessor delegate; - ConvertingParameterAccessor(CassandraConverter converter, CassandraParameterAccessor delegate, - CodecRegistry codecRegistry) { + ConvertingParameterAccessor(CassandraConverter converter, CassandraParameterAccessor delegate) { this.converter = converter; this.delegate = delegate; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java index 52ce331e3..d4a712a1e 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/CassandraPersistentEntitySchemaCreatorUnitTests.java @@ -33,14 +33,17 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.cassandra.core.convert.SchemaFactory; import org.springframework.data.cassandra.core.cql.CqlOperations; import org.springframework.data.cassandra.core.cql.keyspace.CreateUserTypeSpecification; import org.springframework.data.cassandra.core.cql.keyspace.UserTypeNameSpecification; import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity; import org.springframework.data.cassandra.core.mapping.UserDefinedType; +import org.springframework.data.convert.CustomConversions; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** * Unit tests for {@link CassandraPersistentEntitySchemaCreator}. @@ -66,6 +69,9 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests extends CassandraPe }); when(adminOperations.getCqlOperations()).thenReturn(operations); + when(adminOperations.getSchemaFactory()).thenReturn(new SchemaFactory(context, + new CustomConversions(CustomConversions.StoreConversions.NONE, Collections.emptyList()), + CodecRegistry.DEFAULT)); } @Test // DATACASS-687 diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/CassandraTypeMappingIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/CassandraTypeMappingIntegrationTests.java index c2387da0c..74e409158 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/CassandraTypeMappingIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/CassandraTypeMappingIntegrationTests.java @@ -46,7 +46,6 @@ import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.data.cassandra.core.CassandraTemplate; -import org.springframework.data.cassandra.core.mapping.SimpleTupleTypeFactory; import org.springframework.data.cassandra.domain.AllPossibleTypes; import org.springframework.data.cassandra.repository.support.SchemaTestUtils; import org.springframework.data.cassandra.support.CassandraVersion; @@ -470,7 +469,7 @@ public class CassandraTypeMappingIntegrationTests extends AbstractKeyspaceCreati @Test // DATACASS-284 public void shouldReadAndWriteTupleType() { - TupleType tupleType = SimpleTupleTypeFactory.DEFAULT.create(DataTypes.TEXT, DataTypes.BIGINT); + TupleType tupleType = DataTypes.tupleOf(DataTypes.TEXT, DataTypes.BIGINT); AllPossibleTypes entity = new AllPossibleTypes("1"); @@ -487,7 +486,7 @@ public class CassandraTypeMappingIntegrationTests extends AbstractKeyspaceCreati @Test // DATACASS-284 public void shouldReadAndWriteListOfTuples() { - TupleType tupleType = SimpleTupleTypeFactory.DEFAULT.create(DataTypes.TEXT, DataTypes.BIGINT); + TupleType tupleType = DataTypes.tupleOf(DataTypes.TEXT, DataTypes.BIGINT); ListOfTuples entity = new ListOfTuples(); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolverUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolverUnitTests.java index e810f96dd..d2a87d028 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolverUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolverUnitTests.java @@ -21,16 +21,20 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import org.junit.Test; import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity; import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; import org.springframework.data.cassandra.core.mapping.CassandraType; +import org.springframework.data.mapping.MappingException; import org.springframework.data.util.ClassTypeInformation; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** * Unit tests for {@link DefaultColumnTypeResolver}. @@ -40,7 +44,8 @@ import com.datastax.oss.driver.api.core.type.DataTypes; public class ColumnTypeResolverUnitTests { CassandraMappingContext mappingContext = new CassandraMappingContext(); - ColumnTypeResolver resolver = new DefaultColumnTypeResolver(mappingContext); + ColumnTypeResolver resolver = new DefaultColumnTypeResolver(mappingContext, null, () -> CodecRegistry.DEFAULT, + mappingContext::getCustomConversions); @Test // DATACASS-743 public void shouldResolveSimpleType() { @@ -177,10 +182,23 @@ public class ColumnTypeResolverUnitTests { BasicCassandraPersistentEntity entity = mappingContext.getRequiredPersistentEntity(Person.class); - assertThat(resolver.resolve(entity.getRequiredPersistentProperty("tupleValue")).getType()) + CassandraColumnType columnType = resolver.resolve(entity.getRequiredPersistentProperty("tupleValue")); + assertThat(columnType.getType()) .isEqualTo(TupleValue.class); - assertThat(resolver.resolve(entity.getRequiredPersistentProperty("tupleValue")).getDataType()) - .isEqualTo(DataTypes.tupleOf()); + + assertThatThrownBy(columnType::getDataType).isInstanceOf(MappingException.class); + } + + @Test // DATACASS-375, DATACASS-743 + public void UuidshouldMapToUUIDByDefault() { + + CassandraPersistentProperty uuidProperty = mappingContext.getRequiredPersistentEntity(TypeWithUUIDColumn.class) + .getRequiredPersistentProperty("uuid"); + CassandraPersistentProperty timeUUIDProperty = mappingContext.getRequiredPersistentEntity(TypeWithUUIDColumn.class) + .getRequiredPersistentProperty("timeUUID"); + + assertThat(resolver.resolve(uuidProperty).getDataType()).isEqualTo(DataTypes.UUID); + assertThat(resolver.resolve(timeUUIDProperty).getDataType()).isEqualTo(DataTypes.TIMEUUID); } static class Person { @@ -213,6 +231,13 @@ public class ColumnTypeResolverUnitTests { TupleValue tupleValue; } + static class TypeWithUUIDColumn { + + UUID uuid; + + @CassandraType(type = CassandraType.Name.TIMEUUID) UUID timeUUID; + } + enum MyEnum { INSTANCE; } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/IndexCreationIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/IndexCreationIntegrationTests.java similarity index 80% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/IndexCreationIntegrationTests.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/IndexCreationIntegrationTests.java index cfb1bb2fe..236951c2b 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/IndexCreationIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/IndexCreationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2020 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.core.mapping; +package org.springframework.data.cassandra.core.convert; import static org.assertj.core.api.Assertions.*; import static org.junit.Assume.*; @@ -29,6 +29,10 @@ import org.springframework.data.cassandra.core.cql.generator.CreateIndexCqlGener import org.springframework.data.cassandra.core.cql.generator.CreateTableCqlGenerator; import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification; import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification; +import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity; +import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.core.mapping.Indexed; +import org.springframework.data.cassandra.core.mapping.SASI; import org.springframework.data.cassandra.core.mapping.SASI.StandardAnalyzed; import org.springframework.data.cassandra.support.CassandraVersion; import org.springframework.data.cassandra.test.util.AbstractKeyspaceCreatingIntegrationTest; @@ -46,6 +50,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; public class IndexCreationIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest { private CassandraMappingContext mappingContext = new CassandraMappingContext(); + private SchemaFactory schemaFactory = new SchemaFactory(new MappingCassandraConverter(mappingContext)); private Version cassandraVersion; @Before @@ -60,8 +65,8 @@ public class IndexCreationIntegrationTests extends AbstractKeyspaceCreatingInteg public void shouldCreateSecondaryIndex() throws InterruptedException { BasicCassandraPersistentEntity entity = mappingContext.getRequiredPersistentEntity(WithSecondaryIndex.class); - CreateTableSpecification createTable = mappingContext.getCreateTableSpecificationFor(entity); - List createIndexes = mappingContext.getCreateIndexSpecificationsFor(entity); + CreateTableSpecification createTable = schemaFactory.getCreateTableSpecificationFor(entity); + List createIndexes = schemaFactory.getCreateIndexSpecificationsFor(entity); session.execute(CreateTableCqlGenerator.toCql(createTable)); createIndexes.forEach(it -> session.execute(CreateIndexCqlGenerator.toCql(it))); @@ -78,8 +83,8 @@ public class IndexCreationIntegrationTests extends AbstractKeyspaceCreatingInteg public void shouldCreateSasiIndex() throws InterruptedException { BasicCassandraPersistentEntity entity = mappingContext.getRequiredPersistentEntity(WithSasiIndex.class); - CreateTableSpecification createTable = mappingContext.getCreateTableSpecificationFor(entity); - List createIndexes = mappingContext.getCreateIndexSpecificationsFor(entity); + CreateTableSpecification createTable = schemaFactory.getCreateTableSpecificationFor(entity); + List createIndexes = schemaFactory.getCreateIndexSpecificationsFor(entity); session.execute(CreateTableCqlGenerator.toCql(createTable)); createIndexes.forEach(it -> session.execute(CreateIndexCqlGenerator.toCql(it))); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/IndexSpecificationFactoryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/IndexSpecificationFactoryUnitTests.java similarity index 90% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/IndexSpecificationFactoryUnitTests.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/IndexSpecificationFactoryUnitTests.java index edb8079c6..134e0e509 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/IndexSpecificationFactoryUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/IndexSpecificationFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2020 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.core.mapping; +package org.springframework.data.cassandra.core.convert; import static org.assertj.core.api.Assertions.*; @@ -26,6 +26,11 @@ import org.springframework.data.annotation.AccessType; import org.springframework.data.annotation.AccessType.Type; import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification; import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification.ColumnFunction; +import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; +import org.springframework.data.cassandra.core.mapping.Indexed; +import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.core.mapping.SASI; import org.springframework.data.cassandra.core.mapping.SASI.NonTokenizingAnalyzed; import org.springframework.data.cassandra.core.mapping.SASI.Normalization; import org.springframework.data.cassandra.core.mapping.SASI.StandardAnalyzed; @@ -91,8 +96,8 @@ public class IndexSpecificationFactoryUnitTests { assertThat(simpleSasi.isCustom()).isTrue(); assertThat(simpleSasi.getUsing()).isEqualTo("org.apache.cassandra.index.sasi.SASIIndex"); assertThat(simpleSasi.getColumnFunction()).isEqualTo(ColumnFunction.NONE); - assertThat(simpleSasi.getOptions()).containsEntry("mode", "PREFIX") - .doesNotContainKeys("analyzed", "analyzer_class"); + assertThat(simpleSasi.getOptions()).containsEntry("mode", "PREFIX").doesNotContainKeys("analyzed", + "analyzer_class"); } @Test // DATACASS-306 @@ -101,8 +106,7 @@ public class IndexSpecificationFactoryUnitTests { CreateIndexSpecification simpleSasi = createIndexFor(IndexedType.class, "sasiStandard"); assertThat(simpleSasi.getColumnFunction()).isEqualTo(ColumnFunction.NONE); - assertThat(simpleSasi.getOptions()).containsEntry("mode", "PREFIX") - .containsEntry("analyzed", "true") + assertThat(simpleSasi.getOptions()).containsEntry("mode", "PREFIX").containsEntry("analyzed", "true") .containsEntry("tokenization_skip_stop_words", "false") .containsEntry("analyzer_class", "org.apache.cassandra.index.sasi.analyzer.StandardAnalyzer") .containsEntry("tokenization_locale", "de"); @@ -115,8 +119,7 @@ public class IndexSpecificationFactoryUnitTests { assertThat(simpleSasi.getColumnFunction()).isEqualTo(ColumnFunction.NONE); assertThat(simpleSasi.getOptions()).containsEntry("tokenization_skip_stop_words", "true") - .containsEntry("tokenization_locale", "de") - .containsEntry("tokenization_enable_stemming", "true") + .containsEntry("tokenization_locale", "de").containsEntry("tokenization_enable_stemming", "true") .containsEntry("tokenization_normalize_uppercase", "true") .doesNotContainKey("tokenization_normalize_lowercase"); } @@ -136,8 +139,7 @@ public class IndexSpecificationFactoryUnitTests { CreateIndexSpecification simpleSasi = createIndexFor(IndexedType.class, "sasiNontokenizing"); - assertThat(simpleSasi.getOptions()).containsEntry("mode", "PREFIX") - .containsEntry("analyzed", "true") + assertThat(simpleSasi.getOptions()).containsEntry("mode", "PREFIX").containsEntry("analyzed", "true") .containsEntry("case_sensitive", "true") .containsEntry("analyzer_class", "org.apache.cassandra.index.sasi.analyzer.NonTokenizingAnalyzer") .doesNotContainKeys("normalize_lowercase", "normalize_uppercase"); @@ -149,8 +151,7 @@ public class IndexSpecificationFactoryUnitTests { CreateIndexSpecification simpleSasi = createIndexFor(IndexedType.class, "sasiNontokenizingLowercase"); assertThat(simpleSasi.getOptions()).containsEntry("normalize_lowercase", "true") - .containsEntry("case_sensitive", "false") - .doesNotContainKey("normalize_uppercase"); + .containsEntry("case_sensitive", "false").doesNotContainKey("normalize_uppercase"); } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterMappedTupleUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterMappedTupleUnitTests.java index 78288f6ac..1e2f0ebcb 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterMappedTupleUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterMappedTupleUnitTests.java @@ -39,6 +39,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; import com.datastax.oss.driver.api.core.cql.Row; import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.type.DataTypes; +import com.datastax.oss.driver.api.core.type.TupleType; /** * Unit tests for mapped tuples through {@link MappingCassandraConverter}. @@ -67,10 +68,12 @@ public class MappingCassandraConverterMappedTupleUnitTests { BasicCassandraPersistentEntity entity = this.mappingContext.getRequiredPersistentEntity(MappedTuple.class); - TupleValue value = entity.getTupleType().newValue("hello", 1); + CassandraColumnType type = mappingCassandraConverter.getColumnTypeResolver().resolve(entity.getTypeInformation()); + + TupleValue value = ((TupleType) type.getDataType()).newValue("hello", 1); this.rowMock = RowMockUtil.newRowMock(column("name", "Jon Doe", DataTypes.TEXT), - column("tuple", value, entity.getTupleType())); + column("tuple", value, type.getDataType())); Person person = this.mappingCassandraConverter.read(Person.class, rowMock); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterTupleIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterTupleIntegrationTests.java index 19f6f14b7..894ec12bb 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterTupleIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterTupleIntegrationTests.java @@ -44,7 +44,6 @@ import org.springframework.data.cassandra.core.cql.keyspace.CreateUserTypeSpecif import org.springframework.data.cassandra.core.cql.util.StatementBuilder; import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; import org.springframework.data.cassandra.core.mapping.Element; -import org.springframework.data.cassandra.core.mapping.SimpleTupleTypeFactory; import org.springframework.data.cassandra.core.mapping.Table; import org.springframework.data.cassandra.core.mapping.Tuple; import org.springframework.data.cassandra.core.mapping.UserDefinedType; @@ -105,8 +104,9 @@ public class MappingCassandraConverterTupleIntegrationTests extends AbstractSpri this.session.execute("DROP TABLE IF EXISTS person;"); CassandraMappingContext mappingContext = converter.getMappingContext(); + SchemaFactory schemaFactory = new SchemaFactory(converter); - CreateUserTypeSpecification createAddress = mappingContext + CreateUserTypeSpecification createAddress = schemaFactory .getCreateUserTypeSpecificationFor(mappingContext.getRequiredPersistentEntity(AddressUserType.class)); this.session.execute(CreateUserTypeCqlGenerator.toCql(createAddress)); @@ -127,7 +127,7 @@ public class MappingCassandraConverterTupleIntegrationTests extends AbstractSpri @Test // DATACASS-651 public void shouldInsertRowWithTuple() { - TupleType tupleType = SimpleTupleTypeFactory.DEFAULT.create(DataTypes.TEXT, DataTypes.INT); + TupleType tupleType = DataTypes.tupleOf(DataTypes.TEXT, DataTypes.INT); Person person = new Person(); @@ -207,7 +207,7 @@ public class MappingCassandraConverterTupleIntegrationTests extends AbstractSpri person.setMapOfTuples(Collections.singletonMap("foo", tuple)); - TupleType tupleType = SimpleTupleTypeFactory.DEFAULT.create(DataTypes.TEXT, DataTypes.INT); + TupleType tupleType = DataTypes.tupleOf(DataTypes.TEXT, DataTypes.INT); person.setMapOfTupleValues(Collections.singletonMap("mykey", tupleType.newValue("hello", 42))); StatementFactory statementFactory = new StatementFactory(new UpdateMapper(converter)); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUDTIntegrationTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUDTIntegrationTests.java index fd4ad4627..d8f2ef929 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUDTIntegrationTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUDTIntegrationTests.java @@ -218,7 +218,9 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring CassandraPersistentEntity persistentEntity = converter.getMappingContext() .getRequiredPersistentEntity(AddressUserType.class); - UdtValue udtValue = persistentEntity.getUserType().newValue(); + com.datastax.oss.driver.api.core.type.UserDefinedType udtType = (com.datastax.oss.driver.api.core.type.UserDefinedType) converter + .getColumnTypeResolver().resolve(persistentEntity.getTypeInformation()).getDataType(); + UdtValue udtValue = udtType.newValue(); udtValue.setString("zip", "69469"); udtValue.setString("city", "Weinheim"); udtValue.setList("streetlines", Arrays.asList("Heckenpfad", "14"), String.class); @@ -256,7 +258,10 @@ public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpring CassandraPersistentEntity persistentEntity = converter.getMappingContext() .getRequiredPersistentEntity(AddressUserType.class); - UdtValue udtValue = persistentEntity.getUserType().newValue(); + com.datastax.oss.driver.api.core.type.UserDefinedType udtType = (com.datastax.oss.driver.api.core.type.UserDefinedType) converter + .getColumnTypeResolver().resolve(persistentEntity.getTypeInformation()).getDataType(); + + UdtValue udtValue = udtType.newValue(); udtValue.setString("zip", "69469"); udtValue.setString("city", "Weinheim"); udtValue.setList("streetlines", Arrays.asList("Heckenpfad", "14"), String.class); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/QueryMapperUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/QueryMapperUnitTests.java index 1011247fe..70b0963fe 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/QueryMapperUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/QueryMapperUnitTests.java @@ -352,8 +352,7 @@ public class QueryMapperUnitTests { Filter mappedObject = this.queryMapper.getMappedObject(filter, this.mappingContext.getRequiredPersistentEntity(Person.class)); - TupleValue tupleValue = this.mappingContext.getRequiredPersistentEntity(MappedTuple.class).getTupleType() - .newValue(); + TupleValue tupleValue = DataTypes.tupleOf(DataTypes.TEXT).newValue(); tupleValue.setString(0, "foo"); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CreateTableSpecificationBasicCassandraMappingContextUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/SchemaFactoryUnitTests.java similarity index 50% rename from spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CreateTableSpecificationBasicCassandraMappingContextUnitTests.java rename to spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/SchemaFactoryUnitTests.java index e95ed9118..ad4b29674 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CreateTableSpecificationBasicCassandraMappingContextUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/SchemaFactoryUnitTests.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.cassandra.core.mapping; +package org.springframework.data.cassandra.core.convert; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.data.cassandra.core.mapping.CassandraType.*; @@ -24,10 +25,12 @@ import lombok.Data; import lombok.NoArgsConstructor; import java.io.IOException; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; import org.junit.Before; @@ -35,42 +38,320 @@ import org.junit.Test; import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; -import org.springframework.data.cassandra.core.convert.CassandraCustomConversions; +import org.springframework.data.cassandra.core.cql.Ordering; +import org.springframework.data.cassandra.core.cql.PrimaryKeyType; import org.springframework.data.cassandra.core.cql.keyspace.ColumnSpecification; +import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification; +import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification.ColumnFunction; import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification; +import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; +import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity; +import org.springframework.data.cassandra.core.mapping.CassandraType; +import org.springframework.data.cassandra.core.mapping.Element; +import org.springframework.data.cassandra.core.mapping.Indexed; +import org.springframework.data.cassandra.core.mapping.PrimaryKey; +import org.springframework.data.cassandra.core.mapping.PrimaryKeyClass; +import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.core.mapping.Table; +import org.springframework.data.cassandra.core.mapping.Tuple; import org.springframework.data.cassandra.domain.AllPossibleTypes; +import org.springframework.data.cassandra.support.UserDefinedTypeBuilder; +import org.springframework.data.mapping.MappingException; import org.springframework.util.StringUtils; import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.type.DataType; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.ListType; import com.datastax.oss.driver.api.core.type.SetType; +import com.datastax.oss.driver.api.core.type.TupleType; import com.datastax.oss.driver.api.core.type.UserDefinedType; import com.datastax.oss.protocol.internal.ProtocolConstants; import com.fasterxml.jackson.databind.ObjectMapper; /** - * Unit tests for {@link CassandraMappingContext} targeted on {@link CreateTableSpecification}. + * Unit tests for {@link SchemaFactory}. * + * @author Matthew T. Adams * @author Mark Paluch - * @author Vagif Zeynalov - * @soundtrack Black Rose - Volbeat */ -public class CreateTableSpecificationBasicCassandraMappingContextUnitTests { +public class SchemaFactoryUnitTests { - private CassandraMappingContext ctx = new CassandraMappingContext(); + CassandraMappingContext ctx = new CassandraMappingContext(); + SchemaFactory schemaFactory; @Before - public void setUp() { + public void before() { List> converters = new ArrayList<>(); converters.add(new PersonReadConverter()); converters.add(new PersonWriteConverter()); + converters.add(HumanToStringConverter.INSTANCE); CassandraCustomConversions customConversions = new CassandraCustomConversions(converters); ctx.setCustomConversions(customConversions); + schemaFactory = new SchemaFactory(new MappingCassandraConverter(ctx)); + } + + @Test // DATACASS-340 + public void createdTableSpecificationShouldConsiderClusterColumnOrdering() { + + CassandraPersistentEntity persistentEntity = ctx + .getRequiredPersistentEntity(EntityWithOrderedClusteredColumns.class); + + CreateTableSpecification tableSpecification = schemaFactory.getCreateTableSpecificationFor(persistentEntity); + + assertThat(tableSpecification.getPartitionKeyColumns()).hasSize(1); + assertThat(tableSpecification.getClusteredKeyColumns()).hasSize(3); + + ColumnSpecification breed = tableSpecification.getClusteredKeyColumns().get(0); + assertThat(breed.getName().toString()).isEqualTo("breed"); + assertThat(breed.getOrdering()).isEqualTo(Ordering.ASCENDING); + + ColumnSpecification color = tableSpecification.getClusteredKeyColumns().get(1); + assertThat(color.getName().toString()).isEqualTo("color"); + assertThat(color.getOrdering()).isEqualTo(Ordering.DESCENDING); + + ColumnSpecification kind = tableSpecification.getClusteredKeyColumns().get(2); + assertThat(kind.getName().toString()).isEqualTo("kind"); + assertThat(kind.getOrdering()).isEqualTo(Ordering.ASCENDING); + } + + @Test // DATACASS-340 + public void createdTableSpecificationShouldConsiderPrimaryKeyClassClusterColumnOrdering() { + + CassandraPersistentEntity persistentEntity = ctx + .getRequiredPersistentEntity(EntityWithPrimaryKeyWithOrderedClusteredColumns.class); + + CreateTableSpecification tableSpecification = schemaFactory.getCreateTableSpecificationFor(persistentEntity); + + assertThat(tableSpecification.getPartitionKeyColumns()).hasSize(1); + assertThat(tableSpecification.getClusteredKeyColumns()).hasSize(3); + + ColumnSpecification breed = tableSpecification.getClusteredKeyColumns().get(0); + assertThat(breed.getName().toString()).isEqualTo("breed"); + assertThat(breed.getOrdering()).isEqualTo(Ordering.ASCENDING); + + ColumnSpecification color = tableSpecification.getClusteredKeyColumns().get(1); + assertThat(color.getName().toString()).isEqualTo("color"); + assertThat(color.getOrdering()).isEqualTo(Ordering.DESCENDING); + + ColumnSpecification kind = tableSpecification.getClusteredKeyColumns().get(2); + assertThat(kind.getName().toString()).isEqualTo("kind"); + assertThat(kind.getOrdering()).isEqualTo(Ordering.ASCENDING); + } + + @Test // DATACASS-487 + public void shouldCreateTableForMappedAndConvertedColumn() { + + UserDefinedType mappedudt = UserDefinedTypeBuilder.forName("mappedudt").withField("foo", DataTypes.ASCII).build(); + + this.ctx.setUserTypeResolver(typeName -> mappedudt); + + CassandraPersistentEntity persistentEntity = this.ctx.getRequiredPersistentEntity(WithMapOfMixedTypes.class); + + CreateTableSpecification tableSpecification = schemaFactory.getCreateTableSpecificationFor(persistentEntity); + + assertThat(tableSpecification.getColumns()).hasSize(2); + + ColumnSpecification column = tableSpecification.getColumns().get(1); + + assertThat(column.getType().asCql(true, true)).isEqualTo("map, list>"); + } + + @PrimaryKeyClass + private static class CompositePrimaryKeyClassWithProperties implements Serializable { + + String firstname; + String lastname; + + @PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.PARTITIONED) + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + @PrimaryKeyColumn(name = "mylastname", ordinal = 2, type = PrimaryKeyType.CLUSTERED) + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + } + + @Table + static class EntityWithOrderedClusteredColumns { + + @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) String species; + @PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.ASCENDING) String breed; + @PrimaryKeyColumn(ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING) String color; + @PrimaryKeyColumn(ordinal = 3, type = PrimaryKeyType.CLUSTERED) String kind; + } + + @PrimaryKeyClass + static class PrimaryKeyWithOrderedClusteredColumns implements Serializable { + + @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) String species; + @PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.ASCENDING) String breed; + @PrimaryKeyColumn(ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING) String color; + @PrimaryKeyColumn(ordinal = 3, type = PrimaryKeyType.CLUSTERED) String kind; + } + + @Table + private static class EntityWithPrimaryKeyWithOrderedClusteredColumns { + + @PrimaryKey PrimaryKeyWithOrderedClusteredColumns key; + } + + @Test // DATACASS-213 + public void createIndexShouldConsiderAnnotatedProperties() { + + List specifications = schemaFactory + .getCreateIndexSpecificationsFor(ctx.getRequiredPersistentEntity(IndexedType.class)); + + CreateIndexSpecification firstname = getSpecificationFor("first_name", specifications); + + assertThat(firstname.getColumnName()).isEqualTo(CqlIdentifier.fromCql("first_name")); + assertThat(firstname.getTableName()).isEqualTo(CqlIdentifier.fromCql("indexedtype")); + assertThat(firstname.getName()).isEqualTo(CqlIdentifier.fromCql("my_index")); + assertThat(firstname.getColumnFunction()).isEqualTo(ColumnFunction.NONE); + + CreateIndexSpecification phoneNumbers = getSpecificationFor("phoneNumbers", specifications); + + assertThat(phoneNumbers.getColumnName()).isEqualTo(CqlIdentifier.fromCql("phoneNumbers")); + assertThat(phoneNumbers.getTableName()).isEqualTo(CqlIdentifier.fromCql("indexedtype")); + assertThat(phoneNumbers.getName()).isNull(); + assertThat(phoneNumbers.getColumnFunction()).isEqualTo(ColumnFunction.NONE); + } + + @Test // DATACASS-213 + public void createIndexForClusteredPrimaryKeyShouldConsiderAnnotatedAccessors() { + + List specifications = schemaFactory + .getCreateIndexSpecificationsFor(ctx.getRequiredPersistentEntity(CompositeKeyEntity.class)); + + CreateIndexSpecification entries = getSpecificationFor("last_name", specifications); + + assertThat(entries.getColumnName()).isEqualTo(CqlIdentifier.fromCql("last_name")); + assertThat(entries.getTableName()).isEqualTo(CqlIdentifier.fromCql("compositekeyentity")); + assertThat(entries.getName()).isEqualTo(CqlIdentifier.fromCql("my_index")); + assertThat(entries.getColumnFunction()).isEqualTo(ColumnFunction.NONE); + } + + @Test // DATACASS-284, DATACASS-651 + public void shouldRejectUntypedTuples() { + + assertThatThrownBy(() -> this.schemaFactory + .getCreateTableSpecificationFor(this.ctx.getRequiredPersistentEntity(UntypedTupleEntity.class))) + .isInstanceOf(MappingException.class); + + assertThatThrownBy(() -> this.schemaFactory + .getCreateTableSpecificationFor(this.ctx.getRequiredPersistentEntity(UntypedTupleMapEntity.class))) + .isInstanceOf(MappingException.class); + } + + @Test // DATACASS-284 + public void shouldCreateTableForTypedTupleType() { + + CreateTableSpecification tableSpecification = this.schemaFactory + .getCreateTableSpecificationFor(this.ctx.getRequiredPersistentEntity(TypedTupleEntity.class)); + + assertThat(tableSpecification.getColumns()).hasSize(2); + + ColumnSpecification column = tableSpecification.getColumns().get(1); + + assertThat(column.getType()).isInstanceOf(TupleType.class); + assertThat(column.getType()).isEqualTo(DataTypes.tupleOf(DataTypes.TEXT, DataTypes.BIGINT)); + } + + @Test // DATACASS-651 + public void shouldCreateTableForEntityWithMapOfTuples() { + + CreateTableSpecification tableSpecification = this.schemaFactory + .getCreateTableSpecificationFor(this.ctx.getRequiredPersistentEntity(EntityWithMapOfTuples.class)); + + assertThat(tableSpecification.getColumns()).hasSize(2); + + ColumnSpecification column = tableSpecification.getColumns().get(1); + assertThat(column.getName()).isEqualTo(CqlIdentifier.fromCql("map")); + assertThat(column.getType().asCql(true, true)).isEqualTo("map>>"); + } + + private static CreateIndexSpecification getSpecificationFor(String column, + List specifications) { + + return specifications.stream().filter(it -> it.getColumnName().equals(CqlIdentifier.fromCql(column))).findFirst() + .orElseThrow(() -> new NoSuchElementException(column)); + } + + static class IndexedType { + + @PrimaryKeyColumn("first_name") @Indexed("my_index") String firstname; + + @Indexed List phoneNumbers; + } + + @PrimaryKeyClass + static class CompositeKeyWithIndex { + + @PrimaryKeyColumn(value = "first_name", type = PrimaryKeyType.PARTITIONED) String firstname; + @PrimaryKeyColumn("last_name") @Indexed("my_index") String lastname; + } + + static class CompositeKeyEntity { + + @PrimaryKey CompositeKeyWithIndex key; + } + + static class InvalidMapIndex { + + @Indexed Map<@Indexed String, String> mixed; + } + + @Test // DATACASS-506 + public void shouldCreatedUserTypeSpecificationsWithAnnotatedTypeName() { + + assertThat(schemaFactory.getCreateUserTypeSpecificationFor(ctx.getRequiredPersistentEntity(WithUdt.class))) + .isNotNull(); + assertThat(schemaFactory.getCreateUserTypeSpecificationFor(ctx.getRequiredPersistentEntity(Nested.class))) + .isNotNull(); + } + + @Test // DATACASS-172 + public void createTableForComplexPrimaryKeyShouldFail() { + + try { + schemaFactory + .getCreateTableSpecificationFor(ctx.getRequiredPersistentEntity(EntityWithComplexPrimaryKeyColumn.class)); + fail("Missing MappingException"); + } catch (MappingException e) { + assertThat(e).hasMessageContaining( + "Cannot resolve DataType for type [class java.lang.Object] for property [complexObject]"); + } + + try { + schemaFactory.getCreateTableSpecificationFor(ctx.getRequiredPersistentEntity(EntityWithComplexId.class)); + fail("Missing MappingException"); + } catch (MappingException e) { + assertThat(e).hasMessageContaining( + "Cannot resolve DataType for type [class java.lang.Object] for property [complexObject]"); + } + + try { + schemaFactory.getCreateTableSpecificationFor( + ctx.getRequiredPersistentEntity(EntityWithPrimaryKeyClassWithComplexId.class)); + fail("Missing MappingException"); + } catch (MappingException e) { + assertThat(e).hasMessageContaining( + "Cannot resolve DataType for type [class java.lang.Object] for property [complexObject]"); + } } @Test // DATACASS-296 @@ -78,7 +359,7 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests { CassandraPersistentEntity persistentEntity = ctx.getRequiredPersistentEntity(Employee.class); - CreateTableSpecification specification = ctx.getCreateTableSpecificationFor(persistentEntity); + CreateTableSpecification specification = schemaFactory.getCreateTableSpecificationFor(persistentEntity); assertThat(getColumnType("human", specification)).isEqualTo(DataTypes.TEXT); @@ -102,7 +383,7 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests { CassandraPersistentEntity persistentEntity = ctx.getRequiredPersistentEntity(Employee.class); - CreateTableSpecification specification = ctx.getCreateTableSpecificationFor(persistentEntity); + CreateTableSpecification specification = schemaFactory.getCreateTableSpecificationFor(persistentEntity); assertThat(getColumnType("floater", specification)).isEqualTo(DataTypes.FLOAT); @@ -248,26 +529,6 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests { @Test // DATACASS-172 public void columnsShouldMapToUdt() { - UserDefinedType human_udt = new CassandraMappingContext.ShallowUserDefinedType("human_udt", false); - UserDefinedType species_udt = new CassandraMappingContext.ShallowUserDefinedType("species_udt", false); - UserDefinedType peeps_udt = new CassandraMappingContext.ShallowUserDefinedType("peeps_udt", false); - - ctx.setUserTypeResolver(typeName -> { - - if (typeName.equals(human_udt.getName())) { - return human_udt; - } - - if (typeName.equals(species_udt.getName())) { - return species_udt; - } - - if (typeName.equals(peeps_udt.getName())) { - return peeps_udt; - } - return null; - }); - CreateTableSpecification specification = getCreateTableSpecificationFor(WithUdtFields.class); assertThat(getColumnType("human", specification).asCql(false, true)).isEqualTo("human_udt"); @@ -278,7 +539,7 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests { @Test // DATACASS-172 public void columnsShouldMapToMappedUserType() { - UserDefinedType mappedUdt = new CassandraMappingContext.ShallowUserDefinedType("mappedudt", true); + UserDefinedType mappedUdt = new SchemaFactory.ShallowUserDefinedType("mappedudt", true); ctx.setUserTypeResolver(typeName -> { @@ -336,7 +597,8 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests { CqlIdentifier customTableName = CqlIdentifier.fromCql("my_custom_came"); CassandraPersistentEntity persistentEntity = ctx.getRequiredPersistentEntity(Employee.class); - CreateTableSpecification specification = ctx.getCreateTableSpecificationFor(customTableName, persistentEntity); + CreateTableSpecification specification = schemaFactory.getCreateTableSpecificationFor(persistentEntity, + customTableName); assertThat(specification).isNotNull(); assertThat(specification.getName()).isEqualTo(customTableName); @@ -348,7 +610,7 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests { ctx.setCustomConversions(customConversions); CassandraPersistentEntity persistentEntity = ctx.getRequiredPersistentEntity(persistentEntityClass); - return ctx.getCreateTableSpecificationFor(persistentEntity); + return schemaFactory.getCreateTableSpecificationFor(persistentEntity); } private DataType getColumnType(String columnName, CreateTableSpecification specification) { @@ -405,10 +667,8 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests { @Id String id; @CassandraType(type = Name.UDT, userTypeName = "human_udt") UdtValue human; - @CassandraType(type = Name.LIST, typeArguments = Name.UDT, - userTypeName = "species_udt") List friends; - @CassandraType(type = Name.SET, typeArguments = Name.UDT, - userTypeName = "peeps_udt") Set people; + @CassandraType(type = Name.LIST, typeArguments = Name.UDT, userTypeName = "species_udt") List friends; + @CassandraType(type = Name.SET, typeArguments = Name.UDT, userTypeName = "peeps_udt") Set people; } @Data @@ -473,4 +733,98 @@ public class CreateTableSpecificationBasicCassandraMappingContextUnitTests { } } } + + @Table + private static class InvalidEntityWithIdAndPrimaryKeyColumn { + @Id String foo; + @PrimaryKeyColumn String bar; + } + + @Table + static class EntityWithComplexPrimaryKeyColumn { + @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) Object complexObject; + } + + @Table + static class EntityWithComplexId { + @Id Object complexObject; + } + + @PrimaryKeyClass + static class PrimaryKeyClassWithComplexId { + @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) Object complexObject; + } + + @Table + static class EntityWithPrimaryKeyClassWithComplexId { + @Id PrimaryKeyClassWithComplexId primaryKeyClassWithComplexId; + } + + @Table + private static class X { + @PrimaryKey String key; + } + + @Table + private static class Y { + @PrimaryKey String key; + } + + @Table + private static class WithUdt { + @Id String id; + @CassandraType(type = Name.UDT, userTypeName = "mappedudt") UdtValue udtValue; + @CassandraType(type = Name.UDT, userTypeName = "NestedType") Nested nested; + } + + @Table + private static class WithMapOfMixedTypes { + @Id String id; + Map> people; + } + + enum HumanToStringConverter implements Converter { + + INSTANCE; + + @Override + public String convert(Human source) { + return "hello"; + } + } + + @org.springframework.data.cassandra.core.mapping.UserDefinedType(value = "NestedType") + public static class Nested { + String s1; + @CassandraType(type = Name.UDT, userTypeName = "AnotherNestedType") AnotherNested anotherNested; + } + + @org.springframework.data.cassandra.core.mapping.UserDefinedType(value = "AnotherNestedType") + public static class AnotherNested { + String str; + } + + @Table + static class TypedTupleEntity { + @Id String id; + @CassandraType(type = Name.TUPLE, typeArguments = { Name.VARCHAR, Name.BIGINT }) TupleValue typed; + } + + @Table + static class EntityWithMapOfTuples { + @Id String id; + Map map; + } + + @Table + static class UntypedTupleEntity { + @Id String id; + TupleValue untyped; + } + + @Table + static class UntypedTupleMapEntity { + @Id String id; + Map untyped; + } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentPropertyUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentPropertyUnitTests.java index 84d03ed6e..c9c5b9fdc 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentPropertyUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentPropertyUnitTests.java @@ -25,7 +25,6 @@ import java.lang.reflect.Field; import java.util.Date; import java.util.List; import java.util.Map; -import java.util.UUID; import org.junit.Test; @@ -35,7 +34,6 @@ import org.springframework.data.util.ClassTypeInformation; import org.springframework.util.ReflectionUtils; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.protocol.internal.ProtocolConstants; /** * Unit tests for {@link BasicCassandraPersistentProperty}. @@ -102,26 +100,6 @@ public class BasicCassandraPersistentPropertyUnitTests { assertThat(persistentProperty.isPrimaryKeyColumn()).isTrue(); } - @Test // DATACASS-259 - public void shouldConsiderComposedCassandraTypeAnnotation() { - - CassandraPersistentProperty persistentProperty = getPropertyFor(TypeWithComposedCassandraTypeAnnotation.class, - "column"); - - assertThat(persistentProperty.getDataType().getProtocolCode()).isEqualTo(ProtocolConstants.DataType.COUNTER); - assertThat(persistentProperty.findAnnotation(CassandraType.class)).isNotNull(); - } - - @Test // DATACASS-375 - public void UuidshouldMapToUUIDByDefault() { - - CassandraPersistentProperty uuidProperty = getPropertyFor(TypeWithUUIDColumn.class, "uuid"); - CassandraPersistentProperty timeUUIDProperty = getPropertyFor(TypeWithUUIDColumn.class, "timeUUID"); - - assertThat(uuidProperty.getDataType().getProtocolCode()).isEqualTo(ProtocolConstants.DataType.UUID); - assertThat(timeUUIDProperty.getDataType().getProtocolCode()).isEqualTo(ProtocolConstants.DataType.TIMEUUID); - } - @Test // DATACASS-568 public void shouldFindAnnotationInMapTypes() { @@ -144,6 +122,15 @@ public class BasicCassandraPersistentPropertyUnitTests { assertThat(findAnnotatedType(TypeWithCollections.class, "subtype")).isNull(); } + @Test // DATACASS-259 + public void shouldConsiderComposedCassandraTypeAnnotation() { + + CassandraPersistentProperty persistentProperty = getPropertyFor(TypeWithComposedCassandraTypeAnnotation.class, + "column"); + + assertThat(persistentProperty.findAnnotation(CassandraType.class)).isNotNull(); + } + private AnnotatedType findAnnotatedType(Class type, String parameterized) { return getPropertyFor(TypeWithMaps.class, parameterized).findAnnotatedType(Indexed.class); } @@ -221,13 +208,6 @@ public class BasicCassandraPersistentPropertyUnitTests { @ComposedCassandraTypeAnnotation String column; } - static class TypeWithUUIDColumn { - - UUID uuid; - - @CassandraType(type = Name.TIMEUUID) UUID timeUUID; - } - static class TypeWithMaps { Map parameterized; diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleEntityUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleEntityUnitTests.java index c30d55495..24619558e 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleEntityUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTupleEntityUnitTests.java @@ -16,24 +16,17 @@ package org.springframework.data.cassandra.core.mapping; import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.annotation.Transient; import org.springframework.data.mapping.MappingException; -import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.internal.core.type.DefaultTupleType; - /** * Unit tests for {@link BasicCassandraPersistentTupleEntity}. * @@ -44,13 +37,6 @@ public class BasicCassandraPersistentTupleEntityUnitTests { CassandraMappingContext mappingContext = new CassandraMappingContext(); - @Mock TupleTypeFactory tupleTypeFactory; - - @Before - public void before() { - this.mappingContext.setTupleTypeFactory(tupleTypeFactory); - } - @Test // DATACASS-523 public void shouldCreatePersistentTupleEntity() { @@ -75,21 +61,6 @@ public class BasicCassandraPersistentTupleEntityUnitTests { assertThat(propertyNames).containsSequence("street", "city", "sortOrder"); } - @Test // DATACASS-523 - public void shouldCreateTupleType() { - - when(this.tupleTypeFactory.create(anyList())) - .thenReturn(new DefaultTupleType(Arrays.asList(DataTypes.TEXT, DataTypes.TEXT, DataTypes.INT))); - - BasicCassandraPersistentEntity entity = this.mappingContext.getRequiredPersistentEntity(Address.class); - - entity.verify(); - - entity.getTupleType(); - - verify(tupleTypeFactory).create(Arrays.asList(DataTypes.TEXT, DataTypes.TEXT, DataTypes.INT)); - } - @Test // DATACASS-523 public void shouldReportDuplicateMappings() { diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTuplePropertyUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTuplePropertyUnitTests.java index dd9515d02..eb3473131 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTuplePropertyUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentTuplePropertyUnitTests.java @@ -15,14 +15,13 @@ */ package org.springframework.data.cassandra.core.mapping; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import java.lang.reflect.Field; import java.util.Date; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.mapping.model.Property; @@ -37,8 +36,6 @@ import org.springframework.util.ReflectionUtils; @RunWith(MockitoJUnitRunner.class) public class BasicCassandraPersistentTuplePropertyUnitTests { - @Mock TupleTypeFactory tupleTypeFactory; - @Test // DATACASS-523 public void mappedTupleShouldNotReportColumnName() { @@ -64,7 +61,7 @@ public class BasicCassandraPersistentTuplePropertyUnitTests { } private BasicCassandraPersistentEntity getEntity(Class type) { - return new BasicCassandraPersistentTupleEntity<>(ClassTypeInformation.from(type), tupleTypeFactory); + return new BasicCassandraPersistentTupleEntity<>(ClassTypeInformation.from(type)); } @Tuple diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraCompositePrimaryKeyUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraCompositePrimaryKeyUnitTests.java index 501fff2cd..a95563e62 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraCompositePrimaryKeyUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraCompositePrimaryKeyUnitTests.java @@ -27,6 +27,7 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import org.springframework.data.cassandra.core.convert.SchemaFactory; import org.springframework.data.cassandra.core.cql.PrimaryKeyType; import org.springframework.data.cassandra.core.cql.keyspace.ColumnSpecification; import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification; @@ -46,6 +47,8 @@ public class CassandraCompositePrimaryKeyUnitTests { CassandraMappingContext context; + SchemaFactory schemaFactory; + CassandraPersistentEntity entity; CassandraPersistentEntity key; @@ -54,6 +57,7 @@ public class CassandraCompositePrimaryKeyUnitTests { public void setup() { context = new CassandraMappingContext(); + schemaFactory = new SchemaFactory(context, context.getCustomConversions(), context.getCodecRegistry()); entity = context.getRequiredPersistentEntity(ClassTypeInformation.from(TypeWithCompositeKey.class)); key = context.getRequiredPersistentEntity(ClassTypeInformation.from(CompositeKey.class)); } @@ -68,7 +72,7 @@ public class CassandraCompositePrimaryKeyUnitTests { assertThat(property.isIdProperty()).isTrue(); assertThat(property.isCompositePrimaryKey()).isTrue(); - CreateTableSpecification spec = context.getCreateTableSpecificationFor(entity); + CreateTableSpecification spec = schemaFactory.getCreateTableSpecificationFor(entity); List partitionKeyColumns = spec.getPartitionKeyColumns(); assertThat(partitionKeyColumns).hasSize(1); 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 5064a4c0a..f23922d7d 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 @@ -20,8 +20,6 @@ import static org.mockito.Mockito.*; import static org.springframework.data.cassandra.core.mapping.CassandraType.*; import java.io.Serializable; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -35,25 +33,14 @@ import org.springframework.data.annotation.Id; import org.springframework.data.cassandra.core.convert.CassandraCustomConversions; import org.springframework.data.cassandra.core.cql.Ordering; import org.springframework.data.cassandra.core.cql.PrimaryKeyType; -import org.springframework.data.cassandra.core.cql.keyspace.ColumnSpecification; import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification; -import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification.ColumnFunction; -import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification; -import org.springframework.data.cassandra.domain.AllPossibleTypes; -import org.springframework.data.cassandra.support.UserDefinedTypeBuilder; -import org.springframework.data.convert.WritingConverter; import org.springframework.data.mapping.MappingException; import org.springframework.data.util.ClassTypeInformation; import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.data.TupleValue; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata; -import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.core.type.MapType; -import com.datastax.oss.driver.api.core.type.TupleType; import com.datastax.oss.driver.api.core.type.UserDefinedType; -import com.datastax.oss.driver.internal.core.type.DefaultTupleType; /** * Unit tests for {@link CassandraMappingContext}. @@ -197,75 +184,6 @@ public class CassandraMappingContextUnitTests { assertThat(lastname.getColumnName().toString()).isEqualTo("mylastname"); } - @Test // DATACASS-340 - public void createdTableSpecificationShouldConsiderClusterColumnOrdering() { - - CassandraPersistentEntity persistentEntity = mappingContext - .getRequiredPersistentEntity(EntityWithOrderedClusteredColumns.class); - - CreateTableSpecification tableSpecification = mappingContext.getCreateTableSpecificationFor(persistentEntity); - - assertThat(tableSpecification.getPartitionKeyColumns()).hasSize(1); - assertThat(tableSpecification.getClusteredKeyColumns()).hasSize(3); - - ColumnSpecification breed = tableSpecification.getClusteredKeyColumns().get(0); - assertThat(breed.getName().toString()).isEqualTo("breed"); - assertThat(breed.getOrdering()).isEqualTo(Ordering.ASCENDING); - - ColumnSpecification color = tableSpecification.getClusteredKeyColumns().get(1); - assertThat(color.getName().toString()).isEqualTo("color"); - assertThat(color.getOrdering()).isEqualTo(Ordering.DESCENDING); - - ColumnSpecification kind = tableSpecification.getClusteredKeyColumns().get(2); - assertThat(kind.getName().toString()).isEqualTo("kind"); - assertThat(kind.getOrdering()).isEqualTo(Ordering.ASCENDING); - } - - @Test // DATACASS-340 - public void createdTableSpecificationShouldConsiderPrimaryKeyClassClusterColumnOrdering() { - - CassandraPersistentEntity persistentEntity = mappingContext - .getRequiredPersistentEntity(EntityWithPrimaryKeyWithOrderedClusteredColumns.class); - - CreateTableSpecification tableSpecification = mappingContext.getCreateTableSpecificationFor(persistentEntity); - - assertThat(tableSpecification.getPartitionKeyColumns()).hasSize(1); - assertThat(tableSpecification.getClusteredKeyColumns()).hasSize(3); - - ColumnSpecification breed = tableSpecification.getClusteredKeyColumns().get(0); - assertThat(breed.getName().toString()).isEqualTo("breed"); - assertThat(breed.getOrdering()).isEqualTo(Ordering.ASCENDING); - - ColumnSpecification color = tableSpecification.getClusteredKeyColumns().get(1); - assertThat(color.getName().toString()).isEqualTo("color"); - assertThat(color.getOrdering()).isEqualTo(Ordering.DESCENDING); - - ColumnSpecification kind = tableSpecification.getClusteredKeyColumns().get(2); - assertThat(kind.getName().toString()).isEqualTo("kind"); - assertThat(kind.getOrdering()).isEqualTo(Ordering.ASCENDING); - } - - @Test // DATACASS-487 - public void shouldCreateTableForMappedAndConvertedColumn() { - - UserDefinedType mappedudt = UserDefinedTypeBuilder.forName("mappedudt").withField("foo", DataTypes.ASCII).build(); - - this.mappingContext.setUserTypeResolver(typeName -> mappedudt); - this.mappingContext.setCustomConversions( - new CassandraCustomConversions(Collections.singletonList(HumanToStringConverter.INSTANCE))); - - CassandraPersistentEntity persistentEntity = this.mappingContext - .getRequiredPersistentEntity(WithMapOfMixedTypes.class); - - CreateTableSpecification tableSpecification = this.mappingContext.getCreateTableSpecificationFor(persistentEntity); - - assertThat(tableSpecification.getColumns()).hasSize(2); - - ColumnSpecification column = tableSpecification.getColumns().get(1); - - assertThat(column.getType().asCql(true, true)).isEqualTo("map, list>"); - } - @Table private static class PrimaryKeyOnPropertyWithPrimaryKeyClass { @@ -330,82 +248,6 @@ public class CassandraMappingContextUnitTests { @PrimaryKey PrimaryKeyWithOrderedClusteredColumns key; } - @Test // DATACASS-213 - public void createIndexShouldConsiderAnnotatedProperties() { - - List specifications = mappingContext - .getCreateIndexSpecificationsFor(mappingContext.getRequiredPersistentEntity(IndexedType.class)); - - CreateIndexSpecification firstname = getSpecificationFor("first_name", specifications); - - assertThat(firstname.getColumnName()).isEqualTo(CqlIdentifier.fromCql("first_name")); - assertThat(firstname.getTableName()).isEqualTo(CqlIdentifier.fromCql("indexedtype")); - assertThat(firstname.getName()).isEqualTo(CqlIdentifier.fromCql("my_index")); - assertThat(firstname.getColumnFunction()).isEqualTo(ColumnFunction.NONE); - - CreateIndexSpecification phoneNumbers = getSpecificationFor("phoneNumbers", specifications); - - assertThat(phoneNumbers.getColumnName()).isEqualTo(CqlIdentifier.fromCql("phoneNumbers")); - assertThat(phoneNumbers.getTableName()).isEqualTo(CqlIdentifier.fromCql("indexedtype")); - assertThat(phoneNumbers.getName()).isNull(); - assertThat(phoneNumbers.getColumnFunction()).isEqualTo(ColumnFunction.NONE); - } - - @Test // DATACASS-213 - public void createIndexForClusteredPrimaryKeyShouldConsiderAnnotatedAccessors() { - - List specifications = mappingContext - .getCreateIndexSpecificationsFor(mappingContext.getRequiredPersistentEntity(CompositeKeyEntity.class)); - - CreateIndexSpecification entries = getSpecificationFor("last_name", specifications); - - assertThat(entries.getColumnName()).isEqualTo(CqlIdentifier.fromCql("last_name")); - assertThat(entries.getTableName()).isEqualTo(CqlIdentifier.fromCql("compositekeyentity")); - assertThat(entries.getName()).isEqualTo(CqlIdentifier.fromCql("my_index")); - assertThat(entries.getColumnFunction()).isEqualTo(ColumnFunction.NONE); - } - - @Test // DATACASS-284, DATACASS-651 - public void shouldRejectUntypedTuples() { - - assertThatThrownBy(() -> this.mappingContext - .getCreateTableSpecificationFor(this.mappingContext.getRequiredPersistentEntity(UntypedTupleEntity.class))) - .isInstanceOf(MappingException.class); - - assertThatThrownBy(() -> this.mappingContext - .getCreateTableSpecificationFor(this.mappingContext.getRequiredPersistentEntity(UntypedTupleMapEntity.class))) - .isInstanceOf(MappingException.class); - } - - @Test // DATACASS-284 - public void shouldCreateTableForTypedTupleType() { - - CreateTableSpecification tableSpecification = this.mappingContext - .getCreateTableSpecificationFor(this.mappingContext.getRequiredPersistentEntity(TypedTupleEntity.class)); - - assertThat(tableSpecification.getColumns()).hasSize(2); - - ColumnSpecification column = tableSpecification.getColumns().get(1); - - assertThat(column.getType()).isInstanceOf(TupleType.class); - assertThat(column.getType()).isEqualTo(new DefaultTupleType(Arrays.asList(DataTypes.TEXT, DataTypes.BIGINT))); - } - - @Test // DATACASS-651 - public void shouldCreateTableForEntityWithMapOfTuples() { - - CreateTableSpecification tableSpecification = this.mappingContext - .getCreateTableSpecificationFor(this.mappingContext.getRequiredPersistentEntity(EntityWithMapOfTuples.class)); - - assertThat(tableSpecification.getColumns()).hasSize(2); - - ColumnSpecification column = tableSpecification.getColumns().get(1); - - assertThat(column.getType()).isInstanceOf(MapType.class); - assertThat(column.getType()) - .isEqualTo(DataTypes.mapOf(DataTypes.TEXT, new DefaultTupleType(Arrays.asList(DataTypes.TEXT)))); - } - private static CreateIndexSpecification getSpecificationFor(String column, List specifications) { @@ -453,46 +295,6 @@ public class CassandraMappingContextUnitTests { assertThat(mappingContext.shouldCreatePersistentEntityFor(ClassTypeInformation.from(Human.class))).isFalse(); } - @Test // DATACASS-349 - public void propertyTypeShouldConsiderRegisteredConverterForPropertyType() { - - mappingContext.setCustomConversions( - new CassandraCustomConversions(Collections.singletonList(StringMapToStringConverter.INSTANCE))); - - CassandraPersistentEntity persistentEntity = mappingContext - .getRequiredPersistentEntity(TypeWithCustomConvertedMap.class); - - assertThat(mappingContext.getDataType(persistentEntity.getRequiredPersistentProperty("stringMap"))) - .isEqualTo(DataTypes.TEXT); - - assertThat(mappingContext.getDataType(persistentEntity.getRequiredPersistentProperty("blobMap"))) - .isEqualTo(DataTypes.ASCII); - } - - @Test // DATACASS-349 - public void propertyTypeShouldConsiderRegisteredConverterForCollectionComponentType() { - - mappingContext.setCustomConversions( - new CassandraCustomConversions(Collections.singletonList(HumanToStringConverter.INSTANCE))); - - CassandraPersistentEntity persistentEntity = mappingContext - .getRequiredPersistentEntity(TypeWithListOfHumans.class); - - assertThat(mappingContext.getDataType(persistentEntity.getRequiredPersistentProperty("humans"))) - .isEqualTo(DataTypes.listOf(DataTypes.TEXT)); - } - - @Test // DATACASS-302 - public void propertyTypeShouldMapToTime() { - - CassandraPersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(AllPossibleTypes.class); - - assertThat(mappingContext.getDataType(persistentEntity.getRequiredPersistentProperty("time"))) - .isEqualTo(DataTypes.TIME); - assertThat(mappingContext.getDataType(persistentEntity.getRequiredPersistentProperty("jodaLocalTime"))) - .isEqualTo(DataTypes.TIME); - } - @Test // DATACASS-172, DATACASS-455 public void shouldRegisterUdtTypes() { @@ -566,45 +368,6 @@ public class CassandraMappingContextUnitTests { assertThat(mappingContext.usesUserType(CqlIdentifier.fromCql("mappedudt"))).isTrue(); } - @Test // DATACASS-506 - public void shouldCreatedUserTypeSpecificationsWithAnnotatedTypeName() { - - assertThat( - mappingContext.getCreateUserTypeSpecificationFor(mappingContext.getRequiredPersistentEntity(WithUdt.class))) - .isNotNull(); - assertThat( - mappingContext.getCreateUserTypeSpecificationFor(mappingContext.getRequiredPersistentEntity(Nested.class))) - .isNotNull(); - } - - @Test // DATACASS-172 - public void createTableForComplexPrimaryKeyShouldFail() { - - try { - mappingContext.getCreateTableSpecificationFor( - mappingContext.getRequiredPersistentEntity(EntityWithComplexPrimaryKeyColumn.class)); - fail("Missing MappingException"); - } catch (MappingException e) { - assertThat(e).hasMessageContaining("Unknown type [class java.lang.Object] for property [complexObject]"); - } - - try { - mappingContext - .getCreateTableSpecificationFor(mappingContext.getRequiredPersistentEntity(EntityWithComplexId.class)); - fail("Missing MappingException"); - } catch (MappingException e) { - assertThat(e).hasMessageContaining("Unknown type [class java.lang.Object] for property [complexObject]"); - } - - try { - mappingContext.getCreateTableSpecificationFor( - mappingContext.getRequiredPersistentEntity(EntityWithPrimaryKeyClassWithComplexId.class)); - fail("Missing MappingException"); - } catch (MappingException e) { - assertThat(e).hasMessageContaining("Unknown type [class java.lang.Object] for property [complexObject]"); - } - } - @Test // DATACASS-282, DATACASS-455 public void shouldNotRetainInvalidEntitiesInCache() { @@ -632,26 +395,11 @@ public class CassandraMappingContextUnitTests { @PrimaryKeyColumn String bar; } - @Table - static class EntityWithComplexPrimaryKeyColumn { - @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) Object complexObject; - } - - @Table - static class EntityWithComplexId { - @Id Object complexObject; - } - @PrimaryKeyClass static class PrimaryKeyClassWithComplexId { @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) Object complexObject; } - @Table - static class EntityWithPrimaryKeyClassWithComplexId { - @Id PrimaryKeyClassWithComplexId primaryKeyClassWithComplexId; - } - private static class Human {} @Table @@ -679,12 +427,6 @@ public class CassandraMappingContextUnitTests { @CassandraType(type = Name.UDT, userTypeName = "NestedType") Nested nested; } - @Table - private static class WithMapOfMixedTypes { - @Id String id; - Map> people; - } - enum HumanToStringConverter implements Converter { INSTANCE; @@ -695,63 +437,14 @@ public class CassandraMappingContextUnitTests { } } - @Table - private static class TypeWithCustomConvertedMap { - @Id String id; - Map> stringMap; - @CassandraType(type = Name.ASCII) Map> blobMap; - } - - @Table - private static class TypeWithListOfHumans { - @Id String id; - List humans; - } - - @WritingConverter - enum StringMapToStringConverter implements Converter>, String> { - - INSTANCE; - - @Override - public String convert(Map> source) { - return "serialized"; - } - } - @org.springframework.data.cassandra.core.mapping.UserDefinedType(value = "NestedType") public static class Nested { String s1; - @CassandraType(type = Name.UDT, - userTypeName = "AnotherNestedType") AnotherNested anotherNested; + @CassandraType(type = Name.UDT, userTypeName = "AnotherNestedType") AnotherNested anotherNested; } @org.springframework.data.cassandra.core.mapping.UserDefinedType(value = "AnotherNestedType") public static class AnotherNested { String str; } - - @Table - static class TypedTupleEntity { - @Id String id; - @CassandraType(type = Name.TUPLE, typeArguments = { Name.VARCHAR, Name.BIGINT }) TupleValue typed; - } - - @Table - static class EntityWithMapOfTuples { - @Id String id; - Map map; - } - - @Table - static class UntypedTupleEntity { - @Id String id; - TupleValue untyped; - } - - @Table - static class UntypedTupleMapEntity { - @Id String id; - Map untyped; - } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraUserTypePersistentEntityUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraUserTypePersistentEntityUnitTests.java index 4e347f919..91ae389bd 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraUserTypePersistentEntityUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/CassandraUserTypePersistentEntityUnitTests.java @@ -24,7 +24,6 @@ import java.lang.annotation.Target; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.core.annotation.AliasFor; @@ -40,8 +39,6 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; @RunWith(MockitoJUnitRunner.class) public class CassandraUserTypePersistentEntityUnitTests { - @Mock UserTypeResolver userTypeResolverMock; - @Test // DATACASS-172 public void isUserDefinedTypeShouldReportTrue() { @@ -83,7 +80,7 @@ public class CassandraUserTypePersistentEntityUnitTests { } private CassandraUserTypePersistentEntity getEntity(Class entityClass) { - return new CassandraUserTypePersistentEntity<>(ClassTypeInformation.from(entityClass), null, userTypeResolverMock); + return new CassandraUserTypePersistentEntity<>(ClassTypeInformation.from(entityClass), null); } @UserDefinedType 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 7c537148d..306f8f1c9 100755 --- 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 @@ -34,7 +34,6 @@ import org.springframework.data.cassandra.core.mapping.CassandraPersistentProper import org.springframework.data.cassandra.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator; import com.datastax.oss.driver.api.core.type.DataTypes; -import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** * Unit tests for {@link ConvertingParameterAccessor}. @@ -56,15 +55,13 @@ public class ConvertingParameterAccessorUnitTests { this.converter = new MappingCassandraConverter(); this.converter.afterPropertiesSet(); - this.convertingParameterAccessor = new ConvertingParameterAccessor(converter, mockParameterAccessor, - CodecRegistry.DEFAULT); + this.convertingParameterAccessor = new ConvertingParameterAccessor(converter, mockParameterAccessor); } @Test // DATACASS-296 public void shouldReturnNullBindableValue() { - ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter, mockParameterAccessor, - CodecRegistry.DEFAULT); + ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter, mockParameterAccessor); assertThat(accessor.getBindableValue(0)).isNull(); } @@ -75,8 +72,7 @@ public class ConvertingParameterAccessorUnitTests { when(mockParameterAccessor.getBindableValue(0)).thenReturn("hello"); - ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter, mockParameterAccessor, - CodecRegistry.DEFAULT); + ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(converter, mockParameterAccessor); assertThat(accessor.getBindableValue(0)).isEqualTo((Object) "hello"); } 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 index b7889751a..c52758f6e 100644 --- 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 @@ -263,7 +263,7 @@ public class PartTreeCassandraQueryUnitTests { args); return partTreeQuery.createQuery( - new ConvertingParameterAccessor(mockCassandraOperations.getConverter(), accessor, CodecRegistry.DEFAULT)); + new ConvertingParameterAccessor(mockCassandraOperations.getConverter(), accessor)); } private PartTreeCassandraQuery createQueryForMethod(Class repositoryInterface, String methodName, diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQueryUnitTests.java index eb258d187..64574a836 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQueryUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactivePartTreeCassandraQueryUnitTests.java @@ -47,7 +47,6 @@ import org.springframework.util.ClassUtils; import com.datastax.oss.driver.api.core.DefaultConsistencyLevel; import com.datastax.oss.driver.api.core.cql.SimpleStatement; -import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** * Unit tests for {@link ReactivePartTreeCassandraQuery}. @@ -177,8 +176,7 @@ public class ReactivePartTreeCassandraQueryUnitTests { CassandraParameterAccessor accessor = new CassandraParametersParameterAccessor(partTreeQuery.getQueryMethod(), args); - return partTreeQuery.createQuery(new ConvertingParameterAccessor(mockCassandraOperations.getConverter(), accessor, - CodecRegistry.DEFAULT)); + return partTreeQuery.createQuery(new ConvertingParameterAccessor(mockCassandraOperations.getConverter(), accessor)); } private ReactivePartTreeCassandraQuery createQueryForMethod(Class repositoryInterface, String methodName, diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java index 532060383..b369f0063 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java @@ -60,7 +60,6 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement; import com.datastax.oss.driver.api.core.data.UdtValue; import com.datastax.oss.driver.api.core.type.DataTypes; import com.datastax.oss.driver.api.core.type.UserDefinedType; -import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; /** * Unit tests for {@link StringBasedCassandraQuery}. @@ -327,8 +326,7 @@ public class StringBasedCassandraQueryUnitTests { StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByCreatedDate", LocalDate.class); CassandraParameterAccessor accessor = new ConvertingParameterAccessor(converter, - new CassandraParametersParameterAccessor(cassandraQuery.getQueryMethod(), LocalDate.of(2010, 7, 4)), - CodecRegistry.DEFAULT); + new CassandraParametersParameterAccessor(cassandraQuery.getQueryMethod(), LocalDate.of(2010, 7, 4))); SimpleStatement actual = cassandraQuery.createQuery(accessor); @@ -347,8 +345,7 @@ public class StringBasedCassandraQueryUnitTests { StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByMainAddress", AddressType.class); CassandraParameterAccessor accessor = new ConvertingParameterAccessor(converter, - new CassandraParametersParameterAccessor(cassandraQuery.getQueryMethod(), new AddressType()), - CodecRegistry.DEFAULT); + new CassandraParametersParameterAccessor(cassandraQuery.getQueryMethod(), new AddressType())); SimpleStatement actual = cassandraQuery.createQuery(accessor); @@ -360,8 +357,13 @@ public class StringBasedCassandraQueryUnitTests { public void bindsUdtValuePropertyCorrectly() throws Exception { StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByMainAddress", UdtValue.class); + + UserDefinedType addressType = UserDefinedTypeBuilder.forName("address").withField("city", DataTypes.TEXT) + .withField("country", DataTypes.TEXT).build(); + when(udtValue.getType()).thenReturn(addressType); + CassandraParameterAccessor accessor = new ConvertingParameterAccessor(converter, - new CassandraParametersParameterAccessor(cassandraQuery.getQueryMethod(), udtValue), CodecRegistry.DEFAULT); + new CassandraParametersParameterAccessor(cassandraQuery.getQueryMethod(), udtValue)); SimpleStatement actual = cassandraQuery.createQuery(accessor); 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 index 498e9c47c..4ff06fde3 100644 --- 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 @@ -48,8 +48,7 @@ class StubParameterAccessor implements CassandraParameterAccessor { * @return */ public static ConvertingParameterAccessor getAccessor(CassandraConverter converter, Object... parameters) { - return new ConvertingParameterAccessor(converter, new StubParameterAccessor(parameters), - CodecRegistry.DEFAULT); + return new ConvertingParameterAccessor(converter, new StubParameterAccessor(parameters)); } @SuppressWarnings("unchecked") diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SchemaTestUtils.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SchemaTestUtils.java index 157f28ea9..1bbb44690 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SchemaTestUtils.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/support/SchemaTestUtils.java @@ -18,6 +18,7 @@ package org.springframework.data.cassandra.repository.support; import java.util.Optional; import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.data.cassandra.core.convert.SchemaFactory; import org.springframework.data.cassandra.core.cql.SessionCallback; import org.springframework.data.cassandra.core.cql.generator.CreateTableCqlGenerator; import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification; @@ -44,6 +45,7 @@ public class SchemaTestUtils { CassandraMappingContext mappingContext = operations.getConverter().getMappingContext(); CassandraPersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(entityClass); + SchemaFactory schemaFactory = new SchemaFactory(operations.getConverter()); operations.getCqlOperations().execute((SessionCallback) session -> { @@ -51,7 +53,7 @@ public class SchemaTestUtils { .flatMap(it -> it.getTable(persistentEntity.getTableName())); if (!table.isPresent()) { - CreateTableSpecification tableSpecification = mappingContext.getCreateTableSpecificationFor(persistentEntity); + CreateTableSpecification tableSpecification = schemaFactory.getCreateTableSpecificationFor(persistentEntity); operations.getCqlOperations().execute(new CreateTableCqlGenerator(tableSpecification).toCql()); } return null; diff --git a/src/main/asciidoc/reference/migration-guide-2.2-to-3.0.adoc b/src/main/asciidoc/reference/migration-guide-2.2-to-3.0.adoc index 77d92e10e..405113192 100644 --- a/src/main/asciidoc/reference/migration-guide-2.2-to-3.0.adoc +++ b/src/main/asciidoc/reference/migration-guide-2.2-to-3.0.adoc @@ -139,6 +139,8 @@ Paging state now uses `ByteBuffer`. * Introduction of `StatementBuilder` to functionally build statements as the QueryBuilder API uses immutable statement types. * `Session` bean renamed from `session` to `cassandraSession` and `SessionFactory` bean renamed from `sessionFactory` to `cassandraSessionFactory`. * `ReactiveSession` bean renamed from `reactiveSession` to `reactiveCassandraSession` and `ReactiveSessionFactory` bean renamed from `reactiveSessionFactory` to `reactiveCassandraSessionFactory`. +* Data type resolution was moved into `ColumnTypeResolver` so all `DataType`-related methods were moved from `CassandraPersistentEntity`/`CassandraPersistentProperty` into `ColumnTypeResolver` (affected methods are `MappingContext.getDataType(…)`, `CassandraPersistentProperty.getDataType()`, `CassandraPersistentEntity.getUserType()`, and `CassandraPersistentEntity.getTupleType()`). +* Schema creation was moved from `MappingContext` to `SchemaFactory` (affected methods are `CassandraMappingContext.getCreateTableSpecificationFor(…)`, `CassandraMappingContext.getCreateIndexSpecificationsFor(…)`, and `CassandraMappingContext.getCreateUserTypeSpecificationFor(…)`). == Deprecations @@ -151,6 +153,8 @@ Paging state now uses `ByteBuffer`. * Spring Data's `CqlIdentifier`, use the driver `CqlIdentifier` instead. * `forceQuote` attributes as quoting is no longer required. `CqlIdentifier` properly escapes reserved keywords and takes care of case-sensitivity. * `fetchSize` on `QueryOptions` and `…CqlTemplate` types was deprecated, use `pageSize` instead +* `CassandraMappingContext.setUserTypeResolver(…)`, `CassandraMappingContext.setCodecRegistry(…)`, and `CassandraMappingContext.setCustomConversions(…)`: Configure these properties on `CassandraConverter`. +* `TupleTypeFactory` and `CassandraMappingContext.setTupleTypeFactory(…)`: `TupleTypeFactory` is no longer used as the Cassandra driver ships with a `DataTypes.tupleOf(…)` factory method. * Schema creation via `CqlSessionFactoryBean` (`cassandra:session`) is deprecated. Keyspace creation via `CqlSessionFactoryBean` (`cassandra:session`) is not affected.