diff --git a/src/main/java/org/springframework/data/gemfire/transaction/event/ComposableTransactionWriter.java b/src/main/java/org/springframework/data/gemfire/transaction/event/ComposableTransactionWriter.java index a4bd30dd..7180297e 100644 --- a/src/main/java/org/springframework/data/gemfire/transaction/event/ComposableTransactionWriter.java +++ b/src/main/java/org/springframework/data/gemfire/transaction/event/ComposableTransactionWriter.java @@ -22,6 +22,8 @@ import org.apache.geode.cache.TransactionEvent; import org.apache.geode.cache.TransactionWriter; import org.apache.geode.cache.TransactionWriterException; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -45,8 +47,8 @@ public class ComposableTransactionWriter implements TransactionWriter { * the composition of both {@link TransactionWriter} one and {@link TransactionWriter} two. * @see org.apache.geode.cache.TransactionWriter */ - public static TransactionWriter compose(TransactionWriter transactionWriterOne, - TransactionWriter transactionWriterTwo) { + public static @Nullable TransactionWriter compose(@Nullable TransactionWriter transactionWriterOne, + @Nullable TransactionWriter transactionWriterTwo) { return transactionWriterOne == null ? transactionWriterTwo : transactionWriterTwo == null ? transactionWriterOne @@ -56,7 +58,8 @@ public class ComposableTransactionWriter implements TransactionWriter { private final TransactionWriter transactionWriterOne; private final TransactionWriter transactionWriterTwo; - private ComposableTransactionWriter(TransactionWriter transactionWriterOne, TransactionWriter transactionWriterTwo) { + private ComposableTransactionWriter(@NonNull TransactionWriter transactionWriterOne, + @NonNull TransactionWriter transactionWriterTwo) { Assert.notNull(transactionWriterOne, "TransactionWriter one must not be null"); Assert.notNull(transactionWriterTwo, "TransactionWriter two must not be null"); @@ -65,14 +68,34 @@ public class ComposableTransactionWriter implements TransactionWriter { this.transactionWriterTwo = transactionWriterTwo; } + /** + * Returns a reference to the first {@link TransactionWriter} in the composition. + * + * @return a reference to the first {@link TransactionWriter} in the composition. + * @see org.apache.geode.cache.TransactionWriter + */ + protected TransactionWriter getTransactionWriterOne() { + return this.transactionWriterOne; + } + + /** + * Returns a reference to the second {@link TransactionWriter} in the composition. + * + * @return a reference to the second {@link TransactionWriter} in the composition. + * @see org.apache.geode.cache.TransactionWriter + */ + protected TransactionWriter getTransactionWriterTwo() { + return this.transactionWriterTwo; + } + /** * @inheritDoc */ @Override public void beforeCommit(TransactionEvent event) throws TransactionWriterException { - this.transactionWriterOne.beforeCommit(event); - this.transactionWriterTwo.beforeCommit(event); + getTransactionWriterOne().beforeCommit(event); + getTransactionWriterTwo().beforeCommit(event); } /** @@ -81,8 +104,8 @@ public class ComposableTransactionWriter implements TransactionWriter { @Override public void close() { - this.transactionWriterOne.close(); - this.transactionWriterTwo.close(); + getTransactionWriterOne().close(); + getTransactionWriterTwo().close(); } /** @@ -91,8 +114,8 @@ public class ComposableTransactionWriter implements TransactionWriter { @Override public void init(Properties properties) { - this.transactionWriterOne.init(properties); - this.transactionWriterTwo.init(properties); + getTransactionWriterOne().init(properties); + getTransactionWriterTwo().init(properties); } /** @@ -101,7 +124,7 @@ public class ComposableTransactionWriter implements TransactionWriter { @Override public void initialize(Cache cache, Properties properties) { - this.transactionWriterOne.initialize(cache, properties); - this.transactionWriterTwo.initialize(cache, properties); + getTransactionWriterOne().initialize(cache, properties); + getTransactionWriterTwo().initialize(cache, properties); } } diff --git a/src/main/java/org/springframework/data/gemfire/transaction/event/TransactionListenerAdapter.java b/src/main/java/org/springframework/data/gemfire/transaction/event/TransactionListenerAdapter.java index 14ca101d..39b356a8 100644 --- a/src/main/java/org/springframework/data/gemfire/transaction/event/TransactionListenerAdapter.java +++ b/src/main/java/org/springframework/data/gemfire/transaction/event/TransactionListenerAdapter.java @@ -21,6 +21,7 @@ import org.apache.geode.cache.TransactionWriter; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.lang.NonNull; import org.springframework.util.Assert; /** @@ -57,6 +58,16 @@ public class TransactionListenerAdapter implements TransactionListener, Transact this.applicationEventPublisher = applicationEventPublisher; } + /** + * Returns a reference to the configured {@link ApplicationEventPublisher}. + * + * @return a reference to the configured {@link ApplicationEventPublisher}. + * @see org.springframework.context.ApplicationEventPublisher + */ + protected @NonNull ApplicationEventPublisher getApplicationEventPublisher() { + return this.applicationEventPublisher; + } + /** * @inheritDoc */ @@ -68,7 +79,7 @@ public class TransactionListenerAdapter implements TransactionListener, Transact // all application @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) annotated transaction // event handler methods are invoked. - //this.applicationEventPublisher.publishEvent(TransactionApplicationEvent.of(event)); + //getApplicationEventPublisher().publishEvent(TransactionApplicationEvent.of(event)); } /** @@ -76,7 +87,7 @@ public class TransactionListenerAdapter implements TransactionListener, Transact */ @Override public void afterCommit(TransactionEvent event) { - this.applicationEventPublisher.publishEvent(TransactionApplicationEvent.of(event)); + getApplicationEventPublisher().publishEvent(TransactionApplicationEvent.of(event)); } /** @@ -90,6 +101,6 @@ public class TransactionListenerAdapter implements TransactionListener, Transact */ @Override public void afterRollback(TransactionEvent event) { - this.applicationEventPublisher.publishEvent(TransactionApplicationEvent.of(event)); + getApplicationEventPublisher().publishEvent(TransactionApplicationEvent.of(event)); } } diff --git a/src/test/java/org/springframework/data/gemfire/transaction/event/ComposableTransactionWriterUnitTests.java b/src/test/java/org/springframework/data/gemfire/transaction/event/ComposableTransactionWriterUnitTests.java new file mode 100644 index 00000000..8802ec10 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/transaction/event/ComposableTransactionWriterUnitTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2019 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.gemfire.transaction.event; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.Properties; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.TransactionEvent; +import org.apache.geode.cache.TransactionWriter; +import org.apache.geode.cache.TransactionWriterException; + +/** + * Unit Tests for {@link ComposableTransactionWriter}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.junit.MockitoJUnitRunner + * @see org.apache.geode.cache.TransactionWriter + * @see org.springframework.data.gemfire.transaction.event.ComposableTransactionWriter + * @since 2.3.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class ComposableTransactionWriterUnitTests { + + @Mock + private TransactionEvent mockTransactionEvent; + + @Mock + private TransactionWriter mockTransactionWriterOne; + + @Mock + private TransactionWriter mockTransactionWriterTwo; + + @Test + public void composeWithNullIsNullSafeAndReturnsNull() { + assertThat(ComposableTransactionWriter.compose(null, null)).isNull(); + } + + @Test + public void composeWithSingleNonNullTransactionWriterReturnsTransactionWriter() { + + assertThat(ComposableTransactionWriter.compose(this.mockTransactionWriterOne, null)) + .isEqualTo(this.mockTransactionWriterOne); + + assertThat(ComposableTransactionWriter.compose(null, this.mockTransactionWriterTwo)) + .isEqualTo(this.mockTransactionWriterTwo); + } + + @Test + public void composeWithTwoTransactionWritersReturnsComposite() { + + TransactionWriter compositeTransactionWriter = + ComposableTransactionWriter.compose(this.mockTransactionWriterOne, this.mockTransactionWriterTwo); + + assertThat(compositeTransactionWriter).isInstanceOf(ComposableTransactionWriter.class); + + assertThat(((ComposableTransactionWriter) compositeTransactionWriter).getTransactionWriterOne()) + .isEqualTo(this.mockTransactionWriterOne); + + assertThat(((ComposableTransactionWriter) compositeTransactionWriter).getTransactionWriterTwo()) + .isEqualTo(this.mockTransactionWriterTwo); + } + + @Test + public void beforeCommitInvokesComposedTransactionWriters() throws Exception { + + ComposableTransactionWriter.compose(this.mockTransactionWriterOne, this.mockTransactionWriterTwo) + .beforeCommit(this.mockTransactionEvent); + + verify(this.mockTransactionWriterOne, times(1)) + .beforeCommit(eq(this.mockTransactionEvent)); + + verify(this.mockTransactionWriterTwo, times(1)) + .beforeCommit(eq(this.mockTransactionEvent)); + } + + @Test(expected = TransactionWriterException.class) + public void beforeCommitWhenFirstTransactionWriterThrowsException() + throws Exception { + + doThrow(new TransactionWriterException("TEST")) + .when(this.mockTransactionWriterOne).beforeCommit(any(TransactionEvent.class)); + + try { + ComposableTransactionWriter.compose(this.mockTransactionWriterOne, this.mockTransactionWriterTwo) + .beforeCommit(this.mockTransactionEvent); + } + catch (TransactionWriterException expected) { + + assertThat(expected).hasMessage("TEST"); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + + verify(this.mockTransactionWriterOne, times(1)) + .beforeCommit(eq(this.mockTransactionEvent)); + + verify(this.mockTransactionWriterTwo, never()).beforeCommit(any(TransactionEvent.class)); + } + } + + @Test + public void closeCallsComposedTransactionWritersCloseMethod() { + + ComposableTransactionWriter.compose(this.mockTransactionWriterOne, this.mockTransactionWriterTwo).close(); + + verify(this.mockTransactionWriterOne, times(1)).close(); + verify(this.mockTransactionWriterTwo, times(1)).close(); + } + + @Test + public void initCallsComposedTransactionWritersInitMethod() { + + Properties testProperties = new Properties(); + + ComposableTransactionWriter.compose(this.mockTransactionWriterOne, this.mockTransactionWriterTwo) + .init(testProperties); + + verify(this.mockTransactionWriterOne, times(1)).init(eq(testProperties)); + verify(this.mockTransactionWriterTwo, times(1)).init(eq(testProperties)); + } + + @Test + public void initializeCallsComposedTransactionWritersInitializeMethod() { + + Cache mockCache = mock(Cache.class); + + Properties testProperties = new Properties(); + + ComposableTransactionWriter.compose(this.mockTransactionWriterOne, this.mockTransactionWriterTwo) + .initialize(mockCache, testProperties); + + verify(this.mockTransactionWriterOne, times(1)) + .initialize(eq(mockCache), eq(testProperties)); + + verify(this.mockTransactionWriterTwo, times(1)) + .initialize(eq(mockCache), eq(testProperties)); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/transaction/event/TransactionListenerAdapterUnitTests.java b/src/test/java/org/springframework/data/gemfire/transaction/event/TransactionListenerAdapterUnitTests.java new file mode 100644 index 00000000..f9c04c69 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/transaction/event/TransactionListenerAdapterUnitTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2019 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.gemfire.transaction.event; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.function.Consumer; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.apache.geode.cache.TransactionEvent; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; + +/** + * Unit Tests for {@link TransactionListenerAdapter}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.junit.MockitoJUnitRunner + * @see org.springframework.context.ApplicationEventPublisher + * @see org.springframework.data.gemfire.transaction.event.TransactionListenerAdapter + * @since 2.3.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class TransactionListenerAdapterUnitTests { + + @Mock + private ApplicationEventPublisher mockApplicationEventPublisher; + + @Mock + private TransactionEvent mockTransactionEvent; + + @Test + public void constructTransactionListenerAdapterIsCorrect() { + + TransactionListenerAdapter listener = new TransactionListenerAdapter(this.mockApplicationEventPublisher); + + assertThat(listener).isNotNull(); + assertThat(listener.getApplicationEventPublisher()).isEqualTo(this.mockApplicationEventPublisher); + } + + @Test(expected = IllegalArgumentException.class) + public void constructTransactionListenerAdapterWithNullApplicationEventPublisherThrowsIllegalArgumentException() { + + try { + new TransactionListenerAdapter(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("ApplicationEventPublisher must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + private void invokingApplicationEventPublisherTest(Consumer listenerConsumer) { + + doAnswer(invocation -> { + + ApplicationEvent applicationEvent = invocation.getArgument(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(TransactionApplicationEvent.class); + assertThat(applicationEvent.getSource()).isEqualTo(this.mockTransactionEvent); + + return null; + + }).when(this.mockApplicationEventPublisher).publishEvent(any(ApplicationEvent.class)); + + TransactionListenerAdapter listener = new TransactionListenerAdapter(this.mockApplicationEventPublisher); + + listenerConsumer.accept(listener); + + verify(this.mockApplicationEventPublisher, times(1)) + .publishEvent(isA(TransactionApplicationEvent.class)); + } + + private void nonInvokingApplicationEventPublisherTest(Consumer listenerConsumer) { + + TransactionListenerAdapter listener = new TransactionListenerAdapter(this.mockApplicationEventPublisher); + + listenerConsumer.accept(listener); + + verify(this.mockApplicationEventPublisher, never()).publishEvent(any(ApplicationEvent.class)); + } + + @Test + public void beforeCommitDoesNotInvokeApplicationEventPublisher() { + nonInvokingApplicationEventPublisherTest(listener -> listener.beforeCommit(this.mockTransactionEvent)); + } + + @Test + public void afterCommitInvokesApplicationEventPublisher() { + invokingApplicationEventPublisherTest(listener -> listener.afterCommit(this.mockTransactionEvent)); + } + + @Test + public void afterFailedCommitDoesNotInvokeApplicationEventPublisher() { + nonInvokingApplicationEventPublisherTest(listener -> listener.afterFailedCommit(this.mockTransactionEvent)); + } + + @Test + public void afterRollbackInvokesApplicationEventPublisher() { + invokingApplicationEventPublisherTest(listener -> listener.afterRollback(this.mockTransactionEvent)); + } +}