Skip to content

Commit 0c65c7b

Browse files
committed
DATACMNS-638, DATACMNS-643 - Support for JSR-310 and ThreeTen datetime types.
Extend AuditableBeanWrapper to allow access to the last modification date of a target object. Made AuditableBeanWrapperFactory an interface and renamed what’s been previously known under this name as DefaultAuditableBeanWrapperFactory. The components previously relying on a MappingContext to lookup a PersistentEntity now use PersistentEntities to be able to back a collection of MappingContexts behind that and also avoid unmanaged types to be added to the MappingContext. We now also register the JSR-310 and ThreeTen back-port converters with the ConversionService to be able to get and set auditing dates as these types.
1 parent fe49e4c commit 0c65c7b

17 files changed

+656
-313
lines changed

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

+8-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.springframework.data.annotation.CreatedDate;
2828
import org.springframework.data.annotation.LastModifiedBy;
2929
import org.springframework.data.annotation.LastModifiedDate;
30+
import org.springframework.data.convert.Jsr310Converters;
31+
import org.springframework.data.convert.ThreeTenBackPortConverters;
3032
import org.springframework.data.util.ReflectionUtils;
3133
import org.springframework.data.util.ReflectionUtils.AnnotationFieldFilter;
3234
import org.springframework.util.Assert;
@@ -93,22 +95,23 @@ private AnnotationAuditingMetadata(Class<?> type) {
9395
/**
9496
* Checks whether the given field has a type that is a supported date type.
9597
*
96-
* @param field
98+
* @param field can be {@literal null}.
9799
*/
98100
private void assertValidDateFieldType(Field field) {
99101

100102
if (field == null || SUPPORTED_DATE_TYPES.contains(field.getType().getName())) {
101103
return;
102104
}
103105

104-
// Support JDK 8 date types if runtime allows
105-
if (IS_JDK_8 && field.getType().getPackage().getName().startsWith(JDK8_TIME_PACKAGE_PREFIX)) {
106+
Class<?> type = field.getType();
107+
108+
if (Jsr310Converters.supports(type) || ThreeTenBackPortConverters.supports(type)) {
106109
return;
107110
}
108111

109112
throw new IllegalStateException(String.format(
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));
113+
"Found created/modified date field with type %s but only %s as well as java.time types are supported!", type,
114+
SUPPORTED_DATE_TYPES));
112115
}
113116

114117
/**

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 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.
@@ -46,6 +46,14 @@ public interface AuditableBeanWrapper {
4646
*/
4747
void setLastModifiedBy(Object value);
4848

49+
/**
50+
* Returns the date of the last modification date of the backing bean.
51+
*
52+
* @return the date of the last modification, can be {@literal null}.
53+
* @since 1.10
54+
*/
55+
Calendar getLastModifiedDate();
56+
4957
/**
5058
* Set the last modification date.
5159
*
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2015 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,256 +15,20 @@
1515
*/
1616
package org.springframework.data.auditing;
1717

18-
import java.lang.reflect.Field;
19-
import java.util.Calendar;
20-
21-
import org.joda.time.DateTime;
22-
import org.joda.time.LocalDateTime;
23-
import org.springframework.core.convert.ConversionService;
24-
import org.springframework.core.convert.converter.Converter;
25-
import org.springframework.data.domain.Auditable;
26-
import org.springframework.data.util.ReflectionUtils;
27-
import org.springframework.format.support.DefaultFormattingConversionService;
28-
import org.springframework.util.Assert;
29-
import org.springframework.util.ClassUtils;
30-
3118
/**
32-
* A factory class to {@link AuditableBeanWrapper} instances.
19+
* A factory to lookup {@link AuditableBeanWrapper}s.
3320
*
3421
* @author Oliver Gierke
35-
* @since 1.5
22+
* @since 1.10
3623
*/
37-
class AuditableBeanWrapperFactory {
38-
39-
/**
40-
* Returns an {@link AuditableBeanWrapper} if the given object is capable of being equipped with auditing information.
41-
*
42-
* @param source the auditing candidate.
43-
* @return
44-
*/
45-
@SuppressWarnings("unchecked")
46-
public AuditableBeanWrapper getBeanWrapperFor(Object source) {
47-
48-
if (source == null) {
49-
return null;
50-
}
51-
52-
if (source instanceof Auditable) {
53-
return new AuditableInterfaceBeanWrapper((Auditable<Object, ?>) source);
54-
}
55-
56-
AnnotationAuditingMetadata metadata = AnnotationAuditingMetadata.getMetadata(source.getClass());
57-
58-
if (metadata.isAuditable()) {
59-
return new ReflectionAuditingBeanWrapper(source);
60-
}
61-
62-
return null;
63-
}
64-
65-
/**
66-
* An {@link AuditableBeanWrapper} that works with objects implementing
67-
*
68-
* @author Oliver Gierke
69-
*/
70-
static class AuditableInterfaceBeanWrapper implements AuditableBeanWrapper {
71-
72-
private final Auditable<Object, ?> auditable;
73-
74-
public AuditableInterfaceBeanWrapper(Auditable<Object, ?> auditable) {
75-
this.auditable = auditable;
76-
}
77-
78-
/*
79-
* (non-Javadoc)
80-
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedBy(java.lang.Object)
81-
*/
82-
public void setCreatedBy(Object value) {
83-
auditable.setCreatedBy(value);
84-
}
85-
86-
/*
87-
* (non-Javadoc)
88-
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(org.joda.time.DateTime)
89-
*/
90-
public void setCreatedDate(Calendar value) {
91-
auditable.setCreatedDate(new DateTime(value));
92-
}
93-
94-
/*
95-
* (non-Javadoc)
96-
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedBy(java.lang.Object)
97-
*/
98-
public void setLastModifiedBy(Object value) {
99-
auditable.setLastModifiedBy(value);
100-
}
101-
102-
/*
103-
* (non-Javadoc)
104-
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(org.joda.time.DateTime)
105-
*/
106-
public void setLastModifiedDate(Calendar value) {
107-
auditable.setLastModifiedDate(new DateTime(value));
108-
}
109-
}
24+
public interface AuditableBeanWrapperFactory {
11025

11126
/**
112-
* Base class for {@link AuditableBeanWrapper} implementations that might need to convert {@link Calendar} values into
113-
* compatible types when setting date/time information.
27+
* Returns the {@link AuditableBeanWrapper} for the given source obejct if it's eligible for auditing.
11428
*
115-
* @author Oliver Gierke
116-
* @since 1.8
29+
* @param source can be {@literal null}.
30+
* @return the {@link AuditableBeanWrapper} for the given source obejct if it's eligible for auditing or
31+
* {@literal null} otherwise.
11732
*/
118-
abstract static class DateConvertingAuditableBeanWrapper implements AuditableBeanWrapper {
119-
120-
private static final boolean IS_JODA_TIME_PRESENT = ClassUtils.isPresent("org.joda.time.DateTime",
121-
ReflectionAuditingBeanWrapper.class.getClassLoader());
122-
123-
private final ConversionService conversionService;
124-
125-
/**
126-
* Creates a new {@link DateConvertingAuditableBeanWrapper}.
127-
*/
128-
public DateConvertingAuditableBeanWrapper() {
129-
130-
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
131-
132-
if (IS_JODA_TIME_PRESENT) {
133-
conversionService.addConverter(CalendarToDateTimeConverter.INSTANCE);
134-
conversionService.addConverter(CalendarToLocalDateTimeConverter.INSTANCE);
135-
}
136-
137-
this.conversionService = conversionService;
138-
}
139-
140-
/**
141-
* Returns the {@link Calendar} in a type, compatible to the given field.
142-
*
143-
* @param value can be {@literal null}.
144-
* @param targetType must not be {@literal null}.
145-
* @param source must not be {@literal null}.
146-
* @return
147-
*/
148-
protected Object getDateValueToSet(Calendar value, Class<?> targetType, Object source) {
149-
150-
if (value == null) {
151-
return null;
152-
}
153-
154-
if (Calendar.class.equals(targetType)) {
155-
return value;
156-
}
157-
158-
if (conversionService.canConvert(Calendar.class, targetType)) {
159-
return conversionService.convert(value, targetType);
160-
}
161-
162-
throw new IllegalArgumentException(String.format("Invalid date type for member %s! Supported types are %s.",
163-
source, AnnotationAuditingMetadata.SUPPORTED_DATE_TYPES));
164-
}
165-
}
166-
167-
/**
168-
* An {@link AuditableBeanWrapper} implementation that sets values on the target object using refelction.
169-
*
170-
* @author Oliver Gierke
171-
*/
172-
static class ReflectionAuditingBeanWrapper extends DateConvertingAuditableBeanWrapper {
173-
174-
private final AnnotationAuditingMetadata metadata;
175-
private final Object target;
176-
177-
/**
178-
* Creates a new {@link ReflectionAuditingBeanWrapper} to set auditing data on the given target object.
179-
*
180-
* @param target must not be {@literal null}.
181-
*/
182-
public ReflectionAuditingBeanWrapper(Object target) {
183-
184-
Assert.notNull(target, "Target object must not be null!");
185-
186-
this.metadata = AnnotationAuditingMetadata.getMetadata(target.getClass());
187-
this.target = target;
188-
}
189-
190-
/*
191-
* (non-Javadoc)
192-
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedBy(java.lang.Object)
193-
*/
194-
public void setCreatedBy(Object value) {
195-
setField(metadata.getCreatedByField(), value);
196-
}
197-
198-
/*
199-
* (non-Javadoc)
200-
* @see org.springframework.data.auditing.AuditableBeanWrapper#setCreatedDate(java.util.Calendar)
201-
*/
202-
public void setCreatedDate(Calendar value) {
203-
setDateField(metadata.getCreatedDateField(), value);
204-
}
205-
206-
/*
207-
* (non-Javadoc)
208-
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedBy(java.lang.Object)
209-
*/
210-
public void setLastModifiedBy(Object value) {
211-
setField(metadata.getLastModifiedByField(), value);
212-
}
213-
214-
/*
215-
* (non-Javadoc)
216-
* @see org.springframework.data.auditing.AuditableBeanWrapper#setLastModifiedDate(java.util.Calendar)
217-
*/
218-
public void setLastModifiedDate(Calendar value) {
219-
setDateField(metadata.getLastModifiedDateField(), value);
220-
}
221-
222-
/**
223-
* Sets the given field to the given value if the field is not {@literal null}.
224-
*
225-
* @param field
226-
* @param value
227-
*/
228-
private void setField(Field field, Object value) {
229-
230-
if (field != null) {
231-
ReflectionUtils.setField(field, target, value);
232-
}
233-
}
234-
235-
/**
236-
* Sets the given field to the given value if the field is not {@literal null}.
237-
*
238-
* @param field
239-
* @param value
240-
*/
241-
private void setDateField(Field field, Calendar value) {
242-
243-
if (field == null) {
244-
return;
245-
}
246-
247-
ReflectionUtils.setField(field, target, getDateValueToSet(value, field.getType(), field));
248-
}
249-
}
250-
251-
private static enum CalendarToDateTimeConverter implements Converter<Calendar, DateTime> {
252-
253-
INSTANCE;
254-
255-
@Override
256-
public DateTime convert(Calendar source) {
257-
return new DateTime(source);
258-
}
259-
}
260-
261-
private static enum CalendarToLocalDateTimeConverter implements Converter<Calendar, LocalDateTime> {
262-
263-
INSTANCE;
264-
265-
@Override
266-
public LocalDateTime convert(Calendar source) {
267-
return new LocalDateTime(source);
268-
}
269-
}
33+
AuditableBeanWrapper getBeanWrapperFor(Object source);
27034
}

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

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 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,7 @@
1515
*/
1616
package org.springframework.data.auditing;
1717

18+
import java.util.Arrays;
1819
import java.util.Calendar;
1920

2021
import org.joda.time.DateTime;
@@ -26,6 +27,7 @@
2627
import org.springframework.data.mapping.PersistentEntity;
2728
import org.springframework.data.mapping.PersistentProperty;
2829
import org.springframework.data.mapping.context.MappingContext;
30+
import org.springframework.data.mapping.context.PersistentEntities;
2931
import org.springframework.util.Assert;
3032

3133
/**
@@ -38,7 +40,7 @@ public class AuditingHandler implements InitializingBean {
3840

3941
private static final Logger LOGGER = LoggerFactory.getLogger(AuditingHandler.class);
4042

41-
private final AuditableBeanWrapperFactory factory;
43+
private final DefaultAuditableBeanWrapperFactory factory;
4244

4345
private DateTimeProvider dateTimeProvider = CurrentDateTimeProvider.INSTANCE;
4446
private AuditorAware<?> auditorAware;
@@ -51,12 +53,26 @@ public class AuditingHandler implements InitializingBean {
5153
*
5254
* @param mappingContext must not be {@literal null}.
5355
* @since 1.8
56+
* @deprecated use {@link AuditingHandler(PersistentEntities)} instead.
5457
*/
58+
@Deprecated
59+
@SuppressWarnings("unchecked")
5560
public AuditingHandler(
5661
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext) {
62+
this(new PersistentEntities(Arrays.asList(mappingContext)));
63+
}
64+
65+
/**
66+
* Creates a new {@link AuditableBeanWrapper} using the given {@link PersistentEntities} when looking up auditing
67+
* metadata via reflection.
68+
*
69+
* @param entities must not be {@literal null}.
70+
* @since 1.10
71+
*/
72+
public AuditingHandler(PersistentEntities entities) {
5773

58-
Assert.notNull(mappingContext, "MappingContext must not be null!");
59-
this.factory = new MappingAuditableBeanWrapperFactory(mappingContext);
74+
Assert.notNull(entities, "PersistentEntities must not be null!");
75+
this.factory = new MappingAuditableBeanWrapperFactory(entities);
6076
}
6177

6278
/**

0 commit comments

Comments
 (0)