implements Auditable
@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 @@ protected Object getDateValueToSet(TemporalAccessor value, 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,
+ String.format("Cannot convert date type for %s; From %s to java.util.Date to %s", source,
value.getClass(), targetType));
}
@@ -196,7 +198,7 @@ protected Object getDateValueToSet(TemporalAccessor value, Class> targetType,
return conversionService.convert(date, targetType);
}
- throw rejectUnsupportedType(source);
+ throw rejectUnsupportedType(value.getClass(), targetType);
}
/**
@@ -217,19 +219,20 @@ protected Optional getAsTemporalAccessor(Optiona
}
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 @@ public Object setCreatedBy(Object value) {
@Override
public TemporalAccessor setCreatedDate(TemporalAccessor value) {
-
return setDateField(metadata.getCreatedDateField(), value);
}
diff --git a/src/main/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactory.java b/src/main/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactory.java
index 424de0631b..4691245cd4 100644
--- a/src/main/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactory.java
+++ b/src/main/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactory.java
@@ -114,7 +114,8 @@ static class MappingAuditingMetadata {
/**
* 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 MappingAuditingMetadata(MappingContext, ? extends PersistentProperty>> context, Class> type) {
diff --git a/src/main/java/org/springframework/data/convert/Jsr310Converters.java b/src/main/java/org/springframework/data/convert/Jsr310Converters.java
index a1b08119b8..5e4f1afe64 100644
--- a/src/main/java/org/springframework/data/convert/Jsr310Converters.java
+++ b/src/main/java/org/springframework/data/convert/Jsr310Converters.java
@@ -30,6 +30,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -37,7 +38,7 @@
import org.springframework.lang.NonNull;
/**
- * Helper class to register JSR-310 specific {@link Converter} implementations in case the we're running on Java 8.
+ * Helper class to register JSR-310 specific {@link Converter} implementations.
*
* @author Oliver Gierke
* @author Barak Schoster
@@ -51,9 +52,9 @@ public abstract class Jsr310Converters {
Instant.class, ZoneId.class, Duration.class, Period.class);
/**
- * Returns the converters to be registered. Will only return converters in case we're running on Java 8.
+ * Returns the converters to be registered.
*
- * @return
+ * @return the converters to be registered.
*/
public static Collection> getConvertersToRegister() {
@@ -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> getSupportedClasses() {
+ return Collections.unmodifiableList(CLASSES);
+ }
+
@ReadingConverter
public enum DateToLocalDateTimeConverter implements Converter {
diff --git a/src/test/java/org/springframework/data/auditing/DefaultAuditableBeanWrapperFactoryUnitTests.java b/src/test/java/org/springframework/data/auditing/DefaultAuditableBeanWrapperFactoryUnitTests.java
index 0e7b4d7395..cc3d84d33b 100755
--- a/src/test/java/org/springframework/data/auditing/DefaultAuditableBeanWrapperFactoryUnitTests.java
+++ b/src/test/java/org/springframework/data/auditing/DefaultAuditableBeanWrapperFactoryUnitTests.java
@@ -19,7 +19,9 @@
import java.time.Instant;
import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import org.junit.jupiter.api.Test;
@@ -34,7 +36,7 @@
* @author Oliver Gierke
* @author Christoph Strobl
* @author Jens Schauder
- * @since 1.5
+ * @author Mark Paluch
*/
class DefaultAuditableBeanWrapperFactoryUnitTests {
@@ -135,10 +137,56 @@ void lastModifiedAsLocalDateTimeDateIsAvailableViaWrapperAsLocalDateTime() {
assertThat(result).hasValue(now);
}
+ @Test
+ void shouldRejectUnsupportedTemporalConversion() {
+
+ var source = new WithZonedDateTime();
+ AuditableBeanWrapper wrapper = factory.getBeanWrapperFor(source).get();
+
+ assertThatIllegalArgumentException().isThrownBy(() -> wrapper.setCreatedDate(LocalDateTime.now()))
+ .withMessageContaining(
+ "Cannot convert unsupported date type java.time.LocalDateTime to java.time.ZonedDateTime");
+ }
+
+ @Test // GH-2719
+ void shouldPassthruZonedDateTimeValue() {
+
+ var source = new WithZonedDateTime();
+ var now = ZonedDateTime.now();
+ AuditableBeanWrapper wrapper = factory.getBeanWrapperFor(source).get();
+
+ wrapper.setCreatedDate(now);
+
+ assertThat(source.created).isEqualTo(now);
+ }
+
+ @Test // GH-2719
+ void shouldPassthruOffsetDatetimeValue() {
+
+ var source = new WithOffsetDateTime();
+ var now = OffsetDateTime.now();
+ AuditableBeanWrapper wrapper = factory.getBeanWrapperFor(source).get();
+
+ wrapper.setCreatedDate(now);
+
+ assertThat(source.created).isEqualTo(now);
+ }
+
public static class LongBasedAuditable {
@CreatedDate public Long dateCreated;
@LastModifiedDate public Long dateModified;
}
+
+ static class WithZonedDateTime {
+
+ @CreatedDate ZonedDateTime created;
+ }
+
+ static class WithOffsetDateTime {
+
+ @CreatedDate OffsetDateTime created;
+ }
+
}
diff --git a/src/test/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactoryUnitTests.java b/src/test/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactoryUnitTests.java
index 1d08aea50e..9b161561d4 100755
--- a/src/test/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactoryUnitTests.java
+++ b/src/test/java/org/springframework/data/auditing/MappingAuditableBeanWrapperFactoryUnitTests.java
@@ -21,6 +21,7 @@
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Arrays;
@@ -242,6 +243,29 @@ void skipsCollectionPropertiesWhenSettingProperties() {
});
}
+ @Test // GH-2719
+ void shouldRejectUnsupportedTemporalConversion() {
+
+ var source = new WithZonedDateTime();
+ AuditableBeanWrapper wrapper = factory.getBeanWrapperFor(source).get();
+
+ assertThatIllegalArgumentException().isThrownBy(() -> wrapper.setCreatedDate(LocalDateTime.now()))
+ .withMessageContaining(
+ "Cannot convert unsupported date type java.time.LocalDateTime to java.time.ZonedDateTime");
+ }
+
+ @Test // GH-2719
+ void shouldPassthruTemporalValue() {
+
+ var source = new WithZonedDateTime();
+ var now = ZonedDateTime.now();
+ AuditableBeanWrapper wrapper = factory.getBeanWrapperFor(source).get();
+
+ wrapper.setCreatedDate(now);
+
+ assertThat(source.created).isEqualTo(now);
+ }
+
private void assertLastModificationDate(Object source, TemporalAccessor expected) {
var sample = new Sample();
@@ -302,6 +326,11 @@ static class Embedded {
@LastModifiedBy String modifier;
}
+ static class WithZonedDateTime {
+
+ @CreatedDate ZonedDateTime created;
+ }
+
static class WithEmbedded {
Embedded embedded;
Collection embeddeds;