From 51b128fac1c17c8327d6fdd8c7ef87b039ac6c60 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Sat, 28 Dec 2013 12:40:23 +0100 Subject: [PATCH] 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. --- .../auditing/AnnotationAuditingMetadata.java | 49 ++++++++++++---- .../data/auditing/AuditableBeanWrapper.java | 8 +-- .../auditing/AuditableBeanWrapperFactory.java | 57 +++++++++++-------- .../auditing/CurrentDateTimeProvider.java | 14 +++-- .../data/auditing/DateTimeProvider.java | 10 ++-- 5 files changed, 90 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/springframework/data/auditing/AnnotationAuditingMetadata.java b/src/main/java/org/springframework/data/auditing/AnnotationAuditingMetadata.java index ee98c3f16..40b624b5a 100644 --- a/src/main/java/org/springframework/data/auditing/AnnotationAuditingMetadata.java +++ b/src/main/java/org/springframework/data/auditing/AnnotationAuditingMetadata.java @@ -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, AnnotationAuditingMetadata> METADATA_CACHE = new ConcurrentHashMap, AnnotationAuditingMetadata>(); - static final List> 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 SUPPORTED_DATE_TYPES; static { - List> types = new ArrayList>(4); - types.add(DateTime.class); - types.add(Date.class); - types.add(Long.class); - types.add(long.class); + List types = new ArrayList(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)); } /** diff --git a/src/main/java/org/springframework/data/auditing/AuditableBeanWrapper.java b/src/main/java/org/springframework/data/auditing/AuditableBeanWrapper.java index 3223dcdd5..8003a4e05 100644 --- a/src/main/java/org/springframework/data/auditing/AuditableBeanWrapper.java +++ b/src/main/java/org/springframework/data/auditing/AuditableBeanWrapper.java @@ -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); } diff --git a/src/main/java/org/springframework/data/auditing/AuditableBeanWrapperFactory.java b/src/main/java/org/springframework/data/auditing/AuditableBeanWrapperFactory.java index 9cfc9da4f..84e17353d 100644 --- a/src/main/java/org/springframework/data/auditing/AuditableBeanWrapperFactory.java +++ b/src/main/java/org/springframework/data/auditing/AuditableBeanWrapperFactory.java @@ -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 { + static enum CalendarToDateTimeConverter implements Converter { INSTANCE; @Override - public Long convert(DateTime source) { - return source.getMillis(); + public DateTime convert(Calendar source) { + return new DateTime(source); } } - static enum DateTimeToDateConverter implements Converter { + static enum CalendarToLocalDateTimeConverter implements Converter { INSTANCE; @Override - public Date convert(DateTime source) { - return source.toDate(); + public LocalDateTime convert(Calendar source) { + return new LocalDateTime(source); } } } diff --git a/src/main/java/org/springframework/data/auditing/CurrentDateTimeProvider.java b/src/main/java/org/springframework/data/auditing/CurrentDateTimeProvider.java index c8bbed8d8..4664c2105 100644 --- a/src/main/java/org/springframework/data/auditing/CurrentDateTimeProvider.java +++ b/src/main/java/org/springframework/data/auditing/CurrentDateTimeProvider.java @@ -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(); } } diff --git a/src/main/java/org/springframework/data/auditing/DateTimeProvider.java b/src/main/java/org/springframework/data/auditing/DateTimeProvider.java index 1b4a7d5a4..f03290ca3 100644 --- a/src/main/java/org/springframework/data/auditing/DateTimeProvider.java +++ b/src/main/java/org/springframework/data/auditing/DateTimeProvider.java @@ -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(); }