From 01da5a94b9dee2c1149bf528c0366ecaa1180770 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 24 Sep 2018 09:47:43 -0400 Subject: [PATCH] #437 - Add example for Cassandra Lifecycle Events. --- .../springdata/cassandra/convert/Address.java | 39 ++++++++ .../cassandra/convert/Addressbook.java | 5 + .../convert/ConverterConfiguration.java | 22 +++++ .../cassandra/events/BasicConfiguration.java | 36 ++++++++ .../events/LoggingEventListener.java | 91 +++++++++++++++++++ .../springdata/cassandra/events/User.java | 40 ++++++++ .../cassandra/events/package-info.java | 4 + .../convert/ConversionIntegrationTests.java | 45 ++++++++- .../events/LifecycleEventsTests.java | 76 ++++++++++++++++ 9 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 cassandra/example/src/main/java/example/springdata/cassandra/convert/Address.java create mode 100644 cassandra/example/src/main/java/example/springdata/cassandra/events/BasicConfiguration.java create mode 100644 cassandra/example/src/main/java/example/springdata/cassandra/events/LoggingEventListener.java create mode 100644 cassandra/example/src/main/java/example/springdata/cassandra/events/User.java create mode 100644 cassandra/example/src/main/java/example/springdata/cassandra/events/package-info.java create mode 100644 cassandra/example/src/test/java/example/springdata/cassandra/events/LifecycleEventsTests.java diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/convert/Address.java b/cassandra/example/src/main/java/example/springdata/cassandra/convert/Address.java new file mode 100644 index 00000000..ef88a79c --- /dev/null +++ b/cassandra/example/src/main/java/example/springdata/cassandra/convert/Address.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 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 example.springdata.cassandra.convert; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import org.springframework.data.cassandra.core.mapping.Element; +import org.springframework.data.cassandra.core.mapping.Tuple; + +/** + * Simple mapped tuple. + * + * @author Mark Paluch + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Tuple +public class Address { + + @Element(0) String address; + @Element(1) String city; + @Element(2) String zip; +} diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/convert/Addressbook.java b/cassandra/example/src/main/java/example/springdata/cassandra/convert/Addressbook.java index e0f2d8b9..7b8f0e78 100644 --- a/cassandra/example/src/main/java/example/springdata/cassandra/convert/Addressbook.java +++ b/cassandra/example/src/main/java/example/springdata/cassandra/convert/Addressbook.java @@ -17,7 +17,9 @@ package example.springdata.cassandra.convert; import lombok.Data; +import java.util.Currency; import java.util.List; +import java.util.Map; import org.springframework.data.annotation.Id; import org.springframework.data.cassandra.core.mapping.Table; @@ -35,4 +37,7 @@ public class Addressbook { Contact me; List friends; + + Address address; + Map preferredCurrencies; } diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/convert/ConverterConfiguration.java b/cassandra/example/src/main/java/example/springdata/cassandra/convert/ConverterConfiguration.java index 86485131..94aca2e7 100644 --- a/cassandra/example/src/main/java/example/springdata/cassandra/convert/ConverterConfiguration.java +++ b/cassandra/example/src/main/java/example/springdata/cassandra/convert/ConverterConfiguration.java @@ -17,6 +17,7 @@ package example.springdata.cassandra.convert; import java.io.IOException; import java.util.ArrayList; +import java.util.Currency; import java.util.List; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -46,6 +47,8 @@ class ConverterConfiguration { converters.add(new PersonWriteConverter()); converters.add(new PersonReadConverter()); converters.add(new CustomAddressbookReadConverter()); + converters.add(CurrencyToStringConverter.INSTANCE); + converters.add(StringToCurrencyConverter.INSTANCE); return new CassandraCustomConversions(converters); } @@ -98,4 +101,23 @@ class ConverterConfiguration { return result; } } + + enum StringToCurrencyConverter implements Converter { + INSTANCE; + + @Override + public Currency convert(String source) { + return Currency.getInstance(source); + } + } + + enum CurrencyToStringConverter implements Converter { + + INSTANCE; + + @Override + public String convert(Currency source) { + return source.getCurrencyCode(); + } + } } diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/events/BasicConfiguration.java b/cassandra/example/src/main/java/example/springdata/cassandra/events/BasicConfiguration.java new file mode 100644 index 00000000..ad68a349 --- /dev/null +++ b/cassandra/example/src/main/java/example/springdata/cassandra/events/BasicConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 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 example.springdata.cassandra.events; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Basic {@link Configuration} to create the necessary schema for the {@link User} table. + * + * @author Mark Paluch + */ +@SpringBootApplication +@EntityScan(basePackageClasses = User.class) +class BasicConfiguration { + + @Bean + LoggingEventListener listener() { + return new LoggingEventListener(); + } +} diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/events/LoggingEventListener.java b/cassandra/example/src/main/java/example/springdata/cassandra/events/LoggingEventListener.java new file mode 100644 index 00000000..7ba5edec --- /dev/null +++ b/cassandra/example/src/main/java/example/springdata/cassandra/events/LoggingEventListener.java @@ -0,0 +1,91 @@ +/* + * Copyright 2018 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 example.springdata.cassandra.events; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener; +import org.springframework.data.cassandra.core.mapping.event.AfterConvertEvent; +import org.springframework.data.cassandra.core.mapping.event.AfterDeleteEvent; +import org.springframework.data.cassandra.core.mapping.event.AfterLoadEvent; +import org.springframework.data.cassandra.core.mapping.event.AfterSaveEvent; +import org.springframework.data.cassandra.core.mapping.event.BeforeDeleteEvent; +import org.springframework.data.cassandra.core.mapping.event.BeforeSaveEvent; + +/** + * {@link ApplicationListener} for Cassandra mapping events logging the events. + * + * @author Mark Paluch + */ +public class LoggingEventListener extends AbstractCassandraEventListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(LoggingEventListener.class); + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onBeforeSave(org.springframework.data.cassandra.core.mapping.event.BeforeSaveEvent) + */ + @Override + public void onBeforeSave(BeforeSaveEvent event) { + LOGGER.info("onBeforeSave: {}, {}", event.getSource(), event.getStatement()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onAfterSave(org.springframework.data.cassandra.core.mapping.event.AfterSaveEvent) + */ + @Override + public void onAfterSave(AfterSaveEvent event) { + LOGGER.info("onAfterSave: {}", event.getSource()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onBeforeDelete(org.springframework.data.cassandra.core.mapping.event.BeforeDeleteEvent) + */ + @Override + public void onBeforeDelete(BeforeDeleteEvent event) { + LOGGER.info("onBeforeDelete: {}", event.getSource()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onAfterDelete(org.springframework.data.cassandra.core.mapping.event.AfterDeleteEvent) + */ + @Override + public void onAfterDelete(AfterDeleteEvent event) { + LOGGER.info("onAfterDelete: {}", event.getSource()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onAfterLoad(org.springframework.data.cassandra.core.mapping.event.AfterLoadEvent) + */ + @Override + public void onAfterLoad(AfterLoadEvent event) { + LOGGER.info("onAfterLoad: {}", event.getSource()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.cassandra.core.mapping.event.AbstractCassandraEventListener#onAfterConvert(org.springframework.data.cassandra.core.mapping.event.AfterConvertEvent) + */ + @Override + public void onAfterConvert(AfterConvertEvent event) { + LOGGER.info("onAfterConvert: {}", event.getSource()); + } +} diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/events/User.java b/cassandra/example/src/main/java/example/springdata/cassandra/events/User.java new file mode 100644 index 00000000..72f74852 --- /dev/null +++ b/cassandra/example/src/main/java/example/springdata/cassandra/events/User.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018 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 example.springdata.cassandra.events; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import org.springframework.data.cassandra.core.mapping.PrimaryKey; +import org.springframework.data.cassandra.core.mapping.Table; + +/** + * Sample user class. + * + * @author Mark Paluch + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Table(value = "users") +public class User { + + private @PrimaryKey long id; + + private String firstname; + private String lastname; +} diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/events/package-info.java b/cassandra/example/src/main/java/example/springdata/cassandra/events/package-info.java new file mode 100644 index 00000000..131e9dff --- /dev/null +++ b/cassandra/example/src/main/java/example/springdata/cassandra/events/package-info.java @@ -0,0 +1,4 @@ +/** + * Package usage of Lifecycle events. + */ +package example.springdata.cassandra.events; diff --git a/cassandra/example/src/test/java/example/springdata/cassandra/convert/ConversionIntegrationTests.java b/cassandra/example/src/test/java/example/springdata/cassandra/convert/ConversionIntegrationTests.java index 26ca1133..aec4d225 100644 --- a/cassandra/example/src/test/java/example/springdata/cassandra/convert/ConversionIntegrationTests.java +++ b/cassandra/example/src/test/java/example/springdata/cassandra/convert/ConversionIntegrationTests.java @@ -20,6 +20,9 @@ import static org.assertj.core.api.Assertions.*; import example.springdata.cassandra.util.CassandraKeyspace; import java.util.Arrays; +import java.util.Currency; +import java.util.HashMap; +import java.util.Map; import org.junit.Before; import org.junit.ClassRule; @@ -31,6 +34,7 @@ import org.springframework.data.cassandra.core.CassandraOperations; import org.springframework.test.context.junit4.SpringRunner; import com.datastax.driver.core.Row; +import com.datastax.driver.core.TupleValue; import com.datastax.driver.core.querybuilder.QueryBuilder; /** @@ -45,8 +49,8 @@ public class ConversionIntegrationTests { @Autowired CassandraOperations operations; @Before - public void setUp() throws Exception { - operations.getCqlOperations().execute("TRUNCATE addressbook"); + public void setUp() { + operations.truncate(Addressbook.class); } /** @@ -114,4 +118,41 @@ public class ConversionIntegrationTests { assertThat(loaded.getTheId()).isEqualTo(addressbook.getId()); assertThat(loaded.getMyDetailsAsJson()).contains("\"firstname\":\"Walter\""); } + + /** + * Creates and stores a new {@link Addressbook} inside of Cassandra writing map and tuple columns. + */ + @Test + public void shouldWriteConvertedMapsAndTuples() { + + Addressbook addressbook = new Addressbook(); + addressbook.setId("private"); + + Map preferredCurrencies = new HashMap<>(); + preferredCurrencies.put(1, Currency.getInstance("USD")); + preferredCurrencies.put(2, Currency.getInstance("EUR")); + + Address address = new Address(); + address.setAddress("3828 Piermont Dr"); + address.setCity("Albuquerque"); + address.setZip("87111"); + + addressbook.setPreferredCurrencies(preferredCurrencies); + addressbook.setAddress(address); + + operations.insert(addressbook); + + Row row = operations.selectOne(QueryBuilder.select().from("addressbook"), Row.class); + + assertThat(row).isNotNull(); + + TupleValue tupleValue = row.getTupleValue("address"); + assertThat(tupleValue.getString(0)).isEqualTo(address.getAddress()); + assertThat(tupleValue.getString(1)).isEqualTo(address.getCity()); + assertThat(tupleValue.getString(2)).isEqualTo(address.getZip()); + + Map rawPreferredCurrencies = row.getMap("preferredCurrencies", Integer.class, String.class); + + assertThat(rawPreferredCurrencies).containsEntry(1, "USD").containsEntry(2, "EUR"); + } } diff --git a/cassandra/example/src/test/java/example/springdata/cassandra/events/LifecycleEventsTests.java b/cassandra/example/src/test/java/example/springdata/cassandra/events/LifecycleEventsTests.java new file mode 100644 index 00000000..238b1200 --- /dev/null +++ b/cassandra/example/src/test/java/example/springdata/cassandra/events/LifecycleEventsTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2018 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 example.springdata.cassandra.events; + +import example.springdata.cassandra.util.CassandraKeyspace; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.data.cassandra.core.query.Query; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Test showing differences between fetching results as {@link List} and {@link Stream streaming} results using + * Cassandra Lifecyle Events. + * + * @author Mark Paluch + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = BasicConfiguration.class) +public class LifecycleEventsTests { + + @ClassRule public final static CassandraKeyspace CASSANDRA_KEYSPACE = CassandraKeyspace.onLocalhost(); + + @Autowired CassandraOperations operations; + + @Test + public void shouldStreamEntities() { + + insertEntities(); + + Stream userStream = operations.stream(Query.empty(), User.class); + userStream.forEach(System.out::println); + } + + @Test + public void shouldReturnEntitiesAsList() { + + insertEntities(); + + List userStream = operations.select(Query.empty(), User.class); + userStream.forEach(System.out::println); + } + + private void insertEntities() { + + User walter = new User(1, "Walter", "White"); + User skyler = new User(2, "Skyler", "White"); + User jesse = new User(3, "Jesse Pinkman", "Jesse Pinkman"); + + operations.truncate(User.class); + + operations.insert(walter); + operations.insert(skyler); + operations.insert(jesse); + } +}