From 27d4918ad571e8e2df7d04e563d1f1e1865f589e Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 14 Sep 2021 10:08:54 +0200 Subject: [PATCH] Publish delete events by repository methods `deleteInBatch` and `deleteAllInBatch` methods. We now publish events from deleteInBatch and deleteAllInBatch methods to consider well-known repository methods. We also now prevent event publication if the method argument is not an instance of the domain type. Closes: #2448. --- ...ublishingRepositoryProxyPostProcessor.java | 20 +++++-- ...RepositoryProxyPostProcessorUnitTests.java | 55 +++++++++++++++++-- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java b/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java index 2df5aae6a..433839310 100644 --- a/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java +++ b/src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java @@ -129,7 +129,8 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr } private static boolean isDeleteMethod(String methodName) { - return methodName.equals("delete") || methodName.equals("deleteAll"); + return methodName.equals("delete") || methodName.equals("deleteAll") || methodName.equals("deleteInBatch") + || methodName.equals("deleteAllInBatch"); } /** @@ -141,13 +142,16 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr static class EventPublishingMethod { private static Map, EventPublishingMethod> cache = new ConcurrentReferenceHashMap<>(); - private static @SuppressWarnings("null") EventPublishingMethod NONE = new EventPublishingMethod(null, null); + private static @SuppressWarnings("null") EventPublishingMethod NONE = new EventPublishingMethod(Object.class, null, + null); + private final Class type; private final Method publishingMethod; private final @Nullable Method clearingMethod; - EventPublishingMethod(Method publishingMethod, Method clearingMethod) { + EventPublishingMethod(Class type, Method publishingMethod, @Nullable Method clearingMethod) { + this.type = type; this.publishingMethod = publishingMethod; this.clearingMethod = clearingMethod; } @@ -170,7 +174,7 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr return eventPublishingMethod.orNull(); } - EventPublishingMethod result = from(getDetector(type, DomainEvents.class), + EventPublishingMethod result = from(type, getDetector(type, DomainEvents.class), () -> getDetector(type, AfterDomainEventPublication.class)); cache.put(type, result); @@ -192,6 +196,10 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr for (Object aggregateRoot : asCollection(object)) { + if (!type.isInstance(aggregateRoot)) { + continue; + } + for (Object event : asCollection(ReflectionUtils.invokeMethod(publishingMethod, aggregateRoot))) { publisher.publishEvent(event); } @@ -229,7 +237,7 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr * @param clearing must not be {@literal null}. * @return */ - private static EventPublishingMethod from(AnnotationDetectionMethodCallback publishing, + private static EventPublishingMethod from(Class type, AnnotationDetectionMethodCallback publishing, Supplier> clearing) { if (!publishing.hasFoundAnnotation()) { @@ -239,7 +247,7 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr Method eventMethod = publishing.getRequiredMethod(); ReflectionUtils.makeAccessible(eventMethod); - return new EventPublishingMethod(eventMethod, getClearingMethod(clearing.get())); + return new EventPublishingMethod(type, eventMethod, getClearingMethod(clearing.get())); } /** diff --git a/src/test/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessorUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessorUnitTests.java index a4595715e..118e2a063 100644 --- a/src/test/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessorUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessorUnitTests.java @@ -127,7 +127,7 @@ class EventPublishingRepositoryProxyPostProcessorUnitTests { } @Test // DATACMNS-1663 - public void interceptsDeleteMethod() throws Throwable { + void interceptsDeleteMethod() throws Throwable { SomeEvent event = new SomeEvent(); MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event)); mockInvocation(invocation, SampleRepository.class.getMethod("delete", Object.class), sample); @@ -152,7 +152,7 @@ class EventPublishingRepositoryProxyPostProcessorUnitTests { } @Test // DATACMNS-1663 - public void doesNotInterceptDeleteByIdMethod() throws Throwable { + void doesNotInterceptDeleteByIdMethod() throws Throwable { doReturn(SampleRepository.class.getMethod("deleteById", Object.class)).when(invocation).getMethod(); @@ -204,7 +204,7 @@ class EventPublishingRepositoryProxyPostProcessorUnitTests { } @Test // DATACMNS-1663 - public void publishesEventsForCallToDeleteWithIterable() throws Throwable { + void publishesEventsForCallToDeleteWithIterable() throws Throwable { SomeEvent event = new SomeEvent(); MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event)); @@ -217,6 +217,34 @@ class EventPublishingRepositoryProxyPostProcessorUnitTests { verify(publisher).publishEvent(any(SomeEvent.class)); } + @Test // GH-2448 + void publishesEventsForCallToDeleteInBatchWithIterable() throws Throwable { + + SomeEvent event = new SomeEvent(); + MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event)); + mockInvocation(invocation, SampleRepository.class.getMethod("deleteInBatch", Iterable.class), sample); + + EventPublishingMethodInterceptor// + .of(EventPublishingMethod.of(MultipleEvents.class), publisher)// + .invoke(invocation); + + verify(publisher).publishEvent(any(SomeEvent.class)); + } + + @Test // GH-2448 + void publishesEventsForCallToDeleteAllInBatchWithIterable() throws Throwable { + + SomeEvent event = new SomeEvent(); + MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event)); + mockInvocation(invocation, SampleRepository.class.getMethod("deleteAllInBatch", Iterable.class), sample); + + EventPublishingMethodInterceptor// + .of(EventPublishingMethod.of(MultipleEvents.class), publisher)// + .invoke(invocation); + + verify(publisher).publishEvent(any(SomeEvent.class)); + } + @Test // DATACMNS-975 void publishesEventsAfterSaveInvocation() throws Throwable { @@ -294,6 +322,17 @@ class EventPublishingRepositoryProxyPostProcessorUnitTests { verify(publisher, times(1)).publishEvent(event); } + @Test // GH-2448 + void doesNotEmitEventsFromPrimitiveValue() throws Throwable { + + Method method = SampleRepository.class.getMethod("delete", Object.class); + mockInvocation(invocation, method, "foo", MultipleEvents.of(Collections.emptySet())); + + EventPublishingMethodInterceptor.of(EventPublishingMethod.of(MultipleEvents.class), publisher).invoke(invocation); + + verify(publisher, never()).publishEvent(any()); + } + private static void mockInvocation(MethodInvocation invocation, Method method, Object parameterAndReturnValue) throws Throwable { @@ -322,17 +361,23 @@ class EventPublishingRepositoryProxyPostProcessorUnitTests { } @Value(staticConstructor = "of") - static class OneEvent { + private static class OneEvent { @Getter(onMethod = @__(@DomainEvents)) Object event; } @Value - static class SomeEvent { + private static class SomeEvent { UUID id = UUID.randomUUID(); } interface SampleRepository extends CrudRepository { MultipleEvents saveAndFlush(MultipleEvents events); + + MultipleEvents delete(String name); + + MultipleEvents deleteAllInBatch(Iterable events); + + MultipleEvents deleteInBatch(Iterable events); } }