Skip to content

Commit 4976751

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 addeead commit 4976751

File tree

4 files changed

+85
-7
lines changed

4 files changed

+85
-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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@
4646
import org.springframework.data.cassandra.core.cql.util.StatementBuilder;
4747
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
4848
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
49+
import org.springframework.data.cassandra.core.mapping.CassandraType;
4950
import org.springframework.data.cassandra.core.mapping.Embedded;
51+
import org.springframework.data.cassandra.core.mapping.Frozen;
5052
import org.springframework.data.cassandra.core.mapping.Table;
5153
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
5254
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
@@ -425,6 +427,21 @@ void shouldWriteNestedUdt() {
425427
.isEqualTo("INSERT INTO car (engine,id) VALUES ({manufacturer:{name:'a good one',displayname:NULL}},'1')");
426428
}
427429

430+
@Test // #1098
431+
void shouldWriteMapWithTypeHintToUdtValue() {
432+
433+
when(userTypeResolver.resolveType(CqlIdentifier.fromCql("udt"))).thenReturn(manufacturer);
434+
435+
MapWithUdt mapWithUdt = new MapWithUdt();
436+
mapWithUdt.map = Collections.singletonMap("key", new Manufacturer("name", "display"));
437+
438+
Map<CqlIdentifier, Object> sink = new LinkedHashMap<>();
439+
converter.write(mapWithUdt, sink);
440+
441+
Map<String, UdtValue> map = (Map) sink.get(CqlIdentifier.fromCql("map"));
442+
assertThat(map.get("key")).isInstanceOf(UdtValue.class);
443+
}
444+
428445
@Table
429446
@Getter
430447
@AllArgsConstructor
@@ -603,4 +620,13 @@ public UdtValue convert(Currency source) {
603620
return udtValue;
604621
}
605622
}
623+
624+
static class MapWithUdt {
625+
626+
@Id String id;
627+
628+
@CassandraType(type = CassandraType.Name.MAP, userTypeName = "udt", typeArguments = { CassandraType.Name.TEXT,
629+
CassandraType.Name.UDT }) private Map<String, @Frozen Manufacturer> map;
630+
}
631+
606632
}

0 commit comments

Comments
 (0)