Skip to content

Commit 51b128f

Browse files
committed
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.
1 parent 05f303c commit 51b128f

File tree

5 files changed

+90
-48
lines changed

5 files changed

+90
-48
lines changed

src/main/java/org/springframework/data/auditing/AnnotationAuditingMetadata.java

+39-10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package org.springframework.data.auditing;
217

318
import java.lang.reflect.Field;
@@ -8,7 +23,6 @@
823
import java.util.Map;
924
import java.util.concurrent.ConcurrentHashMap;
1025

11-
import org.joda.time.DateTime;
1226
import org.springframework.data.annotation.CreatedBy;
1327
import org.springframework.data.annotation.CreatedDate;
1428
import org.springframework.data.annotation.LastModifiedBy;
@@ -35,15 +49,20 @@ class AnnotationAuditingMetadata {
3549

3650
private static final Map<Class<?>, AnnotationAuditingMetadata> METADATA_CACHE = new ConcurrentHashMap<Class<?>, AnnotationAuditingMetadata>();
3751

38-
static final List<Class<?>> SUPPORTED_DATE_TYPES;
52+
private static final String JDK8_TIME_PACKAGE_PREFIX = "java.time";
53+
public static final boolean IS_JDK_8 = org.springframework.util.ClassUtils.isPresent("java.time.Clock",
54+
AnnotationAuditingMetadata.class.getClassLoader());
55+
56+
static final List<String> SUPPORTED_DATE_TYPES;
3957

4058
static {
4159

42-
List<Class<?>> types = new ArrayList<Class<?>>(4);
43-
types.add(DateTime.class);
44-
types.add(Date.class);
45-
types.add(Long.class);
46-
types.add(long.class);
60+
List<String> types = new ArrayList<String>(5);
61+
types.add("org.joda.time.DateTime");
62+
types.add("org.joda.time.LocalDateTime");
63+
types.add(Date.class.getName());
64+
types.add(Long.class.getName());
65+
types.add(long.class.getName());
4766

4867
SUPPORTED_DATE_TYPES = Collections.unmodifiableList(types);
4968
}
@@ -53,6 +72,11 @@ class AnnotationAuditingMetadata {
5372
private final Field lastModifiedByField;
5473
private final Field lastModifiedDateField;
5574

75+
/**
76+
* Creates a new {@link AnnotationAuditingMetadata} instance for the given type.
77+
*
78+
* @param type must not be {@literal null}.
79+
*/
5680
private AnnotationAuditingMetadata(Class<?> type) {
5781

5882
Assert.notNull(type, "Given type must not be null!");
@@ -73,13 +97,18 @@ private AnnotationAuditingMetadata(Class<?> type) {
7397
*/
7498
private void assertValidDateFieldType(Field field) {
7599

76-
if (field == null || SUPPORTED_DATE_TYPES.contains(field.getType())) {
100+
if (field == null || SUPPORTED_DATE_TYPES.contains(field.getType().getName())) {
101+
return;
102+
}
103+
104+
// Support JDK 8 date types if runtime allows
105+
if (IS_JDK_8 && field.getType().getPackage().getName().startsWith(JDK8_TIME_PACKAGE_PREFIX)) {
77106
return;
78107
}
79108

80109
throw new IllegalStateException(String.format(
81-
"Found created/modified date field with type %s but only %s are supported!", field.getType(),
82-
SUPPORTED_DATE_TYPES));
110+
"Found created/modified date field with type %s but only %s as well as java.time types are supported!",
111+
field.getType(), SUPPORTED_DATE_TYPES));
83112
}
84113

85114
/**

src/main/java/org/springframework/data/auditing/AuditableBeanWrapper.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012 the original author or authors.
2+
* Copyright 2012-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
1515
*/
1616
package org.springframework.data.auditing;
1717

18-
import org.joda.time.DateTime;
18+
import java.util.Calendar;
1919

2020
/**
2121
* Interface to abstract the ways setting the auditing information can be implemented.
@@ -37,7 +37,7 @@ public interface AuditableBeanWrapper {
3737
*
3838
* @param value
3939
*/
40-
void setCreatedDate(DateTime value);
40+
void setCreatedDate(Calendar value);
4141

4242
/**
4343
* Set the last modifier of the object.
@@ -51,5 +51,5 @@ public interface AuditableBeanWrapper {
5151
*
5252
* @param value
5353
*/
54-
void setLastModifiedDate(DateTime value);
54+
void setLastModifiedDate(Calendar value);
5555
}

src/main/java/org/springframework/data/auditing/AuditableBeanWrapperFactory.java

+33-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012 the original author or authors.
2+
* Copyright 2012-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,15 +16,18 @@
1616
package org.springframework.data.auditing;
1717

1818
import java.lang.reflect.Field;
19-
import java.util.Date;
19+
import java.util.Calendar;
2020

2121
import org.joda.time.DateTime;
22+
import org.joda.time.LocalDateTime;
2223
import org.springframework.core.convert.ConversionService;
2324
import org.springframework.core.convert.converter.Converter;
24-
import org.springframework.core.convert.support.DefaultConversionService;
25+
import org.springframework.core.convert.support.ConfigurableConversionService;
2526
import org.springframework.data.domain.Auditable;
2627
import org.springframework.data.util.ReflectionUtils;
28+
import org.springframework.format.support.DefaultFormattingConversionService;
2729
import org.springframework.util.Assert;
30+
import org.springframework.util.ClassUtils;
2831

2932
/**
3033
* A factory class to {@link AuditableBeanWrapper} instances.
@@ -34,6 +37,9 @@
3437
*/
3538
class AuditableBeanWrapperFactory {
3639

40+
private static boolean IS_JODA_TIME_PRESENT = ClassUtils.isPresent("org.joda.time.DateTime",
41+
ReflectionAuditingBeanWrapper.class.getClassLoader());
42+
3743
/**
3844
* Returns an {@link AuditableBeanWrapper} if the given object is capable of being equipped with auditing information.
3945
*
@@ -85,8 +91,8 @@ public void setCreatedBy(Object value) {
8591
* (non-Javadoc)
8692
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(org.joda.time.DateTime)
8793
*/
88-
public void setCreatedDate(DateTime value) {
89-
auditable.setCreatedDate(value);
94+
public void setCreatedDate(Calendar value) {
95+
auditable.setCreatedDate(new DateTime(value));
9096
}
9197

9298
/*
@@ -101,8 +107,8 @@ public void setLastModifiedBy(Object value) {
101107
* (non-Javadoc)
102108
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(org.joda.time.DateTime)
103109
*/
104-
public void setLastModifiedDate(DateTime value) {
105-
auditable.setLastModifiedDate(value);
110+
public void setLastModifiedDate(Calendar value) {
111+
auditable.setLastModifiedDate(new DateTime(value));
106112
}
107113
}
108114

@@ -129,9 +135,12 @@ public ReflectionAuditingBeanWrapper(Object target) {
129135
this.metadata = AnnotationAuditingMetadata.getMetadata(target.getClass());
130136
this.target = target;
131137

132-
DefaultConversionService conversionService = new DefaultConversionService();
133-
conversionService.addConverter(DateTimeToLongConverter.INSTANCE);
134-
conversionService.addConverter(DateTimeToDateConverter.INSTANCE);
138+
ConfigurableConversionService conversionService = new DefaultFormattingConversionService();
139+
140+
if (IS_JODA_TIME_PRESENT) {
141+
conversionService.addConverter(CalendarToDateTimeConverter.INSTANCE);
142+
conversionService.addConverter(CalendarToLocalDateTimeConverter.INSTANCE);
143+
}
135144

136145
this.conversionService = conversionService;
137146
}
@@ -146,9 +155,9 @@ public void setCreatedBy(Object value) {
146155

147156
/*
148157
* (non-Javadoc)
149-
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(org.joda.time.DateTime)
158+
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(java.util.Calendar)
150159
*/
151-
public void setCreatedDate(DateTime value) {
160+
public void setCreatedDate(Calendar value) {
152161
setDateField(metadata.getCreatedDateField(), value);
153162
}
154163

@@ -162,9 +171,9 @@ public void setLastModifiedBy(Object value) {
162171

163172
/*
164173
* (non-Javadoc)
165-
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(org.joda.time.DateTime)
174+
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(java.util.Calendar)
166175
*/
167-
public void setLastModifiedDate(DateTime value) {
176+
public void setLastModifiedDate(Calendar value) {
168177
setDateField(metadata.getLastModifiedDateField(), value);
169178
}
170179

@@ -187,7 +196,7 @@ private void setField(Field field, Object value) {
187196
* @param field
188197
* @param value
189198
*/
190-
private void setDateField(Field field, DateTime value) {
199+
private void setDateField(Field field, Calendar value) {
191200

192201
if (field == null) {
193202
return;
@@ -203,19 +212,19 @@ private void setDateField(Field field, DateTime value) {
203212
* @param field must not be {@literal null}.
204213
* @return
205214
*/
206-
private Object getDateValueToSet(DateTime value, Field field) {
215+
private Object getDateValueToSet(Calendar value, Field field) {
207216

208217
if (value == null) {
209218
return null;
210219
}
211220

212221
Class<?> targetType = field.getType();
213222

214-
if (DateTime.class.equals(targetType)) {
223+
if (Calendar.class.equals(targetType)) {
215224
return value;
216225
}
217226

218-
if (conversionService.canConvert(DateTime.class, targetType)) {
227+
if (conversionService.canConvert(Calendar.class, targetType)) {
219228
return conversionService.convert(value, targetType);
220229
}
221230

@@ -224,23 +233,23 @@ private Object getDateValueToSet(DateTime value, Field field) {
224233
}
225234
}
226235

227-
static enum DateTimeToLongConverter implements Converter<DateTime, Long> {
236+
static enum CalendarToDateTimeConverter implements Converter<Calendar, DateTime> {
228237

229238
INSTANCE;
230239

231240
@Override
232-
public Long convert(DateTime source) {
233-
return source.getMillis();
241+
public DateTime convert(Calendar source) {
242+
return new DateTime(source);
234243
}
235244
}
236245

237-
static enum DateTimeToDateConverter implements Converter<DateTime, Date> {
246+
static enum CalendarToLocalDateTimeConverter implements Converter<Calendar, LocalDateTime> {
238247

239248
INSTANCE;
240249

241250
@Override
242-
public Date convert(DateTime source) {
243-
return source.toDate();
251+
public LocalDateTime convert(Calendar source) {
252+
return new LocalDateTime(source);
244253
}
245254
}
246255
}

src/main/java/org/springframework/data/auditing/CurrentDateTimeProvider.java

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012 the original author or authors.
2+
* Copyright 2012-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,9 @@
1515
*/
1616
package org.springframework.data.auditing;
1717

18+
import java.util.Calendar;
19+
import java.util.GregorianCalendar;
20+
1821
import org.joda.time.DateTime;
1922

2023
/**
@@ -27,11 +30,12 @@ public enum CurrentDateTimeProvider implements DateTimeProvider {
2730

2831
INSTANCE;
2932

30-
/*
33+
/*
3134
* (non-Javadoc)
32-
* @see org.springframework.data.jpa.domain.support.DateTimeProvider#getDateTime()
35+
* @see org.springframework.data.auditing.DateTimeProvider#getNow()
3336
*/
34-
public DateTime getDateTime() {
35-
return new DateTime();
37+
@Override
38+
public Calendar getNow() {
39+
return new GregorianCalendar();
3640
}
3741
}

src/main/java/org/springframework/data/auditing/DateTimeProvider.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012 the original author or authors.
2+
* Copyright 2012-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,20 +15,20 @@
1515
*/
1616
package org.springframework.data.auditing;
1717

18-
import org.joda.time.DateTime;
18+
import java.util.Calendar;
1919

2020
/**
21-
* SPI to calculate the {@link DateTime} instance to be used when auditing.
21+
* SPI to calculate the current time to be used when auditing.
2222
*
2323
* @author Oliver Gierke
2424
* @since 1.5
2525
*/
2626
public interface DateTimeProvider {
2727

2828
/**
29-
* Returns the {@link DateTime} to be used as modification date.
29+
* Returns the current time to be used as modification or creation date.
3030
*
3131
* @return
3232
*/
33-
DateTime getDateTime();
33+
Calendar getNow();
3434
}

0 commit comments

Comments
 (0)