DATACASS-406 - Support nested @UserDefinedType for schema creation.

Works for simple user defined types and those used in Lists or Sets.

It does not work yet for maps and other collection-like types.

Related tickets: DATACASS-409.

Original pull request: #100.
This commit is contained in:
Jens Schauder
2017-02-28 14:27:17 +01:00
committed by Mark Paluch
parent 15f9169640
commit ca60ca6433
3 changed files with 181 additions and 39 deletions

View File

@@ -41,6 +41,7 @@ import org.springframework.util.Assert;
* This class generates CQL to create user types (UDT) and tables.
*
* @author Mark Paluch
* @author Jens Schauder
* @since 1.5
* @see org.springframework.data.cassandra.mapping.Table
* @see org.springframework.data.cassandra.mapping.UserDefinedType
@@ -143,6 +144,16 @@ public class CassandraPersistentEntitySchemaCreator {
return specifications;
}
private Map<CqlIdentifier, CassandraPersistentEntity<?>> getEntitiesByTableName(Collection<? extends CassandraPersistentEntity<?>> entities) {
// TODO simplify by using Java 8 Streams API in 2.0.x
Map<CqlIdentifier, CassandraPersistentEntity<?>> byTableName = new HashMap<CqlIdentifier, CassandraPersistentEntity<?>>();
for (CassandraPersistentEntity<?> entity : entities) {
byTableName.put(entity.getTableName(), entity);
}
return byTableName;
}
private void visitUserTypes(CassandraPersistentEntity<?> entity, final Set<CqlIdentifier> seen) {
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {

View File

@@ -60,6 +60,7 @@ import com.datastax.driver.core.UserType;
* @author Matthew T. Adams
* @author Mark Paluch
* @author John Blum
* @author Jens Schauder
*/
public class BasicCassandraMappingContext
extends AbstractMappingContext<CassandraPersistentEntity<?>, CassandraPersistentProperty>
@@ -68,7 +69,7 @@ public class BasicCassandraMappingContext
protected ApplicationContext context;
protected CassandraPersistentEntityMetadataVerifier verifier =
new CompositeCassandraPersistentEntityMetadataVerifier();
new CompositeCassandraPersistentEntityMetadataVerifier();
protected ClassLoader beanClassLoader;
@@ -118,7 +119,7 @@ public class BasicCassandraMappingContext
CassandraPersistentEntity<?> entity = getPersistentEntity(entityClass);
Assert.state(entity != null,
String.format("Unknown persistent entity class name [%s]", entityClassName));
String.format("Unknown persistent entity class name [%s]", entityClassName));
String entityTableName = entityMapping.getTableName();
@@ -127,10 +128,9 @@ public class BasicCassandraMappingContext
}
processMappingOverrides(entity, entityMapping);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(
String.format("Unknown persistent entity name [%s]", entityClassName), e);
String.format("Unknown persistent entity name [%s]", entityClassName), e);
}
});
}
@@ -148,7 +148,7 @@ public class BasicCassandraMappingContext
CassandraPersistentProperty property = entity.getPersistentProperty(mapping.getPropertyName());
Assert.notNull(property, String.format("Entity class [%s] has no persistent property named [%s]",
entity.getType().getName(), mapping.getPropertyName()));
entity.getType().getName(), mapping.getPropertyName()));
boolean forceQuote = Boolean.valueOf(mapping.getForceQuote());
@@ -259,7 +259,7 @@ public class BasicCassandraMappingContext
protected <T> CassandraPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
UserDefinedType userDefinedType = AnnotatedElementUtils.findMergedAnnotation(
typeInformation.getType(), UserDefinedType.class);
typeInformation.getType(), UserDefinedType.class);
CassandraPersistentEntity<T> entity;
@@ -302,13 +302,13 @@ public class BasicCassandraMappingContext
@Override
public CassandraPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
CassandraPersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
CassandraPersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
return createPersistentProperty(field, descriptor, owner, (CassandraSimpleTypeHolder) simpleTypeHolder);
}
public CassandraPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
CassandraPersistentEntity<?> owner, CassandraSimpleTypeHolder simpleTypeHolder) {
CassandraPersistentEntity<?> owner, CassandraSimpleTypeHolder simpleTypeHolder) {
return new BasicCassandraPersistentProperty(field, descriptor, owner, simpleTypeHolder, userTypeResolver);
}
@@ -389,24 +389,20 @@ public class BasicCassandraMappingContext
public void doWithPersistentProperty(CassandraPersistentProperty primaryKeyProperty) {
if (primaryKeyProperty.isPartitionKeyColumn()) {
specification.partitionKeyColumn(primaryKeyProperty.getColumnName(),
getDataType(primaryKeyProperty));
}
else { // it's a cluster column
getDataType(primaryKeyProperty));
} else { // it's a cluster column
specification.clusteredKeyColumn(primaryKeyProperty.getColumnName(),
getDataType(primaryKeyProperty), primaryKeyProperty.getPrimaryKeyOrdering());
getDataType(primaryKeyProperty), primaryKeyProperty.getPrimaryKeyOrdering());
}
}
});
} else {
if (property.isIdProperty() || property.isPartitionKeyColumn()) {
specification.partitionKeyColumn(property.getColumnName(), getDataType(property));
}
else if (property.isClusterKeyColumn()) {
} else if (property.isClusterKeyColumn()) {
specification.clusteredKeyColumn(property.getColumnName(), getDataType(property),
property.getPrimaryKeyOrdering());
}
else {
property.getPrimaryKeyOrdering());
} else {
specification.column(property.getColumnName(), getDataType(property));
}
}
@@ -434,8 +430,12 @@ public class BasicCassandraMappingContext
entity.doWithProperties(new PropertyHandler<CassandraPersistentProperty>() {
@Override
public void doWithPersistentProperty(CassandraPersistentProperty property) {
specification.field(property.getColumnName(), getDataType(property));
public void doWithPersistentProperty(final CassandraPersistentProperty property) {
specification.field(
property.getColumnName(),
getDataTypeWithUserTypeFactory(property, UserDataTypeProvider.Fake)
);
}
});
@@ -470,6 +470,10 @@ public class BasicCassandraMappingContext
@Override
public DataType getDataType(CassandraPersistentProperty property) {
return getDataTypeWithUserTypeFactory(property, UserDataTypeProvider.Simple);
}
private DataType getDataTypeWithUserTypeFactory(CassandraPersistentProperty property, UserDataTypeProvider userDataTypeProvider) {
if (property.isCompositePrimaryKey()) {
return property.getDataType();
}
@@ -478,10 +482,12 @@ public class BasicCassandraMappingContext
return property.getDataType();
}
CassandraPersistentEntity<?> persistentEntity = getPersistentEntity(property.getType());
CassandraPersistentEntity<?> persistentEntity = getPersistentEntity(property.getActualType());
if (persistentEntity != null && persistentEntity.isUserDefinedType()) {
return persistentEntity.getUserType();
DataType elementType = getUserDataType(property, userDataTypeProvider, persistentEntity);
if (elementType != null) return elementType;
}
if (customConversions.hasCustomWriteTarget(property.getType())) {
@@ -507,13 +513,33 @@ public class BasicCassandraMappingContext
return property.getDataType();
}
private DataType getUserDataType(CassandraPersistentProperty property, UserDataTypeProvider userDataTypeProvider, CassandraPersistentEntity<?> persistentEntity) {
DataType elementType = userDataTypeProvider.get(persistentEntity);
if (property.isCollectionLike()) {
if (Set.class.isAssignableFrom(property.getType())) {
return DataType.set(elementType);
}
if (List.class.isAssignableFrom(property.getType())) {
return DataType.list(elementType);
}
}
if (!property.isCollectionLike() && !property.isMapLike()) {
return elementType;
}
return null;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.mapping.CassandraMappingContext#getDataType(java.lang.Class)
*/
@Override
public DataType getDataType(Class<?> type) {
return (customConversions.hasCustomWriteTarget(type)
? getDataTypeFor(customConversions.getCustomWriteTarget(type)) : getDataTypeFor(type));
? getDataTypeFor(customConversions.getCustomWriteTarget(type)) : getDataTypeFor(type));
}
@Override
@@ -530,4 +556,44 @@ public class BasicCassandraMappingContext
public boolean contains(Class<?> type) {
return entitiesByType.containsKey(type);
}
enum UserDataTypeProvider {
Simple {
@Override
public DataType get(CassandraPersistentEntity<?> entity) {
return entity.getUserType();
}
},
Fake {
@Override
public DataType get(CassandraPersistentEntity<?> entity) {
return new FakeUserType(entity.getTableName());
}
};
abstract DataType get(CassandraPersistentEntity<?> entity);
}
static class FakeUserType extends DataType {
private final CqlIdentifier type;
protected FakeUserType(CqlIdentifier type) {
super(Name.UDT);
this.type = type;
}
@Override
public boolean isFrozen() {
return false;
}
@Override
public String toString() {
return String.format("frozen<%s>", type.toCql());
}
}
}

View File

@@ -19,6 +19,7 @@ import static org.mockito.Mockito.*;
import lombok.Data;
import java.util.List;
import java.util.Set;
import org.junit.Before;
@@ -39,8 +40,9 @@ import com.datastax.driver.core.UserType;
/**
* Unit tests for {@link CassandraPersistentEntitySchemaCreator}.
*
* @author Mark Paluch.
*
* @author Mark Paluch
* @author Jens Schauder
*/
@RunWith(MockitoJUnitRunner.class)
public class CassandraPersistentEntitySchemaCreatorUnitTests {
@@ -66,34 +68,87 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests {
when(adminOperations.getCqlOperations()).thenReturn(operations);
}
@Test // DATACASS-172
public void shouldCreateTypesInOrder() {
@Test
public void createsCorrectTypeForSimpleTypes(){
context.getPersistentEntity(MoonType.class);
context.getPersistentEntity(PlanetType.class);
context.getPersistentEntity(UniverseType.class);
when(metadata.getUserType("universetype")).thenReturn(universetype);
when(metadata.getUserType("moontype")).thenReturn(moontype);
CassandraPersistentEntitySchemaCreator schemaCreator =
new CassandraPersistentEntitySchemaCreator(context, adminOperations);
schemaCreator.createUserTypes(false);
verifyTypesGetCreatedInOrderFor(
"universetype",
"moontype"
);
}
@Test
public void createsCorrectTypeForSets(){
context.getPersistentEntity(PlanetType.class);
CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(context,
adminOperations);
schemaCreator.createUserTypes(false);
verify(operations).execute(Mockito.contains("CREATE TYPE universetype"));
verify(operations).execute(Mockito.contains("CREATE TYPE moontype"));
verify(operations).execute(Mockito.contains("CREATE TYPE planettype"));
verify(operations).execute(matches("CREATE TYPE planettype .* set<.*moontype>.*"));
verifyTypesGetCreatedInOrderFor(
"universetype",
"moontype",
"planettype"
);
}
@Test
public void createsCorrectTypeForLists(){
context.getPersistentEntity(SpaceAgencyType.class);
CassandraPersistentEntitySchemaCreator schemaCreator =
new CassandraPersistentEntitySchemaCreator(context, adminOperations);
schemaCreator.createUserTypes(false);
verify(operations).execute(matches("CREATE TYPE spaceagencytype .* list<.*astronauttype>.*"));
verifyTypesGetCreatedInOrderFor(
"astronauttype",
"spaceagencytype"
);
}
@Test
public void createsCorrectTypesForNestedTypes(){
context.getPersistentEntity(PlanetType.class);
CassandraPersistentEntitySchemaCreator schemaCreator =
new CassandraPersistentEntitySchemaCreator(context, adminOperations);
schemaCreator.createUserTypes(false);
verifyTypesGetCreatedInOrderFor(
"universetype",
"moontype",
"planettype"
);
}
private void verifyTypesGetCreatedInOrderFor(String ... typenames) {
InOrder inOrder = Mockito.inOrder(operations);
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE universetype"));
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE moontype"));
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE planettype"));
for (String typename : typenames) {
inOrder.verify(operations).execute(Mockito.contains("CREATE TYPE " + typename));
}
}
@UserDefinedType
@Data
static class UniverseType {
String name;
}
@@ -110,4 +165,14 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests {
Set<MoonType> moons;
UniverseType universeType;
}
@UserDefinedType
static class AstronautType {
String name;
}
@UserDefinedType
static class SpaceAgencyType {
List<AstronautType> astronauts;
}
}