1
16 package org.springframework.data.auditing;
17
18 import java.lang.annotation.Annotation;
19 import java.time.temporal.TemporalAccessor;
20 import java.util.Map;
21 import java.util.Optional;
22 import java.util.function.Predicate;
23 import java.util.stream.Stream;
24
25 import org.springframework.core.convert.ConversionService;
26 import org.springframework.data.annotation.CreatedBy;
27 import org.springframework.data.annotation.CreatedDate;
28 import org.springframework.data.annotation.LastModifiedBy;
29 import org.springframework.data.annotation.LastModifiedDate;
30 import org.springframework.data.domain.Auditable;
31 import org.springframework.data.mapping.AccessOptions;
32 import org.springframework.data.mapping.AccessOptions.SetOptions;
33 import org.springframework.data.mapping.AccessOptions.SetOptions.Propagation;
34 import org.springframework.data.mapping.PersistentEntity;
35 import org.springframework.data.mapping.PersistentProperty;
36 import org.springframework.data.mapping.PersistentPropertyAccessor;
37 import org.springframework.data.mapping.PersistentPropertyPathAccessor;
38 import org.springframework.data.mapping.PersistentPropertyPaths;
39 import org.springframework.data.mapping.context.MappingContext;
40 import org.springframework.data.mapping.context.PersistentEntities;
41 import org.springframework.data.util.Lazy;
42 import org.springframework.util.Assert;
43 import org.springframework.util.ConcurrentReferenceHashMap;
44
45
55 public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrapperFactory {
56
57 private final PersistentEntities entities;
58 private final Map<Class<?>, MappingAuditingMetadata> metadataCache;
59
60
65 public MappingAuditableBeanWrapperFactory(PersistentEntities entities) {
66
67 Assert.notNull(entities, "PersistentEntities must not be null!");
68
69 this.entities = entities;
70 this.metadataCache = new ConcurrentReferenceHashMap<>();
71 }
72
73
77 @Override
78 public <T> Optional<AuditableBeanWrapper<T>> getBeanWrapperFor(T source) {
79
80 return Optional.of(source).flatMap(it -> {
81
82 if (it instanceof Auditable) {
83 return super.getBeanWrapperFor(source);
84 }
85
86 return entities.mapOnContext(it.getClass(), (context, entity) -> {
87
88 MappingAuditingMetadata metadata = metadataCache.computeIfAbsent(it.getClass(),
89 key -> new MappingAuditingMetadata(context, it.getClass()));
90
91 return Optional.<AuditableBeanWrapper<T>> ofNullable(metadata.isAuditable()
92 ? new MappingMetadataAuditableBeanWrapper<>(getConversionService(), entity.getPropertyPathAccessor(it),
93 metadata)
94 : null);
95
96 }).orElseGet(() -> super.getBeanWrapperFor(source));
97 });
98 }
99
100
106 static class MappingAuditingMetadata {
107
108 private static final Predicate<? super PersistentProperty<?>> HAS_COLLECTION_PROPERTY = it -> it.isCollectionLike()
109 || it.isMap();
110
111 private final PersistentPropertyPaths<?, ? extends PersistentProperty<?>> createdByPaths, createdDatePaths,
112 lastModifiedByPaths, lastModifiedDatePaths;
113
114 private final Lazy<Boolean> isAuditable;
115
116
121 public <P> MappingAuditingMetadata(MappingContext<?, ? extends PersistentProperty<?>> context, Class<?> type) {
122
123 Assert.notNull(type, "Type must not be null!");
124
125 this.createdByPaths = findPropertyPaths(type, CreatedBy.class, context);
126 this.createdDatePaths = findPropertyPaths(type, CreatedDate.class, context);
127 this.lastModifiedByPaths = findPropertyPaths(type, LastModifiedBy.class, context);
128 this.lastModifiedDatePaths = findPropertyPaths(type, LastModifiedDate.class, context);
129
130 this.isAuditable = Lazy.of(
131 () ->
132 Stream.of(createdByPaths, createdDatePaths, lastModifiedByPaths, lastModifiedDatePaths)
133 .anyMatch(it -> !it.isEmpty())
134 );
135 }
136
137
143 public boolean isAuditable() {
144 return isAuditable.get();
145 }
146
147 private PersistentPropertyPaths<?, ? extends PersistentProperty<?>> findPropertyPaths(Class<?> type,
148 Class<? extends Annotation> annotation, MappingContext<?, ? extends PersistentProperty<?>> context) {
149
150 return context
151 .findPersistentPropertyPaths(type, withAnnotation(annotation))
152 .dropPathIfSegmentMatches(HAS_COLLECTION_PROPERTY);
153 }
154
155 private static Predicate<PersistentProperty<?>> withAnnotation(Class<? extends Annotation> type) {
156 return t -> t.findAnnotation(type) != null;
157 }
158 }
159
160
167 static class MappingMetadataAuditableBeanWrapper<T> extends DateConvertingAuditableBeanWrapper<T> {
168
169 private static final SetOptions OPTIONS = AccessOptions.defaultSetOptions()
170 .skipNulls()
171 .withCollectionAndMapPropagation(Propagation.SKIP);
172
173 private final PersistentPropertyPathAccessor<T> accessor;
174 private final MappingAuditingMetadata metadata;
175
176
183 public MappingMetadataAuditableBeanWrapper(
184 ConversionService conversionService,
185 PersistentPropertyPathAccessor<T> accessor,
186 MappingAuditingMetadata metadata) {
187 super(conversionService);
188
189 Assert.notNull(accessor, "PersistentPropertyAccessor must not be null!");
190 Assert.notNull(metadata, "Auditing metadata must not be null!");
191
192 this.accessor = accessor;
193 this.metadata = metadata;
194 }
195
196
200 @Override
201 public Object setCreatedBy(Object value) {
202 return setProperty(metadata.createdByPaths, value);
203 }
204
205
209 @Override
210 public TemporalAccessor setCreatedDate(TemporalAccessor value) {
211 return setDateProperty(metadata.createdDatePaths, value);
212 }
213
214
218 @Override
219 public Object setLastModifiedBy(Object value) {
220 return setProperty(metadata.lastModifiedByPaths, value);
221 }
222
223
227 @Override
228 public Optional<TemporalAccessor> getLastModifiedDate() {
229
230 Optional<Object> firstValue = metadata.lastModifiedDatePaths.getFirst()
231 .map(accessor::getProperty);
232
233 return getAsTemporalAccessor(firstValue, TemporalAccessor.class);
234 }
235
236
240 @Override
241 public TemporalAccessor setLastModifiedDate(TemporalAccessor value) {
242 return setDateProperty(metadata.lastModifiedDatePaths, value);
243 }
244
245
249 @Override
250 public T getBean() {
251 return accessor.getBean();
252 }
253
254 private <S> S setProperty(
255 PersistentPropertyPaths<?, ? extends PersistentProperty<?>> paths, S value) {
256
257 paths.forEach(it -> this.accessor.setProperty(it, value, OPTIONS));
258
259 return value;
260 }
261
262 private TemporalAccessor setDateProperty(
263 PersistentPropertyPaths<?, ? extends PersistentProperty<?>> property, TemporalAccessor value) {
264
265 property.forEach(it -> {
266
267 Class<?> type = it.getRequiredLeafProperty().getType();
268
269 this.accessor.setProperty(it, getDateValueToSet(value, type, accessor.getBean()), OPTIONS);
270 });
271
272 return value;
273 }
274 }
275 }
276