Correctly resolve user type from CassandraType(userTypeName) for Maps.
We now correctly resolve user types for type arguments configured to UDT. These can either apply to keys or values depending on the typeArguments. Closes #1098
This commit is contained in:
@@ -225,9 +225,9 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
assertTypeArguments(annotation.typeArguments().length, 2);
|
||||
|
||||
CassandraColumnType keyType = createCassandraTypeDescriptor(
|
||||
CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[0]));
|
||||
getRequiredDataType(annotation, 0));
|
||||
CassandraColumnType valueType = createCassandraTypeDescriptor(
|
||||
CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[1]));
|
||||
getRequiredDataType(annotation, 1));
|
||||
|
||||
return ColumnType.mapOf(keyType, valueType);
|
||||
|
||||
@@ -235,8 +235,7 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
case SET:
|
||||
assertTypeArguments(annotation.typeArguments().length, 1);
|
||||
|
||||
DataType componentType = annotation.typeArguments()[0] == Name.UDT ? getUserType(annotation.userTypeName())
|
||||
: CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[0]);
|
||||
DataType componentType = getRequiredDataType(annotation, 0);
|
||||
|
||||
if (type == Name.SET) {
|
||||
return ColumnType.setOf(createCassandraTypeDescriptor(componentType));
|
||||
@@ -259,7 +258,7 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
|
||||
return createCassandraTypeDescriptor(getUserType(annotation.userTypeName()));
|
||||
default:
|
||||
return createCassandraTypeDescriptor(CassandraSimpleTypeHolder.getDataTypeFor(type));
|
||||
return createCassandraTypeDescriptor(CassandraSimpleTypeHolder.getRequiredDataTypeFor(type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,6 +442,13 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
|
||||
return new DefaultCassandraColumnType(typeInformation, dataType);
|
||||
}
|
||||
|
||||
private DataType getRequiredDataType(CassandraType annotation, int typeIndex) {
|
||||
|
||||
Name typeName = annotation.typeArguments()[typeIndex];
|
||||
return typeName == Name.UDT ? getUserType(annotation.userTypeName())
|
||||
: CassandraSimpleTypeHolder.getRequiredDataTypeFor(typeName);
|
||||
}
|
||||
|
||||
private Class<?> resolveToJavaType(DataType dataType) {
|
||||
TypeCodec<Object> codec = getCodecRegistry().codecFor(dataType);
|
||||
return codec.getJavaType().getRawType();
|
||||
|
||||
@@ -189,14 +189,58 @@ public class CassandraSimpleTypeHolder extends SimpleTypeHolder {
|
||||
return javaType.isEnum() ? DataTypes.TEXT : classToDataType.get(javaType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required default {@link DataType} for a {@link Class}. This method resolves only simple types to a
|
||||
* Cassandra {@link DataType}. Throws {@link IllegalStateException} if the {@link Class} cannot be resolved to a
|
||||
* {@link DataType}.
|
||||
*
|
||||
* @param javaType must not be {@literal null}.
|
||||
* @return the {@link DataType} for {@code javaClass} if resolvable, otherwise {@literal null}.
|
||||
* @throws IllegalStateException if the {@link Class} cannot be resolved to a {@link DataType}.
|
||||
* @since 3.1.6
|
||||
* @see #getDataTypeFor(Class)
|
||||
*/
|
||||
public static DataType getRequiredDataTypeFor(Class<?> javaType) {
|
||||
|
||||
DataType dataType = getDataTypeFor(javaType);
|
||||
|
||||
if (dataType == null) {
|
||||
throw new IllegalStateException(String.format("Required DataType cannot be resolved for %s", javaType.getName()));
|
||||
}
|
||||
|
||||
return dataType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link DataType} for a {@link CassandraType.Name}.
|
||||
*
|
||||
* @param dataTypeName must not be {@literal null}.
|
||||
* @return the {@link DataType} for {@link CassandraType.Name}.
|
||||
*/
|
||||
@Nullable
|
||||
public static DataType getDataTypeFor(CassandraType.Name dataTypeName) {
|
||||
return nameToDataType.get(dataTypeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required {@link DataType} for a {@link CassandraType.Name}. Throws {@link IllegalStateException} if the
|
||||
* {@link CassandraType.Name} cannot be resolved to a {@link DataType}.
|
||||
*
|
||||
* @param dataTypeName must not be {@literal null}.
|
||||
* @return the {@link DataType} for {@link CassandraType.Name}.
|
||||
* @throws IllegalStateException if the {@link CassandraType.Name} cannot be resolved to a {@link DataType}.
|
||||
* @since 3.1.6
|
||||
* @see #getDataTypeFor(CassandraType.Name)
|
||||
*/
|
||||
public static DataType getRequiredDataTypeFor(CassandraType.Name dataTypeName) {
|
||||
|
||||
DataType dataType = getDataTypeFor(dataTypeName);
|
||||
|
||||
if (dataType == null) {
|
||||
throw new IllegalStateException(String.format("Required DataType cannot be resolved for %s", dataTypeName));
|
||||
}
|
||||
|
||||
return dataType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,8 +57,10 @@ public @interface CassandraType {
|
||||
|
||||
/**
|
||||
* If the property maps to a User-Defined Type (UDT) then this attribute holds the user type name. 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}.
|
||||
* {@link java.util.Collection Collection-like} properties the user type name applies to the component type. For
|
||||
* {@link java.util.Map} properties, {@link #typeArguments()} configured to {@link Name#UDT} are resolved using the
|
||||
* user type name. 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
|
||||
|
||||
@@ -40,7 +40,9 @@ import org.mockito.quality.Strictness;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.cassandra.core.mapping.Embedded;
|
||||
import org.springframework.data.cassandra.core.mapping.Frozen;
|
||||
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
|
||||
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
|
||||
import org.springframework.data.cassandra.support.UserDefinedTypeBuilder;
|
||||
@@ -237,6 +239,21 @@ class MappingCassandraConverterUDTUnitTests {
|
||||
assertThat(target.udtValue.nested.age).isEqualTo(30);
|
||||
}
|
||||
|
||||
@Test // #1098
|
||||
void shouldWriteMapWithTypeHintToUdtValue() {
|
||||
|
||||
when(userTypeResolver.resolveType(CqlIdentifier.fromCql("udt"))).thenReturn(manufacturer);
|
||||
|
||||
MapWithUdt mapWithUdt = new MapWithUdt();
|
||||
mapWithUdt.map = Collections.singletonMap("key", new Manufacturer("name", "display"));
|
||||
|
||||
Map<CqlIdentifier, Object> sink = new LinkedHashMap<>();
|
||||
mappingCassandraConverter.write(mapWithUdt, sink);
|
||||
|
||||
Map<String, UdtValue> map = (Map) sink.get(CqlIdentifier.fromCql("map"));
|
||||
assertThat(map.get("key")).isInstanceOf(UdtValue.class);
|
||||
}
|
||||
|
||||
@UserDefinedType
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@@ -317,4 +334,12 @@ class MappingCassandraConverterUDTUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
static class MapWithUdt {
|
||||
|
||||
@Id String id;
|
||||
|
||||
@CassandraType(type = CassandraType.Name.MAP, userTypeName = "udt", typeArguments = { CassandraType.Name.TEXT,
|
||||
CassandraType.Name.UDT }) private Map<String, @Frozen Manufacturer> map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user