package org.springframework.data.auditing;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.lang.reflect.Field;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.convert.JodaTimeConverters;
import org.springframework.data.convert.Jsr310Converters;
import org.springframework.data.convert.ThreeTenBackPortConverters;
import org.springframework.data.domain.Auditable;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
class DefaultAuditableBeanWrapperFactory implements AuditableBeanWrapperFactory {
@SuppressWarnings("unchecked")
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 (AuditableBeanWrapper<T>) new AuditableInterfaceBeanWrapper((Auditable<Object, ?, TemporalAccessor>) it);
}
AnnotationAuditingMetadata metadata = AnnotationAuditingMetadata.getMetadata(it.getClass());
if (metadata.isAuditable()) {
return new ReflectionAuditingBeanWrapper<T>(it);
}
return null;
});
}
@RequiredArgsConstructor
static class AuditableInterfaceBeanWrapper
extends DateConvertingAuditableBeanWrapper<Auditable<Object, ?, TemporalAccessor>> {
private final @NonNull Auditable<Object, ?, TemporalAccessor> auditable;
private final Class<? extends TemporalAccessor> type;
@SuppressWarnings("unchecked")
public AuditableInterfaceBeanWrapper(Auditable<Object, ?, TemporalAccessor> auditable) {
this.auditable = auditable;
this.type = (Class<? extends TemporalAccessor>) ResolvableType.forClass(Auditable.class, auditable.getClass())
.getGeneric(2).resolve(TemporalAccessor.class);
}
@Override
public Object setCreatedBy(Object value) {
auditable.setCreatedBy(value);
return value;
}
@Override
public TemporalAccessor setCreatedDate(TemporalAccessor value) {
auditable.setCreatedDate(getAsTemporalAccessor(Optional.of(value), type).orElseThrow(IllegalStateException::new));
return value;
}
@Override
public Object setLastModifiedBy(Object value) {
auditable.setLastModifiedBy(value);
return value;
}
@Override
public Optional<TemporalAccessor> getLastModifiedDate() {
return getAsTemporalAccessor(auditable.getLastModifiedDate(), TemporalAccessor.class);
}
@Override
public TemporalAccessor setLastModifiedDate(TemporalAccessor value) {
auditable
.setLastModifiedDate(getAsTemporalAccessor(Optional.of(value), type).orElseThrow(IllegalStateException::new));
return value;
}
@Override
public Auditable<Object, ?, TemporalAccessor> getBean() {
return auditable;
}
}
abstract static class DateConvertingAuditableBeanWrapper<T> implements AuditableBeanWrapper<T> {
private final ConversionService conversionService;
public DateConvertingAuditableBeanWrapper() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
JodaTimeConverters.getConvertersToRegister().forEach(conversionService::addConverter);
Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter);
ThreeTenBackPortConverters.getConvertersToRegister().forEach(conversionService::addConverter);
this.conversionService = conversionService;
}
@Nullable
protected Object getDateValueToSet(TemporalAccessor value, Class<?> targetType, Object source) {
if (TemporalAccessor.class.equals(targetType)) {
return value;
}
if (conversionService.canConvert(value.getClass(), targetType)) {
return conversionService.convert(value, targetType);
}
if (conversionService.canConvert(Date.class, targetType)) {
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,
value.getClass(), targetType));
}
Date date = conversionService.convert(value, Date.class);
return conversionService.convert(date, targetType);
}
throw rejectUnsupportedType(source);
}
@SuppressWarnings("unchecked")
protected <S extends TemporalAccessor> Optional<S> getAsTemporalAccessor(Optional<?> source,
Class<? extends S> target) {
return source.map(it -> {
if (target.isInstance(it)) {
return (S) it;
}
Class<?> typeToConvertTo = Stream.of(target, Instant.class)
.filter(type -> target.isAssignableFrom(type))
.filter(type -> conversionService.canConvert(it.getClass(), type))
.findFirst()
.orElseThrow(() -> rejectUnsupportedType(source.map(Object.class::cast).orElseGet(() -> source)));
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));
}
static class ReflectionAuditingBeanWrapper<T> extends DateConvertingAuditableBeanWrapper<T> {
private final AnnotationAuditingMetadata metadata;
private final T target;
public ReflectionAuditingBeanWrapper(T target) {
Assert.notNull(target, "Target object must not be null!");
this.metadata = AnnotationAuditingMetadata.getMetadata(target.getClass());
this.target = target;
}
@Override
public Object setCreatedBy(Object value) {
return setField(metadata.getCreatedByField(), value);
}
@Override
public TemporalAccessor setCreatedDate(TemporalAccessor value) {
return setDateField(metadata.getCreatedDateField(), value);
}
@Override
public Object setLastModifiedBy(Object value) {
return setField(metadata.getLastModifiedByField(), value);
}
@Override
public Optional<TemporalAccessor> getLastModifiedDate() {
return getAsTemporalAccessor(metadata.getLastModifiedDateField().map(field -> {
Object value = org.springframework.util.ReflectionUtils.getField(field, target);
return value instanceof Optional ? ((Optional<?>) value).orElse(null) : value;
}), TemporalAccessor.class);
}
@Override
public TemporalAccessor setLastModifiedDate(TemporalAccessor value) {
return setDateField(metadata.getLastModifiedDateField(), value);
}
@Override
public T getBean() {
return target;
}
private <S> S setField(Optional<Field> field, S value) {
field.ifPresent(it -> ReflectionUtils.setField(it, target, value));
return value;
}
private TemporalAccessor setDateField(Optional<Field> field, TemporalAccessor value) {
field.ifPresent(it -> ReflectionUtils.setField(it, target, getDateValueToSet(value, it.getType(), it)));
return value;
}
}
}