Browse Source

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.
pull/418/head
Oliver Drotbohm 7 years ago committed by Christoph Strobl
parent
commit
ced3dde2a4
  1. 27
      src/main/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactory.java
  2. 11
      src/main/java/org/springframework/data/mapping/PersistentPropertyPaths.java
  3. 17
      src/main/java/org/springframework/data/mapping/context/PersistentPropertyPathFactory.java
  4. 41
      src/test/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactoryUnitTests.java

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

@ -99,6 +99,9 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap @@ -99,6 +99,9 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap
*/
static class MappingAuditingMetadata {
private static final Predicate<? super PersistentProperty<?>> HAS_COLLECTION_PROPERTY = it -> it.isCollectionLike()
|| it.isMap();
private final PersistentPropertyPaths<?, ? extends PersistentProperty<?>> createdByPaths, createdDatePaths,
lastModifiedByPaths, lastModifiedDatePaths;
@ -113,10 +116,10 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap @@ -113,10 +116,10 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap
Assert.notNull(type, "Type must not be null!");
this.createdByPaths = context.findPersistentPropertyPaths(type, withAnnotation(CreatedBy.class));
this.createdDatePaths = context.findPersistentPropertyPaths(type, withAnnotation(CreatedDate.class));
this.lastModifiedByPaths = context.findPersistentPropertyPaths(type, withAnnotation(LastModifiedBy.class));
this.lastModifiedDatePaths = context.findPersistentPropertyPaths(type, withAnnotation(LastModifiedDate.class));
this.createdByPaths = findPropertyPaths(type, CreatedBy.class, context);
this.createdDatePaths = findPropertyPaths(type, CreatedDate.class, context);
this.lastModifiedByPaths = findPropertyPaths(type, LastModifiedBy.class, context);
this.lastModifiedDatePaths = findPropertyPaths(type, LastModifiedDate.class, context);
this.isAuditable = Lazy.of( //
() -> Arrays.asList(createdByPaths, createdDatePaths, lastModifiedByPaths, lastModifiedDatePaths) //
@ -125,10 +128,6 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap @@ -125,10 +128,6 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap
);
}
private static Predicate<PersistentProperty<?>> withAnnotation(Class<? extends Annotation> type) {
return t -> t.findAnnotation(type) != null;
}
/**
* Returns whether the {@link PersistentEntity} is auditable at all (read: any of the auditing annotations is
* present).
@ -138,6 +137,18 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap @@ -138,6 +137,18 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap
public boolean isAuditable() {
return isAuditable.get();
}
private PersistentPropertyPaths<?, ? extends PersistentProperty<?>> findPropertyPaths(Class<?> type,
Class<? extends Annotation> annotation, MappingContext<?, ? extends PersistentProperty<?>> context) {
return context //
.findPersistentPropertyPaths(type, withAnnotation(annotation)) //
.dropPathIfSegmentMatches(HAS_COLLECTION_PROPERTY);
}
private static Predicate<PersistentProperty<?>> withAnnotation(Class<? extends Annotation> type) {
return t -> t.findAnnotation(type) != null;
}
}
/**

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

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.data.mapping;
import java.util.Optional;
import java.util.function.Predicate;
import org.springframework.data.util.Streamable;
@ -51,4 +52,14 @@ public interface PersistentPropertyPaths<T, P extends PersistentProperty<P>> @@ -51,4 +52,14 @@ public interface PersistentPropertyPaths<T, P extends PersistentProperty<P>>
* @return
*/
boolean contains(PropertyPath path);
/**
* Drops {@link PersistentPropertyPath}s that contain a path segment matching the given predicate.
*
* @param predicate must not be {@literal null}.
* @return a {@link PersistentPropertyPaths} instance with all {@link PersistentPropertyPath} instances removed that
* contain path segments matching the given predicate.
* @since 2.1.4 / 2.2.2
*/
PersistentPropertyPaths<T, P> dropPathIfSegmentMatches(Predicate<? super P> predicate);
}

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

@ -33,6 +33,7 @@ import java.util.Optional; @@ -33,6 +33,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.data.mapping.AssociationHandler;
@ -356,6 +357,22 @@ class PersistentPropertyPathFactory<E extends PersistentEntity<?, P>, P extends @@ -356,6 +357,22 @@ class PersistentPropertyPathFactory<E extends PersistentEntity<?, P>, P extends
return paths.iterator();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentPropertyPaths#dropPathIfSegmentMatches(java.util.function.Predicate)
*/
@Override
public PersistentPropertyPaths<T, P> dropPathIfSegmentMatches(Predicate<? super P> predicate) {
Assert.notNull(predicate, "Predicate must not be null!");
List<PersistentPropertyPath<P>> paths = this.stream() //
.filter(it -> !it.stream().anyMatch(predicate)) //
.collect(Collectors.toList());
return paths.equals(this.paths) ? this : new DefaultPersistentPropertyPaths<>(type, paths);
}
/**
* Simple {@link Comparator} to sort {@link PersistentPropertyPath} instances by their property segment's name
* length.

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

@ -23,9 +23,13 @@ import java.time.LocalDateTime; @@ -23,9 +23,13 @@ import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.assertj.core.api.AbstractLongAssert;
@ -222,6 +226,41 @@ public class MappingAuditableBeanWrapperFactoryUnitTests { @@ -222,6 +226,41 @@ public class MappingAuditableBeanWrapperFactoryUnitTests {
});
}
@Test // DATACMNS-1438
public void skipsCollectionPropertiesWhenSettingProperties() {
WithEmbedded withEmbedded = new WithEmbedded();
withEmbedded.embedded = new Embedded();
withEmbedded.embeddeds = Arrays.asList(new Embedded());
withEmbedded.embeddedMap = new HashMap<>();
withEmbedded.embeddedMap.put("key", new Embedded());
assertThat(factory.getBeanWrapperFor(withEmbedded)).hasValueSatisfying(it -> {
String user = "user";
Instant now = Instant.now();
it.setCreatedBy(user);
it.setLastModifiedBy(user);
it.setLastModifiedDate(now);
it.setCreatedDate(now);
Embedded embedded = withEmbedded.embeddeds.iterator().next();
assertThat(embedded.created).isNull();
assertThat(embedded.creator).isNull();
assertThat(embedded.modified).isNull();
assertThat(embedded.modifier).isNull();
embedded = withEmbedded.embeddedMap.get("key");
assertThat(embedded.created).isNull();
assertThat(embedded.creator).isNull();
assertThat(embedded.modified).isNull();
assertThat(embedded.modifier).isNull();
});
}
private void assertLastModificationDate(Object source, TemporalAccessor expected) {
Sample sample = new Sample();
@ -284,5 +323,7 @@ public class MappingAuditableBeanWrapperFactoryUnitTests { @@ -284,5 +323,7 @@ public class MappingAuditableBeanWrapperFactoryUnitTests {
static class WithEmbedded {
Embedded embedded;
Collection<Embedded> embeddeds;
Map<String, Embedded> embeddedMap;
}
}

Loading…
Cancel
Save