Skip to content

Commit 01c6350

Browse files
committed
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
1 parent 695df45 commit 01c6350

File tree

4 files changed

+84
-7
lines changed

4 files changed

+84
-7
lines changed

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/DefaultColumnTypeResolver.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,18 +225,17 @@ public CassandraColumnType resolve(CassandraType annotation) {
225225
assertTypeArguments(annotation.typeArguments().length, 2);
226226

227227
CassandraColumnType keyType = createCassandraTypeDescriptor(
228-
CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[0]));
228+
getRequiredDataType(annotation, 0));
229229
CassandraColumnType valueType = createCassandraTypeDescriptor(
230-
CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[1]));
230+
getRequiredDataType(annotation, 1));
231231

232232
return ColumnType.mapOf(keyType, valueType);
233233

234234
case LIST:
235235
case SET:
236236
assertTypeArguments(annotation.typeArguments().length, 1);
237237

238-
DataType componentType = annotation.typeArguments()[0] == Name.UDT ? getUserType(annotation.userTypeName())
239-
: CassandraSimpleTypeHolder.getDataTypeFor(annotation.typeArguments()[0]);
238+
DataType componentType = getRequiredDataType(annotation, 0);
240239

241240
if (type == Name.SET) {
242241
return ColumnType.setOf(createCassandraTypeDescriptor(componentType));
@@ -259,7 +258,7 @@ public CassandraColumnType resolve(CassandraType annotation) {
259258

260259
return createCassandraTypeDescriptor(getUserType(annotation.userTypeName()));
261260
default:
262-
return createCassandraTypeDescriptor(CassandraSimpleTypeHolder.getDataTypeFor(type));
261+
return createCassandraTypeDescriptor(CassandraSimpleTypeHolder.getRequiredDataTypeFor(type));
263262
}
264263
}
265264

@@ -443,6 +442,13 @@ private CassandraColumnType createCassandraTypeDescriptor(TypeInformation<?> typ
443442
return new DefaultCassandraColumnType(typeInformation, dataType);
444443
}
445444

445+
private DataType getRequiredDataType(CassandraType annotation, int typeIndex) {
446+
447+
Name typeName = annotation.typeArguments()[typeIndex];
448+
return typeName == Name.UDT ? getUserType(annotation.userTypeName())
449+
: CassandraSimpleTypeHolder.getRequiredDataTypeFor(typeName);
450+
}
451+
446452
private Class<?> resolveToJavaType(DataType dataType) {
447453
TypeCodec<Object> codec = getCodecRegistry().codecFor(dataType);
448454
return codec.getJavaType().getRawType();

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraSimpleTypeHolder.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,14 +189,58 @@ public static DataType getDataTypeFor(Class<?> javaType) {
189189
return javaType.isEnum() ? DataTypes.TEXT : classToDataType.get(javaType);
190190
}
191191

192+
/**
193+
* Returns the required default {@link DataType} for a {@link Class}. This method resolves only simple types to a
194+
* Cassandra {@link DataType}. Throws {@link IllegalStateException} if the {@link Class} cannot be resolved to a
195+
* {@link DataType}.
196+
*
197+
* @param javaType must not be {@literal null}.
198+
* @return the {@link DataType} for {@code javaClass} if resolvable, otherwise {@literal null}.
199+
* @throws IllegalStateException if the {@link Class} cannot be resolved to a {@link DataType}.
200+
* @since 3.1.6
201+
* @see #getDataTypeFor(Class)
202+
*/
203+
public static DataType getRequiredDataTypeFor(Class<?> javaType) {
204+
205+
DataType dataType = getDataTypeFor(javaType);
206+
207+
if (dataType == null) {
208+
throw new IllegalStateException(String.format("Required DataType cannot be resolved for %s", javaType.getName()));
209+
}
210+
211+
return dataType;
212+
}
213+
192214
/**
193215
* Returns the {@link DataType} for a {@link CassandraType.Name}.
194216
*
195217
* @param dataTypeName must not be {@literal null}.
196218
* @return the {@link DataType} for {@link CassandraType.Name}.
197219
*/
220+
@Nullable
198221
public static DataType getDataTypeFor(CassandraType.Name dataTypeName) {
199222
return nameToDataType.get(dataTypeName);
200223
}
201224

225+
/**
226+
* Returns the required {@link DataType} for a {@link CassandraType.Name}. Throws {@link IllegalStateException} if the
227+
* {@link CassandraType.Name} cannot be resolved to a {@link DataType}.
228+
*
229+
* @param dataTypeName must not be {@literal null}.
230+
* @return the {@link DataType} for {@link CassandraType.Name}.
231+
* @throws IllegalStateException if the {@link CassandraType.Name} cannot be resolved to a {@link DataType}.
232+
* @since 3.1.6
233+
* @see #getDataTypeFor(CassandraType.Name)
234+
*/
235+
public static DataType getRequiredDataTypeFor(CassandraType.Name dataTypeName) {
236+
237+
DataType dataType = getDataTypeFor(dataTypeName);
238+
239+
if (dataType == null) {
240+
throw new IllegalStateException(String.format("Required DataType cannot be resolved for %s", dataTypeName));
241+
}
242+
243+
return dataType;
244+
}
245+
202246
}

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraType.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@
5757

5858
/**
5959
* If the property maps to a User-Defined Type (UDT) then this attribute holds the user type name. For
60-
* {@link java.util.Collection Collection-like} properties the user type name applies to the component type. The user
61-
* type name is only required if the UDT does not map to a class annotated with {@link UserDefinedType}.
60+
* {@link java.util.Collection Collection-like} properties the user type name applies to the component type. For
61+
* {@link java.util.Map} properties, {@link #typeArguments()} configured to {@link Name#UDT} are resolved using the
62+
* user type name. The user type name is only required if the UDT does not map to a class annotated with
63+
* {@link UserDefinedType}.
6264
*
6365
* @return {@link String name} of the user type
6466
* @since 1.5

spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUDTUnitTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
import org.springframework.data.annotation.Id;
4141
import org.springframework.data.annotation.ReadOnlyProperty;
4242
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
43+
import org.springframework.data.cassandra.core.mapping.CassandraType;
4344
import org.springframework.data.cassandra.core.mapping.Embedded;
45+
import org.springframework.data.cassandra.core.mapping.Frozen;
4446
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
4547
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
4648
import org.springframework.data.cassandra.support.UserDefinedTypeBuilder;
@@ -237,6 +239,21 @@ void readPrefixedEmbeddedType() {
237239
assertThat(target.udtValue.nested.age).isEqualTo(30);
238240
}
239241

242+
@Test // #1098
243+
void shouldWriteMapWithTypeHintToUdtValue() {
244+
245+
when(userTypeResolver.resolveType(CqlIdentifier.fromCql("udt"))).thenReturn(manufacturer);
246+
247+
MapWithUdt mapWithUdt = new MapWithUdt();
248+
mapWithUdt.map = Collections.singletonMap("key", new Manufacturer("name", "display"));
249+
250+
Map<CqlIdentifier, Object> sink = new LinkedHashMap<>();
251+
mappingCassandraConverter.write(mapWithUdt, sink);
252+
253+
Map<String, UdtValue> map = (Map) sink.get(CqlIdentifier.fromCql("map"));
254+
assertThat(map.get("key")).isInstanceOf(UdtValue.class);
255+
}
256+
240257
@UserDefinedType
241258
@Data
242259
@AllArgsConstructor
@@ -317,4 +334,12 @@ public Integer getAge() {
317334
}
318335
}
319336

337+
static class MapWithUdt {
338+
339+
@Id String id;
340+
341+
@CassandraType(type = CassandraType.Name.MAP, userTypeName = "udt", typeArguments = { CassandraType.Name.TEXT,
342+
CassandraType.Name.UDT }) private Map<String, @Frozen Manufacturer> map;
343+
}
344+
320345
}

0 commit comments

Comments
 (0)