/* * Copyright 2016-2017 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 * * http://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.mapping; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.springframework.cassandra.core.Ordering; import org.springframework.cassandra.core.PrimaryKeyType; import org.springframework.cassandra.core.cql.CqlIdentifier; import org.springframework.cassandra.core.keyspace.ColumnSpecification; import org.springframework.cassandra.core.keyspace.CreateTableSpecification; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.annotation.Id; import org.springframework.data.cassandra.convert.CassandraCustomConversions; import org.springframework.data.convert.WritingConverter; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.util.ClassTypeInformation; import com.datastax.driver.core.DataType; import com.datastax.driver.core.DataType.Name; import com.datastax.driver.core.TableMetadata; import com.datastax.driver.core.UDTValue; import com.datastax.driver.core.UserType; /** * Unit tests for {@link BasicCassandraMappingContext}. * * @author Matthew T. Adams * @author Mark Paluch */ public class BasicCassandraMappingContextUnitTests { BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext(); @Before public void before() { mappingContext.setUserTypeResolver(typeName -> null); } @Test public void testgetRequiredPersistentEntityOfTransientType() { mappingContext.getRequiredPersistentEntity(Transient.class); } private static class Transient {} @Test // DATACASS-282 public void testGetExistingPersistentEntityHappyPath() { TableMetadata tableMetadata = mock(TableMetadata.class); when(tableMetadata.getName()).thenReturn(X.class.getSimpleName().toLowerCase()); mappingContext.getRequiredPersistentEntity(X.class); assertThat(mappingContext.getExistingPersistentEntity(X.class)).isNotNull(); assertThat(mappingContext.contains(Y.class)).isFalse(); assertThat(mappingContext.getNonPrimaryKeyEntities()).hasSize(1); assertThat(mappingContext.getPrimaryKeyEntities()).isEmpty(); assertThat(mappingContext.getUserDefinedTypeEntities()).isEmpty(); assertThat(mappingContext.getTableEntities()).hasSize(1); assertThat(mappingContext.contains(X.class)).isTrue(); assertThat(mappingContext.usesTable(tableMetadata)).isTrue(); } @Test // DATACASS-248 public void primaryKeyOnPropertyShouldWork() { CassandraPersistentEntity<?> persistentEntity = mappingContext .getRequiredPersistentEntity(PrimaryKeyOnProperty.class); Optional<CassandraPersistentProperty> idProperty = persistentEntity.getIdProperty(); assertThat(idProperty).hasValueSatisfying(actual -> { assertThat(actual.getColumnName().toCql()).isEqualTo("foo"); List<CqlIdentifier> columnNames = actual.getColumnNames(); assertThat(columnNames).hasSize(1); assertThat(columnNames.get(0).toCql()).isEqualTo("foo"); }); } @Table private static class PrimaryKeyOnProperty { String key; @PrimaryKey(value = "foo") public String getKey() { return key; } public void setKey(String key) { this.key = key; } } @Test // DATACASS-248 public void primaryKeyColumnsOnPropertyShouldWork() { CassandraPersistentEntity<?> persistentEntity = mappingContext .getRequiredPersistentEntity(PrimaryKeyColumnsOnProperty.class); assertThat(persistentEntity.isCompositePrimaryKey()).isFalse(); CassandraPersistentProperty firstname = persistentEntity.getRequiredPersistentProperty("firstname"); assertThat(firstname.isCompositePrimaryKey()).isFalse(); assertThat(firstname.isPrimaryKeyColumn()).isTrue(); assertThat(firstname.isPartitionKeyColumn()).isTrue(); assertThat(firstname.getColumnName().toCql()).isEqualTo("firstname"); CassandraPersistentProperty lastname = persistentEntity.getRequiredPersistentProperty("lastname"); assertThat(lastname.isPrimaryKeyColumn()).isTrue(); assertThat(lastname.isClusterKeyColumn()).isTrue(); assertThat(lastname.getColumnName().toCql()).isEqualTo("mylastname"); } @Table private static class PrimaryKeyColumnsOnProperty { String firstname; String lastname; @PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.PARTITIONED) public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } @PrimaryKeyColumn(name = "mylastname", ordinal = 2, type = PrimaryKeyType.CLUSTERED) public String getLastname() { return lastname; } public void setLastname(String lastname) { this.lastname = lastname; } } @Test // DATACASS-248 public void primaryKeyClassWithPrimaryKeyColumnsOnPropertyShouldWork() { CassandraPersistentEntity<?> persistentEntity = mappingContext .getRequiredPersistentEntity(PrimaryKeyOnPropertyWithPrimaryKeyClass.class); CassandraPersistentEntity<?> primaryKeyClass = mappingContext .getRequiredPersistentEntity(CompositePrimaryKeyClassWithProperties.class); assertThat(persistentEntity.isCompositePrimaryKey()).isFalse(); assertThat( persistentEntity.getPersistentProperty("key").map(CassandraPersistentProperty::isCompositePrimaryKey).get()) .isTrue(); assertThat(primaryKeyClass.isCompositePrimaryKey()).isTrue(); assertThat(primaryKeyClass.getCompositePrimaryKeyProperties()).hasSize(2); CassandraPersistentProperty firstname = primaryKeyClass.getRequiredPersistentProperty("firstname"); assertThat(firstname.isPrimaryKeyColumn()).isTrue(); assertThat(firstname.isPartitionKeyColumn()).isTrue(); assertThat(firstname.isClusterKeyColumn()).isFalse(); assertThat(firstname.getColumnName().toCql()).isEqualTo("firstname"); CassandraPersistentProperty lastname = primaryKeyClass.getRequiredPersistentProperty("lastname"); assertThat(lastname.isPrimaryKeyColumn()).isTrue(); assertThat(lastname.isPartitionKeyColumn()).isFalse(); assertThat(lastname.isClusterKeyColumn()).isTrue(); assertThat(lastname.getColumnName().toCql()).isEqualTo("mylastname"); } @Test // DATACASS-340 public void createdTableSpecificationShouldConsiderClusterColumnOrdering() { CassandraPersistentEntity<?> persistentEntity = mappingContext .getRequiredPersistentEntity(EntityWithOrderedClusteredColumns.class); CreateTableSpecification tableSpecification = mappingContext.getCreateTableSpecificationFor(persistentEntity); assertThat(tableSpecification.getPartitionKeyColumns()).hasSize(1); assertThat(tableSpecification.getClusteredKeyColumns()).hasSize(3); ColumnSpecification breed = tableSpecification.getClusteredKeyColumns().get(0); assertThat(breed.getName().toCql()).isEqualTo("breed"); assertThat(breed.getOrdering()).isEqualTo(Ordering.ASCENDING); ColumnSpecification color = tableSpecification.getClusteredKeyColumns().get(1); assertThat(color.getName().toCql()).isEqualTo("color"); assertThat(color.getOrdering()).isEqualTo(Ordering.DESCENDING); ColumnSpecification kind = tableSpecification.getClusteredKeyColumns().get(2); assertThat(kind.getName().toCql()).isEqualTo("kind"); assertThat(kind.getOrdering()).isEqualTo(Ordering.ASCENDING); } @Test // DATACASS-340 public void createdTableSpecificationShouldConsiderPrimaryKeyClassClusterColumnOrdering() { CassandraPersistentEntity<?> persistentEntity = mappingContext .getRequiredPersistentEntity(EntityWithPrimaryKeyWithOrderedClusteredColumns.class); CreateTableSpecification tableSpecification = mappingContext.getCreateTableSpecificationFor(persistentEntity); assertThat(tableSpecification.getPartitionKeyColumns()).hasSize(1); assertThat(tableSpecification.getClusteredKeyColumns()).hasSize(3); ColumnSpecification breed = tableSpecification.getClusteredKeyColumns().get(0); assertThat(breed.getName().toCql()).isEqualTo("breed"); assertThat(breed.getOrdering()).isEqualTo(Ordering.ASCENDING); ColumnSpecification color = tableSpecification.getClusteredKeyColumns().get(1); assertThat(color.getName().toCql()).isEqualTo("color"); assertThat(color.getOrdering()).isEqualTo(Ordering.DESCENDING); ColumnSpecification kind = tableSpecification.getClusteredKeyColumns().get(2); assertThat(kind.getName().toCql()).isEqualTo("kind"); assertThat(kind.getOrdering()).isEqualTo(Ordering.ASCENDING); } @Table private static class PrimaryKeyOnPropertyWithPrimaryKeyClass { CompositePrimaryKeyClassWithProperties key; @PrimaryKey public CompositePrimaryKeyClassWithProperties getKey() { return key; } public void setKey(CompositePrimaryKeyClassWithProperties key) { this.key = key; } } @PrimaryKeyClass private static class CompositePrimaryKeyClassWithProperties implements Serializable { String firstname; String lastname; @PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.PARTITIONED) public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } @PrimaryKeyColumn(name = "mylastname", ordinal = 2, type = PrimaryKeyType.CLUSTERED) public String getLastname() { return lastname; } public void setLastname(String lastname) { this.lastname = lastname; } } @Table static class EntityWithOrderedClusteredColumns { @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) String species; @PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.ASCENDING) String breed; @PrimaryKeyColumn(ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING) String color; @PrimaryKeyColumn(ordinal = 3, type = PrimaryKeyType.CLUSTERED) String kind; } @PrimaryKeyClass static class PrimaryKeyWithOrderedClusteredColumns implements Serializable { @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) String species; @PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.ASCENDING) String breed; @PrimaryKeyColumn(ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING) String color; @PrimaryKeyColumn(ordinal = 3, type = PrimaryKeyType.CLUSTERED) String kind; } @Table private static class EntityWithPrimaryKeyWithOrderedClusteredColumns { @PrimaryKey PrimaryKeyWithOrderedClusteredColumns key; } @Test // DATACASS-296 public void shouldCreatePersistentEntityIfNoConversionRegistered() { mappingContext.setCustomConversions(new CassandraCustomConversions(Collections.EMPTY_LIST)); assertThat(mappingContext.shouldCreatePersistentEntityFor(ClassTypeInformation.from(Human.class))).isTrue(); } @Test // DATACASS-296 public void shouldNotCreateEntitiesForCustomConvertedTypes() { mappingContext.setCustomConversions( new CassandraCustomConversions(Collections.singletonList(HumanToStringConverter.INSTANCE))); assertThat(mappingContext.shouldCreatePersistentEntityFor(ClassTypeInformation.from(Human.class))).isFalse(); } @Test // DATACASS-349 public void propertyTypeShouldConsiderRegisteredConverterForPropertyType() { mappingContext.setCustomConversions( new CassandraCustomConversions(Collections.singletonList(StringMapToStringConverter.INSTANCE))); CassandraPersistentEntity<?> persistentEntity = mappingContext .getRequiredPersistentEntity(TypeWithCustomConvertedMap.class); assertThat(mappingContext.getDataType(persistentEntity.getRequiredPersistentProperty("stringMap"))) .isEqualTo(DataType.varchar()); assertThat(mappingContext.getDataType(persistentEntity.getRequiredPersistentProperty("blobMap"))) .isEqualTo(DataType.ascii()); } @Test // DATACASS-349 public void propertyTypeShouldConsiderRegisteredConverterForCollectionComponentType() { mappingContext.setCustomConversions( new CassandraCustomConversions(Collections.singletonList(HumanToStringConverter.INSTANCE))); CassandraPersistentEntity<?> persistentEntity = mappingContext .getRequiredPersistentEntity(TypeWithListOfHumans.class); assertThat(mappingContext.getDataType(persistentEntity.getRequiredPersistentProperty("humans"))) .isEqualTo(DataType.list(DataType.varchar())); } @Test // DATACASS-172 public void shouldRegisterUdtTypes() { CassandraPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(MappedUdt.class); assertThat(persistentEntity.isUserDefinedType()).isTrue(); assertThat(mappingContext.getExistingPersistentEntity(MappedUdt.class)).isNotNull(); assertThat(mappingContext.getNonPrimaryKeyEntities()).isEmpty(); assertThat(mappingContext.getPrimaryKeyEntities()).isEmpty(); assertThat(mappingContext.getUserDefinedTypeEntities()).hasSize(1); assertThat(mappingContext.getTableEntities()).hasSize(0); assertThat(mappingContext.contains(MappedUdt.class)).isTrue(); } @Test // DATACASS-172 public void getNonPrimaryKeyEntitiesShouldNotContainUdt() { CassandraPersistentEntity<?> existingPersistentEntity = mappingContext.getRequiredPersistentEntity(MappedUdt.class); assertThat(mappingContext.getTableEntities()).doesNotContain(existingPersistentEntity); } @Test // DATACASS-172, DATACASS-359 public void getPersistentEntitiesShouldContainUdt() { CassandraPersistentEntity<?> existingPersistentEntity = mappingContext.getRequiredPersistentEntity(MappedUdt.class); assertThat(mappingContext.getPersistentEntities(true)).contains(existingPersistentEntity); assertThat(mappingContext.getUserDefinedTypeEntities()).contains(existingPersistentEntity); assertThat(mappingContext.getPersistentEntities(false)).doesNotContain(existingPersistentEntity); assertThat(mappingContext.getTableEntities()).doesNotContain(existingPersistentEntity); } @Test // DATACASS-172 public void usesTypeShouldNotReportTypeUsage() { UserType myTypeMock = mock(UserType.class, "mappedudt"); when(myTypeMock.getTypeName()).thenReturn("mappedudt"); assertThat(mappingContext.usesUserType(myTypeMock)).isFalse(); } @Test // DATACASS-172 public void usesTypeShouldReportTypeUsageInMappedUdt() { UserType myTypeMock = mock(UserType.class, "mappedudt"); when(myTypeMock.getTypeName()).thenReturn("mappedudt"); mappingContext.setUserTypeResolver(typeName -> myTypeMock); mappingContext.getRequiredPersistentEntity(WithUdt.class); assertThat(mappingContext.usesUserType(myTypeMock)).isTrue(); } @Test // DATACASS-172 public void usesTypeShouldReportTypeUsageInColumn() { UserType myTypeMock = mock(UserType.class, "mappedudt"); when(myTypeMock.getTypeName()).thenReturn("mappedudt"); mappingContext.setUserTypeResolver(typeName -> myTypeMock); mappingContext.getRequiredPersistentEntity(MappedUdt.class); assertThat(mappingContext.usesUserType(myTypeMock)).isTrue(); } @Test // DATACASS-172 public void createTableForComplexPrimaryKeyShouldFail() { try { mappingContext.getCreateTableSpecificationFor( mappingContext.getRequiredPersistentEntity(EntityWithComplexPrimaryKeyColumn.class)); fail("Missing InvalidDataAccessApiUsageException"); } catch (InvalidDataAccessApiUsageException e) { assertThat(e).hasMessageContaining("Unknown type [class java.lang.Object] for property [complexObject]"); } try { mappingContext .getCreateTableSpecificationFor(mappingContext.getRequiredPersistentEntity(EntityWithComplexId.class)); fail("Missing InvalidDataAccessApiUsageException"); } catch (InvalidDataAccessApiUsageException e) { assertThat(e).hasMessageContaining("Unknown type [class java.lang.Object] for property [complexObject]"); } try { mappingContext.getCreateTableSpecificationFor( mappingContext.getRequiredPersistentEntity(EntityWithPrimaryKeyClassWithComplexId.class)); fail("Missing InvalidDataAccessApiUsageException"); } catch (InvalidDataAccessApiUsageException e) { assertThat(e).hasMessageContaining("Unknown type [class java.lang.Object] for property [complexObject]"); } } @Test // DATACASS-282 public void shouldNotRetainInvalidEntitiesInCache() { TableMetadata tableMetadata = mock(TableMetadata.class); when(tableMetadata.getName()) .thenReturn(InvalidEntityWithIdAndPrimaryKeyColumn.class.getSimpleName().toLowerCase()); try { mappingContext.getPersistentEntity(InvalidEntityWithIdAndPrimaryKeyColumn.class); fail("Missing MappingException"); } catch (MappingException e) { assertThat(e).isInstanceOf(VerifierMappingExceptions.class); } assertThat(mappingContext.getNonPrimaryKeyEntities()).isEmpty(); assertThat(mappingContext.getPrimaryKeyEntities()).isEmpty(); assertThat(mappingContext.getUserDefinedTypeEntities()).isEmpty(); assertThat(mappingContext.getTableEntities()).isEmpty(); assertThat(mappingContext.contains(InvalidEntityWithIdAndPrimaryKeyColumn.class)).isFalse(); assertThat(mappingContext.usesTable(tableMetadata)).isFalse(); } @Table private static class InvalidEntityWithIdAndPrimaryKeyColumn { @Id String foo; @PrimaryKeyColumn String bar; } @Table static class EntityWithComplexPrimaryKeyColumn { @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) Object complexObject; } @Table static class EntityWithComplexId { @Id Object complexObject; } @PrimaryKeyClass static class PrimaryKeyClassWithComplexId { @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) Object complexObject; } @Table static class EntityWithPrimaryKeyClassWithComplexId { @Id PrimaryKeyClassWithComplexId primaryKeyClassWithComplexId; } private static class Human {} @Table private static class X { @PrimaryKey String key; } @Table private static class Y { @PrimaryKey String key; } @UserDefinedType private static class MappedUdt {} @Table private static class WithUdt { @Id String id; @CassandraType(type = DataType.Name.UDT, userTypeName = "mappedudt") UDTValue udtValue; } enum HumanToStringConverter implements Converter<Human, String> { INSTANCE; @Override public String convert(Human source) { return "hello"; } } @Table private static class TypeWithCustomConvertedMap { @Id String id; Map<String, Collection<String>> stringMap; @CassandraType(type = Name.ASCII) Map<String, Collection<String>> blobMap; } @Table private static class TypeWithListOfHumans { @Id String id; List<Human> humans; } @WritingConverter enum StringMapToStringConverter implements Converter<Map<String, Collection<String>>, String> { INSTANCE; @Override public String convert(Map<String, Collection<String>> source) { return "serialized"; } } }