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