/* * 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.convert; import static org.assertj.core.api.Assertions.*; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; import java.util.Arrays; import java.util.Collections; import java.util.Currency; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cassandra.core.cql.CqlIdentifier; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; import org.springframework.data.cassandra.config.SchemaAction; import org.springframework.data.cassandra.domain.AllPossibleTypes; import org.springframework.data.cassandra.mapping.CassandraPersistentEntity; import org.springframework.data.cassandra.mapping.SimpleUserTypeResolver; import org.springframework.data.cassandra.mapping.Table; import org.springframework.data.cassandra.mapping.UserDefinedType; import org.springframework.data.cassandra.mapping.UserTypeResolver; import org.springframework.data.cassandra.test.integration.support.AbstractSpringDataEmbeddedCassandraIntegrationTest; import org.springframework.data.cassandra.test.integration.support.IntegrationTestConfig; import org.springframework.data.convert.CustomConversions; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Session; import com.datastax.driver.core.UDTValue; import com.datastax.driver.core.UserType; import com.datastax.driver.core.querybuilder.Delete; import com.datastax.driver.core.querybuilder.Insert; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; import com.datastax.driver.core.querybuilder.Update; /** * Integration tests for UDT types through {@link MappingCassandraConverter}. * * @author Mark Paluch */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class MappingCassandraConverterUDTIntegrationTests extends AbstractSpringDataEmbeddedCassandraIntegrationTest { private static AtomicBoolean initialized = new AtomicBoolean(); @Configuration public static class Config extends IntegrationTestConfig { @Override public SchemaAction getSchemaAction() { return SchemaAction.NONE; } @Override public String[] getEntityBasePackages() { return new String[] { AllPossibleTypes.class.getPackage().getName() }; } @Override public CustomConversions customConversions() { return new CassandraCustomConversions(Arrays.asList(new UDTToCurrencyConverter(), new CurrencyToUDTConverter(new SimpleUserTypeResolver(cluster().getObject(), getKeyspaceName())))); } } @Autowired Session session; @Autowired MappingCassandraConverter converter; @Before public void setUp() { if (initialized.compareAndSet(false, true)) { session.execute("DROP TABLE IF EXISTS addressbook;"); session.execute("CREATE TYPE IF NOT EXISTS address (zip text, city text, streetlines list<text>);"); session.execute("CREATE TABLE addressbook (id text PRIMARY KEY, currentaddress FROZEN<address>, " + "alternate FROZEN<address>, previousaddresses FROZEN<list<address>>);"); session.execute("DROP TABLE IF EXISTS bank;"); session.execute("CREATE TYPE IF NOT EXISTS currency (currency text);"); session.execute( "CREATE TABLE bank (id text PRIMARY KEY, currency FROZEN<currency>, othercurrencies FROZEN<list<currency>>);"); session.execute("DROP TABLE IF EXISTS money;"); session.execute("CREATE TYPE IF NOT EXISTS currency (currency text);"); session.execute("CREATE TABLE money (currency FROZEN<currency> PRIMARY KEY);"); session.execute("DROP TABLE IF EXISTS car;"); session.execute("CREATE TYPE IF NOT EXISTS manufacturer (name text);"); session.execute("CREATE TYPE IF NOT EXISTS engine (manufacturer FROZEN<manufacturer>);"); session.execute("CREATE TABLE car (id text PRIMARY KEY, engine FROZEN<engine>);"); } else { session.execute("TRUNCATE addressbook;"); session.execute("TRUNCATE bank;"); session.execute("TRUNCATE money;"); session.execute("TRUNCATE car;"); } } @Test // DATACASS-172 public void shouldReadMappedUdt() { session.execute("INSERT INTO addressbook (id, currentaddress) " + "VALUES ('1', " + "{zip:'69469', city: 'Weinheim', streetlines: ['Heckenpfad', '14']});"); ResultSet resultSet = session.execute("SELECT * from addressbook"); AddressBook addressBook = converter.read(AddressBook.class, resultSet.one()); assertThat(addressBook.getCurrentaddress()).isNotNull(); AddressUserType address = addressBook.getCurrentaddress(); assertThat(address.getCity()).isEqualTo("Weinheim"); assertThat(address.getZip()).isEqualTo("69469"); assertThat(address.getStreetLines()).contains("Heckenpfad", "14"); } @Test // DATACASS-172 public void shouldWriteMappedUdt() { AddressUserType addressUserType = new AddressUserType(); addressUserType.setZip("69469"); addressUserType.setCity("Weinheim"); addressUserType.setStreetLines(Arrays.asList("Heckenpfad", "14")); AddressBook addressBook = new AddressBook(); addressBook.setId("1"); addressBook.setCurrentaddress(addressUserType); Insert insert = QueryBuilder.insertInto("addressbook"); converter.write(addressBook, insert); assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (currentaddress,id) " + "VALUES ({zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']},'1');"); } @Test // DATACASS-172 public void shouldReadMappedUdtCollection() { session.execute("INSERT INTO addressbook (id, previousaddresses) " + "VALUES ('1', " + " [{zip:'53773', city: 'Bonn'}, {zip:'12345', city: 'Bonn'}]);"); ResultSet resultSet = session.execute("SELECT * from addressbook"); AddressBook addressBook = converter.read(AddressBook.class, resultSet.one()); assertThat(addressBook.getPreviousaddresses()).hasSize(2); AddressUserType address = addressBook.getPreviousaddresses().get(0); assertThat(address.getCity()).isEqualTo("Bonn"); assertThat(address.getZip()).isEqualTo("53773"); assertThat(address.getStreetLines()).isEmpty(); } @Test // DATACASS-172 public void shouldWriteMappedUdtCollection() { AddressUserType addressUserType = new AddressUserType(); addressUserType.setZip("69469"); addressUserType.setCity("Weinheim"); addressUserType.setStreetLines(Arrays.asList("Heckenpfad", "14")); AddressBook addressBook = new AddressBook(); addressBook.setId("1"); addressBook.setPreviousaddresses(Collections.singletonList(addressUserType)); Insert insert = QueryBuilder.insertInto("addressbook"); converter.write(addressBook, insert); assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (id,previousaddresses) " + "VALUES ('1',[{zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']}]);"); } @Test // DATACASS-172 public void shouldReadUdt() { session.execute("INSERT INTO addressbook (id, alternate) " + "VALUES ('1', " + "{zip:'69469', city: 'Weinheim', streetlines: ['Heckenpfad', '14']});"); ResultSet resultSet = session.execute("SELECT * from addressbook"); AddressBook addressBook = converter.read(AddressBook.class, resultSet.one()); assertThat(addressBook.getAlternate()).isNotNull(); assertThat(addressBook.getAlternate().getString("city")).isEqualTo("Weinheim"); assertThat(addressBook.getAlternate().getString("zip")).isEqualTo("69469"); } @Test // DATACASS-172 public void shouldWriteUdt() { CassandraPersistentEntity<?> persistentEntity = converter.getMappingContext() .getRequiredPersistentEntity(AddressUserType.class); UDTValue udtValue = persistentEntity.getUserType().newValue(); udtValue.setString("zip", "69469"); udtValue.setString("city", "Weinheim"); udtValue.setList("streetlines", Arrays.asList("Heckenpfad", "14")); AddressBook addressBook = new AddressBook(); addressBook.setId("1"); addressBook.setAlternate(udtValue); Insert insert = QueryBuilder.insertInto("addressbook"); converter.write(addressBook, insert); assertThat(insert.toString()).isEqualTo("INSERT INTO addressbook (alternate,id) " + "VALUES ({zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']},'1');"); } @Test // DATACASS-172 public void shouldWriteUdtPk() { AddressUserType addressUserType = new AddressUserType(); addressUserType.setZip("69469"); addressUserType.setCity("Weinheim"); addressUserType.setStreetLines(Arrays.asList("Heckenpfad", "14")); WithMappedUdtId withUdtId = new WithMappedUdtId(); withUdtId.setId(addressUserType); Insert insert = QueryBuilder.insertInto("addressbook"); converter.write(withUdtId, insert); assertThat(insert.toString()).isEqualTo( "INSERT INTO addressbook (id) " + "VALUES ({zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']});"); } @Test // DATACASS-172 public void shouldWriteMappedUdtPk() { CassandraPersistentEntity<?> persistentEntity = converter.getMappingContext() .getRequiredPersistentEntity(AddressUserType.class); UDTValue udtValue = persistentEntity.getUserType().newValue(); udtValue.setString("zip", "69469"); udtValue.setString("city", "Weinheim"); udtValue.setList("streetlines", Arrays.asList("Heckenpfad", "14")); WithUdtId withUdtId = new WithUdtId(); withUdtId.setId(udtValue); Insert insert = QueryBuilder.insertInto("addressbook"); converter.write(withUdtId, insert); assertThat(insert.toString()).isEqualTo( "INSERT INTO addressbook (id) " + "VALUES ({zip:'69469',city:'Weinheim',streetlines:['Heckenpfad','14']});"); } @Test // DATACASS-172 public void shouldReadUdtWithCustomConversion() { session.execute("INSERT INTO bank (id, currency) " + "VALUES ('1', {currency:'EUR'});"); ResultSet resultSet = session.execute("SELECT * from bank"); Bank addressBook = converter.read(Bank.class, resultSet.one()); assertThat(addressBook.getCurrency()).isNotNull(); assertThat(addressBook.getCurrency().getCurrencyCode()).isEqualTo("EUR"); } @Test // DATACASS-172 public void shouldReadUdtListWithCustomConversion() { session.execute("INSERT INTO bank (id, othercurrencies) " + "VALUES ('1', [{currency:'EUR'}]);"); ResultSet resultSet = session.execute("SELECT * from bank"); Bank addressBook = converter.read(Bank.class, resultSet.one()); assertThat(addressBook.getOtherCurrencies()).hasSize(1).contains(Currency.getInstance("EUR")); } @Test // DATACASS-172, DATACASS-400 public void shouldWriteUdtWithCustomConversion() { Bank bank = new Bank(null, Currency.getInstance("EUR"), null); Insert insert = QueryBuilder.insertInto("bank"); converter.write(bank, insert); assertThat(insert.toString()).isEqualTo("INSERT INTO bank (currency) VALUES ({currency:'EUR'});"); } @Test // DATACASS-172 public void shouldWriteUdtUpdateWherePrimaryKeyWithCustomConversion() { Money money = new Money(); money.setCurrency(Currency.getInstance("EUR")); Update update = QueryBuilder.update("money"); converter.write(money, update); assertThat(update.toString()).isEqualTo("UPDATE money WHERE currency={currency:'EUR'};"); } @Test // DATACASS-172, DATACASS-400 public void shouldWriteUdtUpdateAssignmentsWithCustomConversion() { MoneyTransfer money = new MoneyTransfer("1", Currency.getInstance("EUR")); Update update = QueryBuilder.update("money"); converter.write(money, update); assertThat(update.toString()).isEqualTo("UPDATE money SET currency={currency:'EUR'} WHERE id='1';"); } @Test // DATACASS-172 public void shouldWriteUdtSelectWherePrimaryKeyWithCustomConversion() { Money money = new Money(); money.setCurrency(Currency.getInstance("EUR")); Select select = QueryBuilder.select().from("money"); converter.write(money, select.where()); assertThat(select.toString()).isEqualTo("SELECT * FROM money WHERE currency={currency:'EUR'};"); } @Test // DATACASS-172 public void shouldWriteUdtDeleteWherePrimaryKeyWithCustomConversion() { Money money = new Money(); money.setCurrency(Currency.getInstance("EUR")); Delete delete = QueryBuilder.delete().from("money"); converter.write(money, delete.where()); assertThat(delete.toString()).isEqualTo("DELETE FROM money WHERE currency={currency:'EUR'};"); } @Test // DATACASS-172, DATACASS-400 public void shouldWriteUdtListWithCustomConversion() { Bank bank = new Bank(null, null, Collections.singletonList(Currency.getInstance("EUR"))); Insert insert = QueryBuilder.insertInto("bank"); converter.write(bank, insert); assertThat(insert.toString()).isEqualTo("INSERT INTO bank (othercurrencies) VALUES ([{currency:'EUR'}]);"); } @Test // DATACASS-172 public void shouldReadNestedUdt() { session.execute("INSERT INTO car (id, engine) VALUES ('1', {manufacturer: {name:'a good one'}});"); ResultSet resultSet = session.execute("SELECT * from car"); Car car = converter.read(Car.class, resultSet.one()); assertThat(car.getEngine()).isNotNull(); assertThat(car.getEngine().getManufacturer()).isNotNull(); assertThat(car.getEngine().getManufacturer().getName()).isEqualTo("a good one"); } @Test // DATACASS-172, DATACASS-400 public void shouldWriteNestedUdt() { session.execute("INSERT INTO car (id, engine) VALUES ('1', {manufacturer: {name:'a good one'}});"); Engine engine = new Engine(new Manufacturer("a good one")); Car car = new Car("1", engine); Insert insert = QueryBuilder.insertInto("car"); converter.write(car, insert); assertThat(insert.toString()) .isEqualTo("INSERT INTO car (engine,id) VALUES ({manufacturer:{name:'a good one'}},'1');"); } @Table @Getter @AllArgsConstructor private static class Bank { @Id String id; Currency currency; List<Currency> otherCurrencies; } @Data @Table public static class Money { @Id private Currency currency; } @Table @AllArgsConstructor @Getter public static class MoneyTransfer { @Id String id; private Currency currency; } @Table @Getter @AllArgsConstructor private static class Car { @Id String id; Engine engine; } @UserDefinedType @Getter @AllArgsConstructor private static class Engine { Manufacturer manufacturer; } @UserDefinedType @Getter @AllArgsConstructor private static class Manufacturer { String name; } @Data @Table public static class AddressBook { @Id private String id; private AddressUserType currentaddress; private List<AddressUserType> previousaddresses; private UDTValue alternate; } @Data @Table public static class WithUdtId { @Id private UDTValue id; } @Data @Table public static class WithMappedUdtId { @Id private AddressUserType id; } @UserDefinedType("address") @Data public static class AddressUserType { String zip; String city; List<String> streetLines; } private static class UDTToCurrencyConverter implements Converter<UDTValue, Currency> { @Override public Currency convert(UDTValue source) { return Currency.getInstance(source.getString("currency")); } } private static class CurrencyToUDTConverter implements Converter<Currency, UDTValue> { final UserTypeResolver userTypeResolver; CurrencyToUDTConverter(UserTypeResolver userTypeResolver) { this.userTypeResolver = userTypeResolver; } @Override public UDTValue convert(Currency source) { UserType userType = userTypeResolver.resolveType(CqlIdentifier.cqlId("currency")); UDTValue udtValue = userType.newValue(); udtValue.setString("currency", source.getCurrencyCode()); return udtValue; } } }