Skip to content

Commit d3af4a7

Browse files
mp911dechristophstrobl
authored andcommitted
DATACMNS-1231 - Add reactive auditing infrastructure.
We now provide a reactive variant for auditing with ReactiveAuditingHandler and ReactiveIsNewAwareAuditingHandler. Extracted common auditing functionality into AuditingHandlerSupport which serves as base class for AuditingHandler and ReactiveAuditingHandler. Original Pull Request: #458
1 parent 0c37484 commit d3af4a7

File tree

8 files changed

+544
-130
lines changed

8 files changed

+544
-130
lines changed

src/main/asciidoc/auditing.adoc

+35-5
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,49 @@ In case you use either `@CreatedBy` or `@LastModifiedBy`, the auditing infrastru
4141

4242
The following example shows an implementation of the interface that uses Spring Security's `Authentication` object:
4343

44-
.Implementation of AuditorAware based on Spring Security
44+
.Implementation of `AuditorAware` based on Spring Security
4545
====
4646
[source, java]
4747
----
4848
class SpringSecurityAuditorAware implements AuditorAware<User> {
4949
50+
@Override
5051
public Optional<User> getCurrentAuditor() {
5152
5253
return Optional.ofNullable(SecurityContextHolder.getContext())
53-
.map(SecurityContext::getAuthentication)
54-
.filter(Authentication::isAuthenticated)
55-
.map(Authentication::getPrincipal)
56-
.map(User.class::cast);
54+
.map(SecurityContext::getAuthentication)
55+
.filter(Authentication::isAuthenticated)
56+
.map(Authentication::getPrincipal)
57+
.map(User.class::cast);
58+
}
59+
}
60+
----
61+
====
62+
63+
The implementation accesses the `Authentication` object provided by Spring Security and looks up the custom `UserDetails` instance that you have created in your `UserDetailsService` implementation. We assume here that you are exposing the domain user through the `UserDetails` implementation but that, based on the `Authentication` found, you could also look it up from anywhere.
64+
65+
[[auditing.reactive-auditor-aware]]
66+
=== `ReactiveAuditorAware`
67+
68+
When using reactive infrastructure you might want to make use of contextual information to provide `@CreatedBy` or `@LastModifiedBy` information.
69+
We provide an `ReactiveAuditorAware<T>` SPI interface that you have to implement to tell the infrastructure who the current user or system interacting with the application is. The generic type `T` defines what type the properties annotated with `@CreatedBy` or `@LastModifiedBy` have to be.
70+
71+
The following example shows an implementation of the interface that uses reactive Spring Security's `Authentication` object:
72+
73+
.Implementation of `ReactiveAuditorAware` based on Spring Security
74+
====
75+
[source, java]
76+
----
77+
class SpringSecurityAuditorAware implements ReactiveAuditorAware<User> {
78+
79+
@Override
80+
public Mono<User> getCurrentAuditor() {
81+
82+
return ReactiveSecurityContextHolder.getContext()
83+
.map(SecurityContext::getAuthentication)
84+
.filter(Authentication::isAuthenticated)
85+
.map(Authentication::getPrincipal)
86+
.map(User.class::cast);
5787
}
5888
}
5989
----

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

+7-123
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,12 @@
1515
*/
1616
package org.springframework.data.auditing;
1717

18-
import java.time.temporal.TemporalAccessor;
1918
import java.util.Optional;
2019

2120
import org.apache.commons.logging.Log;
2221
import org.apache.commons.logging.LogFactory;
23-
import org.joda.time.DateTime;
24-
import org.springframework.aop.support.AopUtils;
22+
2523
import org.springframework.beans.factory.InitializingBean;
26-
import org.springframework.core.log.LogMessage;
27-
import org.springframework.data.domain.Auditable;
2824
import org.springframework.data.domain.AuditorAware;
2925
import org.springframework.data.mapping.PersistentEntity;
3026
import org.springframework.data.mapping.PersistentProperty;
@@ -39,16 +35,11 @@
3935
* @author Christoph Strobl
4036
* @since 1.5
4137
*/
42-
public class AuditingHandler implements InitializingBean {
38+
public class AuditingHandler extends AuditingHandlerSupport implements InitializingBean {
4339

4440
private static final Log logger = LogFactory.getLog(AuditingHandler.class);
4541

46-
private final DefaultAuditableBeanWrapperFactory factory;
47-
48-
private DateTimeProvider dateTimeProvider = CurrentDateTimeProvider.INSTANCE;
4942
private Optional<AuditorAware<?>> auditorAware;
50-
private boolean dateTimeForNow = true;
51-
private boolean modifyOnCreation = true;
5243

5344
/**
5445
* Creates a new {@link AuditableBeanWrapper} using the given {@link MappingContext} when looking up auditing metadata
@@ -73,9 +64,9 @@ public AuditingHandler(
7364
*/
7465
public AuditingHandler(PersistentEntities entities) {
7566

67+
super(entities);
7668
Assert.notNull(entities, "PersistentEntities must not be null!");
7769

78-
this.factory = new MappingAuditableBeanWrapperFactory(entities);
7970
this.auditorAware = Optional.empty();
8071
}
8172

@@ -90,36 +81,6 @@ public void setAuditorAware(AuditorAware<?> auditorAware) {
9081
this.auditorAware = Optional.of(auditorAware);
9182
}
9283

93-
/**
94-
* Setter do determine if {@link Auditable#setCreatedDate(DateTime)} and
95-
* {@link Auditable#setLastModifiedDate(DateTime)} shall be filled with the current Java time. Defaults to
96-
* {@code true}. One might set this to {@code false} to use database features to set entity time.
97-
*
98-
* @param dateTimeForNow the dateTimeForNow to set
99-
*/
100-
public void setDateTimeForNow(boolean dateTimeForNow) {
101-
this.dateTimeForNow = dateTimeForNow;
102-
}
103-
104-
/**
105-
* Set this to true if you want to treat entity creation as modification and thus setting the current date as
106-
* modification date during creation, too. Defaults to {@code true}.
107-
*
108-
* @param modifyOnCreation if modification information shall be set on creation, too
109-
*/
110-
public void setModifyOnCreation(boolean modifyOnCreation) {
111-
this.modifyOnCreation = modifyOnCreation;
112-
}
113-
114-
/**
115-
* Sets the {@link DateTimeProvider} to be used to determine the dates to be set.
116-
*
117-
* @param dateTimeProvider
118-
*/
119-
public void setDateTimeProvider(DateTimeProvider dateTimeProvider) {
120-
this.dateTimeProvider = dateTimeProvider == null ? CurrentDateTimeProvider.INSTANCE : dateTimeProvider;
121-
}
122-
12384
/**
12485
* Marks the given object as created.
12586
*
@@ -129,7 +90,7 @@ public <T> T markCreated(T source) {
12990

13091
Assert.notNull(source, "Entity must not be null!");
13192

132-
return touch(source, true);
93+
return markCreated(auditorAware.flatMap(AuditorAware::getCurrentAuditor).orElse(null), source);
13394
}
13495

13596
/**
@@ -141,86 +102,7 @@ public <T> T markModified(T source) {
141102

142103
Assert.notNull(source, "Entity must not be null!");
143104

144-
return touch(source, false);
145-
}
146-
147-
/**
148-
* Returns whether the given source is considered to be auditable in the first place
149-
*
150-
* @param source must not be {@literal null}.
151-
* @return
152-
*/
153-
protected final boolean isAuditable(Object source) {
154-
155-
Assert.notNull(source, "Source must not be null!");
156-
157-
return factory.getBeanWrapperFor(source).isPresent();
158-
}
159-
160-
private <T> T touch(T target, boolean isNew) {
161-
162-
Optional<AuditableBeanWrapper<T>> wrapper = factory.getBeanWrapperFor(target);
163-
164-
return wrapper.map(it -> {
165-
166-
Optional<Object> auditor = touchAuditor(it, isNew);
167-
Optional<TemporalAccessor> now = dateTimeForNow ? touchDate(it, isNew) : Optional.empty();
168-
169-
if (logger.isDebugEnabled()) {
170-
171-
Object defaultedNow = now.map(Object::toString).orElse("not set");
172-
Object defaultedAuditor = auditor.map(Object::toString).orElse("unknown");
173-
174-
logger.debug(LogMessage.format("Touched %s - Last modification at %s by %s", target, defaultedNow, defaultedAuditor));
175-
}
176-
177-
return it.getBean();
178-
179-
}).orElse(target);
180-
}
181-
182-
/**
183-
* Sets modifying and creating auditor. Creating auditor is only set on new auditables.
184-
*
185-
* @param auditable
186-
* @return
187-
*/
188-
private Optional<Object> touchAuditor(AuditableBeanWrapper<?> wrapper, boolean isNew) {
189-
190-
Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!");
191-
192-
return auditorAware.map(it -> {
193-
194-
Optional<?> auditor = it.getCurrentAuditor();
195-
196-
Assert.notNull(auditor,
197-
() -> String.format("Auditor must not be null! Returned by: %s!", AopUtils.getTargetClass(it)));
198-
199-
auditor.filter(__ -> isNew).ifPresent(wrapper::setCreatedBy);
200-
auditor.filter(__ -> !isNew || modifyOnCreation).ifPresent(wrapper::setLastModifiedBy);
201-
202-
return auditor;
203-
});
204-
}
205-
206-
/**
207-
* Touches the auditable regarding modification and creation date. Creation date is only set on new auditables.
208-
*
209-
* @param wrapper
210-
* @return
211-
*/
212-
private Optional<TemporalAccessor> touchDate(AuditableBeanWrapper<?> wrapper, boolean isNew) {
213-
214-
Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!");
215-
216-
Optional<TemporalAccessor> now = dateTimeProvider.getNow();
217-
218-
Assert.notNull(now, () -> String.format("Now must not be null! Returned by: %s!", dateTimeProvider.getClass()));
219-
220-
now.filter(__ -> isNew).ifPresent(wrapper::setCreatedDate);
221-
now.filter(__ -> !isNew || modifyOnCreation).ifPresent(wrapper::setLastModifiedDate);
222-
223-
return now;
105+
return markModified(auditorAware.flatMap(AuditorAware::getCurrentAuditor).orElse(null), source);
224106
}
225107

226108
/*
@@ -229,6 +111,8 @@ private Optional<TemporalAccessor> touchDate(AuditableBeanWrapper<?> wrapper, bo
229111
*/
230112
public void afterPropertiesSet() {
231113

114+
super.afterPropertiesSet();
115+
232116
if (!auditorAware.isPresent()) {
233117
logger.debug("No AuditorAware set! Auditing will not be applied!");
234118
}

0 commit comments

Comments
 (0)