1
16 package org.springframework.data.repository.core.support;
17
18 import lombok.RequiredArgsConstructor;
19
20 import java.lang.annotation.Annotation;
21 import java.lang.reflect.Method;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Map;
25 import java.util.function.Supplier;
26
27 import org.aopalliance.intercept.MethodInterceptor;
28 import org.aopalliance.intercept.MethodInvocation;
29 import org.springframework.aop.framework.ProxyFactory;
30 import org.springframework.context.ApplicationEventPublisher;
31 import org.springframework.data.domain.AfterDomainEventPublication;
32 import org.springframework.data.domain.DomainEvents;
33 import org.springframework.data.repository.CrudRepository;
34 import org.springframework.data.repository.core.RepositoryInformation;
35 import org.springframework.data.util.AnnotationDetectionMethodCallback;
36 import org.springframework.lang.Nullable;
37 import org.springframework.util.Assert;
38 import org.springframework.util.ConcurrentReferenceHashMap;
39 import org.springframework.util.ReflectionUtils;
40
41
54 @RequiredArgsConstructor
55 public class EventPublishingRepositoryProxyPostProcessor implements RepositoryProxyPostProcessor {
56
57 private final ApplicationEventPublisher publisher;
58
59
63 @Override
64 public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) {
65
66 EventPublishingMethod method = EventPublishingMethod.of(repositoryInformation.getDomainType());
67
68 if (method == null) {
69 return;
70 }
71
72 factory.addAdvice(new EventPublishingMethodInterceptor(method, publisher));
73 }
74
75
81 @RequiredArgsConstructor(staticName = "of")
82 static class EventPublishingMethodInterceptor implements MethodInterceptor {
83
84 private final EventPublishingMethod eventMethod;
85 private final ApplicationEventPublisher publisher;
86
87
91 @Override
92 public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
93
94 Object[] arguments = invocation.getArguments();
95 Object result = invocation.proceed();
96
97 if (!invocation.getMethod().getName().startsWith("save")) {
98 return result;
99 }
100
101 Object eventSource = arguments.length == 1 ? arguments[0] : result;
102
103 eventMethod.publishEventsFrom(eventSource, publisher);
104
105 return result;
106 }
107 }
108
109
115 @RequiredArgsConstructor
116 static class EventPublishingMethod {
117
118 private static Map<Class<?>, EventPublishingMethod> CACHE = new ConcurrentReferenceHashMap<>();
119 private static @SuppressWarnings("null") EventPublishingMethod NONE = new EventPublishingMethod(null, null);
120
121 private final Method publishingMethod;
122 private final @Nullable Method clearingMethod;
123
124
131 @Nullable
132 public static EventPublishingMethod of(Class<?> type) {
133
134 Assert.notNull(type, "Type must not be null!");
135
136 EventPublishingMethod eventPublishingMethod = CACHE.get(type);
137
138 if (eventPublishingMethod != null) {
139 return eventPublishingMethod.orNull();
140 }
141
142 EventPublishingMethod result = from(getDetector(type, DomainEvents.class),
143 () -> getDetector(type, AfterDomainEventPublication.class));
144
145 CACHE.put(type, result);
146
147 return result.orNull();
148 }
149
150
156 public void publishEventsFrom(@Nullable Object object, ApplicationEventPublisher publisher) {
157
158 if (object == null) {
159 return;
160 }
161
162 for (Object aggregateRoot : asCollection(object)) {
163
164 for (Object event : asCollection(ReflectionUtils.invokeMethod(publishingMethod, aggregateRoot))) {
165 publisher.publishEvent(event);
166 }
167
168 if (clearingMethod != null) {
169 ReflectionUtils.invokeMethod(clearingMethod, aggregateRoot);
170 }
171 }
172 }
173
174
179 @Nullable
180 private EventPublishingMethod orNull() {
181 return this == EventPublishingMethod.NONE ? null : this;
182 }
183
184 private static <T extends Annotation> AnnotationDetectionMethodCallback<T> getDetector(Class<?> type,
185 Class<T> annotation) {
186
187 AnnotationDetectionMethodCallback<T> callback = new AnnotationDetectionMethodCallback<>(annotation);
188 ReflectionUtils.doWithMethods(type, callback);
189
190 return callback;
191 }
192
193
201 private static EventPublishingMethod from(AnnotationDetectionMethodCallback<?> publishing,
202 Supplier<AnnotationDetectionMethodCallback<?>> clearing) {
203
204 if (!publishing.hasFoundAnnotation()) {
205 return EventPublishingMethod.NONE;
206 }
207
208 Method eventMethod = publishing.getRequiredMethod();
209 ReflectionUtils.makeAccessible(eventMethod);
210
211 return new EventPublishingMethod(eventMethod, getClearingMethod(clearing.get()));
212 }
213
214
220 @Nullable
221 private static Method getClearingMethod(AnnotationDetectionMethodCallback<?> clearing) {
222
223 if (!clearing.hasFoundAnnotation()) {
224 return null;
225 }
226
227 Method method = clearing.getRequiredMethod();
228 ReflectionUtils.makeAccessible(method);
229
230 return method;
231 }
232
233
240 @SuppressWarnings("unchecked")
241 private static Collection<Object> asCollection(@Nullable Object source) {
242
243 if (source == null) {
244 return Collections.emptyList();
245 }
246
247 if (Collection.class.isInstance(source)) {
248 return (Collection<Object>) source;
249 }
250
251 return Collections.singletonList(source);
252 }
253 }
254 }
255