DATACMNS-411 - Add support for Java 8 date/time types in auditing.

The auditing subsystem now supports the usage of JDK 8 date time types when running on Spring 4.0.1 or better. To achieve that switched to use the DefaultFormattingConversionService which registers the relevant converters needed. We also modified the SPI CurrentDateTimeProvider to return a Calendar instance as it carries time-zone information to be able to convert the instance into time-zone based JDK 8 date/time types.

This also allows us to make the use of JodaTime optional and only rely on it in case of the usage of Auditable or any of the JodaTime types used in the annotation based scenario. Added support to set LocalDateTime as well.
This commit is contained in:
Oliver Gierke
2013-12-28 12:40:23 +01:00
parent 05f303c2be
commit 51b128fac1
5 changed files with 90 additions and 48 deletions

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.auditing;
import java.lang.reflect.Field;
@@ -8,7 +23,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.joda.time.DateTime;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
@@ -35,15 +49,20 @@ class AnnotationAuditingMetadata {
private static final Map<Class<?>, AnnotationAuditingMetadata> METADATA_CACHE = new ConcurrentHashMap<Class<?>, AnnotationAuditingMetadata>();
static final List<Class<?>> SUPPORTED_DATE_TYPES;
private static final String JDK8_TIME_PACKAGE_PREFIX = "java.time";
public static final boolean IS_JDK_8 = org.springframework.util.ClassUtils.isPresent("java.time.Clock",
AnnotationAuditingMetadata.class.getClassLoader());
static final List<String> SUPPORTED_DATE_TYPES;
static {
List<Class<?>> types = new ArrayList<Class<?>>(4);
types.add(DateTime.class);
types.add(Date.class);
types.add(Long.class);
types.add(long.class);
List<String> types = new ArrayList<String>(5);
types.add("org.joda.time.DateTime");
types.add("org.joda.time.LocalDateTime");
types.add(Date.class.getName());
types.add(Long.class.getName());
types.add(long.class.getName());
SUPPORTED_DATE_TYPES = Collections.unmodifiableList(types);
}
@@ -53,6 +72,11 @@ class AnnotationAuditingMetadata {
private final Field lastModifiedByField;
private final Field lastModifiedDateField;
/**
* Creates a new {@link AnnotationAuditingMetadata} instance for the given type.
*
* @param type must not be {@literal null}.
*/
private AnnotationAuditingMetadata(Class<?> type) {
Assert.notNull(type, "Given type must not be null!");
@@ -73,13 +97,18 @@ class AnnotationAuditingMetadata {
*/
private void assertValidDateFieldType(Field field) {
if (field == null || SUPPORTED_DATE_TYPES.contains(field.getType())) {
if (field == null || SUPPORTED_DATE_TYPES.contains(field.getType().getName())) {
return;
}
// Support JDK 8 date types if runtime allows
if (IS_JDK_8 && field.getType().getPackage().getName().startsWith(JDK8_TIME_PACKAGE_PREFIX)) {
return;
}
throw new IllegalStateException(String.format(
"Found created/modified date field with type %s but only %s are supported!", field.getType(),
SUPPORTED_DATE_TYPES));
"Found created/modified date field with type %s but only %s as well as java.time types are supported!",
field.getType(), SUPPORTED_DATE_TYPES));
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
package org.springframework.data.auditing;
import org.joda.time.DateTime;
import java.util.Calendar;
/**
* Interface to abstract the ways setting the auditing information can be implemented.
@@ -37,7 +37,7 @@ public interface AuditableBeanWrapper {
*
* @param value
*/
void setCreatedDate(DateTime value);
void setCreatedDate(Calendar value);
/**
* Set the last modifier of the object.
@@ -51,5 +51,5 @@ public interface AuditableBeanWrapper {
*
* @param value
*/
void setLastModifiedDate(DateTime value);
void setLastModifiedDate(Calendar value);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,15 +16,18 @@
package org.springframework.data.auditing;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.Calendar;
import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.data.domain.Auditable;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* A factory class to {@link AuditableBeanWrapper} instances.
@@ -34,6 +37,9 @@ import org.springframework.util.Assert;
*/
class AuditableBeanWrapperFactory {
private static boolean IS_JODA_TIME_PRESENT = ClassUtils.isPresent("org.joda.time.DateTime",
ReflectionAuditingBeanWrapper.class.getClassLoader());
/**
* Returns an {@link AuditableBeanWrapper} if the given object is capable of being equipped with auditing information.
*
@@ -85,8 +91,8 @@ class AuditableBeanWrapperFactory {
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(org.joda.time.DateTime)
*/
public void setCreatedDate(DateTime value) {
auditable.setCreatedDate(value);
public void setCreatedDate(Calendar value) {
auditable.setCreatedDate(new DateTime(value));
}
/*
@@ -101,8 +107,8 @@ class AuditableBeanWrapperFactory {
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(org.joda.time.DateTime)
*/
public void setLastModifiedDate(DateTime value) {
auditable.setLastModifiedDate(value);
public void setLastModifiedDate(Calendar value) {
auditable.setLastModifiedDate(new DateTime(value));
}
}
@@ -129,9 +135,12 @@ class AuditableBeanWrapperFactory {
this.metadata = AnnotationAuditingMetadata.getMetadata(target.getClass());
this.target = target;
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(DateTimeToLongConverter.INSTANCE);
conversionService.addConverter(DateTimeToDateConverter.INSTANCE);
ConfigurableConversionService conversionService = new DefaultFormattingConversionService();
if (IS_JODA_TIME_PRESENT) {
conversionService.addConverter(CalendarToDateTimeConverter.INSTANCE);
conversionService.addConverter(CalendarToLocalDateTimeConverter.INSTANCE);
}
this.conversionService = conversionService;
}
@@ -146,9 +155,9 @@ class AuditableBeanWrapperFactory {
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(org.joda.time.DateTime)
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(java.util.Calendar)
*/
public void setCreatedDate(DateTime value) {
public void setCreatedDate(Calendar value) {
setDateField(metadata.getCreatedDateField(), value);
}
@@ -162,9 +171,9 @@ class AuditableBeanWrapperFactory {
/*
* (non-Javadoc)
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(org.joda.time.DateTime)
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(java.util.Calendar)
*/
public void setLastModifiedDate(DateTime value) {
public void setLastModifiedDate(Calendar value) {
setDateField(metadata.getLastModifiedDateField(), value);
}
@@ -187,7 +196,7 @@ class AuditableBeanWrapperFactory {
* @param field
* @param value
*/
private void setDateField(Field field, DateTime value) {
private void setDateField(Field field, Calendar value) {
if (field == null) {
return;
@@ -203,7 +212,7 @@ class AuditableBeanWrapperFactory {
* @param field must not be {@literal null}.
* @return
*/
private Object getDateValueToSet(DateTime value, Field field) {
private Object getDateValueToSet(Calendar value, Field field) {
if (value == null) {
return null;
@@ -211,11 +220,11 @@ class AuditableBeanWrapperFactory {
Class<?> targetType = field.getType();
if (DateTime.class.equals(targetType)) {
if (Calendar.class.equals(targetType)) {
return value;
}
if (conversionService.canConvert(DateTime.class, targetType)) {
if (conversionService.canConvert(Calendar.class, targetType)) {
return conversionService.convert(value, targetType);
}
@@ -224,23 +233,23 @@ class AuditableBeanWrapperFactory {
}
}
static enum DateTimeToLongConverter implements Converter<DateTime, Long> {
static enum CalendarToDateTimeConverter implements Converter<Calendar, DateTime> {
INSTANCE;
@Override
public Long convert(DateTime source) {
return source.getMillis();
public DateTime convert(Calendar source) {
return new DateTime(source);
}
}
static enum DateTimeToDateConverter implements Converter<DateTime, Date> {
static enum CalendarToLocalDateTimeConverter implements Converter<Calendar, LocalDateTime> {
INSTANCE;
@Override
public Date convert(DateTime source) {
return source.toDate();
public LocalDateTime convert(Calendar source) {
return new LocalDateTime(source);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,9 @@
*/
package org.springframework.data.auditing;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.joda.time.DateTime;
/**
@@ -27,11 +30,12 @@ public enum CurrentDateTimeProvider implements DateTimeProvider {
INSTANCE;
/*
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.domain.support.DateTimeProvider#getDateTime()
* @see org.springframework.data.auditing.DateTimeProvider#getNow()
*/
public DateTime getDateTime() {
return new DateTime();
@Override
public Calendar getNow() {
return new GregorianCalendar();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,10 +15,10 @@
*/
package org.springframework.data.auditing;
import org.joda.time.DateTime;
import java.util.Calendar;
/**
* SPI to calculate the {@link DateTime} instance to be used when auditing.
* SPI to calculate the current time to be used when auditing.
*
* @author Oliver Gierke
* @since 1.5
@@ -26,9 +26,9 @@ import org.joda.time.DateTime;
public interface DateTimeProvider {
/**
* Returns the {@link DateTime} to be used as modification date.
* Returns the current time to be used as modification or creation date.
*
* @return
*/
DateTime getDateTime();
Calendar getNow();
}