Browse Source

DATAJPA-116 - Improved implementation to support annotation-based auditing.

Re-engineered our auditing setup to leverage the APIs introduced from DATACMNS-137.
pull/19/head
Oliver Gierke 13 years ago
parent
commit
fc818b7087
  1. 145
      src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java
  2. 36
      src/main/java/org/springframework/data/jpa/domain/support/CurrentDateTimeProvider.java
  3. 33
      src/main/java/org/springframework/data/jpa/domain/support/DateTimeProvider.java
  4. 41
      src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java
  5. 3
      src/main/resources/META-INF/spring.schemas
  6. 54
      src/main/resources/org/springframework/data/jpa/repository/config/spring-jpa-1.3.xsd
  7. 153
      src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityListenerUnitTests.java
  8. 9
      src/test/java/org/springframework/data/jpa/domain/support/AuditingNamespaceUnitTests.java
  9. 4
      src/test/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParserTests.java
  10. 11
      src/test/resources/auditing/auditing-namespace-context3.xml

145
src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java

@ -18,14 +18,9 @@ package org.springframework.data.jpa.domain.support; @@ -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; @@ -50,55 +45,15 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
*/
@Configurable
public class AuditingEntityListener<T> implements InitializingBean {
public class AuditingEntityListener<T> {
private static final Logger LOG = LoggerFactory.getLogger(AuditingEntityListener.class);
private AuditorAware<T> auditorAware;
private DateTimeProvider dateTimeProvider = CurrentDateTimeProvider.INSTANCE;
private boolean dateTimeForNow = true;
private boolean modifyOnCreation = true;
/**
* Setter to inject a {@code AuditorAware} component to retrieve the current auditor.
*
* @param auditorAware the auditorAware to set
*/
public void setAuditorAware(final AuditorAware<T> 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;
}
private AuditingHandler<T> handler;
/**
* Sets the {@link DateTimeProvider} to be used to determine the dates to be set.
*
* @param dateTimeProvider
* @param auditingHandler the handler to set
*/
public void setDateTimeProvider(DateTimeProvider dateTimeProvider) {
this.dateTimeProvider = dateTimeProvider == null ? CurrentDateTimeProvider.INSTANCE : dateTimeProvider;
public void setAuditingHandler(AuditingHandler<T> auditingHandler) {
this.handler = auditingHandler;
}
/**
@ -109,8 +64,7 @@ public class AuditingEntityListener<T> implements InitializingBean { @@ -109,8 +64,7 @@ public class AuditingEntityListener<T> implements InitializingBean {
*/
@PrePersist
public void touchForCreate(Object target) {
touch(target, true);
handler.markCreated(target);
}
/**
@ -121,89 +75,6 @@ public class AuditingEntityListener<T> implements InitializingBean { @@ -121,89 +75,6 @@ public class AuditingEntityListener<T> 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<T, ?> auditable = (Auditable<T, ?>) 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<T, ?> 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<T, ?> 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);
}
}

36
src/main/java/org/springframework/data/jpa/domain/support/CurrentDateTimeProvider.java

@ -1,36 +0,0 @@ @@ -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();
}
}

33
src/main/java/org/springframework/data/jpa/domain/support/DateTimeProvider.java

@ -1,33 +0,0 @@ @@ -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();
}

41
src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java

@ -17,8 +17,6 @@ package org.springframework.data.jpa.repository.config; @@ -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; @@ -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 { @@ -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) {

3
src/main/resources/META-INF/spring.schemas

@ -1,4 +1,5 @@ @@ -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

54
src/main/resources/org/springframework/data/jpa/repository/config/spring-jpa-1.3.xsd

@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://www.springframework.org/schema/data/jpa"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:repository="http://www.springframework.org/schema/data/repository"
targetNamespace="http://www.springframework.org/schema/data/jpa"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool"
schemaLocation="http://www.springframework.org/schema/tool/spring-tool.xsd"/>
<xsd:import namespace="http://www.springframework.org/schema/context"
schemaLocation="http://www.springframework.org/schema/context/spring-context.xsd" />
<xsd:import namespace="http://www.springframework.org/schema/data/repository"
schemaLocation="http://www.springframework.org/schema/data/repository/spring-repository.xsd" />
<xsd:element name="repositories">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="repository:repositories">
<xsd:attributeGroup ref="repository:transactional-repository-attributes" />
<xsd:attribute name="entity-manager-factory-ref" type="entityManagerFactoryRef" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="auditing">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.data.jpa.domain.support.AuditingEntityListener" />
<tool:exports type="org.springframework.data.auditing.AuditingHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:attributeGroup ref="repository:auditing-attributes" />
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="entityManagerFactoryRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.orm.jpa.AbstractEntityManagerFactoryBean" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:schema>

153
src/test/java/org/springframework/data/jpa/domain/support/AuditingEntityListenerUnitTests.java

@ -1,153 +0,0 @@ @@ -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<AuditableUser> listener;
AuditorAware<AuditableUser> auditorAware;
AuditableUser user;
@Before
public void setUp() {
listener = new AuditingEntityListener<AuditableUser>();
// 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();
}
}

9
src/test/java/org/springframework/data/jpa/domain/support/AuditingNamespaceUnitTests.java

@ -1,5 +1,5 @@ @@ -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 @@ -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 @@ -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()));
}
}

4
src/test/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParserTests.java

@ -76,7 +76,9 @@ public class AuditingBeanDefinitionParserTests { @@ -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) {

11
src/test/resources/auditing/auditing-namespace-context3.xml

@ -2,13 +2,14 @@ @@ -2,13 +2,14 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
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">
<jpa:auditing set-dates="false" date-time-provider-ref="dateTimeProvider" />
<bean id="dateTimeProvider" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.data.jpa.domain.support.DateTimeProvider" />
</bean>
<util:constant id="dateTimeProvider" static-field="org.springframework.data.auditing.CurrentDateTimeProvider.INSTANCE" />
</beans>
Loading…
Cancel
Save