From d4e94109dfddea12f65cbb151f82b43eaac4d4de Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 16 Jul 2020 12:21:09 +0200 Subject: [PATCH] DATACMNS-1231 - Introduce dedicated Auditor value object. Original Pull Request: #458 --- .../data/auditing/AuditingHandler.java | 13 +- .../data/auditing/AuditingHandlerSupport.java | 50 ++++--- .../data/auditing/Auditor.java | 122 ++++++++++++++++++ .../auditing/ReactiveAuditingHandler.java | 24 ++-- .../data/domain/ReactiveAuditorAware.java | 7 +- .../auditing/AuditingHandlerUnitTests.java | 22 ++++ .../data/auditing/AuditorUnitTests.java | 50 +++++++ .../ReactiveAuditingHandlerUnitTests.java | 7 +- 8 files changed, 245 insertions(+), 50 deletions(-) create mode 100644 src/main/java/org/springframework/data/auditing/Auditor.java create mode 100644 src/test/java/org/springframework/data/auditing/AuditorUnitTests.java diff --git a/src/main/java/org/springframework/data/auditing/AuditingHandler.java b/src/main/java/org/springframework/data/auditing/AuditingHandler.java index 44f5ad1fb..106aaeeb6 100644 --- a/src/main/java/org/springframework/data/auditing/AuditingHandler.java +++ b/src/main/java/org/springframework/data/auditing/AuditingHandler.java @@ -19,7 +19,6 @@ 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.data.domain.AuditorAware; import org.springframework.data.mapping.PersistentEntity; @@ -90,7 +89,7 @@ public class AuditingHandler extends AuditingHandlerSupport implements Initializ Assert.notNull(source, "Entity must not be null!"); - return markCreated(auditorAware.flatMap(AuditorAware::getCurrentAuditor).orElse(null), source); + return markCreated(getAuditor(), source); } /** @@ -102,7 +101,13 @@ public class AuditingHandler extends AuditingHandlerSupport implements Initializ 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::getCurrentAuditor).map(Auditor::ofOptional) // + .orElse(Auditor.none()); } /* @@ -111,8 +116,6 @@ public class AuditingHandler extends AuditingHandlerSupport implements Initializ */ 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 ad9846455..bcc1bfc44 100644 --- a/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java +++ b/src/main/java/org/springframework/data/auditing/AuditingHandlerSupport.java @@ -21,7 +21,6 @@ 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; @@ -36,11 +35,11 @@ import org.springframework.util.Assert; * @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 +82,21 @@ public abstract class AuditingHandlerSupport implements InitializingBean { /** * 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 +104,12 @@ public abstract class AuditingHandlerSupport implements InitializingBean { /** * 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 +120,26 @@ public abstract class AuditingHandlerSupport implements InitializingBean { * @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 +157,20 @@ public abstract class AuditingHandlerSupport implements InitializingBean { * @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/Auditor.java b/src/main/java/org/springframework/data/auditing/Auditor.java new file mode 100644 index 000000000..67668d6be --- /dev/null +++ b/src/main/java/org/springframework/data/auditing/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.auditing; + +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 + */ +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/auditing/ReactiveAuditingHandler.java b/src/main/java/org/springframework/data/auditing/ReactiveAuditingHandler.java index 42e48b95e..36e6ae67f 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 @@ 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; @@ -27,6 +25,7 @@ import org.springframework.util.Assert; * 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 class ReactiveAuditingHandler extends AuditingHandlerSupport { 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 getAuditor() // + .map(auditor -> markCreated(auditor, source)); } /** @@ -77,8 +75,14 @@ public class ReactiveAuditingHandler extends AuditingHandlerSupport { 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 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/ReactiveAuditorAware.java b/src/main/java/org/springframework/data/domain/ReactiveAuditorAware.java index c6f1cff69..91dce857d 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,10 @@ import reactor.core.publisher.Mono; 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(); + } diff --git a/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java b/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java index 20119d5aa..271bcbae6 100755 --- a/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java +++ b/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java @@ -36,6 +36,7 @@ import org.springframework.data.mapping.context.SampleMappingContext; * Unit test for {@code AuditingHandler}. * * @author Oliver Gierke + * @author Christoph Strobl * @since 1.5 */ @SuppressWarnings("unchecked") @@ -177,6 +178,27 @@ class AuditingHandlerUnitTests { 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 000000000..316e4de38 --- /dev/null +++ b/src/test/java/org/springframework/data/auditing/AuditorUnitTests.java @@ -0,0 +1,50 @@ +/* + * 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; + +/** + * @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 e7bbebc6f..f84080eab 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 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; @@ -62,7 +61,7 @@ class ReactiveAuditingHandlerUnitTests { when(auditorAware.getCurrentAuditor()).thenReturn(Mono.just(user)); } - @Test + @Test // DATACMNS-1231 void markCreatedShouldSetDatesIfAuditorNotSet() { Immutable immutable = new Immutable(null, null, null, null); @@ -79,7 +78,7 @@ class ReactiveAuditingHandlerUnitTests { assertThat(immutable.getCreatedDate()).isNull(); } - @Test + @Test // DATACMNS-1231 void markModifiedSetsModifiedFields() { AuditedUser audited = new AuditedUser();