Skip to content

Commit 36e734e

Browse files
committed
Properly emit domain events from calls to saveAll(…).
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.
1 parent 3c943da commit 36e734e

File tree

2 files changed

+46
-41
lines changed

2 files changed

+46
-41
lines changed

src/main/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessor.java

+35-26
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,15 @@ public class EventPublishingRepositoryProxyPostProcessor implements RepositoryPr
5454

5555
private final ApplicationEventPublisher publisher;
5656

57+
/**
58+
* Creates a new {@link EventPublishingRepositoryProxyPostProcessor} for the given {@link ApplicationEventPublisher}.
59+
*
60+
* @param publisher must not be {@literal null}.
61+
*/
5762
public EventPublishingRepositoryProxyPostProcessor(ApplicationEventPublisher publisher) {
63+
64+
Assert.notNull(publisher, "Object must not be null");
65+
5866
this.publisher = publisher;
5967
}
6068

@@ -111,9 +119,9 @@ public Object invoke(MethodInvocation invocation) throws Throwable {
111119
return result;
112120
}
113121

114-
Object[] arguments = invocation.getArguments();
122+
Iterable<?> arguments = asCollection(invocation.getArguments()[0], invocation.getMethod());
115123

116-
eventMethod.publishEventsFrom(arguments[0], publisher);
124+
eventMethod.publishEventsFrom(arguments, publisher);
117125

118126
return result;
119127
}
@@ -185,22 +193,18 @@ public static EventPublishingMethod of(Class<?> type) {
185193
/**
186194
* Publishes all events in the given aggregate root using the given {@link ApplicationEventPublisher}.
187195
*
188-
* @param object can be {@literal null}.
196+
* @param aggregates can be {@literal null}.
189197
* @param publisher must not be {@literal null}.
190198
*/
191-
public void publishEventsFrom(@Nullable Object object, ApplicationEventPublisher publisher) {
199+
public void publishEventsFrom(Iterable<?> aggregates, ApplicationEventPublisher publisher) {
192200

193-
if (object == null) {
194-
return;
195-
}
196-
197-
for (Object aggregateRoot : asCollection(object)) {
201+
for (Object aggregateRoot : aggregates) {
198202

199203
if (!type.isInstance(aggregateRoot)) {
200204
continue;
201205
}
202206

203-
for (Object event : asCollection(ReflectionUtils.invokeMethod(publishingMethod, aggregateRoot))) {
207+
for (Object event : asCollection(ReflectionUtils.invokeMethod(publishingMethod, aggregateRoot), null)) {
204208
publisher.publishEvent(event);
205209
}
206210

@@ -269,25 +273,30 @@ private static Method getClearingMethod(AnnotationDetectionMethodCallback<?> cle
269273
return method;
270274
}
271275

272-
/**
273-
* Returns the given source object as collection, i.e. collections are returned as is, objects are turned into a
274-
* one-element collection, {@literal null} will become an empty collection.
275-
*
276-
* @param source can be {@literal null}.
277-
* @return
278-
*/
279-
@SuppressWarnings("unchecked")
280-
private static Collection<Object> asCollection(@Nullable Object source) {
276+
}
281277

282-
if (source == null) {
283-
return Collections.emptyList();
284-
}
278+
/**
279+
* Returns the given source object as collection, i.e. collections are returned as is, objects are turned into a
280+
* one-element collection, {@literal null} will become an empty collection.
281+
*
282+
* @param source can be {@literal null}.
283+
* @return
284+
*/
285+
@SuppressWarnings("unchecked")
286+
private static Iterable<Object> asCollection(@Nullable Object source, @Nullable Method method) {
285287

286-
if (Collection.class.isInstance(source)) {
287-
return (Collection<Object>) source;
288-
}
288+
if (source == null) {
289+
return Collections.emptyList();
290+
}
289291

290-
return Collections.singletonList(source);
292+
if (method != null && method.getName().startsWith("saveAll")) {
293+
return (Iterable<Object>) source;
291294
}
295+
296+
if (Collection.class.isInstance(source)) {
297+
return (Collection<Object>) source;
298+
}
299+
300+
return Collections.singletonList(source);
292301
}
293302
}

src/test/java/org/springframework/data/repository/core/support/EventPublishingRepositoryProxyPostProcessorUnitTests.java

+11-15
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import org.mockito.junit.jupiter.MockitoExtension;
3838
import org.mockito.junit.jupiter.MockitoSettings;
3939
import org.mockito.quality.Strictness;
40-
4140
import org.springframework.aop.framework.ProxyFactory;
4241
import org.springframework.context.ApplicationEventPublisher;
4342
import org.springframework.data.domain.AfterDomainEventPublication;
@@ -68,19 +67,14 @@ void rejectsNullAggregateTypes() {
6867
assertThatIllegalArgumentException().isThrownBy(() -> EventPublishingMethod.of(null));
6968
}
7069

71-
@Test // DATACMNS-928
72-
void publishingEventsForNullIsNoOp() {
73-
EventPublishingMethod.of(OneEvent.class).publishEventsFrom(null, publisher);
74-
}
75-
7670
@Test // DATACMNS-928
7771
void exposesEventsExposedByEntityToPublisher() {
7872

7973
SomeEvent first = new SomeEvent();
8074
SomeEvent second = new SomeEvent();
8175
MultipleEvents entity = MultipleEvents.of(Arrays.asList(first, second));
8276

83-
EventPublishingMethod.of(MultipleEvents.class).publishEventsFrom(entity, publisher);
77+
EventPublishingMethod.of(MultipleEvents.class).publishEventsFrom(Arrays.asList(entity), publisher);
8478

8579
verify(publisher).publishEvent(eq(first));
8680
verify(publisher).publishEvent(eq(second));
@@ -92,7 +86,7 @@ void exposesSingleEventByEntityToPublisher() {
9286
SomeEvent event = new SomeEvent();
9387
OneEvent entity = OneEvent.of(event);
9488

95-
EventPublishingMethod.of(OneEvent.class).publishEventsFrom(entity, publisher);
89+
EventPublishingMethod.of(OneEvent.class).publishEventsFrom(Arrays.asList(entity), publisher);
9690

9791
verify(publisher, times(1)).publishEvent(event);
9892
}
@@ -102,7 +96,7 @@ void doesNotExposeNullEvent() {
10296

10397
OneEvent entity = OneEvent.of(null);
10498

105-
EventPublishingMethod.of(OneEvent.class).publishEventsFrom(entity, publisher);
99+
EventPublishingMethod.of(OneEvent.class).publishEventsFrom(Arrays.asList(entity), publisher);
106100

107101
verify(publisher, times(0)).publishEvent(any());
108102
}
@@ -194,7 +188,7 @@ void publishesEventsForCallToSaveWithIterable() throws Throwable {
194188

195189
SomeEvent event = new SomeEvent();
196190
MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event));
197-
mockInvocation(invocation, SampleRepository.class.getMethod("saveAll", Iterable.class), sample);
191+
mockInvocation(invocation, SampleRepository.class.getMethod("saveAll", Iterable.class), Arrays.asList(sample));
198192

199193
EventPublishingMethodInterceptor//
200194
.of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
@@ -208,7 +202,7 @@ void publishesEventsForCallToDeleteWithIterable() throws Throwable {
208202

209203
SomeEvent event = new SomeEvent();
210204
MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event));
211-
mockInvocation(invocation, SampleRepository.class.getMethod("deleteAll", Iterable.class), sample);
205+
mockInvocation(invocation, SampleRepository.class.getMethod("deleteAll", Iterable.class), Arrays.asList(sample));
212206

213207
EventPublishingMethodInterceptor//
214208
.of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
@@ -222,7 +216,8 @@ void publishesEventsForCallToDeleteInBatchWithIterable() throws Throwable {
222216

223217
SomeEvent event = new SomeEvent();
224218
MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event));
225-
mockInvocation(invocation, SampleRepository.class.getMethod("deleteInBatch", Iterable.class), sample);
219+
mockInvocation(invocation, SampleRepository.class.getMethod("deleteInBatch", Iterable.class),
220+
Arrays.asList(sample));
226221

227222
EventPublishingMethodInterceptor//
228223
.of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
@@ -236,7 +231,8 @@ void publishesEventsForCallToDeleteAllInBatchWithIterable() throws Throwable {
236231

237232
SomeEvent event = new SomeEvent();
238233
MultipleEvents sample = MultipleEvents.of(Collections.singletonList(event));
239-
mockInvocation(invocation, SampleRepository.class.getMethod("deleteAllInBatch", Iterable.class), sample);
234+
mockInvocation(invocation, SampleRepository.class.getMethod("deleteAllInBatch", Iterable.class),
235+
Arrays.asList(sample));
240236

241237
EventPublishingMethodInterceptor//
242238
.of(EventPublishingMethod.of(MultipleEvents.class), publisher)//
@@ -278,7 +274,7 @@ void clearsEventsEvenIfNoneWereExposedToPublish() {
278274

279275
EventsWithClearing entity = spy(EventsWithClearing.of(Collections.emptyList()));
280276

281-
EventPublishingMethod.of(EventsWithClearing.class).publishEventsFrom(entity, publisher);
277+
EventPublishingMethod.of(EventsWithClearing.class).publishEventsFrom(Arrays.asList(entity), publisher);
282278

283279
verify(entity, times(1)).clearDomainEvents();
284280
}
@@ -288,7 +284,7 @@ void clearsEventsIfThereWereSomeToBePublished() {
288284

289285
EventsWithClearing entity = spy(EventsWithClearing.of(Collections.singletonList(new SomeEvent())));
290286

291-
EventPublishingMethod.of(EventsWithClearing.class).publishEventsFrom(entity, publisher);
287+
EventPublishingMethod.of(EventsWithClearing.class).publishEventsFrom(Arrays.asList(entity), publisher);
292288

293289
verify(entity, times(1)).clearDomainEvents();
294290
}

0 commit comments

Comments
 (0)