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
index 83575e1cc..2e2ff7cb8 100644
--- 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
@@ -88,8 +88,18 @@ public interface ColumnType {
* @return
*/
static CassandraColumnType listOf(CassandraColumnType componentType) {
+ return listOf(componentType, FrozenInfo.NOT_FROZEN);
+ }
+ /**
+ * Creates a List {@link ColumnType} given its {@link CassandraColumnType component type}.
+ *
+ * @param componentType must not be {@literal null}.
+ * @param frozen
+ * @return
+ */
+ static CassandraColumnType listOf(CassandraColumnType componentType, FrozenInfo frozen) {
return new DefaultCassandraColumnType(ClassTypeInformation.LIST,
- () -> DataTypes.listOf(componentType.getDataType()), componentType);
+ () -> DataTypes.listOf(componentType.getDataType(), frozen.self), componentType);
}
/**
@@ -114,7 +124,17 @@ public interface ColumnType {
* @return
*/
static CassandraColumnType setOf(CassandraColumnType componentType) {
- return new DefaultCassandraColumnType(ClassTypeInformation.SET, () -> DataTypes.setOf(componentType.getDataType()),
+ return setOf(componentType, FrozenInfo.NOT_FROZEN);
+ }
+ /**
+ * Creates a Set {@link ColumnType} given its {@link CassandraColumnType component type}.
+ *
+ * @param componentType must not be {@literal null}.
+ * @param frozen must not be {@literal null}.
+ * @return
+ */
+ static CassandraColumnType setOf(CassandraColumnType componentType, FrozenInfo frozen) {
+ return new DefaultCassandraColumnType(ClassTypeInformation.SET, () -> DataTypes.setOf(componentType.getDataType(),frozen.self),
componentType);
}
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
index 8d0b89279..c9ac0557f 100644
--- 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
@@ -17,6 +17,7 @@ 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.cassandra.core.mapping.Frozen;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -62,6 +63,21 @@ public interface ColumnTypeResolver {
*/
CassandraColumnType resolve(TypeInformation> typeInformation);
+ /**
+ * Resolve a {@link CassandraColumnType} from {@link TypeInformation}. Considers potentially registered custom
+ * converters and simple type rules.
+ *
+ * @param typeInformation must not be {@literal null}.
+ * @param frozen must not be {@literal null}.
+ * @return
+ * @see org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder
+ * @see CassandraCustomConversions
+ * @throws org.springframework.dao.InvalidDataAccessApiUsageException
+ */
+ default CassandraColumnType resolve(TypeInformation> typeInformation, FrozenInfo frozen) {
+ return resolve(typeInformation, FrozenInfo.NOT_FROZEN);
+ }
+
/**
* Resolve a {@link CassandraColumnType} from a {@link CassandraType} annotation.
*
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
index 774e3ed7e..0d9392053 100644
--- 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
@@ -15,6 +15,8 @@
*/
package org.springframework.data.cassandra.core.convert;
+import java.lang.reflect.AnnotatedParameterizedType;
+import java.lang.reflect.AnnotatedType;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -25,13 +27,14 @@ import java.util.stream.StreamSupport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
+import org.jetbrains.annotations.NotNull;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.core.mapping.CassandraSimpleTypeHolder;
import org.springframework.data.cassandra.core.mapping.CassandraType;
import org.springframework.data.cassandra.core.mapping.CassandraType.Name;
+import org.springframework.data.cassandra.core.mapping.Frozen;
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mapping.MappingException;
@@ -95,6 +98,8 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
Assert.notNull(property, "Property must not be null");
+ FrozenInfo frozen = getFrozenInfo(property);
+
if (property.isAnnotationPresent(CassandraType.class)) {
CassandraType annotation = property.getRequiredAnnotation(CassandraType.class);
@@ -122,7 +127,32 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
return resolve(annotation);
}
- return resolve(property.getTypeInformation());
+ TypeInformation> typeInformation = property.getTypeInformation();
+ return resolve(typeInformation, frozen);
+ }
+
+ private FrozenInfo getFrozenInfo(CassandraPersistentProperty property) {
+
+ FrozenInfo frozen = FrozenInfo.frozen(property.isAnnotationPresent(Frozen.class));
+ AnnotatedType annotatedType = property.findAnnotatedType(Frozen.class);
+
+ if (annotatedType instanceof AnnotatedParameterizedType) {
+
+ AnnotatedParameterizedType apt = (AnnotatedParameterizedType) annotatedType;
+ AnnotatedType[] annotatedTypes = apt.getAnnotatedActualTypeArguments();
+
+ if (annotatedTypes.length > 0) {
+
+ AnnotatedType type1 = annotatedTypes[0];
+ frozen = frozen.withFirstTypeParameter(type1.isAnnotationPresent(Frozen.class));
+ if (annotatedTypes.length > 1) {
+
+ AnnotatedType type2 = annotatedTypes[1];
+ frozen = frozen.withSecondTypeParameter(type2.isAnnotationPresent(Frozen.class));
+ }
+ }
+ }
+ return frozen;
}
/*
@@ -131,6 +161,15 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
*/
@Override
public CassandraColumnType resolve(TypeInformation> typeInformation) {
+ return resolve(typeInformation, FrozenInfo.NOT_FROZEN);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.cassandra.core.convert.ColumnTypeResolver#resolve(org.springframework.data.util.TypeInformation, org.springframework.data.cassandra.core.convert.FrozenInfo)
+ */
+ @Override
+ public CassandraColumnType resolve(TypeInformation> typeInformation, FrozenInfo frozen) {
return getCustomWriteTarget(typeInformation).map(it -> {
return createCassandraTypeDescriptor(tryResolve(it), ClassTypeInformation.from(it));
@@ -140,7 +179,7 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
return ColumnType.create(String.class, DataTypes.TEXT);
}
- return createCassandraTypeDescriptor(typeInformation);
+ return createCassandraTypeDescriptor(typeInformation, frozen);
});
}
@@ -350,19 +389,27 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
return new DefaultCassandraColumnType(typeInformation, dataType);
}
- private CassandraColumnType createCassandraTypeDescriptor(TypeInformation> typeInformation) {
+ private CassandraColumnType createCassandraTypeDescriptor(TypeInformation> typeInformation, FrozenInfo frozen) {
if (List.class.isAssignableFrom(typeInformation.getType())) {
- return ColumnType.listOf(resolve(typeInformation.getRequiredComponentType()));
+
+ FrozenInfo elementFrozen = frozen.forFirstTypeParameter();
+ return ColumnType.listOf(resolve(typeInformation.getRequiredComponentType(), elementFrozen), frozen);
}
if (Set.class.isAssignableFrom(typeInformation.getType())) {
- return ColumnType.setOf(resolve(typeInformation.getRequiredComponentType()));
+
+ FrozenInfo elementFrozen = frozen.forFirstTypeParameter();
+ return ColumnType.setOf(resolve(typeInformation.getRequiredComponentType(), elementFrozen), frozen);
}
if (typeInformation.isMap()) {
- return ColumnType.mapOf(resolve(typeInformation.getRequiredComponentType()),
- resolve(typeInformation.getRequiredMapValueType()));
+
+ FrozenInfo frozenKey = frozen.forFirstTypeParameter();
+ FrozenInfo frozenValue = frozen.forSecondTypeParameter();
+
+ return ColumnType.mapOf(resolve(typeInformation.getRequiredComponentType(), frozenKey),
+ resolve(typeInformation.getRequiredMapValueType(), frozenValue));
}
CassandraPersistentEntity> persistentEntity = mappingContext.getPersistentEntity(typeInformation);
@@ -370,7 +417,7 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
if (persistentEntity != null) {
if (persistentEntity.isUserDefinedType()) {
- return new DefaultCassandraColumnType(typeInformation, getUserType(persistentEntity));
+ return new DefaultCassandraColumnType(typeInformation, getUserType(persistentEntity, frozen.self));
}
if (persistentEntity.isTupleType()) {
@@ -394,10 +441,11 @@ class DefaultColumnTypeResolver implements ColumnTypeResolver {
return new DefaultCassandraColumnType(typeInformation, dataType);
}
- private DataType getUserType(CassandraPersistentEntity> persistentEntity) {
+ private DataType getUserType(CassandraPersistentEntity> persistentEntity, boolean frozen) {
CqlIdentifier identifier = persistentEntity.getTableName();
- com.datastax.oss.driver.api.core.type.UserDefinedType userType = userTypeResolver.resolveType(identifier);
+ com.datastax.oss.driver.api.core.type.UserDefinedType userType = userTypeResolver.resolveType(identifier)
+ .copy(frozen);
if (userType == null) {
throw new MappingException(String.format("User type [%s] not found", identifier));
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/FrozenInfo.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/FrozenInfo.java
new file mode 100644
index 000000000..d5c7f8e64
--- /dev/null
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/FrozenInfo.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+/**
+ * Information about if a type or its elements should be frozen or not.
+ */
+public class FrozenInfo {
+
+ public static final FrozenInfo NOT_FROZEN = new FrozenInfo();
+
+ final boolean self;
+ final boolean firstTypeParameter;
+ final boolean secondTypeParameter;
+
+ public static FrozenInfo frozen(boolean frozen) {
+ return new FrozenInfo(frozen);
+ }
+
+ private FrozenInfo() {
+ this(false, false, false);
+ }
+
+ private FrozenInfo(boolean selfFrozen) {
+ this(selfFrozen, false, false);
+ }
+
+ private FrozenInfo(boolean self, boolean firstTypeParameter, boolean secondTypeParameter) {
+
+ this.self = self;
+ this.firstTypeParameter = firstTypeParameter;
+ this.secondTypeParameter = secondTypeParameter;
+ }
+
+ FrozenInfo withFirstTypeParameter(boolean firstTypeParameter) {
+ return new FrozenInfo(self, firstTypeParameter, secondTypeParameter);
+ }
+
+ FrozenInfo withSecondTypeParameter(boolean secondTypeParameter) {
+ return new FrozenInfo(self, firstTypeParameter, secondTypeParameter);
+ }
+
+ FrozenInfo forFirstTypeParameter() {
+ return new FrozenInfo(firstTypeParameter);
+ }
+
+ FrozenInfo forSecondTypeParameter() {
+ return new FrozenInfo(secondTypeParameter);
+ }
+}
diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/Frozen.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/Frozen.java
new file mode 100644
index 000000000..2a00e20fb
--- /dev/null
+++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/Frozen.java
@@ -0,0 +1,36 @@
+/*
+ * 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.mapping;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies a type as "frozen".
+ *
+ * @author Jens Schauder
+ * @see Documentation about frozen collections
+ * @since 3.0
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.TYPE_USE, ElementType.FIELD, ElementType.METHOD})
+public @interface Frozen {
+
+}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolverUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolverUnitTests.java
index d2a87d028..fa9707802 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolverUnitTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/ColumnTypeResolverUnitTests.java
@@ -23,17 +23,23 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
-
import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.core.mapping.CassandraType;
+import org.springframework.data.cassandra.core.mapping.Frozen;
+import org.springframework.data.cassandra.core.mapping.UserDefinedType;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.util.ClassTypeInformation;
import com.datastax.oss.driver.api.core.data.TupleValue;
+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.codec.registry.CodecRegistry;
/**
@@ -44,7 +50,8 @@ import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
public class ColumnTypeResolverUnitTests {
CassandraMappingContext mappingContext = new CassandraMappingContext();
- ColumnTypeResolver resolver = new DefaultColumnTypeResolver(mappingContext, null, () -> CodecRegistry.DEFAULT,
+ ColumnTypeResolver resolver = new DefaultColumnTypeResolver(mappingContext,
+ SchemaFactory.ShallowUserTypeResolver.INSTANCE, () -> CodecRegistry.DEFAULT,
mappingContext::getCustomConversions);
@Test // DATACASS-743
@@ -183,8 +190,7 @@ public class ColumnTypeResolverUnitTests {
BasicCassandraPersistentEntity> entity = mappingContext.getRequiredPersistentEntity(Person.class);
CassandraColumnType columnType = resolver.resolve(entity.getRequiredPersistentProperty("tupleValue"));
- assertThat(columnType.getType())
- .isEqualTo(TupleValue.class);
+ assertThat(columnType.getType()).isEqualTo(TupleValue.class);
assertThatThrownBy(columnType::getDataType).isInstanceOf(MappingException.class);
}
@@ -201,6 +207,108 @@ public class ColumnTypeResolverUnitTests {
assertThat(resolver.resolve(timeUUIDProperty).getDataType()).isEqualTo(DataTypes.TIMEUUID);
}
+ @Test // DATACASS-465
+ public void listPropertyWithFrozenAnnotation() {
+
+ BasicCassandraPersistentEntity> entity = mappingContext.getRequiredPersistentEntity(Person.class);
+
+ DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenList")).getDataType();
+ assertThat(dataType).isInstanceOf(ListType.class);
+ assertThat(((ListType) dataType).isFrozen()).describedAs("The list itself should be frozen").isTrue();
+ }
+
+ @Test // DATACASS-465
+ public void listPropertyWithFrozenAnnotationOnElement() {
+
+ BasicCassandraPersistentEntity> entity = mappingContext.getRequiredPersistentEntity(Person.class);
+
+ DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenListContent")).getDataType();
+
+ assertThat(dataType).isInstanceOf(ListType.class);
+ assertThat(((ListType) dataType).isFrozen()).describedAs("The collection itself should not be frozen.").isFalse();
+
+ DataType elementType = ((ListType) dataType).getElementType();
+ assertThat(elementType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
+ assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) elementType).isFrozen())
+ .describedAs("The element type should be frozen").isTrue();
+ }
+
+ @Test // DATACASS-465
+ public void setPropertyWithFrozenAnnotation() {
+
+ BasicCassandraPersistentEntity> entity = mappingContext.getRequiredPersistentEntity(Person.class);
+
+ DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenSet")).getDataType();
+ assertThat(dataType).isInstanceOf(SetType.class);
+ assertThat(((SetType) dataType).isFrozen()).describedAs("The set itself should be frozen").isTrue();
+ }
+
+ @Test // DATACASS-465
+ public void setPropertyWithFrozenAnnotationOnElement() {
+
+ BasicCassandraPersistentEntity> entity = mappingContext.getRequiredPersistentEntity(Person.class);
+
+ DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenSetContent")).getDataType();
+
+ assertThat(dataType).isInstanceOf(SetType.class);
+ assertThat(((SetType) dataType).isFrozen()).describedAs("The collection itself should not be frozen.").isFalse();
+
+ DataType elementType = ((SetType) dataType).getElementType();
+ assertThat(elementType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
+ assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) elementType).isFrozen())
+ .describedAs("The element type should be frozen").isTrue();
+ }
+
+ @Test // DATACASS-465
+ public void mapPropertyWithFrozenAnnotationOnKey() {
+
+ BasicCassandraPersistentEntity> entity = mappingContext.getRequiredPersistentEntity(Person.class);
+
+ DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenMapKey")).getDataType();
+
+ SoftAssertions.assertSoftly(softly -> {
+
+ softly.assertThat(dataType).isInstanceOf(MapType.class);
+ softly.assertThat(((MapType) dataType).isFrozen()).describedAs("The collection itself should not be frozen.")
+ .isFalse();
+
+ DataType keyType = ((MapType) dataType).getKeyType();
+ softly.assertThat(keyType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
+ softly.assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) keyType).isFrozen())
+ .describedAs("The key type should be frozen").isTrue();
+
+ DataType valueType = ((MapType) dataType).getValueType();
+ softly.assertThat(valueType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
+ softly.assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) valueType).isFrozen())
+ .describedAs("The value type should not be frozen").isFalse();
+ });
+ }
+
+ @Test // DATACASS-465
+ public void mapPropertyWithFrozenAnnotationOnValue() {
+
+ BasicCassandraPersistentEntity> entity = mappingContext.getRequiredPersistentEntity(Person.class);
+
+ DataType dataType = resolver.resolve(entity.getRequiredPersistentProperty("frozenMapValue")).getDataType();
+
+ SoftAssertions.assertSoftly(softly -> {
+
+ softly.assertThat(dataType).isInstanceOf(MapType.class);
+ softly.assertThat(((MapType) dataType).isFrozen()).describedAs("The collection itself should not be frozen.")
+ .isFalse();
+
+ DataType keyType = ((MapType) dataType).getKeyType();
+ softly.assertThat(keyType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
+ softly.assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) keyType).isFrozen())
+ .describedAs("The key type should not be frozen").isFalse();
+
+ DataType valueType = ((MapType) dataType).getValueType();
+ softly.assertThat(valueType).isInstanceOf(com.datastax.oss.driver.api.core.type.UserDefinedType.class);
+ softly.assertThat(((com.datastax.oss.driver.api.core.type.UserDefinedType) valueType).isFrozen())
+ .describedAs("The value type should be frozen").isTrue();
+ });
+ }
+
static class Person {
String name;
@@ -229,6 +337,22 @@ public class ColumnTypeResolverUnitTests {
typeArguments = { CassandraType.Name.INT, CassandraType.Name.TEXT }) Map enumMapAsInt;
TupleValue tupleValue;
+
+ @Frozen List frozenList;
+ List<@Frozen MyUdt> frozenListContent;
+
+ @Frozen Set frozenSet;
+ Set<@Frozen MyUdt> frozenSetContent;
+
+ Map<@Frozen MyUdt, MyUdt> frozenMapKey;
+
+ Map frozenMapValue;
+
+ @UserDefinedType
+ private static class MyUdt {
+ String one;
+ Integer two;
+ }
}
static class TypeWithUUIDColumn {
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterMappedTupleUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterMappedTupleUnitTests.java
index 1e2f0ebcb..e513e39c0 100755
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterMappedTupleUnitTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterMappedTupleUnitTests.java
@@ -28,7 +28,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
-
import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.core.mapping.Element;
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentPropertyUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentPropertyUnitTests.java
index c9c5b9fdc..f1228c8c3 100755
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentPropertyUnitTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentPropertyUnitTests.java
@@ -20,14 +20,15 @@ import static org.springframework.data.cassandra.core.mapping.CassandraType.*;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.junit.Test;
-
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.util.ClassTypeInformation;
@@ -131,6 +132,24 @@ public class BasicCassandraPersistentPropertyUnitTests {
assertThat(persistentProperty.findAnnotation(CassandraType.class)).isNotNull();
}
+ /**
+ * Demonstrates how to access annotations on type parameters.
+ */
+ @Test // DATACASS-465
+ public void parameterAnnotations() {
+
+ AnnotatedType annotatedType = findAnnotatedType(TypeWithMaps.class, "parameterizedWithParameterAnnotation");
+ assertThat(annotatedType).isNotNull().isInstanceOf(AnnotatedParameterizedType.class);
+
+ AnnotatedParameterizedType apt = (AnnotatedParameterizedType) annotatedType;
+ AnnotatedType[] annotatedActualTypeArguments = apt.getAnnotatedActualTypeArguments();
+ assertThat(annotatedActualTypeArguments)
+ .extracting(a -> a.getType(), a -> Arrays.stream(a.getAnnotations()).map(an -> an instanceof Indexed).toArray())
+ .containsExactly(tuple(String.class, new boolean[] {}), // annotation on key type
+ tuple(String.class, new boolean[] { true }) // annotation on value type
+ );
+ }
+
private AnnotatedType findAnnotatedType(Class> type, String parameterized) {
return getPropertyFor(TypeWithMaps.class, parameterized).findAnnotatedType(Indexed.class);
}
diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java
index c52758f6e..dae9ab3d9 100644
--- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java
+++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/PartTreeCassandraQueryUnitTests.java
@@ -89,6 +89,7 @@ public class PartTreeCassandraQueryUnitTests {
when(attachmentPoint.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT);
when(attachmentPoint.getProtocolVersion()).thenReturn(ProtocolVersion.DEFAULT);
+ when(userTypeMock.copy(anyBoolean())).thenReturn(userTypeMock);
when(userTypeMock.getAttachmentPoint()).thenReturn(attachmentPoint);
when(userTypeMock.getFieldNames())
.thenReturn(Arrays.asList("city", "country").stream().map(CqlIdentifier::fromCql).collect(Collectors.toList()));