From fc818b7087a8367c271f2bc40a44cfe233ecc3a7 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 26 Nov 2012 19:14:42 +0100 Subject: [PATCH] DATAJPA-116 - Improved implementation to support annotation-based auditing. Re-engineered our auditing setup to leverage the APIs introduced from DATACMNS-137. --- .../support/AuditingEntityListener.java | 145 +---------------- .../support/CurrentDateTimeProvider.java | 36 ----- .../jpa/domain/support/DateTimeProvider.java | 33 ---- .../config/AuditingBeanDefinitionParser.java | 41 +---- src/main/resources/META-INF/spring.schemas | 3 +- .../jpa/repository/config/spring-jpa-1.3.xsd | 54 +++++++ .../AuditingEntityListenerUnitTests.java | 153 ------------------ .../support/AuditingNamespaceUnitTests.java | 9 +- .../AuditingBeanDefinitionParserTests.java | 4 +- .../auditing/auditing-namespace-context3.xml | 11 +- 10 files changed, 84 insertions(+), 405 deletions(-) delete mode 100644 src/main/java/org/springframework/data/jpa/domain/support/CurrentDateTimeProvider.java delete mode 100644 src/main/java/org/springframework/data/jpa/domain/support/DateTimeProvider.java create mode 100644 src/main/resources/org/springframework/data/jpa/repository/config/spring-jpa-1.3.xsd delete mode 100644 src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityListenerUnitTests.java diff --git a/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java b/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java index f1cea80c9..b629467af 100644 --- a/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java +++ b/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java @@ -18,14 +18,9 @@ package org.springframework.data.jpa.domain.support; import javax.persistence.PrePersist; import javax.persistence.PreUpdate; -import org.joda.time.DateTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.data.auditing.AuditingHandler; import org.springframework.data.domain.Auditable; -import org.springframework.data.domain.AuditorAware; -import org.springframework.util.Assert; /** * JPA entity listener to capture auditing information on persiting and updating entities. To get this one flying be @@ -50,55 +45,15 @@ import org.springframework.util.Assert; * @author Oliver Gierke */ @Configurable -public class AuditingEntityListener implements InitializingBean { +public class AuditingEntityListener { - private static final Logger LOG = LoggerFactory.getLogger(AuditingEntityListener.class); - - private AuditorAware auditorAware; - private DateTimeProvider dateTimeProvider = CurrentDateTimeProvider.INSTANCE; - - private boolean dateTimeForNow = true; - private boolean modifyOnCreation = true; + private AuditingHandler handler; /** - * Setter to inject a {@code AuditorAware} component to retrieve the current auditor. - * - * @param auditorAware the auditorAware to set + * @param auditingHandler the handler to set */ - public void setAuditorAware(final AuditorAware auditorAware) { - - Assert.notNull(auditorAware); - this.auditorAware = 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 false if you want to treat entity creation as modification and thus set the current date as - * modification date, too. Defaults to {@code true}. - * - * @param modifyOnCreation if modification information shall be set on creation, too - */ - public void setModifyOnCreation(final 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; + public void setAuditingHandler(AuditingHandler auditingHandler) { + this.handler = auditingHandler; } /** @@ -109,8 +64,7 @@ public class AuditingEntityListener implements InitializingBean { */ @PrePersist public void touchForCreate(Object target) { - - touch(target, true); + handler.markCreated(target); } /** @@ -121,89 +75,6 @@ public class AuditingEntityListener implements InitializingBean { */ @PreUpdate public void touchForUpdate(Object target) { - - touch(target, false); - } - - private void touch(Object target, boolean isNew) { - - if (!(target instanceof Auditable)) { - return; - } - - @SuppressWarnings("unchecked") - Auditable auditable = (Auditable) target; - - T auditor = touchAuditor(auditable, isNew); - DateTime now = dateTimeForNow ? touchDate(auditable, isNew) : null; - - Object defaultedNow = now == null ? "not set" : now; - Object defaultedAuditor = auditor == null ? "unknown" : auditor; - - LOG.debug("Touched {} - Last modification at {} by {}", new Object[] { auditable, defaultedNow, defaultedAuditor }); - } - - /** - * Sets modifying and creating auditioner. Creating auditioner is only set on new auditables. - * - * @param auditable - * @return - */ - private T touchAuditor(final Auditable auditable, boolean isNew) { - - if (null == auditorAware) { - return null; - } - - T auditor = auditorAware.getCurrentAuditor(); - - if (isNew) { - - auditable.setCreatedBy(auditor); - - if (!modifyOnCreation) { - return auditor; - } - } - - auditable.setLastModifiedBy(auditor); - - return auditor; - } - - /** - * Touches the auditable regarding modification and creation date. Creation date is only set on new auditables. - * - * @param auditable - * @return - */ - private DateTime touchDate(final Auditable auditable, boolean isNew) { - - DateTime now = dateTimeProvider.getDateTime(); - - if (isNew) { - auditable.setCreatedDate(now); - - if (!modifyOnCreation) { - return now; - } - } - - auditable.setLastModifiedDate(now); - - return now; - } - - /* - * (non-Javadoc) - * - * @see - * org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - public void afterPropertiesSet() { - - if (auditorAware == null) { - LOG.debug("No AuditorAware set! Auditing will not be applied!"); - } + handler.markModified(target); } } diff --git a/src/main/java/org/springframework/data/jpa/domain/support/CurrentDateTimeProvider.java b/src/main/java/org/springframework/data/jpa/domain/support/CurrentDateTimeProvider.java deleted file mode 100644 index 0dbe30f9a..000000000 --- a/src/main/java/org/springframework/data/jpa/domain/support/CurrentDateTimeProvider.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2012 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 - * - * http://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.jpa.domain.support; - -import org.joda.time.DateTime; - -/** - * Default {@link DateTimeProvider} simply creating new {@link DateTime} instances for each method call. - * - * @author Oliver Gierke - */ -public enum CurrentDateTimeProvider implements DateTimeProvider { - - INSTANCE; - - /* - * (non-Javadoc) - * @see org.springframework.data.jpa.domain.support.DateTimeProvider#getDateTime() - */ - public DateTime getDateTime() { - return new DateTime(); - } -} diff --git a/src/main/java/org/springframework/data/jpa/domain/support/DateTimeProvider.java b/src/main/java/org/springframework/data/jpa/domain/support/DateTimeProvider.java deleted file mode 100644 index 87f082c54..000000000 --- a/src/main/java/org/springframework/data/jpa/domain/support/DateTimeProvider.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012 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 - * - * http://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.jpa.domain.support; - -import org.joda.time.DateTime; - -/** - * SPI to calculate the {@link DateTime} instance to be used when auditing. - * - * @author Oliver Gierke - */ -public interface DateTimeProvider { - - /** - * Returns the {@link DateTime} to be used as modification date. - * - * @return - */ - DateTime getDateTime(); -} diff --git a/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java b/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java index 08402c428..c797d895d 100644 --- a/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java +++ b/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java @@ -17,8 +17,6 @@ package org.springframework.data.jpa.repository.config; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; -import org.springframework.aop.framework.ProxyFactoryBean; -import org.springframework.aop.target.LazyInitTargetSource; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -26,7 +24,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.util.StringUtils; +import org.springframework.data.config.AuditingHandlerBeanDefinitionParser; import org.w3c.dom.Element; /** @@ -39,54 +37,31 @@ public class AuditingBeanDefinitionParser implements BeanDefinitionParser { static final String AUDITING_ENTITY_LISTENER_CLASS_NAME = "org.springframework.data.jpa.domain.support.AuditingEntityListener"; private static final String AUDITING_BFPP_CLASS_NAME = "org.springframework.data.jpa.domain.support.AuditingBeanFactoryPostProcessor"; + private final BeanDefinitionParser auditingHandlerParser = new AuditingHandlerBeanDefinitionParser(); + /* * (non-Javadoc) - * - * @see - * org.springframework.beans.factory.xml.BeanDefinitionParser#parse(org. - * w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext) + * @see org.springframework.beans.factory.xml.BeanDefinitionParser#parse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext) */ public BeanDefinition parse(Element element, ParserContext parser) { new SpringConfiguredBeanDefinitionParser().parse(element, parser); + BeanDefinition auditingHandlerDefinition = auditingHandlerParser.parse(element, parser); + BeanDefinitionBuilder builder = rootBeanDefinition(AUDITING_ENTITY_LISTENER_CLASS_NAME); + builder.addPropertyValue("auditingHandler", auditingHandlerDefinition); builder.setScope("prototype"); - String auditorAwareRef = element.getAttribute("auditor-aware-ref"); - - if (StringUtils.hasText(auditorAwareRef)) { - builder.addPropertyValue("auditorAware", createLazyInitTargetSourceBeanDefinition(auditorAwareRef)); - } - - builder.addPropertyValue("dateTimeForNow", element.getAttribute("set-dates")); - - String dateTimeProviderRef = element.getAttribute("date-time-provider-ref"); - if (StringUtils.hasText(dateTimeProviderRef)) { - builder.addPropertyReference("dateTimeProvider", dateTimeProviderRef); - } - registerInfrastructureBeanWithId(builder.getRawBeanDefinition(), AUDITING_ENTITY_LISTENER_CLASS_NAME, parser, element); - RootBeanDefinition def = new RootBeanDefinition(); - def.setBeanClassName(AUDITING_BFPP_CLASS_NAME); + RootBeanDefinition def = new RootBeanDefinition(AUDITING_BFPP_CLASS_NAME); registerInfrastructureBeanWithId(def, AUDITING_BFPP_CLASS_NAME, parser, element); return null; } - private BeanDefinition createLazyInitTargetSourceBeanDefinition(String auditorAwareRef) { - - BeanDefinitionBuilder targetSourceBuilder = rootBeanDefinition(LazyInitTargetSource.class); - targetSourceBuilder.addPropertyValue("targetBeanName", auditorAwareRef); - - BeanDefinitionBuilder builder = rootBeanDefinition(ProxyFactoryBean.class); - builder.addPropertyValue("targetSource", targetSourceBuilder.getBeanDefinition()); - - return builder.getBeanDefinition(); - } - private void registerInfrastructureBeanWithId(AbstractBeanDefinition def, String id, ParserContext context, Element element) { diff --git a/src/main/resources/META-INF/spring.schemas b/src/main/resources/META-INF/spring.schemas index e09b4307a..5589199e3 100644 --- a/src/main/resources/META-INF/spring.schemas +++ b/src/main/resources/META-INF/spring.schemas @@ -1,4 +1,5 @@ http\://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.0.xsd http\://www.springframework.org/schema/data/jpa/spring-jpa-1.1.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.1.xsd http\://www.springframework.org/schema/data/jpa/spring-jpa-1.2.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.2.xsd -http\://www.springframework.org/schema/data/jpa/spring-jpa.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.2.xsd +http\://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.3.xsd +http\://www.springframework.org/schema/data/jpa/spring-jpa.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.3.xsd diff --git a/src/main/resources/org/springframework/data/jpa/repository/config/spring-jpa-1.3.xsd b/src/main/resources/org/springframework/data/jpa/repository/config/spring-jpa-1.3.xsd new file mode 100644 index 000000000..7e8b15b1a --- /dev/null +++ b/src/main/resources/org/springframework/data/jpa/repository/config/spring-jpa-1.3.xsd @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityListenerUnitTests.java b/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityListenerUnitTests.java deleted file mode 100644 index ca1470168..000000000 --- a/src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityListenerUnitTests.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2008-2012 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 - * - * http://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.jpa.domain.support; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.data.domain.AuditorAware; -import org.springframework.data.jpa.domain.sample.AuditableUser; - -/** - * Unit test for {@code AuditingEntityListener}. - * - * @author Oliver Gierke - */ -@SuppressWarnings("unchecked") -public class AuditingEntityListenerUnitTests { - - AuditingEntityListener listener; - AuditorAware auditorAware; - - AuditableUser user; - - @Before - public void setUp() { - - listener = new AuditingEntityListener(); - // Explicitly null the AuditorAware as it might have been DI'ed if test - // is run in a test suite with integration tests - // listener.setAuditorAware(null); - - user = new AuditableUser(); - - auditorAware = mock(AuditorAware.class); - when(auditorAware.getCurrentAuditor()).thenReturn(user); - } - - /** - * Checks that the advice does not set auditor on the target entity if no {@code AuditorAware} was configured. - */ - @Test - public void doesNotSetAuditorIfNotConfigured() { - - listener.touchForCreate(user); - - assertNotNull(user.getCreatedDate()); - assertNotNull(user.getLastModifiedDate()); - - assertNull(user.getCreatedBy()); - assertNull(user.getLastModifiedBy()); - } - - /** - * Checks that the advice sets the auditor on the target entity if an {@code AuditorAware} was configured. - */ - @Test - public void setsAuditorIfConfigured() { - - listener.setAuditorAware(auditorAware); - - listener.touchForCreate(user); - - assertNotNull(user.getCreatedDate()); - assertNotNull(user.getLastModifiedDate()); - - assertNotNull(user.getCreatedBy()); - assertNotNull(user.getLastModifiedBy()); - - verify(auditorAware).getCurrentAuditor(); - } - - /** - * Checks that the advice does not set modification information on creation if the falg is set to {@code false}. - */ - @Test - public void honoursModifiedOnCreationFlag() { - - listener.setAuditorAware(auditorAware); - listener.setModifyOnCreation(false); - listener.touchForCreate(user); - - assertNotNull(user.getCreatedDate()); - assertNotNull(user.getCreatedBy()); - - assertNull(user.getLastModifiedBy()); - assertNull(user.getLastModifiedDate()); - - verify(auditorAware).getCurrentAuditor(); - } - - /** - * Tests that the advice only sets modification data if a not-new entity is handled. - */ - @Test - public void onlySetsModificationDataOnNotNewEntities() { - - user = new AuditableUser(1L); - - listener.setAuditorAware(auditorAware); - listener.touchForUpdate(user); - - assertNull(user.getCreatedBy()); - assertNull(user.getCreatedDate()); - - assertNotNull(user.getLastModifiedBy()); - assertNotNull(user.getLastModifiedDate()); - - verify(auditorAware).getCurrentAuditor(); - } - - @Test - public void doesNotSetTimeIfConfigured() { - - listener.setDateTimeForNow(false); - listener.setAuditorAware(auditorAware); - listener.touchForCreate(user); - - assertNotNull(user.getCreatedBy()); - assertNull(user.getCreatedDate()); - - assertNotNull(user.getLastModifiedBy()); - assertNull(user.getLastModifiedDate()); - } - - /** - * @see DATAJPA-9 - */ - @Test - public void usesDateTimeProviderIfConfigured() { - - DateTimeProvider provider = mock(DateTimeProvider.class); - - listener.setDateTimeProvider(provider); - listener.touchForCreate(user); - - verify(provider, times(1)).getDateTime(); - } -} diff --git a/src/test/java/org/springframework/data/jpa/domain/support/AuditingNamespaceUnitTests.java b/src/test/java/org/springframework/data/jpa/domain/support/AuditingNamespaceUnitTests.java index 3bebd5ea1..8cccb1eef 100644 --- a/src/test/java/org/springframework/data/jpa/domain/support/AuditingNamespaceUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/domain/support/AuditingNamespaceUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2011 the original author or authors. + * Copyright 2008-2012 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. @@ -31,13 +31,10 @@ public class AuditingNamespaceUnitTests extends AuditingBeanFactoryPostProcessor /* * (non-Javadoc) - * - * @see org.springframework.data.jpa.domain.support. - * AuditingBeanFactoryPostProcessorUnitTests#getConfigFile() + * @see org.springframework.data.jpa.domain.support.AuditingBeanFactoryPostProcessorUnitTests#getConfigFile() */ @Override protected String getConfigFile() { - return "auditing-namespace-context.xml"; } @@ -45,7 +42,7 @@ public class AuditingNamespaceUnitTests extends AuditingBeanFactoryPostProcessor public void registersBeanDefinitions() throws Exception { BeanDefinition definition = beanFactory.getBeanDefinition(AuditingEntityListener.class.getName()); - PropertyValue propertyValue = definition.getPropertyValues().getPropertyValue("auditorAware"); + PropertyValue propertyValue = definition.getPropertyValues().getPropertyValue("auditingHandler"); assertThat(propertyValue, is(notNullValue())); } } diff --git a/src/test/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParserTests.java b/src/test/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParserTests.java index c218431e5..4b5ce4201 100644 --- a/src/test/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParserTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParserTests.java @@ -76,7 +76,9 @@ public class AuditingBeanDefinitionParserTests { private BeanDefinition getBeanDefinition(String configFile) { DefaultListableBeanFactory factory = loadFactoryFrom(configFile); - return factory.getBeanDefinition(AuditingBeanDefinitionParser.AUDITING_ENTITY_LISTENER_CLASS_NAME); + BeanDefinition definition = factory + .getBeanDefinition(AuditingBeanDefinitionParser.AUDITING_ENTITY_LISTENER_CLASS_NAME); + return (BeanDefinition) definition.getPropertyValues().getPropertyValue("auditingHandler").getValue(); } private DefaultListableBeanFactory loadFactoryFrom(String configFile) { diff --git a/src/test/resources/auditing/auditing-namespace-context3.xml b/src/test/resources/auditing/auditing-namespace-context3.xml index 6e2196826..692f2b723 100644 --- a/src/test/resources/auditing/auditing-namespace-context3.xml +++ b/src/test/resources/auditing/auditing-namespace-context3.xml @@ -2,13 +2,14 @@ + http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd + http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - - - - + \ No newline at end of file