From 53eea0202aa921118b9b2536f36ef8706ca0e5a9 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 17 Apr 2018 12:02:57 +0200 Subject: [PATCH] DATAJDBC-204 - Polishing. Extracted ApplicationContext and Repository construction into method in order to make the actual test stand out more. Split a test in two. JavaDoc. Code formatting. Removed access modifiers in tests. --- .../support/JdbcAuditingEventListener.java | 23 +- .../repository/config/EnableJdbcAuditing.java | 16 -- .../config/JdbcAuditingRegistrar.java | 22 +- ...nableJdbcAuditingHsqlIntegrationTests.java | 260 +++++++++++------- 4 files changed, 179 insertions(+), 142 deletions(-) diff --git a/src/main/java/org/springframework/data/jdbc/domain/support/JdbcAuditingEventListener.java b/src/main/java/org/springframework/data/jdbc/domain/support/JdbcAuditingEventListener.java index f500e9b62..bdb31ee21 100644 --- a/src/main/java/org/springframework/data/jdbc/domain/support/JdbcAuditingEventListener.java +++ b/src/main/java/org/springframework/data/jdbc/domain/support/JdbcAuditingEventListener.java @@ -19,30 +19,22 @@ import org.springframework.beans.factory.ObjectFactory; import org.springframework.context.ApplicationListener; import org.springframework.data.auditing.AuditingHandler; import org.springframework.data.jdbc.mapping.event.BeforeSaveEvent; +import org.springframework.data.jdbc.repository.config.EnableJdbcAuditing; import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** * Spring JDBC event listener to capture auditing information on persisting and updating entities. *

- * You can enable this class just a matter of activating auditing using {@link org.springframework.data.jdbc.repository.config.EnableJdbcAuditing} in your Spring config: - * - *

- * @Configuration
- * @EnableJdbcRepositories
- * @EnableJdbcAuditing
- * class JdbcRepositoryConfig {
- * }
- * 
+ * An instance of this class gets registered when you apply {@link EnableJdbcAuditing} to your Spring config. * * @author Kazuki Shimizu - * @see org.springframework.data.jdbc.repository.config.EnableJdbcAuditing + * @see EnableJdbcAuditing * @since 1.0 */ public class JdbcAuditingEventListener implements ApplicationListener { - @Nullable - private AuditingHandler handler; + @Nullable private AuditingHandler handler; /** * Configures the {@link AuditingHandler} to be used to set the current auditor on the domain types touched. @@ -50,18 +42,24 @@ public class JdbcAuditingEventListener implements ApplicationListener auditingHandler) { + Assert.notNull(auditingHandler, "AuditingHandler must not be null!"); + this.handler = auditingHandler.getObject(); } /** * {@inheritDoc} + * * @param event a notification event for indicating before save */ @Override public void onApplicationEvent(BeforeSaveEvent event) { + if (handler != null) { + event.getOptionalEntity().ifPresent(entity -> { + if (event.getId().getOptionalValue().isPresent()) { handler.markModified(entity); } else { @@ -70,5 +68,4 @@ public class JdbcAuditingEventListener implements ApplicationListener - * @Configuration - * @EnableJdbcRepositories - * @EnableJdbcAuditing - * class JdbcRepositoryConfig { - * } - * - * - *

- * Note: This feature cannot use to a entity that implements {@link org.springframework.data.domain.Auditable} - * because the Spring Data JDBC does not support an {@link java.util.Optional} property yet. - *

- * * @see EnableJdbcRepositories * @author Kazuki Shimizu * @since 1.0 diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java index a734592ff..41e6a87a8 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java +++ b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java @@ -27,7 +27,8 @@ import org.springframework.data.config.ParsingUtils; import org.springframework.data.jdbc.domain.support.JdbcAuditingEventListener; /** - * {@link ImportBeanDefinitionRegistrar} to enable {@link EnableJdbcAuditing} annotation. + * {@link ImportBeanDefinitionRegistrar} which registers additional beans in order to enable auditing via the + * {@link EnableJdbcAuditing} annotation. * * @see EnableJdbcAuditing * @author Kazuki Shimizu @@ -37,8 +38,9 @@ class JdbcAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { /** * {@inheritDoc} + * * @return return the {@link EnableJdbcAuditing} - * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation() + * @see AuditingBeanDefinitionRegistrarSupport#getAnnotation() */ @Override protected Class getAnnotation() { @@ -47,8 +49,9 @@ class JdbcAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { /** * {@inheritDoc} + * * @return return "{@literal jdbcAuditingHandler}" - * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName() + * @see AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName() */ @Override protected String getAuditingHandlerBeanName() { @@ -61,22 +64,25 @@ class JdbcAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { */ @Override protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) { + BeanDefinitionBuilder builder = super.getAuditHandlerBeanDefinitionBuilder(configuration); return builder.addConstructorArgReference("jdbcMappingContext"); } /** - * Register the bean definition of {@link JdbcAuditingEventListener}. - * {@inheritDoc} - * @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListenerBeanDefinition(BeanDefinition, BeanDefinitionRegistry) + * Register the bean definition of {@link JdbcAuditingEventListener}. {@inheritDoc} + * + * @see AuditingBeanDefinitionRegistrarSupport#registerAuditListenerBeanDefinition(BeanDefinition, + * BeanDefinitionRegistry) */ @Override protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition, - BeanDefinitionRegistry registry) { + BeanDefinitionRegistry registry) { + Class listenerClass = JdbcAuditingEventListener.class; BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(listenerClass); builder.addPropertyValue("auditingHandler", - ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), null)); + ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), null)); registerInfrastructureBeanWithId(builder.getRawBeanDefinition(), listenerClass.getName(), registry); } diff --git a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java index e685cc55f..24b8b0b49 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java @@ -15,7 +15,17 @@ */ package org.springframework.data.jdbc.repository.config; +import static org.assertj.core.api.Assertions.*; + import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.assertj.core.api.SoftAssertions; import org.junit.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -32,144 +42,178 @@ import org.springframework.data.domain.AuditorAware; import org.springframework.data.jdbc.mapping.model.NamingStrategy; import org.springframework.data.repository.CrudRepository; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.Assertions.assertThat; - /** * Tests the {@link EnableJdbcAuditing} annotation. * * @author Kazuki Shimizu + * @author Jens Schauder */ public class EnableJdbcAuditingHsqlIntegrationTests { + SoftAssertions softly = new SoftAssertions(); + @Test - public void auditForAnnotatedEntity() throws InterruptedException { - try (ConfigurableApplicationContext context = - new AnnotationConfigApplicationContext(TestConfiguration.class, AuditingConfiguration.class)) { + public void auditForAnnotatedEntity() { - AuditingAnnotatedDummyEntityRepository repository = context.getBean(AuditingAnnotatedDummyEntityRepository.class); + configureRepositoryWith( // + AuditingAnnotatedDummyEntityRepository.class, // + TestConfiguration.class, // + AuditingConfiguration.class) // + .accept(repository -> { - AuditingConfiguration.currentAuditor = "user01"; - LocalDateTime now = LocalDateTime.now(); + AuditingConfiguration.currentAuditor = "user01"; + LocalDateTime now = LocalDateTime.now(); - AuditingAnnotatedDummyEntity entity = new AuditingAnnotatedDummyEntity(); - entity.setName("Spring Data"); - repository.save(entity); + AuditingAnnotatedDummyEntity entity = repository.save(new AuditingAnnotatedDummyEntity()); - assertThat(entity.id).isNotNull(); - assertThat(entity.getCreatedBy()).isEqualTo("user01"); - assertThat(entity.getCreatedDate()).isAfter(now); - assertThat(entity.getLastModifiedBy()).isEqualTo("user01"); - assertThat(entity.getLastModifiedDate()).isAfterOrEqualTo(entity.getCreatedDate()); - assertThat(entity.getLastModifiedDate()).isAfter(now); - assertThat(repository.findById(entity.id).get()).isEqualTo(entity); + softly.assertThat(entity.id).isNotNull(); + softly.assertThat(entity.getCreatedBy()).isEqualTo("user01"); + softly.assertThat(entity.getCreatedDate()).isAfter(now); + softly.assertThat(entity.getLastModifiedBy()).isEqualTo("user01"); + softly.assertThat(entity.getLastModifiedDate()).isAfterOrEqualTo(entity.getCreatedDate()); + softly.assertThat(entity.getLastModifiedDate()).isAfter(now); + softly.assertThat(repository.findById(entity.id).get()).isEqualTo(entity); - LocalDateTime beforeCreatedDate = entity.getCreatedDate(); - LocalDateTime beforeLastModifiedDate = entity.getLastModifiedDate(); + LocalDateTime beforeCreatedDate = entity.getCreatedDate(); + LocalDateTime beforeLastModifiedDate = entity.getLastModifiedDate(); - TimeUnit.MILLISECONDS.sleep(100); - AuditingConfiguration.currentAuditor = "user02"; + softly.assertAll(); - entity.setName("Spring Data JDBC"); - repository.save(entity); + sleepMillis(10); - assertThat(entity.getCreatedBy()).isEqualTo("user01"); - assertThat(entity.getCreatedDate()).isEqualTo(beforeCreatedDate); - assertThat(entity.getLastModifiedBy()).isEqualTo("user02"); - assertThat(entity.getLastModifiedDate()).isAfter(beforeLastModifiedDate); - assertThat(repository.findById(entity.id).get()).isEqualTo(entity); - } + AuditingConfiguration.currentAuditor = "user02"; + + entity = repository.save(entity); + + softly.assertThat(entity.getCreatedBy()).isEqualTo("user01"); + softly.assertThat(entity.getCreatedDate()).isEqualTo(beforeCreatedDate); + softly.assertThat(entity.getLastModifiedBy()).isEqualTo("user02"); + softly.assertThat(entity.getLastModifiedDate()).isAfter(beforeLastModifiedDate); + softly.assertThat(repository.findById(entity.id).get()).isEqualTo(entity); + }); } @Test public void noAnnotatedEntity() { - try (ConfigurableApplicationContext context = - new AnnotationConfigApplicationContext(TestConfiguration.class, AuditingConfiguration.class)) { - DummyEntityRepository repository = context.getBean(DummyEntityRepository.class); + configureRepositoryWith( // + DummyEntityRepository.class, // + TestConfiguration.class, // + AuditingConfiguration.class) // + .accept(repository -> { - DummyEntity entity = new DummyEntity(); - entity.setName("Spring Data"); - repository.save(entity); + DummyEntity entity = repository.save(new DummyEntity()); - assertThat(entity.id).isNotNull(); - assertThat(repository.findById(entity.id).get()).isEqualTo(entity); + softly.assertThat(entity.id).isNotNull(); + softly.assertThat(repository.findById(entity.id).get()).isEqualTo(entity); - entity.setName("Spring Data JDBC"); - repository.save(entity); + softly.assertAll(); - assertThat(repository.findById(entity.id).get()).isEqualTo(entity); - } + entity = repository.save(entity); + + assertThat(repository.findById(entity.id).get()).isEqualTo(entity); + }); } @Test - public void customizeEnableJdbcAuditingAttributes() { - // Test for 'auditorAwareRef', 'dateTimeProviderRef' and 'modifyOnCreate' - try (ConfigurableApplicationContext context = - new AnnotationConfigApplicationContext(TestConfiguration.class, CustomizeAuditingConfiguration1.class)) { - AuditingAnnotatedDummyEntityRepository repository = context.getBean(AuditingAnnotatedDummyEntityRepository.class); - - LocalDateTime currentDateTime = LocalDate.of(2018, 4, 14).atStartOfDay(); - CustomizeAuditingConfiguration1.currentDateTime = currentDateTime; - - AuditingAnnotatedDummyEntity entity = new AuditingAnnotatedDummyEntity(); - entity.setName("Spring Data JDBC"); - repository.save(entity); - - assertThat(entity.id).isNotNull(); - assertThat(entity.getCreatedBy()).isEqualTo("custom user"); - assertThat(entity.getCreatedDate()).isEqualTo(currentDateTime); - assertThat(entity.getLastModifiedBy()).isNull(); - assertThat(entity.getLastModifiedDate()).isNull(); - } - // Test for 'setDates' - try (ConfigurableApplicationContext context = - new AnnotationConfigApplicationContext(TestConfiguration.class, CustomizeAuditingConfiguration2.class)) { - AuditingAnnotatedDummyEntityRepository repository = context.getBean(AuditingAnnotatedDummyEntityRepository.class); - - AuditingAnnotatedDummyEntity entity = new AuditingAnnotatedDummyEntity(); - entity.setName("Spring Data JDBC"); - repository.save(entity); - - assertThat(entity.id).isNotNull(); - assertThat(entity.getCreatedBy()).isEqualTo("user"); - assertThat(entity.getCreatedDate()).isNull(); - assertThat(entity.getLastModifiedBy()).isEqualTo("user"); - assertThat(entity.getLastModifiedDate()).isNull(); - } + public void customDateTimeProvider() { + + configureRepositoryWith( // + AuditingAnnotatedDummyEntityRepository.class, // + TestConfiguration.class, // + CustomizeAuditorAwareAndDateTimeProvider.class) // + .accept(repository -> { + + LocalDateTime currentDateTime = LocalDate.of(2018, 4, 14).atStartOfDay(); + CustomizeAuditorAwareAndDateTimeProvider.currentDateTime = currentDateTime; + + AuditingAnnotatedDummyEntity entity = repository.save(new AuditingAnnotatedDummyEntity()); + + softly.assertThat(entity.id).isNotNull(); + softly.assertThat(entity.getCreatedBy()).isEqualTo("custom user"); + softly.assertThat(entity.getCreatedDate()).isEqualTo(currentDateTime); + softly.assertThat(entity.getLastModifiedBy()).isNull(); + softly.assertThat(entity.getLastModifiedDate()).isNull(); + }); + } + + @Test + public void customAuditorAware() { + + configureRepositoryWith( // + AuditingAnnotatedDummyEntityRepository.class, // + TestConfiguration.class, // + CustomizeAuditorAware.class) // + .accept(repository -> { + + AuditingAnnotatedDummyEntity entity = repository.save(new AuditingAnnotatedDummyEntity()); + + softly.assertThat(entity.id).isNotNull(); + softly.assertThat(entity.getCreatedBy()).isEqualTo("user"); + softly.assertThat(entity.getCreatedDate()).isNull(); + softly.assertThat(entity.getLastModifiedBy()).isEqualTo("user"); + softly.assertThat(entity.getLastModifiedDate()).isNull(); + }); + } + + /** + * Usage looks like this: + *

+ * {@code configure(MyRepository.class, MyConfiguration) .accept(repository -> { // perform tests on repository here + * }); } + * + * @param repositoryType the type of repository you want to perform tests on. + * @param configurationClasses the classes containing the configuration for the + * {@link org.springframework.context.ApplicationContext}. + * @param type of the entity managed by the repository. + * @param type of the repository. + * @return a Consumer for repositories of type {@code R}. + */ + private > Consumer> configureRepositoryWith(Class repositoryType, + Class... configurationClasses) { + + return (Consumer test) -> { + + try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configurationClasses)) { + + test.accept(context.getBean(repositoryType)); + + softly.assertAll(); + } + }; } - interface AuditingAnnotatedDummyEntityRepository extends CrudRepository { + private void sleepMillis(int timeout) { + + try { + TimeUnit.MILLISECONDS.sleep(timeout); + } catch (InterruptedException e) { + + throw new RuntimeException("Failed to sleep", e); + } } + interface AuditingAnnotatedDummyEntityRepository extends CrudRepository {} + @Data static class AuditingAnnotatedDummyEntity { - @Id - private Long id; - private String name; - @CreatedBy - private String createdBy; - @CreatedDate - private LocalDateTime createdDate; - @LastModifiedBy - private String lastModifiedBy; - @LastModifiedDate - private LocalDateTime lastModifiedDate; - } - interface DummyEntityRepository extends CrudRepository { + @Id Long id; + @CreatedBy String createdBy; + @CreatedDate LocalDateTime createdDate; + @LastModifiedBy String lastModifiedBy; + @LastModifiedDate LocalDateTime lastModifiedDate; } + interface DummyEntityRepository extends CrudRepository {} + @Data static class DummyEntity { - @Id - private Long id; - private String name; + + @Id private Long id; + // not actually used, exists just to avoid empty value list during insert. + String name; } @ComponentScan("org.springframework.data.jdbc.testing") @@ -183,36 +227,42 @@ public class EnableJdbcAuditingHsqlIntegrationTests { @Bean NamingStrategy namingStrategy() { + return new NamingStrategy() { + public String getTableName(Class type) { return "DummyEntity"; } }; } - } @EnableJdbcAuditing static class AuditingConfiguration { - private static String currentAuditor; + static String currentAuditor; + @Bean AuditorAware auditorAware() { return () -> Optional.ofNullable(currentAuditor); } } - @EnableJdbcAuditing(auditorAwareRef = "customAuditorAware", dateTimeProviderRef = "customDateTimeProvider", modifyOnCreate = false) - static class CustomizeAuditingConfiguration1 { - private static LocalDateTime currentDateTime; + @EnableJdbcAuditing(auditorAwareRef = "customAuditorAware", dateTimeProviderRef = "customDateTimeProvider", + modifyOnCreate = false) + static class CustomizeAuditorAwareAndDateTimeProvider { + static LocalDateTime currentDateTime; + @Bean @Primary AuditorAware auditorAware() { return () -> Optional.of("default user"); } + @Bean AuditorAware customAuditorAware() { return () -> Optional.of("custom user"); } + @Bean DateTimeProvider customDateTimeProvider() { return () -> Optional.ofNullable(currentDateTime); @@ -220,11 +270,11 @@ public class EnableJdbcAuditingHsqlIntegrationTests { } @EnableJdbcAuditing(setDates = false) - static class CustomizeAuditingConfiguration2 { + static class CustomizeAuditorAware { + @Bean AuditorAware auditorAware() { return () -> Optional.of("user"); } } - }