From 83a94dbf8d8ec39486287b0068b81e62bac66106 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 14 Jul 2020 09:53:22 +0200 Subject: [PATCH 1/4] DATACMNS-1231 - Prepare issue branch. --- pom.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3fdbfcb98a..65976fbb1f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,13 @@ - + 4.0.0 org.springframework.data spring-data-commons - 2.4.0-SNAPSHOT + 2.4.0-DATACMNS-1231-SNAPSHOT Spring Data Core From c235a99941908d86535b290a1b48e7d94f790c51 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 14 Jul 2020 11:21:11 +0200 Subject: [PATCH 2/4] 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. --- src/main/asciidoc/auditing.adoc | 40 +++- .../data/auditing/AuditingHandler.java | 130 +---------- .../data/auditing/AuditingHandlerSupport.java | 203 ++++++++++++++++++ .../auditing/ReactiveAuditingHandler.java | 84 ++++++++ .../ReactiveIsNewAwareAuditingHandler.java | 70 ++++++ .../data/domain/AuditorAware.java | 4 +- .../data/domain/ReactiveAuditorAware.java | 35 +++ .../ReactiveAuditingHandlerUnitTests.java | 108 ++++++++++ 8 files changed, 544 insertions(+), 130 deletions(-) create mode 100644 src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java create mode 100644 src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java create mode 100644 src/main/java/org/springframework/data/auditing/ReactiveIsNewAwareAuditingHandler.java create mode 100644 src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java create mode 100755 src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java diff --git a/src/main/asciidoc/auditing.adoc b/src/main/asciidoc/auditing.adoc index fd5fc8f455..ba571e3e86 100644 --- a/src/main/asciidoc/auditing.adoc +++ b/src/main/asciidoc/auditing.adoc @@ -41,19 +41,49 @@ In case you use either `@CreatedBy` or `@LastModifiedBy`, the auditing infrastru The following example shows an implementation of the interface that uses Spring Security's `Authentication` object: -.Implementation of AuditorAware based on Spring Security +.Implementation of `AuditorAware` based on Spring Security ==== [source, java] ---- class SpringSecurityAuditorAware implements AuditorAware { + @Override public Optional getCurrentAuditor() { return Optional.ofNullable(SecurityContextHolder.getContext()) - .map(SecurityContext::getAuthentication) - .filter(Authentication::isAuthenticated) - .map(Authentication::getPrincipal) - .map(User.class::cast); + .map(SecurityContext::getAuthentication) + .filter(Authentication::isAuthenticated) + .map(Authentication::getPrincipal) + .map(User.class::cast); + } +} +---- +==== + +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. + +[[auditing.reactive-auditor-aware]] +=== `ReactiveAuditorAware` + +When using reactive infrastructure you might want to make use of contextual information to provide `@CreatedBy` or `@LastModifiedBy` information. +We provide an `ReactiveAuditorAware` 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. + +The following example shows an implementation of the interface that uses reactive Spring Security's `Authentication` object: + +.Implementation of `ReactiveAuditorAware` based on Spring Security +==== +[source, java] +---- +class SpringSecurityAuditorAware implements ReactiveAuditorAware { + + @Override + public Mono getCurrentAuditor() { + + return ReactiveSecurityContextHolder.getContext() + .map(SecurityContext::getAuthentication) + .filter(Authentication::isAuthenticated) + .map(Authentication::getPrincipal) + .map(User.class::cast); } } ---- diff --git a/src/main/java/org/springframework/data/auditing/AuditingHandler.java b/src/main/java/org/springframework/data/auditing/AuditingHandler.java index 11c5f65ecf..44f5ad1fbf 100644 --- a/src/main/java/org/springframework/data/auditing/AuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/AuditingHandler.java @@ -15,16 +15,12 @@ */ package org.springframework.data.auditing; -import java.time.temporal.TemporalAccessor; import java.util.Optional; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.joda.time.DateTime; -import org.springframework.aop.support.AopUtils; + import org.springframework.beans.factory.InitializingBean; -import org.springframework.core.log.LogMessage; -import org.springframework.data.domain.Auditable; import org.springframework.data.domain.AuditorAware; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; @@ -39,16 +35,11 @@ * @author Christoph Strobl * @since 1.5 */ -public class AuditingHandler implements InitializingBean { +public class AuditingHandler extends AuditingHandlerSupport implements InitializingBean { private static final Log logger = LogFactory.getLog(AuditingHandler.class); - private final DefaultAuditableBeanWrapperFactory factory; - - private DateTimeProvider dateTimeProvider = CurrentDateTimeProvider.INSTANCE; private Optional> auditorAware; - private boolean dateTimeForNow = true; - private boolean modifyOnCreation = true; /** * Creates a new {@link AuditableBeanWrapper} using the given {@link MappingContext} when looking up auditing metadata @@ -73,9 +64,9 @@ public AuditingHandler( */ public AuditingHandler(PersistentEntities entities) { + super(entities); Assert.notNull(entities, "PersistentEntities must not be null!"); - this.factory = new MappingAuditableBeanWrapperFactory(entities); this.auditorAware = Optional.empty(); } @@ -90,36 +81,6 @@ public void setAuditorAware(AuditorAware auditorAware) { this.auditorAware = Optional.of(auditorAware); } - /** - * Setter do determine if {@link Auditable#setCreatedDate(DateTime)} and - * {@link Auditable#setLastModifiedDate(DateTime)} shall be filled with the current Java time. Defaults to - * {@code true}. One might set this to {@code false} to use database features to set entity time. - * - * @param dateTimeForNow the dateTimeForNow to set - */ - public void setDateTimeForNow(boolean dateTimeForNow) { - this.dateTimeForNow = dateTimeForNow; - } - - /** - * Set this to true if you want to treat entity creation as modification and thus setting the current date as - * modification date during creation, too. Defaults to {@code true}. - * - * @param modifyOnCreation if modification information shall be set on creation, too - */ - public void setModifyOnCreation(boolean modifyOnCreation) { - this.modifyOnCreation = modifyOnCreation; - } - - /** - * Sets the {@link DateTimeProvider} to be used to determine the dates to be set. - * - * @param dateTimeProvider - */ - public void setDateTimeProvider(DateTimeProvider dateTimeProvider) { - this.dateTimeProvider = dateTimeProvider == null ? CurrentDateTimeProvider.INSTANCE : dateTimeProvider; - } - /** * Marks the given object as created. * @@ -129,7 +90,7 @@ public T markCreated(T source) { Assert.notNull(source, "Entity must not be null!"); - return touch(source, true); + return markCreated(auditorAware.flatMap(AuditorAware::getCurrentAuditor).orElse(null), source); } /** @@ -141,86 +102,7 @@ public T markModified(T source) { Assert.notNull(source, "Entity must not be null!"); - return touch(source, false); - } - - /** - * Returns whether the given source is considered to be auditable in the first place - * - * @param source must not be {@literal null}. - * @return - */ - protected final boolean isAuditable(Object source) { - - Assert.notNull(source, "Source must not be null!"); - - return factory.getBeanWrapperFor(source).isPresent(); - } - - private T touch(T target, boolean isNew) { - - Optional> wrapper = factory.getBeanWrapperFor(target); - - return wrapper.map(it -> { - - Optional auditor = touchAuditor(it, isNew); - Optional now = dateTimeForNow ? touchDate(it, isNew) : Optional.empty(); - - if (logger.isDebugEnabled()) { - - Object defaultedNow = now.map(Object::toString).orElse("not set"); - Object defaultedAuditor = auditor.map(Object::toString).orElse("unknown"); - - logger.debug(LogMessage.format("Touched %s - Last modification at %s by %s", target, defaultedNow, defaultedAuditor)); - } - - return it.getBean(); - - }).orElse(target); - } - - /** - * Sets modifying and creating auditor. Creating auditor is only set on new auditables. - * - * @param auditable - * @return - */ - private Optional touchAuditor(AuditableBeanWrapper wrapper, boolean isNew) { - - Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!"); - - return auditorAware.map(it -> { - - Optional auditor = it.getCurrentAuditor(); - - Assert.notNull(auditor, - () -> String.format("Auditor must not be null! Returned by: %s!", AopUtils.getTargetClass(it))); - - auditor.filter(__ -> isNew).ifPresent(wrapper::setCreatedBy); - auditor.filter(__ -> !isNew || modifyOnCreation).ifPresent(wrapper::setLastModifiedBy); - - return auditor; - }); - } - - /** - * Touches the auditable regarding modification and creation date. Creation date is only set on new auditables. - * - * @param wrapper - * @return - */ - private Optional touchDate(AuditableBeanWrapper wrapper, boolean isNew) { - - Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!"); - - Optional now = dateTimeProvider.getNow(); - - Assert.notNull(now, () -> String.format("Now must not be null! Returned by: %s!", dateTimeProvider.getClass())); - - now.filter(__ -> isNew).ifPresent(wrapper::setCreatedDate); - now.filter(__ -> !isNew || modifyOnCreation).ifPresent(wrapper::setLastModifiedDate); - - return now; + return markModified(auditorAware.flatMap(AuditorAware::getCurrentAuditor).orElse(null), source); } /* @@ -229,6 +111,8 @@ private Optional touchDate(AuditableBeanWrapper wrapper, bo */ public void afterPropertiesSet() { + super.afterPropertiesSet(); + if (!auditorAware.isPresent()) { logger.debug("No AuditorAware set! Auditing will not be applied!"); } diff --git a/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java b/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java new file mode 100644 index 0000000000..ad9846455d --- /dev/null +++ b/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java @@ -0,0 +1,203 @@ +/* + * Copyright 2012-2020 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 + * + * https://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.time.temporal.TemporalAccessor; +import java.util.Optional; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.log.LogMessage; +import org.springframework.data.domain.Auditable; +import org.springframework.data.mapping.context.PersistentEntities; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Support class to implement auditing handlers. + * + * @author Oliver Gierke + * @author Christoph Strobl + * @author Mark Paluch + * @since 2.4 + */ +public abstract class AuditingHandlerSupport implements InitializingBean { + + private static final Log logger = LogFactory.getLog(AuditingHandlerSupport.class); + + private final DefaultAuditableBeanWrapperFactory factory; + + private DateTimeProvider dateTimeProvider = CurrentDateTimeProvider.INSTANCE; + private boolean dateTimeForNow = true; + private boolean modifyOnCreation = true; + + /** + * Creates a new {@link AuditableBeanWrapper} using the given {@link PersistentEntities} when looking up auditing + * metadata via reflection. + * + * @param entities must not be {@literal null}. + */ + public AuditingHandlerSupport(PersistentEntities entities) { + + Assert.notNull(entities, "PersistentEntities must not be null!"); + + this.factory = new MappingAuditableBeanWrapperFactory(entities); + } + + /** + * Setter do determine if {@link Auditable#setCreatedDate(TemporalAccessor)}} and + * {@link Auditable#setLastModifiedDate(TemporalAccessor)} shall be filled with the current Java time. Defaults to + * {@code true}. One might set this to {@code false} to use database features to set entity time. + * + * @param dateTimeForNow the dateTimeForNow to set + */ + public void setDateTimeForNow(boolean dateTimeForNow) { + this.dateTimeForNow = dateTimeForNow; + } + + /** + * Set this to true if you want to treat entity creation as modification and thus setting the current date as + * modification date during creation, too. Defaults to {@code true}. + * + * @param modifyOnCreation if modification information shall be set on creation, too + */ + public void setModifyOnCreation(boolean modifyOnCreation) { + this.modifyOnCreation = modifyOnCreation; + } + + /** + * Sets the {@link DateTimeProvider} to be used to determine the dates to be set. + * + * @param dateTimeProvider + */ + public void setDateTimeProvider(@Nullable DateTimeProvider dateTimeProvider) { + this.dateTimeProvider = dateTimeProvider == null ? CurrentDateTimeProvider.INSTANCE : dateTimeProvider; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() {} + + /** + * Returns whether the given source is considered to be auditable in the first place + * + * @param source must not be {@literal null}. + * @return + */ + protected final boolean isAuditable(Object source) { + + Assert.notNull(source, "Source must not be null!"); + + return factory.getBeanWrapperFor(source).isPresent(); + } + + /** + * Marks the given object as created. + * + * @param auditor + * @param source + */ + T markCreated(@Nullable Object auditor, T source) { + + Assert.notNull(source, "Entity must not be null!"); + + return touch(auditor, source, true); + } + + /** + * Marks the given object as modified. + * + * @param auditor + * @param source + */ + T markModified(@Nullable Object auditor, T source) { + + Assert.notNull(source, "Entity must not be null!"); + + return touch(auditor, source, false); + } + + private T touch(@Nullable Object auditor, T target, boolean isNew) { + + Optional> wrapper = factory.getBeanWrapperFor(target); + + return wrapper.map(it -> { + + if (auditor != null) { + touchAuditor(auditor, it, isNew); + } + Optional now = dateTimeForNow ? touchDate(it, isNew) : Optional.empty(); + + if (logger.isDebugEnabled()) { + + Object defaultedNow = now.map(Object::toString).orElse("not set"); + Object defaultedAuditor = auditor != null ? auditor.toString() : "unknown"; + + logger.debug( + LogMessage.format("Touched %s - Last modification at %s by %s", target, defaultedNow, defaultedAuditor)); + } + + return it.getBean(); + }).orElse(target); + } + + /** + * Sets modifying and creating auditor. Creating auditor is only set on new auditables. + * + * @param auditor + * @param wrapper + * @param isNew + * @return + */ + private void touchAuditor(Object auditor, AuditableBeanWrapper wrapper, boolean isNew) { + + Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!"); + Assert.notNull(auditor, "Auditor must not be null!"); + + if (isNew) { + wrapper.setCreatedBy(auditor); + } + + if (!isNew || modifyOnCreation) { + wrapper.setLastModifiedBy(auditor); + } + } + + /** + * Touches the auditable regarding modification and creation date. Creation date is only set on new auditables. + * + * @param wrapper + * @param isNew + * @return + */ + private Optional touchDate(AuditableBeanWrapper wrapper, boolean isNew) { + + Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!"); + + Optional now = dateTimeProvider.getNow(); + + Assert.notNull(now, () -> String.format("Now must not be null! Returned by: %s!", dateTimeProvider.getClass())); + + now.filter(__ -> isNew).ifPresent(wrapper::setCreatedDate); + now.filter(__ -> !isNew || modifyOnCreation).ifPresent(wrapper::setLastModifiedDate); + + return now; + } +} diff --git a/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java b/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java new file mode 100644 index 0000000000..42e48b95e6 --- /dev/null +++ b/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020 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 + * + * https://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 reactor.core.publisher.Mono; + +import java.util.Optional; + +import org.springframework.data.domain.ReactiveAuditorAware; +import org.springframework.data.mapping.context.PersistentEntities; +import org.springframework.util.Assert; + +/** + * Auditing handler to mark entity objects created and modified. + * + * @author Mark Paluch + * @since 2.4 + */ +public class ReactiveAuditingHandler extends AuditingHandlerSupport { + + private ReactiveAuditorAware auditorAware = Mono::empty; + + /** + * Creates a new {@link AuditableBeanWrapper} using the given {@link PersistentEntities} when looking up auditing + * metadata via reflection. + * + * @param entities must not be {@literal null}. + */ + public ReactiveAuditingHandler(PersistentEntities entities) { + super(entities); + } + + /** + * Setter to inject a {@link ReactiveAuditorAware} component to retrieve the current auditor. + * + * @param auditorAware must not be {@literal null}. + */ + public void setAuditorAware(ReactiveAuditorAware auditorAware) { + + Assert.notNull(auditorAware, "AuditorAware must not be null!"); + this.auditorAware = auditorAware; + } + + /** + * Marks the given object as created. + * + * @param source must not be {@literal null}. + */ + public Mono markCreated(T source) { + + Assert.notNull(source, "Entity must not be null!"); + + return auditorAware.getCurrentAuditor().map(Optional::of) // + .defaultIfEmpty(Optional.empty()) // + .map(auditor -> markCreated(auditor.orElse(null), source)); + } + + /** + * Marks the given object as modified. + * + * @param source must not be {@literal null}. + */ + public Mono markModified(T source) { + + Assert.notNull(source, "Entity must not be null!"); + + return auditorAware.getCurrentAuditor().map(Optional::of) // + .defaultIfEmpty(Optional.empty()) // + .map(auditor -> markModified(auditor.orElse(null), source)); + } +} diff --git a/src/main/java/org/springframework/data/auditing/ReactiveIsNewAwareAuditingHandler.java b/src/main/java/org/springframework/data/auditing/ReactiveIsNewAwareAuditingHandler.java new file mode 100644 index 0000000000..18e950e44b --- /dev/null +++ b/src/main/java/org/springframework/data/auditing/ReactiveIsNewAwareAuditingHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 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 + * + * https://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 reactor.core.publisher.Mono; + +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.context.PersistentEntities; +import org.springframework.data.support.IsNewStrategy; +import org.springframework.util.Assert; + +/** + * {@link AuditingHandler} extension that uses {@link PersistentEntity#isNew(Object)} to expose a generic + * {@link #markAudited(Object)} method that will route calls to {@link #markCreated(Object)} or + * {@link #markModified(Object)} based on the {@link IsNewStrategy} determined from the factory. + * + * @author Mark Paluch + * @since 2.4 + */ +public class ReactiveIsNewAwareAuditingHandler extends ReactiveAuditingHandler { + + private final PersistentEntities entities; + + /** + * Creates a new {@link ReactiveIsNewAwareAuditingHandler} for the given {@link MappingContext}. + * + * @param entities must not be {@literal null}. + */ + public ReactiveIsNewAwareAuditingHandler(PersistentEntities entities) { + + super(entities); + + this.entities = entities; + } + + /** + * Marks the given object created or modified based on {@link PersistentEntity#isNew(Object)}. Will route the calls to + * {@link #markCreated(Object)} and {@link #markModified(Object)} accordingly. + * + * @param object must not be {@literal null}. + */ + public Mono markAudited(Object object) { + + Assert.notNull(object, "Source object must not be null!"); + + if (!isAuditable(object)) { + return Mono.just(object); + } + + PersistentEntity> entity = entities + .getRequiredPersistentEntity(object.getClass()); + + return entity.isNew(object) ? markCreated(object) : markModified(object); + } +} diff --git a/src/main/java/org/springframework/data/domain/AuditorAware.java b/src/main/java/org/springframework/data/domain/AuditorAware.java index 8da971d956..86501e73d3 100644 --- a/src/main/java/org/springframework/data/domain/AuditorAware.java +++ b/src/main/java/org/springframework/data/domain/AuditorAware.java @@ -20,7 +20,7 @@ /** * Interface for components that are aware of the application's current auditor. This will be some kind of user mostly. * - * @param the type of the auditing instance + * @param the type of the auditing instance. * @author Oliver Gierke */ public interface AuditorAware { @@ -28,7 +28,7 @@ public interface AuditorAware { /** * Returns the current auditor of the application. * - * @return the current auditor + * @return the current auditor. */ Optional getCurrentAuditor(); } diff --git a/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java b/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java new file mode 100644 index 0000000000..c6f1cff69c --- /dev/null +++ b/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java @@ -0,0 +1,35 @@ +/* + * Copyright 2008-2020 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 + * + * https://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.domain; + +import reactor.core.publisher.Mono; + +/** + * Interface for components that are aware of the application's current auditor. This will be some kind of user mostly. + * + * @param the type of the auditing instance. + * @author Mark Paluch + * @since 2.4 + */ +public interface ReactiveAuditorAware { + + /** + * Returns the current auditor of the application. + * + * @return the current auditor. If the mono does not emit a value, the auditor is considered to be unknown. + */ + Mono getCurrentAuditor(); +} diff --git a/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java b/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java new file mode 100755 index 0000000000..e7bbebc6f5 --- /dev/null +++ b/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2008-2020 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 + * + * https://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 static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import lombok.Value; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.time.Instant; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.domain.ReactiveAuditorAware; +import org.springframework.data.mapping.context.PersistentEntities; +import org.springframework.data.mapping.context.SampleMappingContext; + +/** + * Unit test for {@link ReactiveAuditingHandler}. + * + * @author Mark Paluch + */ +@SuppressWarnings("unchecked") +class ReactiveAuditingHandlerUnitTests { + + ReactiveAuditingHandler handler; + ReactiveAuditorAware auditorAware; + + AuditedUser user; + + @BeforeEach + void setUp() { + + SampleMappingContext sampleMappingContext = new SampleMappingContext(); + sampleMappingContext.getRequiredPersistentEntity(Immutable.class); // initialize to ensure we're using mapping + // metadata instead of plain reflection + + handler = new ReactiveAuditingHandler(PersistentEntities.of(sampleMappingContext)); + user = new AuditedUser(); + + auditorAware = mock(ReactiveAuditorAware.class); + when(auditorAware.getCurrentAuditor()).thenReturn(Mono.just(user)); + } + + @Test + void markCreatedShouldSetDatesIfAuditorNotSet() { + + Immutable immutable = new Immutable(null, null, null, null); + + handler.markCreated(immutable).as(StepVerifier::create).consumeNextWith(actual -> { + + assertThat(actual.getCreatedDate()).isNotNull(); + assertThat(actual.getModifiedDate()).isNotNull(); + + assertThat(actual.getCreatedBy()).isNull(); + assertThat(actual.getModifiedBy()).isNull(); + }).verifyComplete(); + + assertThat(immutable.getCreatedDate()).isNull(); + } + + @Test + void markModifiedSetsModifiedFields() { + + AuditedUser audited = new AuditedUser(); + audited.id = 1L; + + handler.setAuditorAware(auditorAware); + handler.markModified(audited).as(StepVerifier::create).expectNext(audited).verifyComplete(); + + assertThat(audited.getCreatedBy()).isNotPresent(); + assertThat(audited.getCreatedDate()).isNotPresent(); + + assertThat(audited.getLastModifiedBy()).isPresent(); + assertThat(audited.getLastModifiedDate()).isPresent(); + + verify(auditorAware).getCurrentAuditor(); + } + + @Value + static class Immutable { + + @CreatedDate Instant createdDate; + @CreatedBy String createdBy; + @LastModifiedDate Instant modifiedDate; + @LastModifiedBy String modifiedBy; + } +} From 76b9826280a1e6942f645156c0e295cc569a4098 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 16 Jul 2020 12:21:09 +0200 Subject: [PATCH 3/4] DATACMNS-1231 - Introduce dedicated Auditor value object. --- .../data/auditing/AuditingHandler.java | 14 +- .../data/auditing/AuditingHandlerSupport.java | 51 ++++---- .../auditing/ReactiveAuditingHandler.java | 17 +-- .../springframework/data/domain/Auditor.java | 122 ++++++++++++++++++ .../data/domain/AuditorAware.java | 11 ++ .../data/domain/ReactiveAuditorAware.java | 14 +- .../auditing/AuditingHandlerUnitTests.java | 24 ++++ .../data/auditing/AuditorUnitTests.java | 51 ++++++++ .../ReactiveAuditingHandlerUnitTests.java | 8 +- .../ReactiveAuditorAwareUnitTests.java | 66 ++++++++++ 10 files changed, 328 insertions(+), 50 deletions(-) create mode 100644 src/main/java/org/springframework/data/domain/Auditor.java create mode 100644 src/test/java/org/springframework/data/auditing/AuditorUnitTests.java create mode 100644 src/test/java/org/springframework/data/auditing/ReactiveAuditorAwareUnitTests.java diff --git a/src/main/java/org/springframework/data/auditing/AuditingHandler.java b/src/main/java/org/springframework/data/auditing/AuditingHandler.java index 44f5ad1fbf..8a1690ae6a 100644 --- a/src/main/java/org/springframework/data/auditing/AuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/AuditingHandler.java @@ -19,8 +19,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.domain.Auditor; import org.springframework.data.domain.AuditorAware; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; @@ -90,7 +90,7 @@ public T markCreated(T source) { Assert.notNull(source, "Entity must not be null!"); - return markCreated(auditorAware.flatMap(AuditorAware::getCurrentAuditor).orElse(null), source); + return markCreated(getAuditor(), source); } /** @@ -102,7 +102,13 @@ public T markModified(T source) { Assert.notNull(source, "Entity must not be null!"); - return markModified(auditorAware.flatMap(AuditorAware::getCurrentAuditor).orElse(null), source); + return markModified(getAuditor(), source); + } + + Auditor getAuditor() { + + return auditorAware.map(AuditorAware::getAuditor) // + .orElse(Auditor.none()); } /* @@ -111,8 +117,6 @@ public T markModified(T source) { */ public void afterPropertiesSet() { - super.afterPropertiesSet(); - if (!auditorAware.isPresent()) { logger.debug("No AuditorAware set! Auditing will not be applied!"); } diff --git a/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java b/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java index ad9846455d..1e1c8b6d65 100644 --- a/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java +++ b/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java @@ -21,9 +21,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; import org.springframework.core.log.LogMessage; import org.springframework.data.domain.Auditable; +import org.springframework.data.domain.Auditor; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -36,11 +36,11 @@ * @author Mark Paluch * @since 2.4 */ -public abstract class AuditingHandlerSupport implements InitializingBean { +public abstract class AuditingHandlerSupport { private static final Log logger = LogFactory.getLog(AuditingHandlerSupport.class); - private final DefaultAuditableBeanWrapperFactory factory; + private final AuditableBeanWrapperFactory factory; private DateTimeProvider dateTimeProvider = CurrentDateTimeProvider.INSTANCE; private boolean dateTimeForNow = true; @@ -83,27 +83,21 @@ public void setModifyOnCreation(boolean modifyOnCreation) { /** * Sets the {@link DateTimeProvider} to be used to determine the dates to be set. * - * @param dateTimeProvider + * @param dateTimeProvider can be {@literal null}, defaults to {@link CurrentDateTimeProvider} in that case. */ public void setDateTimeProvider(@Nullable DateTimeProvider dateTimeProvider) { this.dateTimeProvider = dateTimeProvider == null ? CurrentDateTimeProvider.INSTANCE : dateTimeProvider; } - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - public void afterPropertiesSet() {} - /** - * Returns whether the given source is considered to be auditable in the first place + * Returns whether the given source is considered to be auditable in the first place. * * @param source must not be {@literal null}. - * @return + * @return {@literal true} if the given {@literal source} considered to be auditable. */ protected final boolean isAuditable(Object source) { - Assert.notNull(source, "Source must not be null!"); + Assert.notNull(source, "Source entity must not be null!"); return factory.getBeanWrapperFor(source).isPresent(); } @@ -111,12 +105,12 @@ protected final boolean isAuditable(Object source) { /** * Marks the given object as created. * - * @param auditor - * @param source + * @param auditor can be {@literal null}. + * @param source must not be {@literal null}. */ - T markCreated(@Nullable Object auditor, T source) { + T markCreated(Auditor auditor, T source) { - Assert.notNull(source, "Entity must not be null!"); + Assert.notNull(source, "Source entity must not be null!"); return touch(auditor, source, true); } @@ -127,28 +121,26 @@ T markCreated(@Nullable Object auditor, T source) { * @param auditor * @param source */ - T markModified(@Nullable Object auditor, T source) { + T markModified(Auditor auditor, T source) { - Assert.notNull(source, "Entity must not be null!"); + Assert.notNull(source, "Source entity must not be null!"); return touch(auditor, source, false); } - private T touch(@Nullable Object auditor, T target, boolean isNew) { + private T touch(Auditor auditor, T target, boolean isNew) { Optional> wrapper = factory.getBeanWrapperFor(target); return wrapper.map(it -> { - if (auditor != null) { - touchAuditor(auditor, it, isNew); - } + touchAuditor(auditor, it, isNew); Optional now = dateTimeForNow ? touchDate(it, isNew) : Optional.empty(); if (logger.isDebugEnabled()) { Object defaultedNow = now.map(Object::toString).orElse("not set"); - Object defaultedAuditor = auditor != null ? auditor.toString() : "unknown"; + Object defaultedAuditor = auditor.isPresent() ? auditor.toString() : "unknown"; logger.debug( LogMessage.format("Touched %s - Last modification at %s by %s", target, defaultedNow, defaultedAuditor)); @@ -166,17 +158,20 @@ private T touch(@Nullable Object auditor, T target, boolean isNew) { * @param isNew * @return */ - private void touchAuditor(Object auditor, AuditableBeanWrapper wrapper, boolean isNew) { + private void touchAuditor(Auditor auditor, AuditableBeanWrapper wrapper, boolean isNew) { + + if(!auditor.isPresent()) { + return; + } Assert.notNull(wrapper, "AuditableBeanWrapper must not be null!"); - Assert.notNull(auditor, "Auditor must not be null!"); if (isNew) { - wrapper.setCreatedBy(auditor); + wrapper.setCreatedBy(auditor.getValue()); } if (!isNew || modifyOnCreation) { - wrapper.setLastModifiedBy(auditor); + wrapper.setLastModifiedBy(auditor.getValue()); } } diff --git a/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java b/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java index 42e48b95e6..870d6b7b1a 100644 --- a/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java @@ -17,8 +17,6 @@ import reactor.core.publisher.Mono; -import java.util.Optional; - import org.springframework.data.domain.ReactiveAuditorAware; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.util.Assert; @@ -27,6 +25,7 @@ * Auditing handler to mark entity objects created and modified. * * @author Mark Paluch + * @author Christoph Strobl * @since 2.4 */ public class ReactiveAuditingHandler extends AuditingHandlerSupport { @@ -34,8 +33,8 @@ public class ReactiveAuditingHandler extends AuditingHandlerSupport { private ReactiveAuditorAware auditorAware = Mono::empty; /** - * Creates a new {@link AuditableBeanWrapper} using the given {@link PersistentEntities} when looking up auditing - * metadata via reflection. + * Creates a new {@link ReactiveAuditingHandler} using the given {@link PersistentEntities} when looking up auditing + * metadata. * * @param entities must not be {@literal null}. */ @@ -63,9 +62,8 @@ public Mono markCreated(T source) { Assert.notNull(source, "Entity must not be null!"); - return auditorAware.getCurrentAuditor().map(Optional::of) // - .defaultIfEmpty(Optional.empty()) // - .map(auditor -> markCreated(auditor.orElse(null), source)); + return auditorAware.getAuditor() // + .map(auditor -> markCreated(auditor, source)); } /** @@ -77,8 +75,7 @@ public Mono markModified(T source) { Assert.notNull(source, "Entity must not be null!"); - return auditorAware.getCurrentAuditor().map(Optional::of) // - .defaultIfEmpty(Optional.empty()) // - .map(auditor -> markModified(auditor.orElse(null), source)); + return auditorAware.getAuditor() // + .map(auditor -> markModified(auditor, source)); } } diff --git a/src/main/java/org/springframework/data/domain/Auditor.java b/src/main/java/org/springframework/data/domain/Auditor.java new file mode 100644 index 0000000000..c63bdd0835 --- /dev/null +++ b/src/main/java/org/springframework/data/domain/Auditor.java @@ -0,0 +1,122 @@ +/* + * Copyright 2020 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 + * + * https://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.domain; + +import java.util.Optional; + +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +/** + * Value Object encapsulating the actual auditor value. + * + * @author Christoph Strobl + * @since 2.4 + */ +public class Auditor { + + private static final Auditor NONE = new Auditor(null) { + + @Override + public boolean isPresent() { + return false; + } + }; + + private final @Nullable T value; + + private Auditor(@Nullable T value) { + this.value = value; + } + + /** + * @return + */ + @Nullable + public T getValue() { + return value; + } + + /** + * Create an {@link Auditor} for the given {@literal source} value.
+ * If the given {@literal source} is {@literal null} {@link Auditor#none()} is returned. A source that already is an + * {@link Auditor} gets returned as is. + * + * @param source can be {@literal null}. + * @param + * @return {@link Auditor#none()} if the given {@literal source} is {@literal null}. } + */ + public static Auditor of(@Nullable T source) { + + if (source instanceof Auditor) { + return (Auditor) source; + } + + return source == null ? Auditor.none() : new Auditor<>(source); + } + + /** + * Create an {@link Auditor} for the given {@link Optional} value.
+ * If the given {@literal source} is {@link Optional#empty()} {@link Auditor#none()} is returned. An {@link Optional} + * wrapping and {@link Auditor} returns the unwrapped {@link Auditor} instance as is. + * + * @param source must not be {@literal null}. + * @param + * @return {@link Auditor#none()} if the given {@literal source} is {@literal null}. } + */ + public static Auditor ofOptional(@Nullable Optional source) { + return Auditor.of(source.orElse(null)); + } + + /** + * Return an {@link Auditor} that is not present. + * + * @param + * @return never {@literal null}. + */ + public static Auditor none() { + return NONE; + } + + /** + * @return {@literal true} if {@link #getValue()} returns a non {@literal null} value. + */ + public boolean isPresent() { + return getValue() != null; + } + + @Override + public String toString() { + return value != null ? value.toString() : "Auditor.none()"; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Auditor auditor = (Auditor) o; + + return ObjectUtils.nullSafeEquals(value, auditor.value); + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(value); + } +} diff --git a/src/main/java/org/springframework/data/domain/AuditorAware.java b/src/main/java/org/springframework/data/domain/AuditorAware.java index 86501e73d3..f903babe0c 100644 --- a/src/main/java/org/springframework/data/domain/AuditorAware.java +++ b/src/main/java/org/springframework/data/domain/AuditorAware.java @@ -22,6 +22,7 @@ * * @param the type of the auditing instance. * @author Oliver Gierke + * @author Christoph Strobl */ public interface AuditorAware { @@ -31,4 +32,14 @@ public interface AuditorAware { * @return the current auditor. */ Optional getCurrentAuditor(); + + /** + * Returns the current {@link Auditor} of the application. + * + * @return the current {@link Auditor}, {@link Auditor#none() none} if not present. + * @since 2.4 + */ + default Auditor getAuditor() { + return Auditor.ofOptional(getCurrentAuditor()); + } } diff --git a/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java b/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java index c6f1cff69c..af7340e200 100644 --- a/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java +++ b/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2020 the original author or authors. + * Copyright 2020 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. @@ -27,9 +27,17 @@ public interface ReactiveAuditorAware { /** - * Returns the current auditor of the application. + * Returns a {@link Mono} publishing the current auditor of the application. * - * @return the current auditor. If the mono does not emit a value, the auditor is considered to be unknown. + * @return the {@link Mono} emitting the current auditor, or an empty one, if the auditor is considered to be unknown. */ Mono getCurrentAuditor(); + + default Mono> getAuditor() { + + return getCurrentAuditor() // + .map(Auditor::of) // + .defaultIfEmpty(Auditor.none()); + } + } diff --git a/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java b/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java index 20119d5aaf..556f6bf23c 100755 --- a/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java +++ b/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java @@ -28,6 +28,7 @@ import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedBy; import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.domain.Auditor; import org.springframework.data.domain.AuditorAware; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.mapping.context.SampleMappingContext; @@ -36,6 +37,7 @@ * Unit test for {@code AuditingHandler}. * * @author Oliver Gierke + * @author Christoph Strobl * @since 1.5 */ @SuppressWarnings("unchecked") @@ -54,6 +56,7 @@ void setUp() { auditorAware = mock(AuditorAware.class); when(auditorAware.getCurrentAuditor()).thenReturn(Optional.of(user)); + when(auditorAware.getAuditor()).thenCallRealMethod(); } protected AuditingHandler getHandler() { @@ -177,6 +180,27 @@ void setsAuditingInfoOnEntityUsingInheritance() { assertThat(result.modified).isNotNull(); } + @Test // DATACMNS-1231 + void getAuditorGetsAuditorNoneWhenNoAuditorAwareNotPresent() { + assertThat(handler.getAuditor()).isEqualTo(Auditor.none()); + } + + @Test // DATACMNS-1231 + void getAuditorGetsAuditorWhenPresent() { + + handler.setAuditorAware(auditorAware); + assertThat(handler.getAuditor()).isEqualTo(Auditor.of(user)); + } + + @Test // DATACMNS-1231 + void getAuditorShouldReturnNoneIfAuditorAwareDoesNotHoldObject() { + + when(auditorAware.getCurrentAuditor()).thenReturn(Optional.empty()); + + handler.setAuditorAware(auditorAware); + assertThat(handler.getAuditor()).isEqualTo(Auditor.none()); + } + static abstract class AbstractModel { @CreatedDate Instant created; diff --git a/src/test/java/org/springframework/data/auditing/AuditorUnitTests.java b/src/test/java/org/springframework/data/auditing/AuditorUnitTests.java new file mode 100644 index 0000000000..a20bfafcf3 --- /dev/null +++ b/src/test/java/org/springframework/data/auditing/AuditorUnitTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020 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 + * + * https://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 static org.assertj.core.api.Assertions.*; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Auditor; + +/** + * @author Christoph Strobl + */ +class AuditorUnitTests { + + @Test // DATACMNS-1231 + void auditorOfEmptyOptionalIsNone() { + assertThat(Auditor.ofOptional(Optional.empty())).isEqualTo(Auditor.none()); + } + + @Test // DATACMNS-1231 + void auditorOfOptionalIsValue() { + assertThat(Auditor.ofOptional(Optional.of("batman"))).isEqualTo(Auditor.of("batman")); + } + + @Test // DATACMNS-1231 + void auditorOfMustNotWrapOtherAuditor() { + + Auditor source = Auditor.of("batman"); + assertThat(Auditor.of(source)).isSameAs(source); + } + + @Test // DATACMNS-1231 + void auditorOfNullIsNone() { + assertThat(Auditor.of(null)).isEqualTo(Auditor.none()); + } +} diff --git a/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java b/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java index e7bbebc6f5..ed7fb99880 100755 --- a/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java +++ b/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2020 the original author or authors. + * Copyright 2020 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. @@ -26,7 +26,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedBy; @@ -60,9 +59,10 @@ void setUp() { auditorAware = mock(ReactiveAuditorAware.class); when(auditorAware.getCurrentAuditor()).thenReturn(Mono.just(user)); + when(auditorAware.getAuditor()).thenCallRealMethod(); } - @Test + @Test // DATACMNS-1231 void markCreatedShouldSetDatesIfAuditorNotSet() { Immutable immutable = new Immutable(null, null, null, null); @@ -79,7 +79,7 @@ void markCreatedShouldSetDatesIfAuditorNotSet() { assertThat(immutable.getCreatedDate()).isNull(); } - @Test + @Test // DATACMNS-1231 void markModifiedSetsModifiedFields() { AuditedUser audited = new AuditedUser(); diff --git a/src/test/java/org/springframework/data/auditing/ReactiveAuditorAwareUnitTests.java b/src/test/java/org/springframework/data/auditing/ReactiveAuditorAwareUnitTests.java new file mode 100644 index 0000000000..30023c290d --- /dev/null +++ b/src/test/java/org/springframework/data/auditing/ReactiveAuditorAwareUnitTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 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 + * + * https://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 reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Auditor; +import org.springframework.data.domain.ReactiveAuditorAware; +import org.springframework.lang.Nullable; + +/** + * @author Christoph Strobl + */ +class ReactiveAuditorAwareUnitTests { + + @Test // DATACMNS-1231 + void getAuditorGetsAuditorNoneWhenNoAuditorAwareNotPresent() { + + ReactiveAuditorAwareStub.of("batman").getAuditor() // + .as(StepVerifier::create) // + .expectNext(Auditor.of("batman")) // + .verifyComplete(); + } + + @Test // DATACMNS-1231 + void getAuditorShouldReturnNoneIfAuditorAwareDoesNotEmitObject() { + + ReactiveAuditorAwareStub.of(null).getAuditor() // + .as(StepVerifier::create) // + .expectNext(Auditor.none()) // + .verifyComplete(); + } + + static class ReactiveAuditorAwareStub implements ReactiveAuditorAware { + + @Nullable T value; + + private ReactiveAuditorAwareStub(@Nullable T value) { + this.value = value; + } + + static ReactiveAuditorAware of(T value) { + return new ReactiveAuditorAwareStub<>(value); + } + + @Override + public Mono getCurrentAuditor() { + return Mono.justOrEmpty(value); + } + } +} From a967c18a535acc82aa1aa40f9c1dedd3b28b9caf Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 16 Jul 2020 14:54:42 +0200 Subject: [PATCH 4/4] DATAMONGO-1231 - Move Auditor to package private usage only --- .../data/auditing/AuditingHandler.java | 3 +- .../data/auditing/AuditingHandlerSupport.java | 1 - .../data/{domain => auditing}/Auditor.java | 4 +- .../auditing/ReactiveAuditingHandler.java | 11 +++- .../data/domain/AuditorAware.java | 10 --- .../data/domain/ReactiveAuditorAware.java | 7 -- .../auditing/AuditingHandlerUnitTests.java | 2 - .../data/auditing/AuditorUnitTests.java | 1 - .../ReactiveAuditingHandlerUnitTests.java | 1 - .../ReactiveAuditorAwareUnitTests.java | 66 ------------------- 10 files changed, 12 insertions(+), 94 deletions(-) rename src/main/java/org/springframework/data/{domain => auditing}/Auditor.java (97%) delete mode 100644 src/test/java/org/springframework/data/auditing/ReactiveAuditorAwareUnitTests.java diff --git a/src/main/java/org/springframework/data/auditing/AuditingHandler.java b/src/main/java/org/springframework/data/auditing/AuditingHandler.java index 8a1690ae6a..106aaeeb60 100644 --- a/src/main/java/org/springframework/data/auditing/AuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/AuditingHandler.java @@ -20,7 +20,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; -import org.springframework.data.domain.Auditor; import org.springframework.data.domain.AuditorAware; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; @@ -107,7 +106,7 @@ public T markModified(T source) { Auditor getAuditor() { - return auditorAware.map(AuditorAware::getAuditor) // + return auditorAware.map(AuditorAware::getCurrentAuditor).map(Auditor::ofOptional) // .orElse(Auditor.none()); } diff --git a/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java b/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java index 1e1c8b6d65..bcc1bfc444 100644 --- a/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java +++ b/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java @@ -23,7 +23,6 @@ import org.springframework.core.log.LogMessage; import org.springframework.data.domain.Auditable; -import org.springframework.data.domain.Auditor; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.lang.Nullable; import org.springframework.util.Assert; diff --git a/src/main/java/org/springframework/data/domain/Auditor.java b/src/main/java/org/springframework/data/auditing/Auditor.java similarity index 97% rename from src/main/java/org/springframework/data/domain/Auditor.java rename to src/main/java/org/springframework/data/auditing/Auditor.java index c63bdd0835..67668d6bee 100644 --- a/src/main/java/org/springframework/data/domain/Auditor.java +++ b/src/main/java/org/springframework/data/auditing/Auditor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.domain; +package org.springframework.data.auditing; import java.util.Optional; @@ -26,7 +26,7 @@ * @author Christoph Strobl * @since 2.4 */ -public class Auditor { +class Auditor { private static final Auditor NONE = new Auditor(null) { diff --git a/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java b/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java index 870d6b7b1a..36e6ae67fe 100644 --- a/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java @@ -62,7 +62,7 @@ public Mono markCreated(T source) { Assert.notNull(source, "Entity must not be null!"); - return auditorAware.getAuditor() // + return getAuditor() // .map(auditor -> markCreated(auditor, source)); } @@ -75,7 +75,14 @@ public Mono markModified(T source) { Assert.notNull(source, "Entity must not be null!"); - return auditorAware.getAuditor() // + return getAuditor() // .map(auditor -> markModified(auditor, source)); } + + private Mono> getAuditor() { + + return auditorAware.getCurrentAuditor() // + .map(Auditor::of) // + .defaultIfEmpty(Auditor.none()); + } } diff --git a/src/main/java/org/springframework/data/domain/AuditorAware.java b/src/main/java/org/springframework/data/domain/AuditorAware.java index f903babe0c..03b5d8c15c 100644 --- a/src/main/java/org/springframework/data/domain/AuditorAware.java +++ b/src/main/java/org/springframework/data/domain/AuditorAware.java @@ -22,7 +22,6 @@ * * @param the type of the auditing instance. * @author Oliver Gierke - * @author Christoph Strobl */ public interface AuditorAware { @@ -33,13 +32,4 @@ public interface AuditorAware { */ Optional getCurrentAuditor(); - /** - * Returns the current {@link Auditor} of the application. - * - * @return the current {@link Auditor}, {@link Auditor#none() none} if not present. - * @since 2.4 - */ - default Auditor getAuditor() { - return Auditor.ofOptional(getCurrentAuditor()); - } } diff --git a/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java b/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java index af7340e200..91dce857db 100644 --- a/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java +++ b/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java @@ -33,11 +33,4 @@ public interface ReactiveAuditorAware { */ Mono getCurrentAuditor(); - default Mono> getAuditor() { - - return getCurrentAuditor() // - .map(Auditor::of) // - .defaultIfEmpty(Auditor.none()); - } - } diff --git a/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java b/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java index 556f6bf23c..271bcbae60 100755 --- a/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java +++ b/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java @@ -28,7 +28,6 @@ import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedBy; import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.domain.Auditor; import org.springframework.data.domain.AuditorAware; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.mapping.context.SampleMappingContext; @@ -56,7 +55,6 @@ void setUp() { auditorAware = mock(AuditorAware.class); when(auditorAware.getCurrentAuditor()).thenReturn(Optional.of(user)); - when(auditorAware.getAuditor()).thenCallRealMethod(); } protected AuditingHandler getHandler() { diff --git a/src/test/java/org/springframework/data/auditing/AuditorUnitTests.java b/src/test/java/org/springframework/data/auditing/AuditorUnitTests.java index a20bfafcf3..316e4de387 100644 --- a/src/test/java/org/springframework/data/auditing/AuditorUnitTests.java +++ b/src/test/java/org/springframework/data/auditing/AuditorUnitTests.java @@ -20,7 +20,6 @@ import java.util.Optional; import org.junit.jupiter.api.Test; -import org.springframework.data.domain.Auditor; /** * @author Christoph Strobl diff --git a/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java b/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java index ed7fb99880..f84080eabe 100755 --- a/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java +++ b/src/test/java/org/springframework/data/auditing/ReactiveAuditingHandlerUnitTests.java @@ -59,7 +59,6 @@ void setUp() { auditorAware = mock(ReactiveAuditorAware.class); when(auditorAware.getCurrentAuditor()).thenReturn(Mono.just(user)); - when(auditorAware.getAuditor()).thenCallRealMethod(); } @Test // DATACMNS-1231 diff --git a/src/test/java/org/springframework/data/auditing/ReactiveAuditorAwareUnitTests.java b/src/test/java/org/springframework/data/auditing/ReactiveAuditorAwareUnitTests.java deleted file mode 100644 index 30023c290d..0000000000 --- a/src/test/java/org/springframework/data/auditing/ReactiveAuditorAwareUnitTests.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2020 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 - * - * https://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 reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.junit.jupiter.api.Test; -import org.springframework.data.domain.Auditor; -import org.springframework.data.domain.ReactiveAuditorAware; -import org.springframework.lang.Nullable; - -/** - * @author Christoph Strobl - */ -class ReactiveAuditorAwareUnitTests { - - @Test // DATACMNS-1231 - void getAuditorGetsAuditorNoneWhenNoAuditorAwareNotPresent() { - - ReactiveAuditorAwareStub.of("batman").getAuditor() // - .as(StepVerifier::create) // - .expectNext(Auditor.of("batman")) // - .verifyComplete(); - } - - @Test // DATACMNS-1231 - void getAuditorShouldReturnNoneIfAuditorAwareDoesNotEmitObject() { - - ReactiveAuditorAwareStub.of(null).getAuditor() // - .as(StepVerifier::create) // - .expectNext(Auditor.none()) // - .verifyComplete(); - } - - static class ReactiveAuditorAwareStub implements ReactiveAuditorAware { - - @Nullable T value; - - private ReactiveAuditorAwareStub(@Nullable T value) { - this.value = value; - } - - static ReactiveAuditorAware of(T value) { - return new ReactiveAuditorAwareStub<>(value); - } - - @Override - public Mono getCurrentAuditor() { - return Mono.justOrEmpty(value); - } - } -}