Skip to content

Commit ced3dde

Browse files
odrotbohmchristophstrobl
authored andcommitted
DATACMNS-1609, DATACMNS-1438 - Skip collection path segments for auditing properties.
We now skip PersistentPropertyPath instances pointing to auditing properties for which the path contains a collection or map path segment as the PersistentPropertyAccessor currently cannot handle those. A more extensive fix for that will be put in place for Moore but requires more extensive API changes which we don't want to ship in a Lovelace maintenance release. Related tickets: DATACMNS-1461.
1 parent e28a78e commit ced3dde

File tree

4 files changed

+88
-8
lines changed

4 files changed

+88
-8
lines changed

src/main/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactory.java

+19-8
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ public <T> Optional<AuditableBeanWrapper<T>> getBeanWrapperFor(T source) {
9999
*/
100100
static class MappingAuditingMetadata {
101101

102+
private static final Predicate<? super PersistentProperty<?>> HAS_COLLECTION_PROPERTY = it -> it.isCollectionLike()
103+
|| it.isMap();
104+
102105
private final PersistentPropertyPaths<?, ? extends PersistentProperty<?>> createdByPaths, createdDatePaths,
103106
lastModifiedByPaths, lastModifiedDatePaths;
104107

@@ -113,10 +116,10 @@ public <P> MappingAuditingMetadata(MappingContext<?, ? extends PersistentPropert
113116

114117
Assert.notNull(type, "Type must not be null!");
115118

116-
this.createdByPaths = context.findPersistentPropertyPaths(type, withAnnotation(CreatedBy.class));
117-
this.createdDatePaths = context.findPersistentPropertyPaths(type, withAnnotation(CreatedDate.class));
118-
this.lastModifiedByPaths = context.findPersistentPropertyPaths(type, withAnnotation(LastModifiedBy.class));
119-
this.lastModifiedDatePaths = context.findPersistentPropertyPaths(type, withAnnotation(LastModifiedDate.class));
119+
this.createdByPaths = findPropertyPaths(type, CreatedBy.class, context);
120+
this.createdDatePaths = findPropertyPaths(type, CreatedDate.class, context);
121+
this.lastModifiedByPaths = findPropertyPaths(type, LastModifiedBy.class, context);
122+
this.lastModifiedDatePaths = findPropertyPaths(type, LastModifiedDate.class, context);
120123

121124
this.isAuditable = Lazy.of( //
122125
() -> Arrays.asList(createdByPaths, createdDatePaths, lastModifiedByPaths, lastModifiedDatePaths) //
@@ -125,10 +128,6 @@ public <P> MappingAuditingMetadata(MappingContext<?, ? extends PersistentPropert
125128
);
126129
}
127130

128-
private static Predicate<PersistentProperty<?>> withAnnotation(Class<? extends Annotation> type) {
129-
return t -> t.findAnnotation(type) != null;
130-
}
131-
132131
/**
133132
* Returns whether the {@link PersistentEntity} is auditable at all (read: any of the auditing annotations is
134133
* present).
@@ -138,6 +137,18 @@ private static Predicate<PersistentProperty<?>> withAnnotation(Class<? extends A
138137
public boolean isAuditable() {
139138
return isAuditable.get();
140139
}
140+
141+
private PersistentPropertyPaths<?, ? extends PersistentProperty<?>> findPropertyPaths(Class<?> type,
142+
Class<? extends Annotation> annotation, MappingContext<?, ? extends PersistentProperty<?>> context) {
143+
144+
return context //
145+
.findPersistentPropertyPaths(type, withAnnotation(annotation)) //
146+
.dropPathIfSegmentMatches(HAS_COLLECTION_PROPERTY);
147+
}
148+
149+
private static Predicate<PersistentProperty<?>> withAnnotation(Class<? extends Annotation> type) {
150+
return t -> t.findAnnotation(type) != null;
151+
}
141152
}
142153

143154
/**

src/main/java/org/springframework/data/mapping/PersistentPropertyPaths.java

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.mapping;
1717

1818
import java.util.Optional;
19+
import java.util.function.Predicate;
1920

2021
import org.springframework.data.util.Streamable;
2122

@@ -51,4 +52,14 @@ public interface PersistentPropertyPaths<T, P extends PersistentProperty<P>>
5152
* @return
5253
*/
5354
boolean contains(PropertyPath path);
55+
56+
/**
57+
* Drops {@link PersistentPropertyPath}s that contain a path segment matching the given predicate.
58+
*
59+
* @param predicate must not be {@literal null}.
60+
* @return a {@link PersistentPropertyPaths} instance with all {@link PersistentPropertyPath} instances removed that
61+
* contain path segments matching the given predicate.
62+
* @since 2.1.4 / 2.2.2
63+
*/
64+
PersistentPropertyPaths<T, P> dropPathIfSegmentMatches(Predicate<? super P> predicate);
5465
}

src/main/java/org/springframework/data/mapping/context/PersistentPropertyPathFactory.java

+17
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.Set;
3434
import java.util.function.Function;
3535
import java.util.function.Predicate;
36+
import java.util.stream.Collectors;
3637
import java.util.stream.Stream;
3738

3839
import org.springframework.data.mapping.AssociationHandler;
@@ -356,6 +357,22 @@ public Iterator<PersistentPropertyPath<P>> iterator() {
356357
return paths.iterator();
357358
}
358359

360+
/*
361+
* (non-Javadoc)
362+
* @see org.springframework.data.mapping.PersistentPropertyPaths#dropPathIfSegmentMatches(java.util.function.Predicate)
363+
*/
364+
@Override
365+
public PersistentPropertyPaths<T, P> dropPathIfSegmentMatches(Predicate<? super P> predicate) {
366+
367+
Assert.notNull(predicate, "Predicate must not be null!");
368+
369+
List<PersistentPropertyPath<P>> paths = this.stream() //
370+
.filter(it -> !it.stream().anyMatch(predicate)) //
371+
.collect(Collectors.toList());
372+
373+
return paths.equals(this.paths) ? this : new DefaultPersistentPropertyPaths<>(type, paths);
374+
}
375+
359376
/**
360377
* Simple {@link Comparator} to sort {@link PersistentPropertyPath} instances by their property segment's name
361378
* length.

src/test/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactoryUnitTests.java

+41
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@
2323
import java.time.ZoneOffset;
2424
import java.time.temporal.ChronoField;
2525
import java.time.temporal.TemporalAccessor;
26+
import java.util.Arrays;
2627
import java.util.Calendar;
28+
import java.util.Collection;
2729
import java.util.Date;
2830
import java.util.GregorianCalendar;
31+
import java.util.HashMap;
32+
import java.util.Map;
2933
import java.util.Optional;
3034

3135
import org.assertj.core.api.AbstractLongAssert;
@@ -222,6 +226,41 @@ public void skipsNullIntermediatesWhenSettingProperties() {
222226
});
223227
}
224228

229+
@Test // DATACMNS-1438
230+
public void skipsCollectionPropertiesWhenSettingProperties() {
231+
232+
WithEmbedded withEmbedded = new WithEmbedded();
233+
withEmbedded.embedded = new Embedded();
234+
withEmbedded.embeddeds = Arrays.asList(new Embedded());
235+
withEmbedded.embeddedMap = new HashMap<>();
236+
withEmbedded.embeddedMap.put("key", new Embedded());
237+
238+
assertThat(factory.getBeanWrapperFor(withEmbedded)).hasValueSatisfying(it -> {
239+
240+
String user = "user";
241+
Instant now = Instant.now();
242+
243+
it.setCreatedBy(user);
244+
it.setLastModifiedBy(user);
245+
it.setLastModifiedDate(now);
246+
it.setCreatedDate(now);
247+
248+
Embedded embedded = withEmbedded.embeddeds.iterator().next();
249+
250+
assertThat(embedded.created).isNull();
251+
assertThat(embedded.creator).isNull();
252+
assertThat(embedded.modified).isNull();
253+
assertThat(embedded.modifier).isNull();
254+
255+
embedded = withEmbedded.embeddedMap.get("key");
256+
257+
assertThat(embedded.created).isNull();
258+
assertThat(embedded.creator).isNull();
259+
assertThat(embedded.modified).isNull();
260+
assertThat(embedded.modifier).isNull();
261+
});
262+
}
263+
225264
private void assertLastModificationDate(Object source, TemporalAccessor expected) {
226265

227266
Sample sample = new Sample();
@@ -284,5 +323,7 @@ static class Embedded {
284323

285324
static class WithEmbedded {
286325
Embedded embedded;
326+
Collection<Embedded> embeddeds;
327+
Map<String, Embedded> embeddedMap;
287328
}
288329
}

0 commit comments

Comments
 (0)