Skip to content

Commit 5cdb313

Browse files
committed
DATACMNS-1610 - Improved property path access.
We now expose a dedicated PersistentPropertyPathAccessor and optionally take options for both the read and write access to properties. On read, clients can define to eagerly receive null in case one of the path segments is null. By default, we reject those as it indicates that there might be an issue with the backing object if the client assumes it can look up the more deeply nested path to receive a result. On write access clients can define to either reject, skip or log intermediate null segments where skip means, that the setting is just silently aborted. Clients can also control how to deal with collection and map intermediates. By default we now propagate the attempt to set a value to all collection or map items respectively. This could be potentially extended to provide a filter receiving both the property and property value to selectively propagate those calls in the future. The separation of these APIs (PersistentPropertyAccessor and PersistentPropertyPathAccessor) is introduced as the latter can be implemented on top of the former and the former potentially being dynamically generated. The logic of the latter can then be clearly separated from the actual individual property lookup. ConvertingPropertyAccessor is now a PersistentPropertyPathAccessor, too, and overrides SimplePersistentPropertyPathAccessor.getTypedProperty(…) to plug in conversion logic. This allows custom collection types being used if the ConversionService backing the accessor can convert from and to Collection or Map respectively. Deprecated all PersistentPropertyPath-related methods on PersistentPropertyAccessor for removal in 2.3. MappingAuditableBeanWrapperFactory now uses these new settings to skip both null values as well as collection and map values when setting auditing metadata. Related tickets: DATACMNS-1438, DATACMNS-1461, DATACMNS-1609.
1 parent 70f973f commit 5cdb313

11 files changed

+933
-40
lines changed

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

+18-20
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@
2727
import org.springframework.data.annotation.LastModifiedBy;
2828
import org.springframework.data.annotation.LastModifiedDate;
2929
import org.springframework.data.domain.Auditable;
30-
import org.springframework.data.mapping.MappingException;
30+
import org.springframework.data.mapping.AccessOptions;
31+
import org.springframework.data.mapping.AccessOptions.SetOptions;
32+
import org.springframework.data.mapping.AccessOptions.SetOptions.Propagation;
3133
import org.springframework.data.mapping.PersistentEntity;
3234
import org.springframework.data.mapping.PersistentProperty;
3335
import org.springframework.data.mapping.PersistentPropertyAccessor;
36+
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
3437
import org.springframework.data.mapping.PersistentPropertyPaths;
3538
import org.springframework.data.mapping.context.MappingContext;
3639
import org.springframework.data.mapping.context.PersistentEntities;
@@ -84,7 +87,7 @@ public <T> Optional<AuditableBeanWrapper<T>> getBeanWrapperFor(T source) {
8487
key -> new MappingAuditingMetadata(context, it.getClass()));
8588

8689
return Optional.<AuditableBeanWrapper<T>> ofNullable(metadata.isAuditable() //
87-
? new MappingMetadataAuditableBeanWrapper<T>(entity.getPropertyAccessor(it), metadata)
90+
? new MappingMetadataAuditableBeanWrapper<T>(entity.getPropertyPathAccessor(it), metadata)
8891
: null);
8992

9093
}).orElseGet(() -> super.getBeanWrapperFor(source));
@@ -160,7 +163,11 @@ private static Predicate<PersistentProperty<?>> withAnnotation(Class<? extends A
160163
*/
161164
static class MappingMetadataAuditableBeanWrapper<T> extends DateConvertingAuditableBeanWrapper<T> {
162165

163-
private final PersistentPropertyAccessor<T> accessor;
166+
private static final SetOptions OPTIONS = AccessOptions.defaultSetOptions() //
167+
.skipNulls() // ;
168+
.withCollectionAndMapPropagation(Propagation.SKIP);
169+
170+
private final PersistentPropertyPathAccessor<T> accessor;
164171
private final MappingAuditingMetadata metadata;
165172

166173
/**
@@ -170,7 +177,7 @@ static class MappingMetadataAuditableBeanWrapper<T> extends DateConvertingAudita
170177
* @param accessor must not be {@literal null}.
171178
* @param metadata must not be {@literal null}.
172179
*/
173-
public MappingMetadataAuditableBeanWrapper(PersistentPropertyAccessor<T> accessor,
180+
public MappingMetadataAuditableBeanWrapper(PersistentPropertyPathAccessor<T> accessor,
174181
MappingAuditingMetadata metadata) {
175182

176183
Assert.notNull(accessor, "PersistentPropertyAccessor must not be null!");
@@ -241,29 +248,20 @@ public T getBean() {
241248
private <S, P extends PersistentProperty<?>> S setProperty(
242249
PersistentPropertyPaths<?, ? extends PersistentProperty<?>> paths, S value) {
243250

244-
paths.forEach(it -> {
245-
246-
try {
247-
248-
this.accessor.setProperty(it, value);
249-
250-
} catch (MappingException o_O) {
251-
252-
// Ignore null intermediate errors temporarily
253-
if (!o_O.getMessage().contains("on null intermediate")) {
254-
throw o_O;
255-
}
256-
}
257-
});
251+
paths.forEach(it -> this.accessor.setProperty(it, value, OPTIONS));
258252

259253
return value;
260254
}
261255

262256
private <P extends PersistentProperty<?>> TemporalAccessor setDateProperty(
263257
PersistentPropertyPaths<?, ? extends PersistentProperty<?>> property, TemporalAccessor value) {
264258

265-
property.forEach(it -> this.accessor.setProperty(it,
266-
getDateValueToSet(value, it.getRequiredLeafProperty().getType(), accessor.getBean())));
259+
property.forEach(it -> {
260+
261+
Class<?> type = it.getRequiredLeafProperty().getType();
262+
263+
this.accessor.setProperty(it, getDateValueToSet(value, type, accessor.getBean()), OPTIONS);
264+
});
267265

268266
return value;
269267
}

0 commit comments

Comments
 (0)