DATACMNS-1322 - Adapt auditing handler API to support immutable entities.

We've changed the APIs in the auditing subsystem so that we support immutable entities, mostly through MappingAuditableBeanWrapperFactory that uses a PersistentPropertyAccessor.
This commit is contained in:
Oliver Gierke
2018-06-18 15:16:35 +02:00
parent 85b507ce10
commit 6cb3587ba3
8 changed files with 109 additions and 79 deletions

View File

@@ -24,7 +24,7 @@ import java.util.Optional;
* @author Oliver Gierke
* @since 1.5
*/
public interface AuditableBeanWrapper {
public interface AuditableBeanWrapper<T> {
/**
* Set the creator of the object.
@@ -61,4 +61,13 @@ public interface AuditableBeanWrapper {
* @param value
*/
TemporalAccessor setLastModifiedDate(TemporalAccessor value);
/**
* Returns the underlying bean that potentially has been modified by the setter methods exposed. Client code needs to
* make sure to call this method to see all the changes applied via this {@link AuditableBeanWrapper}.
*
* @return will never be {@literal null}.
* @since 2.1
*/
T getBean();
}

View File

@@ -31,5 +31,5 @@ public interface AuditableBeanWrapperFactory {
* @param source must not be {@literal null}.
* @return the {@link AuditableBeanWrapper} for the given source object if it's eligible for auditing.
*/
Optional<AuditableBeanWrapper> getBeanWrapperFor(Object source);
<T> Optional<AuditableBeanWrapper<T>> getBeanWrapperFor(T source);
}

View File

@@ -124,11 +124,11 @@ public class AuditingHandler implements InitializingBean {
*
* @param source
*/
public void markCreated(Object source) {
public <T> T markCreated(T source) {
Assert.notNull(source, "Entity must not be null!");
touch(source, true);
return touch(source, true);
}
/**
@@ -136,11 +136,11 @@ public class AuditingHandler implements InitializingBean {
*
* @param source
*/
public void markModified(Object source) {
public <T> T markModified(T source) {
Assert.notNull(source, "Entity must not be null!");
touch(source, false);
return touch(source, false);
}
/**
@@ -156,9 +156,11 @@ public class AuditingHandler implements InitializingBean {
return factory.getBeanWrapperFor(source).isPresent();
}
private void touch(Object target, boolean isNew) {
private <T> T touch(T target, boolean isNew) {
factory.getBeanWrapperFor(target).ifPresent(it -> {
Optional<AuditableBeanWrapper<T>> wrapper = factory.getBeanWrapperFor(target);
return wrapper.map(it -> {
Optional<Object> auditor = touchAuditor(it, isNew);
Optional<TemporalAccessor> now = dateTimeForNow ? touchDate(it, isNew) : Optional.empty();
@@ -168,10 +170,12 @@ public class AuditingHandler implements InitializingBean {
Object defaultedNow = now.map(Object::toString).orElse("not set");
Object defaultedAuditor = auditor.map(Object::toString).orElse("unknown");
LOGGER.debug("Touched {} - Last modification at {} by {}",
new Object[] { target, defaultedNow, defaultedAuditor });
LOGGER.debug("Touched {} - Last modification at {} by {}", target, defaultedNow, defaultedAuditor);
}
});
return it.getBean();
}).orElse(target);
}
/**
@@ -180,7 +184,7 @@ public class AuditingHandler implements InitializingBean {
* @param auditable
* @return
*/
private Optional<Object> touchAuditor(AuditableBeanWrapper wrapper, boolean isNew) {
private Optional<Object> touchAuditor(AuditableBeanWrapper<?> wrapper, boolean isNew) {
Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!");
@@ -196,7 +200,6 @@ public class AuditingHandler implements InitializingBean {
return auditor;
});
}
/**
@@ -205,7 +208,7 @@ public class AuditingHandler implements InitializingBean {
* @param wrapper
* @return
*/
private Optional<TemporalAccessor> touchDate(AuditableBeanWrapper wrapper, boolean isNew) {
private Optional<TemporalAccessor> touchDate(AuditableBeanWrapper<?> wrapper, boolean isNew) {
Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!");

View File

@@ -54,20 +54,20 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
* @return
*/
@SuppressWarnings("unchecked")
public Optional<AuditableBeanWrapper> getBeanWrapperFor(Object source) {
public <T> Optional<AuditableBeanWrapper<T>> getBeanWrapperFor(T source) {
Assert.notNull(source, "Source must not be null!");
return Optional.of(source).map(it -> {
if (it instanceof Auditable) {
return new AuditableInterfaceBeanWrapper((Auditable<Object, ?, TemporalAccessor>) it);
return (AuditableBeanWrapper<T>) new AuditableInterfaceBeanWrapper((Auditable<Object, ?, TemporalAccessor>) it);
}
AnnotationAuditingMetadata metadata = AnnotationAuditingMetadata.getMetadata(it.getClass());
if (metadata.isAuditable()) {
return new ReflectionAuditingBeanWrapper(it);
return new ReflectionAuditingBeanWrapper<T>(it);
}
return null;
@@ -80,7 +80,8 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
* @author Oliver Gierke
*/
@RequiredArgsConstructor
static class AuditableInterfaceBeanWrapper extends DateConvertingAuditableBeanWrapper {
static class AuditableInterfaceBeanWrapper
extends DateConvertingAuditableBeanWrapper<Auditable<Object, ?, TemporalAccessor>> {
private final @NonNull Auditable<Object, ?, TemporalAccessor> auditable;
private final Class<? extends TemporalAccessor> type;
@@ -149,6 +150,15 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
return value;
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#getBean()
*/
@Override
public Auditable<Object, ?, TemporalAccessor> getBean() {
return auditable;
}
}
/**
@@ -158,7 +168,7 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
* @author Oliver Gierke
* @since 1.8
*/
abstract static class DateConvertingAuditableBeanWrapper implements AuditableBeanWrapper {
abstract static class DateConvertingAuditableBeanWrapper<T> implements AuditableBeanWrapper<T> {
private final ConversionService conversionService;
@@ -218,13 +228,13 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
* @return
*/
@SuppressWarnings("unchecked")
protected <T extends TemporalAccessor> Optional<T> getAsTemporalAccessor(Optional<?> source,
Class<? extends T> target) {
protected <S extends TemporalAccessor> Optional<S> getAsTemporalAccessor(Optional<?> source,
Class<? extends S> target) {
return source.map(it -> {
if (target.isInstance(it)) {
return (T) it;
return (S) it;
}
Class<?> typeToConvertTo = Stream.of(target, Instant.class)//
@@ -233,7 +243,7 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
.findFirst() //
.orElseThrow(() -> rejectUnsupportedType(source.map(Object.class::cast).orElseGet(() -> source)));
return (T) conversionService.convert(it, typeToConvertTo);
return (S) conversionService.convert(it, typeToConvertTo);
});
}
}
@@ -248,17 +258,17 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
*
* @author Oliver Gierke
*/
static class ReflectionAuditingBeanWrapper extends DateConvertingAuditableBeanWrapper {
static class ReflectionAuditingBeanWrapper<T> extends DateConvertingAuditableBeanWrapper<T> {
private final AnnotationAuditingMetadata metadata;
private final Object target;
private final T target;
/**
* Creates a new {@link ReflectionAuditingBeanWrapper} to set auditing data on the given target object.
*
* @param target must not be {@literal null}.
*/
public ReflectionAuditingBeanWrapper(Object target) {
public ReflectionAuditingBeanWrapper(T target) {
Assert.notNull(target, "Target object must not be null!");
@@ -318,13 +328,22 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
return setDateField(metadata.getLastModifiedDateField(), value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#getBean()
*/
@Override
public T getBean() {
return target;
}
/**
* Sets the given field to the given value if present.
*
* @param field
* @param value
*/
private <T> T setField(Optional<Field> field, T value) {
private <S> S setField(Optional<Field> field, S value) {
field.ifPresent(it -> ReflectionUtils.setField(it, target, value));

View File

@@ -67,23 +67,19 @@ public class IsNewAwareAuditingHandler extends AuditingHandler {
* Marks the given object created or modified based on {@link PersistentEntity#isNew(Object)}. Will route the calls to
* {@link #markCreated(Optional)} and {@link #markModified(Optional)} accordingly.
*
* @param object
* @param object must not be {@literal null}.
*/
public void markAudited(Object object) {
public Object markAudited(Object object) {
Assert.notNull(object, "Source object must not be null!");
if (!isAuditable(object)) {
return;
return object;
}
PersistentEntity<?, ? extends PersistentProperty<?>> entity = entities
.getRequiredPersistentEntity(object.getClass());
if (entity.isNew(object)) {
markCreated(object);
} else {
markModified(object);
}
return entity.isNew(object) ? markCreated(object) : markModified(object);
}
}

View File

@@ -69,7 +69,7 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap
* @see org.springframework.data.auditing.AuditableBeanWrapperFactory#getBeanWrapperFor(java.lang.Object)
*/
@Override
public Optional<AuditableBeanWrapper> getBeanWrapperFor(Object source) {
public <T> Optional<AuditableBeanWrapper<T>> getBeanWrapperFor(T source) {
return Optional.of(source).flatMap(it -> {
@@ -82,8 +82,8 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap
MappingAuditingMetadata metadata = metadataCache.computeIfAbsent(it.getClass(),
key -> new MappingAuditingMetadata(context, it.getClass()));
return Optional.<AuditableBeanWrapper> ofNullable(metadata.isAuditable() //
? new MappingMetadataAuditableBeanWrapper(entity.getPropertyAccessor(it), metadata)
return Optional.<AuditableBeanWrapper<T>> ofNullable(metadata.isAuditable() //
? new MappingMetadataAuditableBeanWrapper<T>(entity.getPropertyAccessor(it), metadata)
: null);
}).orElseGet(() -> super.getBeanWrapperFor(source));
@@ -146,7 +146,7 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap
* @author Oliver Gierke
* @since 1.8
*/
static class MappingMetadataAuditableBeanWrapper extends DateConvertingAuditableBeanWrapper {
static class MappingMetadataAuditableBeanWrapper<T> extends DateConvertingAuditableBeanWrapper<T> {
private final PersistentPropertyAccessor accessor;
private final MappingAuditingMetadata metadata;
@@ -219,8 +219,18 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap
return setDateProperty(metadata.lastModifiedDatePaths, value);
}
private <T, P extends PersistentProperty<?>> T setProperty(
PersistentPropertyPaths<?, ? extends PersistentProperty<?>> paths, T value) {
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#getBean()
*/
@Override
@SuppressWarnings("unchecked")
public T getBean() {
return (T) accessor.getBean();
}
private <S, P extends PersistentProperty<?>> S setProperty(
PersistentPropertyPaths<?, ? extends PersistentProperty<?>> paths, S value) {
paths.forEach(it -> this.accessor.setProperty(it, value));