From 5f64564db574dc4219c4e408863095b357fbff1d Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Wed, 20 Sep 2023 14:21:42 +0200 Subject: [PATCH] =?UTF-8?q?Properly=20emit=20domain=20events=20from=20call?= =?UTF-8?q?s=20to=20saveAll(=E2=80=A6).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now treat CrudRepository.saveAll(…) properly by unwrapping the given *Iterable*. This previously already worked for collections handed into the method but not for types only implementing Iterable directly (like Page or Window). Fixes #3153. Related tickets #2931, #2927. --- ...ublishingRepositoryProxyPostProcessor.java | 61 +++++++++++-------- ...RepositoryProxyPostProcessorUnitTests.java | 15 ++--- 2 files changed, 40 insertions(+), 36 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 4e8ccc549e..540412d0e6 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 @@ -54,7 +54,15 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr private final ApplicationEventPublisher publisher; + /** + * Creates a new {@link EventPublishingRepositoryProxyPostProcessor} for the given {@link ApplicationEventPublisher}. + * + * @param publisher must not be {@literal null}. + */ public EventPublishingRepositoryProxyPostProcessor(ApplicationEventPublisher publisher) { + + Assert.notNull(publisher, "Object must not be null"); + this.publisher = publisher; } @@ -103,9 +111,9 @@ public Object invoke(MethodInvocation invocation) throws Throwable { return result; } - Object[] arguments = invocation.getArguments(); + Iterable arguments = asCollection(invocation.getArguments()[0], invocation.getMethod()); - eventMethod.publishEventsFrom(arguments[0], publisher); + eventMethod.publishEventsFrom(arguments, publisher); return result; } @@ -177,22 +185,18 @@ public static EventPublishingMethod of(Class type) { /** * Publishes all events in the given aggregate root using the given {@link ApplicationEventPublisher}. * - * @param object can be {@literal null}. + * @param aggregates can be {@literal null}. * @param publisher must not be {@literal null}. */ - public void publishEventsFrom(@Nullable Object object, ApplicationEventPublisher publisher) { + public void publishEventsFrom(Iterable aggregates, ApplicationEventPublisher publisher) { - if (object == null) { - return; - } - - for (Object aggregateRoot : asCollection(object)) { + for (Object aggregateRoot : aggregates) { if (!type.isInstance(aggregateRoot)) { continue; } - for (Object event : asCollection(ReflectionUtils.invokeMethod(publishingMethod, aggregateRoot))) { + for (Object event : asCollection(ReflectionUtils.invokeMethod(publishingMethod, aggregateRoot), null)) { publisher.publishEvent(event); } @@ -261,25 +265,30 @@ private static Method getClearingMethod(AnnotationDetectionMethodCallback cle return method; } - /** - * Returns the given source object as collection, i.e. collections are returned as is, objects are turned into a - * one-element collection, {@literal null} will become an empty collection. - * - * @param source can be {@literal null}. - * @return - */ - @SuppressWarnings("unchecked") - private static Collection asCollection(@Nullable Object source) { + } - if (source == null) { - return Collections.emptyList(); - } + /** + * Returns the given source object as collection, i.e. collections are returned as is, objects are turned into a + * one-element collection, {@literal null} will become an empty collection. + * + * @param source can be {@literal null}. + * @return + */ + @SuppressWarnings("unchecked") + private static Iterable asCollection(@Nullable Object source, @Nullable Method method) { - if (Collection.class.isInstance(source)) { - return (Collection) source; - } + if (source == null) { + return Collections.emptyList(); + } - return Collections.singletonList(source); + if (method != null && method.getName().startsWith("saveAll")) { + return (Iterable) source; } + + if (Collection.class.isInstance(source)) { + return (Collection) source; + } + + return Collections.singletonList(source); } } 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 9102733c4b..e0469082a5 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 @@ -64,11 +64,6 @@ void rejectsNullAggregateTypes() { assertThatIllegalArgumentException().isThrownBy(() -> EventPublishingMethod.of(null)); } - @Test // DATACMNS-928 - void publishingEventsForNullIsNoOp() { - EventPublishingMethod.of(OneEvent.class).publishEventsFrom(null, publisher); - } - @Test // DATACMNS-928 void exposesEventsExposedByEntityToPublisher() { @@ -76,7 +71,7 @@ void exposesEventsExposedByEntityToPublisher() { var second = new SomeEvent(); var entity = MultipleEvents.of(Arrays.asList(first, second)); - EventPublishingMethod.of(MultipleEvents.class).publishEventsFrom(entity, publisher); + EventPublishingMethod.of(MultipleEvents.class).publishEventsFrom(List.of(entity), publisher); verify(publisher).publishEvent(eq(first)); verify(publisher).publishEvent(eq(second)); @@ -88,7 +83,7 @@ void exposesSingleEventByEntityToPublisher() { var event = new SomeEvent(); var entity = OneEvent.of(event); - EventPublishingMethod.of(OneEvent.class).publishEventsFrom(entity, publisher); + EventPublishingMethod.of(OneEvent.class).publishEventsFrom(List.of(entity), publisher); verify(publisher, times(1)).publishEvent(event); } @@ -98,7 +93,7 @@ void doesNotExposeNullEvent() { var entity = OneEvent.of(null); - EventPublishingMethod.of(OneEvent.class).publishEventsFrom(entity, publisher); + EventPublishingMethod.of(OneEvent.class).publishEventsFrom(List.of(entity), publisher); verify(publisher, times(0)).publishEvent(any()); } @@ -274,7 +269,7 @@ void clearsEventsEvenIfNoneWereExposedToPublish() { var entity = spy(EventsWithClearing.of(Collections.emptyList())); - EventPublishingMethod.of(EventsWithClearing.class).publishEventsFrom(entity, publisher); + EventPublishingMethod.of(EventsWithClearing.class).publishEventsFrom(List.of(entity), publisher); verify(entity, times(1)).clearDomainEvents(); } @@ -284,7 +279,7 @@ void clearsEventsIfThereWereSomeToBePublished() { var entity = spy(EventsWithClearing.of(Collections.singletonList(new SomeEvent()))); - EventPublishingMethod.of(EventsWithClearing.class).publishEventsFrom(entity, publisher); + EventPublishingMethod.of(EventsWithClearing.class).publishEventsFrom(List.of(entity), publisher); verify(entity, times(1)).clearDomainEvents(); }