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:
Mark Paluch
2023-07-12 12:22:59 +02:00
committed by Jens Schauder
parent f2387f687a
commit d0ba125268
6 changed files with 113 additions and 19 deletions

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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> {