org.springframework.data.cassandra.core.convert.SchemaFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spring-data-cassandra Show documentation
Show all versions of spring-data-cassandra Show documentation
Cassandra support for Spring Data
/*
* Copyright 2020-2022 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.EmbeddedEntityOperations;
import org.springframework.data.cassandra.core.mapping.Indexed;
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.lang.Nullable;
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
* @author Christoph Strobl
* @author Aleksei Zotov
* @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;
private final EmbeddedEntityOperations embeddedEntityOperations;
/**
* 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);
this.embeddedEntityOperations = new EmbeddedEntityOperations(this.mappingContext);
}
/**
* 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);
this.embeddedEntityOperations = new EmbeddedEntityOperations(this.mappingContext);
}
/**
* 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 if (property.isEmbedded()) {
CassandraPersistentEntity embeddedEntity = embeddedEntityOperations.getEntity(property);
for (CassandraPersistentProperty embeddedProperty : embeddedEntity) {
DataType dataType = getDataType(embeddedProperty);
specification.column(embeddedProperty.getRequiredColumnName(), dataType);
}
} 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 if (property.isStaticColumn()) {
specification.staticColumn(property.getRequiredColumnName(), type);
} 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)));
}
if (property.isEmbedded()) {
if (property.isAnnotationPresent(Indexed.class)) {
Indexed indexed = property.findAnnotation(Indexed.class);
for (CassandraPersistentProperty embeddedProperty : embeddedEntityOperations.getEntity(property)) {
indexes.add(IndexSpecificationFactory.createIndexSpecification(indexed, embeddedProperty));
}
} else {
indexes.addAll(getCreateIndexSpecificationsFor(embeddedEntityOperations.getEntity(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) {
if (property.isEmbedded()) {
CassandraPersistentEntity embeddedEntity = embeddedEntityOperations.getEntity(property);
for (CassandraPersistentProperty embeddedProperty : embeddedEntity) {
DataType dataType = getDataType(embeddedProperty);
specification.field(embeddedProperty.getRequiredColumnName(), dataType);
}
} else {
// 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(@Nullable 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) + ")";
}
}
}