Introduce support to pass-thru TemporalAccessor auditing values.
We now allow passing-thru TemporalAccessor auditing values, bypassing conversion if the target value type matches the value provided from e.g. DateTimeProvider. Refined the error messages and listing all commonly supported types for which we provide converters. Closes #2719 Original pull request #2874
This commit is contained in:
committed by
Jens Schauder
parent
f2387f687a
commit
d0ba125268
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.auditing;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
@@ -58,7 +59,12 @@ final class AnnotationAuditingMetadata {
|
||||
|
||||
static {
|
||||
|
||||
List<String> types = new ArrayList<>(3);
|
||||
List<String> types = new ArrayList<>(Jsr310Converters.getSupportedClasses() //
|
||||
.stream() //
|
||||
.filter(TemporalAccessor.class::isAssignableFrom) //
|
||||
.map(Class::getName) //
|
||||
.toList());
|
||||
|
||||
types.add(Date.class.getName());
|
||||
types.add(Long.class.getName());
|
||||
types.add(long.class.getName());
|
||||
@@ -104,7 +110,7 @@ final class AnnotationAuditingMetadata {
|
||||
|
||||
Class<?> type = it.getType();
|
||||
|
||||
if (Jsr310Converters.supports(type)) {
|
||||
if (TemporalAccessor.class.isAssignableFrom(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,8 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
|
||||
return Optional.of(source).map(it -> {
|
||||
|
||||
if (it instanceof Auditable) {
|
||||
return (AuditableBeanWrapper<T>) new AuditableInterfaceBeanWrapper(conversionService, (Auditable<Object, ?, TemporalAccessor>) it);
|
||||
return (AuditableBeanWrapper<T>) new AuditableInterfaceBeanWrapper(conversionService,
|
||||
(Auditable<Object, ?, TemporalAccessor>) it);
|
||||
}
|
||||
|
||||
AnnotationAuditingMetadata metadata = AnnotationAuditingMetadata.getMetadata(it.getClass());
|
||||
@@ -98,7 +99,8 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
|
||||
private final Class<? extends TemporalAccessor> type;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public AuditableInterfaceBeanWrapper(ConversionService conversionService, Auditable<Object, ?, TemporalAccessor> auditable) {
|
||||
public AuditableInterfaceBeanWrapper(ConversionService conversionService,
|
||||
Auditable<Object, ?, TemporalAccessor> auditable) {
|
||||
|
||||
super(conversionService);
|
||||
|
||||
@@ -151,8 +153,8 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for {@link AuditableBeanWrapper} implementations that might need to convert {@link TemporalAccessor} values into
|
||||
* compatible types when setting date/time information.
|
||||
* Base class for {@link AuditableBeanWrapper} implementations that might need to convert {@link TemporalAccessor}
|
||||
* values into compatible types when setting date/time information.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.8
|
||||
@@ -168,7 +170,7 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
|
||||
/**
|
||||
* Returns the {@link TemporalAccessor} in a type, compatible to the given field.
|
||||
*
|
||||
* @param value can be {@literal null}.
|
||||
* @param value must not be {@literal null}.
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
@@ -176,7 +178,7 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
|
||||
@Nullable
|
||||
protected Object getDateValueToSet(TemporalAccessor value, Class<?> targetType, Object source) {
|
||||
|
||||
if (TemporalAccessor.class.equals(targetType)) {
|
||||
if (targetType.isInstance(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -188,7 +190,7 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
|
||||
|
||||
if (!conversionService.canConvert(value.getClass(), Date.class)) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Cannot convert date type for member %s; From %s to java.util.Date to %s", source,
|
||||
String.format("Cannot convert date type for %s; From %s to java.util.Date to %s", source,
|
||||
value.getClass(), targetType));
|
||||
}
|
||||
|
||||
@@ -196,7 +198,7 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
|
||||
return conversionService.convert(date, targetType);
|
||||
}
|
||||
|
||||
throw rejectUnsupportedType(source);
|
||||
throw rejectUnsupportedType(value.getClass(), targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,19 +219,20 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
|
||||
}
|
||||
|
||||
Class<?> typeToConvertTo = Stream.of(target, Instant.class)//
|
||||
.filter(type -> target.isAssignableFrom(type))//
|
||||
.filter(target::isAssignableFrom)//
|
||||
.filter(type -> conversionService.canConvert(it.getClass(), type))//
|
||||
.findFirst() //
|
||||
.orElseThrow(() -> rejectUnsupportedType(source.map(Object.class::cast).orElseGet(() -> source)));
|
||||
.orElseThrow(() -> rejectUnsupportedType(it.getClass(), target));
|
||||
|
||||
return (S) conversionService.convert(it, typeToConvertTo);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static IllegalArgumentException rejectUnsupportedType(Object source) {
|
||||
return new IllegalArgumentException(String.format("Invalid date type %s for member %s; Supported types are %s",
|
||||
source.getClass(), source, AnnotationAuditingMetadata.SUPPORTED_DATE_TYPES));
|
||||
private static IllegalArgumentException rejectUnsupportedType(Class<?> sourceType, Class<?> targetType) {
|
||||
return new IllegalArgumentException(
|
||||
String.format("Cannot convert unsupported date type %s to %s; Supported types are %s", sourceType.getName(),
|
||||
targetType.getName(), AnnotationAuditingMetadata.SUPPORTED_DATE_TYPES));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,7 +267,6 @@ class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory
|
||||
|
||||
@Override
|
||||
public TemporalAccessor setCreatedDate(TemporalAccessor value) {
|
||||
|
||||
return setDateField(metadata.getCreatedDateField(), value);
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,8 @@ public class MappingAuditableBeanWrapperFactory extends DefaultAuditableBeanWrap
|
||||
/**
|
||||
* Creates a new {@link MappingAuditingMetadata} instance from the given {@link PersistentEntity}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public <P> MappingAuditingMetadata(MappingContext<?, ? extends PersistentProperty<?>> context, Class<?> type) {
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@@ -82,10 +83,17 @@ public abstract class Jsr310Converters {
|
||||
}
|
||||
|
||||
public static boolean supports(Class<?> type) {
|
||||
|
||||
return CLASSES.contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the collection of supported temporal classes.
|
||||
* @since 3.2
|
||||
*/
|
||||
public static Collection<Class<?>> getSupportedClasses() {
|
||||
return Collections.unmodifiableList(CLASSES);
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum DateToLocalDateTimeConverter implements Converter<Date, LocalDateTime> {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user