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
new file mode 100644
index 000000000..b5688d2fe
--- /dev/null
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraColumnType.java
@@ -0,0 +1,54 @@
+/*
+ * 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 com.datastax.oss.driver.api.core.type.DataType;
+import com.datastax.oss.driver.api.core.type.TupleType;
+import com.datastax.oss.driver.api.core.type.UserDefinedType;
+
+/**
+ * Descriptor for a Cassandra column type exposing a {@link DataType}.
+ *
+ * @author Mark Paluch
+ * @since 3.0
+ */
+public interface CassandraColumnType extends ColumnType {
+
+ /**
+ * Returns the {@link DataType} associated with this column type.
+ *
+ * @return
+ */
+ DataType getDataType();
+
+ /**
+ * Returns whether the associated {@link DataType} is a {@link TupleType}.
+ *
+ * @return
+ */
+ default boolean isTupleType() {
+ return getDataType() instanceof TupleType;
+ }
+
+ /**
+ * Returns whether the associated {@link DataType} is a {@link UserDefinedType}.
+ *
+ * @return
+ */
+ default boolean isUserDefinedType() {
+ return getDataType() instanceof UserDefinedType;
+ }
+}
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 daf6df034..30e037e22 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
@@ -23,6 +23,7 @@ import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityConverter;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
/**
* Central Cassandra specific converter interface from Object to Row.
@@ -47,6 +48,15 @@ public interface CassandraConverter
@Override
CassandraMappingContext getMappingContext();
+ /**
+ * Returns the {@link ColumnTypeResolver} to resolve {@link ColumnType} for properties, {@link TypeInformation}, and
+ * {@code values}.
+ *
+ * @return the {@link ColumnTypeResolver}
+ * @since 3.0
+ */
+ ColumnTypeResolver getColumnTypeResolver();
+
/**
* Returns the Id for an entity. It can return:
*
@@ -82,7 +92,23 @@ public interface CassandraConverter
* @return the result of the conversion.
* @since 1.5
*/
- Object convertToColumnType(Object value, TypeInformation> typeInformation);
+ default Object convertToColumnType(Object value, TypeInformation> typeInformation) {
+
+ Assert.notNull(value, "Value must not be null");
+ Assert.notNull(typeInformation, "TypeInformation must not be null");
+
+ return convertToColumnType(value, getColumnTypeResolver().resolve(typeInformation));
+ }
+
+ /**
+ * Converts the given object into a value Cassandra will be able to store natively in a column.
+ *
+ * @param value {@link Object} to convert; must not be {@literal null}.
+ * @param typeDescriptor {@link ColumnType} used to describe the object type; must not be {@literal null}.
+ * @return the result of the conversion.
+ * @since 3.0
+ */
+ Object convertToColumnType(Object value, ColumnType typeDescriptor);
/**
* Converts and writes a {@code source} object into a {@code sink} using the given {@link CassandraPersistentEntity}.
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
new file mode 100644
index 000000000..572d2e43e
--- /dev/null
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/ColumnType.java
@@ -0,0 +1,258 @@
+/*
+ * 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 org.springframework.data.util.ClassTypeInformation;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Nullable;
+
+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.TupleType;
+import com.datastax.oss.driver.api.core.type.UserDefinedType;
+
+/**
+ * Interface to access column type information. The {@link CassandraColumnType} subtype exposes Cassandra-specific
+ * {@link DataType} information.
+ *
+ * @author Mark Paluch
+ * @since 3.0
+ */
+public interface ColumnType {
+
+ /**
+ * Creates a {@link ColumnType} for a {@link Class}.
+ *
+ * @param type must not be {@literal null}.
+ * @return
+ */
+ static ColumnType create(Class> type) {
+ return create(ClassTypeInformation.from(type));
+ }
+
+ /**
+ * Creates a {@link ColumnType} for a {@link TypeInformation}.
+ *
+ * @param type must not be {@literal null}.
+ * @return
+ */
+ static ColumnType create(TypeInformation> type) {
+ return new DefaultColumnType(type);
+ }
+
+ /**
+ * Creates a {@link ColumnType} for a {@link Class} and {@link DataType}.
+ *
+ * @param type must not be {@literal null}.
+ * @param dataType must not be {@literal null}.
+ * @return
+ */
+ static CassandraColumnType create(Class> type, DataType dataType) {
+ return new DefaultCassandraColumnType(ClassTypeInformation.from(type), dataType);
+ }
+
+ /**
+ * Creates a List {@link ColumnType} given its {@link ColumnType component type}.
+ *
+ * @param componentType must not be {@literal null}.
+ * @return
+ */
+ static ColumnType listOf(ColumnType componentType) {
+
+ if (componentType instanceof CassandraColumnType) {
+ return listOf((CassandraColumnType) componentType);
+ }
+
+ return new DefaultColumnType(ClassTypeInformation.LIST, componentType);
+ }
+
+ /**
+ * Creates a List {@link ColumnType} given its {@link CassandraColumnType component type}.
+ *
+ * @param componentType must not be {@literal null}.
+ * @return
+ */
+ static CassandraColumnType listOf(CassandraColumnType componentType) {
+ return new DefaultCassandraColumnType(ClassTypeInformation.LIST, DataTypes.listOf(componentType.getDataType()),
+ componentType);
+ }
+
+ /**
+ * Creates a Set {@link ColumnType} given its {@link ColumnType component type}.
+ *
+ * @param componentType must not be {@literal null}.
+ * @return
+ */
+ static ColumnType setOf(ColumnType componentType) {
+
+ if (componentType instanceof CassandraColumnType) {
+ return setOf((CassandraColumnType) componentType);
+ }
+
+ return new DefaultColumnType(ClassTypeInformation.SET, componentType);
+ }
+
+ /**
+ * Creates a Set {@link ColumnType} given its {@link CassandraColumnType component type}.
+ *
+ * @param componentType must not be {@literal null}.
+ * @return
+ */
+ static CassandraColumnType setOf(CassandraColumnType componentType) {
+ return new DefaultCassandraColumnType(ClassTypeInformation.SET, DataTypes.setOf(componentType.getDataType()),
+ componentType);
+ }
+
+ /**
+ * Creates a Map {@link ColumnType} given its {@link ColumnType key and value types}.
+ *
+ * @param keyType must not be {@literal null}.
+ * @param valueType must not be {@literal null}.
+ * @return
+ */
+ static ColumnType mapOf(ColumnType keyType, ColumnType valueType) {
+ return new DefaultColumnType(ClassTypeInformation.MAP, keyType, valueType);
+ }
+
+ /**
+ * Creates a Map {@link CassandraColumnType} given its {@link CassandraColumnType key and value types}.
+ *
+ * @param keyType must not be {@literal null}.
+ * @param valueType must not be {@literal null}.
+ * @return
+ */
+ static CassandraColumnType mapOf(CassandraColumnType keyType, CassandraColumnType valueType) {
+ return new DefaultCassandraColumnType(ClassTypeInformation.MAP,
+ DataTypes.mapOf(keyType.getDataType(), valueType.getDataType()), keyType, valueType);
+ }
+
+ /**
+ * Creates a UDT {@link CassandraColumnType} given its {@link UserDefinedType Cassandra type}.
+ *
+ * @param dataType must not be {@literal null}.
+ * @return
+ */
+ static CassandraColumnType udtOf(UserDefinedType dataType) {
+ return new DefaultCassandraColumnType(UdtValue.class, dataType);
+ }
+
+ /**
+ * Creates a Tuple {@link CassandraColumnType} given its {@link TupleType Cassandra type}.
+ *
+ * @param dataType must not be {@literal null}.
+ * @return
+ */
+ static CassandraColumnType tupleOf(TupleType dataType) {
+ return new DefaultCassandraColumnType(TupleValue.class, dataType);
+ }
+
+ /**
+ * Returns the Java type of the column.
+ *
+ * @return
+ */
+ Class> getType();
+
+ /**
+ * Returns whether the type can be considered a collection, which means it's a container of elements, e.g. a
+ * {@link java.util.Collection} and {@link java.lang.reflect.Array} or anything implementing {@link Iterable}. If this
+ * returns {@literal true} you can expect {@link #getComponentType()} to return a non-{@literal null} value.
+ *
+ * @return
+ */
+ boolean isCollectionLike();
+
+ /**
+ * Returns whether the property is a {@link java.util.List}. If this returns {@literal true} you can expect
+ * {@link #getComponentType()} to return something not {@literal null}.
+ *
+ * @return
+ */
+ boolean isList();
+
+ /**
+ * Returns whether the property is a {@link java.util.Set}. If this returns {@literal true} you can expect
+ * {@link #getComponentType()} to return something not {@literal null}.
+ *
+ * @return
+ */
+ boolean isSet();
+
+ /**
+ * Returns whether the property is a {@link java.util.Map}. If this returns {@literal true} you can expect
+ * {@link #getComponentType()} as well as {@link #getMapValueType()} to return something not {@literal null}.
+ *
+ * @return
+ */
+ boolean isMap();
+
+ /**
+ * Returns the component type for {@link java.util.Collection}s or the key type for {@link java.util.Map}s.
+ *
+ * @return
+ */
+ @Nullable
+ ColumnType getComponentType();
+
+ /**
+ * Returns the component type for {@link java.util.Collection}s, the key type for {@link java.util.Map}s or the single
+ * generic type if available. Throws {@link IllegalStateException} if the component value type cannot be resolved.
+ *
+ * @return
+ * @throws IllegalStateException if the component type cannot be resolved, e.g. if a raw type is used or the type is
+ * not generic in the first place.
+ */
+ default ColumnType getRequiredComponentType() {
+
+ ColumnType columnType = getComponentType();
+
+ if (columnType == null) {
+ throw new IllegalStateException("Type has no component type");
+ }
+
+ return columnType;
+ }
+
+ /**
+ * Returns the map value type in case the underlying type is a {@link java.util.Map}.
+ *
+ * @return
+ */
+ @Nullable
+ ColumnType getMapValueType();
+
+ /**
+ * Returns the map value type in case the underlying type is a {@link java.util.Map}. or throw
+ * {@link IllegalStateException} if the map value type cannot be resolved.
+ *
+ * @return
+ * @throws IllegalStateException if the map value type cannot be resolved, usually due to the current
+ * {@link java.util.Map} type being a raw one.
+ */
+ default ColumnType getRequiredMapValueType() {
+
+ ColumnType columnType = getMapValueType();
+
+ if (columnType == null) {
+ throw new IllegalStateException("Type has no map value type");
+ }
+
+ return columnType;
+ }
+
+}
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
new file mode 100644
index 000000000..932a1d30a
--- /dev/null
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolver.java
@@ -0,0 +1,83 @@
+/*
+ * 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 org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
+import org.springframework.data.cassandra.core.mapping.CassandraType;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+/**
+ * Resolves {@link ColumnType} for properties, {@link TypeInformation}, and {@code values}.
+ *
+ * @author Mark Paluch
+ * @since 3.0
+ */
+public interface ColumnTypeResolver {
+
+ /**
+ * Resolve a {@link CassandraColumnType} from a {@link CassandraPersistentProperty}. Considers
+ * {@link CassandraType}-annotated properties.
+ *
+ * @param property must not be {@literal null}.
+ * @return
+ * @see CassandraType
+ * @see CassandraPersistentProperty
+ */
+ default CassandraColumnType resolve(CassandraPersistentProperty property) {
+
+ Assert.notNull(property, "Property must not be null");
+
+ if (property.isAnnotationPresent(CassandraType.class)) {
+ return resolve(property.getRequiredAnnotation(CassandraType.class));
+ }
+
+ return resolve(property.getTypeInformation());
+ }
+
+ /**
+ * Resolve a {@link CassandraColumnType} from {@link TypeInformation}. Considers potentially registered custom
+ * converters and simple type rules.
+ *
+ * @param typeInformation must not be {@literal null}.
+ * @return
+ * @see org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder
+ * @see CassandraCustomConversions
+ */
+ CassandraColumnType resolve(TypeInformation> typeInformation);
+
+ /**
+ * Resolve a {@link CassandraColumnType} from a {@link CassandraType} annotation.
+ *
+ * @param annotation must not be {@literal null}.
+ * @return
+ * @see CassandraType
+ * @see CassandraPersistentProperty
+ */
+ CassandraColumnType resolve(CassandraType annotation);
+
+ /**
+ * Resolve a {@link ColumnType} from a {@code value}. Considers potentially registered custom converters and simple
+ * type rules.
+ *
+ * @param value
+ * @return
+ * @see org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder
+ * @see CassandraCustomConversions
+ */
+ ColumnType resolve(@Nullable Object value);
+}
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
new file mode 100644
index 000000000..e9cbf1c7c
--- /dev/null
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultCassandraColumnType.java
@@ -0,0 +1,81 @@
+/*
+ * 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 java.util.stream.Collectors;
+
+import org.springframework.data.util.ClassTypeInformation;
+import org.springframework.data.util.TypeInformation;
+
+import com.datastax.oss.driver.api.core.type.DataType;
+import com.datastax.oss.driver.api.core.type.UserDefinedType;
+
+/**
+ * Default {@link CassandraColumnType} implementation.
+ *
+ * @author Mark Paluch
+ * @since 3.0
+ */
+class DefaultCassandraColumnType extends DefaultColumnType implements CassandraColumnType {
+
+ private final DataType dataType;
+
+ DefaultCassandraColumnType(Class> type, DataType dataType, ColumnType... parameters) {
+ this(ClassTypeInformation.from(type), dataType, parameters);
+ }
+
+ DefaultCassandraColumnType(TypeInformation> typeInformation, DataType dataType, ColumnType... parameters) {
+ super(typeInformation, parameters);
+
+ this.dataType = dataType;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.CassandraColumnType#getDataType()
+ */
+ public DataType getDataType() {
+ return dataType;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.DefaultColumnType#toString()
+ */
+ @Override
+ public String toString() {
+
+ StringBuilder builder = new StringBuilder();
+
+ if (isTupleType()) {
+ builder.append("Tuple: ");
+ }
+
+ if (isUserDefinedType()) {
+ builder.append("UDT: ").append(((UserDefinedType) getDataType()).getName());
+ }
+
+ builder.append(getType().getName()).append(" [").append(getDataType()).append("]");
+
+ if (getParameters().isEmpty()) {
+ return builder.toString();
+ }
+
+ builder.append("<").append(getParameters().stream().map(Object::toString).collect(Collectors.toList())).append(">");
+
+ return builder.toString();
+ }
+}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultColumnType.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultColumnType.java
new file mode 100644
index 000000000..7c0291d48
--- /dev/null
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultColumnType.java
@@ -0,0 +1,128 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.springframework.data.util.ClassTypeInformation;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Nullable;
+
+/**
+ * Default {@link ColumnType} implementation.
+ *
+ * @author Mark Paluch
+ * @since 3.0
+ */
+class DefaultColumnType implements ColumnType {
+
+ public static final DefaultColumnType OBJECT = new DefaultColumnType(ClassTypeInformation.OBJECT);
+
+ private final TypeInformation> typeInformation;
+ private final List parameters;
+
+ DefaultColumnType(TypeInformation> typeInformation, ColumnType... parameters) {
+ this.typeInformation = typeInformation;
+ this.parameters = Arrays.asList(parameters);
+ }
+
+ List getParameters() {
+ return parameters;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnType#getType()
+ */
+ @Override
+ public Class> getType() {
+ return typeInformation.getType();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnType#isCollectionLike()
+ */
+ @Override
+ public boolean isCollectionLike() {
+ return typeInformation.isCollectionLike();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnType#isList()
+ */
+ @Override
+ public boolean isList() {
+ return List.class.isAssignableFrom(typeInformation.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnType#isSet()
+ */
+ @Override
+ public boolean isSet() {
+ return Set.class.isAssignableFrom(typeInformation.getType());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnType#isMap()
+ */
+ @Override
+ public boolean isMap() {
+ return typeInformation.isMap();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnType#getComponentType()
+ */
+ @Nullable
+ @Override
+ public ColumnType getComponentType() {
+ return !parameters.isEmpty() ? parameters.get(0) : null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnType#getMapValueType()
+ */
+ @Nullable
+ @Override
+ public ColumnType getMapValueType() {
+ return parameters.size() > 1 ? parameters.get(1) : null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+
+ if (parameters.isEmpty()) {
+ return getType().getName();
+ }
+
+ return String.format("%s<%s>", getType().getName(),
+ parameters.stream().map(Object::toString).collect(Collectors.toList()));
+ }
+}
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
new file mode 100644
index 000000000..04fe301c8
--- /dev/null
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultColumnTypeResolver.java
@@ -0,0 +1,498 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.StreamSupport;
+
+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.CassandraSimpleTypeHolder;
+import org.springframework.data.cassandra.core.mapping.CassandraType;
+import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
+import org.springframework.data.convert.CustomConversions;
+import org.springframework.data.mapping.MappingException;
+import org.springframework.data.util.ClassTypeInformation;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Nullable;
+
+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.TypeCodec;
+import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
+import com.datastax.oss.driver.api.core.type.reflect.GenericType;
+
+/**
+ * Default {@link ColumnTypeResolver} implementation backed by {@link CustomConversions} and {@link CodecRegistry}.
+ *
+ * @author Mark Paluch
+ * @since 3.0
+ */
+class DefaultColumnTypeResolver implements ColumnTypeResolver {
+
+ private final CassandraMappingContext mappingContext;
+ private final UserTypeResolver userTypeResolver;
+ private final CodecRegistry codecRegistry;
+ private CustomConversions customConversions;
+
+ public DefaultColumnTypeResolver(CassandraMappingContext mappingContext) {
+ this.mappingContext = mappingContext;
+ this.userTypeResolver = mappingContext.getUserTypeResolver();
+ this.codecRegistry = mappingContext.getCodecRegistry();
+ this.customConversions = mappingContext.getCustomConversions();
+ }
+
+ public void setCustomConversions(CustomConversions customConversions) {
+ this.customConversions = customConversions;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnTypeResolver#resolve(org.springframework.data.util.TypeInformation)
+ */
+ @Override
+ public CassandraColumnType resolve(TypeInformation> typeInformation) {
+
+ Optional> writeTarget = customConversions.getCustomWriteTarget(typeInformation.getType());
+
+ return writeTarget.map(it -> {
+ return createCassandraTypeDescriptor(tryResolve(it), ClassTypeInformation.from(it));
+ }).orElseGet(() -> {
+
+ if (typeInformation.getType().isEnum()) {
+ return ColumnType.create(String.class, DataTypes.TEXT);
+ }
+
+ return createCassandraTypeDescriptor(typeInformation);
+ });
+ }
+
+ private DataType tryResolve(Class> type) {
+
+ if (TupleValue.class.isAssignableFrom(type)) {
+ return DataTypes.tupleOf();
+ }
+
+ if (UdtValue.class.isAssignableFrom(type)) {
+ return UnknownUserDefinedType.INSTANCE;
+ }
+
+ return codecRegistry.codecFor(type).getCqlType();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnTypeResolver#resolve(org.springframework.data.cassandra.core.mapping.CassandraType)
+ */
+ @Override
+ public CassandraColumnType resolve(CassandraType annotation) {
+
+ CassandraType.Name type = annotation.type();
+
+ switch (type) {
+ case MAP:
+ assertTypeArguments(annotation.typeArguments().length, 2);
+
+ CassandraColumnType keyType = createCassandraTypeDescriptor(
+ CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[0]));
+ CassandraColumnType valueType = createCassandraTypeDescriptor(
+ CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[1]));
+
+ return ColumnType.mapOf(keyType, valueType);
+
+ case LIST:
+ case SET:
+ assertTypeArguments(annotation.typeArguments().length, 1);
+
+ DataType componentType = annotation.typeArguments()[0] == CassandraType.Name.UDT
+ ? getUserType(annotation.userTypeName())
+ : CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[0]);
+
+ if (type == CassandraType.Name.SET) {
+ return ColumnType.setOf(createCassandraTypeDescriptor(componentType));
+ }
+
+ return ColumnType.listOf(createCassandraTypeDescriptor(componentType));
+
+ case TUPLE:
+
+ DataType[] dataTypes = Arrays.stream(annotation.typeArguments()).map(CassandraSimpleTypeHolder::getDataTypeFor)
+ .toArray(DataType[]::new);
+
+ return ColumnType.tupleOf(DataTypes.tupleOf(dataTypes));
+ case UDT:
+
+ return createCassandraTypeDescriptor(getUserType(annotation.userTypeName()));
+ default:
+ return createCassandraTypeDescriptor(CassandraSimpleTypeHolder.getDataTypeFor(type));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnTypeResolver#resolve(java.lang.Object)
+ */
+ @Override
+ public ColumnType resolve(@Nullable Object value) {
+
+ if (value != null) {
+
+ ClassTypeInformation> typeInformation = ClassTypeInformation.from(value.getClass());
+
+ Optional> writeTarget = customConversions.getCustomWriteTarget(typeInformation.getType());
+
+ return writeTarget.map(it -> {
+ return (ColumnType) createCassandraTypeDescriptor(tryResolve(it), typeInformation);
+ }).orElseGet(() -> {
+
+ if (typeInformation.getType().isEnum()) {
+ return ColumnType.create(String.class, DataTypes.TEXT);
+ }
+
+ if (value instanceof Map) {
+ return ColumnType.mapOf(DefaultColumnType.OBJECT, DefaultColumnType.OBJECT);
+ }
+
+ if (value instanceof List) {
+ return ColumnType.listOf(DefaultColumnType.OBJECT);
+ }
+
+ if (value instanceof Set) {
+ return ColumnType.listOf(DefaultColumnType.OBJECT);
+ }
+
+ if (value instanceof UdtValue) {
+ return ColumnType.udtOf(((UdtValue) value).getType());
+ }
+
+ if (value instanceof TupleValue) {
+ return ColumnType.tupleOf(((TupleValue) value).getType());
+ }
+
+ BasicCassandraPersistentEntity> persistentEntity = mappingContext.getPersistentEntity(typeInformation);
+
+ if (persistentEntity != null) {
+
+ if (persistentEntity.isUserDefinedType() || persistentEntity.isTupleType()) {
+ return resolve(persistentEntity.getTypeInformation());
+ }
+ }
+
+ return ColumnType.create(typeInformation.getType());
+ });
+ }
+
+ return DefaultColumnType.OBJECT;
+ }
+
+ private CassandraColumnType createCassandraTypeDescriptor(DataType dataType) {
+
+ GenericType