1 /*
2  * Copyright 2012-2020 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  *      https://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  */

16 package org.springframework.data.auditing;
17
18 import java.time.temporal.TemporalAccessor;
19 import java.util.Optional;
20
21 import org.joda.time.DateTime;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24 import org.springframework.aop.support.AopUtils;
25 import org.springframework.beans.factory.InitializingBean;
26 import org.springframework.data.domain.Auditable;
27 import org.springframework.data.domain.AuditorAware;
28 import org.springframework.data.mapping.PersistentEntity;
29 import org.springframework.data.mapping.PersistentProperty;
30 import org.springframework.data.mapping.context.MappingContext;
31 import org.springframework.data.mapping.context.PersistentEntities;
32 import org.springframework.util.Assert;
33
34 /**
35  * Auditing handler to mark entity objects created and modified.
36  *
37  * @author Oliver Gierke
38  * @author Christoph Strobl
39  * @since 1.5
40  */

41 public class AuditingHandler implements InitializingBean {
42
43     private static final Logger LOGGER = LoggerFactory.getLogger(AuditingHandler.class);
44
45     private final DefaultAuditableBeanWrapperFactory factory;
46
47     private DateTimeProvider dateTimeProvider = CurrentDateTimeProvider.INSTANCE;
48     private Optional<AuditorAware<?>> auditorAware;
49     private boolean dateTimeForNow = true;
50     private boolean modifyOnCreation = true;
51
52     /**
53      * Creates a new {@link AuditableBeanWrapper} using the given {@link MappingContext} when looking up auditing metadata
54      * via reflection.
55      *
56      * @param mappingContext must not be {@literal null}.
57      * @since 1.8
58      * @deprecated use {@link AuditingHandler(PersistentEntities)} instead.
59      */

60     @Deprecated
61     public AuditingHandler(
62             MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext) {
63         this(PersistentEntities.of(mappingContext));
64     }
65
66     /**
67      * Creates a new {@link AuditableBeanWrapper} using the given {@link PersistentEntities} when looking up auditing
68      * metadata via reflection.
69      *
70      * @param entities must not be {@literal null}.
71      * @since 1.10
72      */

73     public AuditingHandler(PersistentEntities entities) {
74
75         Assert.notNull(entities, "PersistentEntities must not be null!");
76
77         this.factory = new MappingAuditableBeanWrapperFactory(entities);
78         this.auditorAware = Optional.empty();
79     }
80
81     /**
82      * Setter to inject a {@code AuditorAware} component to retrieve the current auditor.
83      *
84      * @param auditorAware must not be {@literal null}.
85      */

86     public void setAuditorAware(AuditorAware<?> auditorAware) {
87
88         Assert.notNull(auditorAware, "AuditorAware must not be null!");
89         this.auditorAware = Optional.of(auditorAware);
90     }
91
92     /**
93      * Setter do determine if {@link Auditable#setCreatedDate(DateTime)} and
94      * {@link Auditable#setLastModifiedDate(DateTime)} shall be filled with the current Java time. Defaults to
95      * {@code true}. One might set this to {@code false} to use database features to set entity time.
96      *
97      * @param dateTimeForNow the dateTimeForNow to set
98      */

99     public void setDateTimeForNow(boolean dateTimeForNow) {
100         this.dateTimeForNow = dateTimeForNow;
101     }
102
103     /**
104      * Set this to true if you want to treat entity creation as modification and thus setting the current date as
105      * modification date during creation, too. Defaults to {@code true}.
106      *
107      * @param modifyOnCreation if modification information shall be set on creation, too
108      */

109     public void setModifyOnCreation(boolean modifyOnCreation) {
110         this.modifyOnCreation = modifyOnCreation;
111     }
112
113     /**
114      * Sets the {@link DateTimeProvider} to be used to determine the dates to be set.
115      *
116      * @param dateTimeProvider
117      */

118     public void setDateTimeProvider(DateTimeProvider dateTimeProvider) {
119         this.dateTimeProvider = dateTimeProvider == null ? CurrentDateTimeProvider.INSTANCE : dateTimeProvider;
120     }
121
122     /**
123      * Marks the given object as created.
124      *
125      * @param source
126      */

127     public <T> T markCreated(T source) {
128
129         Assert.notNull(source, "Entity must not be null!");
130
131         return touch(source, true);
132     }
133
134     /**
135      * Marks the given object as modified.
136      *
137      * @param source
138      */

139     public <T> T markModified(T source) {
140
141         Assert.notNull(source, "Entity must not be null!");
142
143         return touch(source, false);
144     }
145
146     /**
147      * Returns whether the given source is considered to be auditable in the first place
148      *
149      * @param source must not be {@literal null}.
150      * @return
151      */

152     protected final boolean isAuditable(Object source) {
153
154         Assert.notNull(source, "Source must not be null!");
155
156         return factory.getBeanWrapperFor(source).isPresent();
157     }
158
159     private <T> T touch(T target, boolean isNew) {
160
161         Optional<AuditableBeanWrapper<T>> wrapper = factory.getBeanWrapperFor(target);
162
163         return wrapper.map(it -> {
164
165             Optional<Object> auditor = touchAuditor(it, isNew);
166             Optional<TemporalAccessor> now = dateTimeForNow ? touchDate(it, isNew) : Optional.empty();
167
168             if (LOGGER.isDebugEnabled()) {
169
170                 Object defaultedNow = now.map(Object::toString).orElse("not set");
171                 Object defaultedAuditor = auditor.map(Object::toString).orElse("unknown");
172
173                 LOGGER.debug("Touched {} - Last modification at {} by {}", target, defaultedNow, defaultedAuditor);
174             }
175
176             return it.getBean();
177
178         }).orElse(target);
179     }
180
181     /**
182      * Sets modifying and creating auditor. Creating auditor is only set on new auditables.
183      *
184      * @param auditable
185      * @return
186      */

187     private Optional<Object> touchAuditor(AuditableBeanWrapper<?> wrapper, boolean isNew) {
188
189         Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!");
190
191         return auditorAware.map(it -> {
192
193             Optional<?> auditor = it.getCurrentAuditor();
194
195             Assert.notNull(auditor,
196                     () -> String.format("Auditor must not be null! Returned by: %s!", AopUtils.getTargetClass(it)));
197
198             auditor.filter(__ -> isNew).ifPresent(foo -> wrapper.setCreatedBy(foo));
199             auditor.filter(__ -> !isNew || modifyOnCreation).ifPresent(foo -> wrapper.setLastModifiedBy(foo));
200
201             return auditor;
202         });
203     }
204
205     /**
206      * Touches the auditable regarding modification and creation date. Creation date is only set on new auditables.
207      *
208      * @param wrapper
209      * @return
210      */

211     private Optional<TemporalAccessor> touchDate(AuditableBeanWrapper<?> wrapper, boolean isNew) {
212
213         Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!");
214
215         Optional<TemporalAccessor> now = dateTimeProvider.getNow();
216
217         Assert.notNull(now, () -> String.format("Now must not be null! Returned by: %s!", dateTimeProvider.getClass()));
218
219         now.filter(__ -> isNew).ifPresent(it -> wrapper.setCreatedDate(it));
220         now.filter(__ -> !isNew || modifyOnCreation).ifPresent(it -> wrapper.setLastModifiedDate(it));
221
222         return now;
223     }
224
225     /*
226      * (non-Javadoc)
227      * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
228      */

229     public void afterPropertiesSet() {
230
231         if (!auditorAware.isPresent()) {
232             LOGGER.debug("No AuditorAware set! Auditing will not be applied!");
233         }
234     }
235 }
236