Skip to content

Commit

Permalink
DATAJPA-9 - Added DateTimeProvider SPI for auditing.
Browse files Browse the repository at this point in the history
Extracted the DateTime instance calculation to be used for auditing into a DateTimeProvider callback interface to open it up for customization. AuditingEntityListener can get an instance of it configured. Exposed the property via the auditing namespace. By default a CurrentDateTimeProvider its used that contains the behavior we had so far.
  • Loading branch information
odrotbohm committed Feb 1, 2012
1 parent bb60623 commit 9ae8caa
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -55,6 +55,7 @@ public class AuditingEntityListener<T> implements InitializingBean {
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;
Expand All @@ -78,7 +79,6 @@ public void setAuditorAware(final AuditorAware<T> auditorAware) {
* @param dateTimeForNow the dateTimeForNow to set
*/
public void setDateTimeForNow(boolean dateTimeForNow) {

this.dateTimeForNow = dateTimeForNow;
}

Expand All @@ -89,10 +89,18 @@ public void setDateTimeForNow(boolean dateTimeForNow) {
* @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;
}

/**
* Sets modification and creation date and auditor on the target object in case it implements {@link Auditable} on
* persist events.
Expand Down Expand Up @@ -171,7 +179,7 @@ private T touchAuditor(final Auditable<T, ?> auditable, boolean isNew) {
*/
private DateTime touchDate(final Auditable<T, ?> auditable, boolean isNew) {

DateTime now = new DateTime();
DateTime now = dateTimeProvider.getDateTime();

if (isNew) {
auditable.setCreatedDate(now);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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();
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -61,6 +61,11 @@ public BeanDefinition parse(Element element, ParserContext parser) {

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);

Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/META-INF/spring.schemas
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
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.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.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.1.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?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" />
<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:complexType name="jpa-repository">
<xsd:complexContent>
<xsd:extension base="repository:repository">
<xsd:attributeGroup ref="repository:transactional-repository-attributes" />
<xsd:attribute name="entity-manager-factory-ref" type="entityManagerFactoryRef" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

<xsd:element name="repositories">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="repository:repositories">
<xsd:sequence>
<xsd:element name="repository" minOccurs="0" maxOccurs="unbounded" type="jpa-repository" />
</xsd:sequence>
<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:complexType>
<xsd:attribute name="auditor-aware-ref">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.domain.AuditorAware" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="set-dates" default="true" type="xsd:boolean" />
<xsd:attribute name="date-time-provider-ref">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.jpa.domain.support.DateTimeProvider" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</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>
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -124,7 +124,7 @@ public void onlySetsModificationDataOnNotNewEntities() {
}

@Test
public void doesNotSetTimeIfConfigured() throws Exception {
public void doesNotSetTimeIfConfigured() {

listener.setDateTimeForNow(false);
listener.setAuditorAware(auditorAware);
Expand All @@ -136,4 +136,18 @@ public void doesNotSetTimeIfConfigured() throws Exception {
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();
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -20,7 +20,9 @@

import org.junit.Test;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
Expand All @@ -33,27 +35,54 @@
public class AuditingBeanDefinitionParserTests {

@Test
public void settingDatesIsConfigured() throws Exception {
public void settingDatesIsConfigured() {

assertSetDatesIsSetTo("auditing/auditing-namespace-context.xml", "true");
}

@Test
public void notSettingDatesIsConfigured() throws Exception {
public void notSettingDatesIsConfigured() {

assertSetDatesIsSetTo("auditing/auditing-namespace-context2.xml", "false");
}

private void assertSetDatesIsSetTo(String configFile, String value) {
/**
* @see DATAJPA-9
*/
@Test
public void wiresDateTimeProviderIfConfigured() {

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource(configFile));
String location = "auditing/auditing-namespace-context3.xml";
BeanDefinition definition = getBeanDefinition(location);
PropertyValue value = definition.getPropertyValues().getPropertyValue("dateTimeProvider");

assertThat(value, is(notNullValue()));
assertThat(value.getValue(), is(RuntimeBeanReference.class));
assertThat(((RuntimeBeanReference) value.getValue()).getBeanName(), is("dateTimeProvider"));

BeanFactory factory = loadFactoryFrom(location);
Object bean = factory.getBean(AuditingBeanDefinitionParser.AUDITING_ENTITY_LISTENER_CLASS_NAME);
assertThat(bean, is(notNullValue()));
}

private void assertSetDatesIsSetTo(String configFile, String value) {

BeanDefinition definition = factory
.getBeanDefinition(AuditingBeanDefinitionParser.AUDITING_ENTITY_LISTENER_CLASS_NAME);
BeanDefinition definition = getBeanDefinition(configFile);
PropertyValue propertyValue = definition.getPropertyValues().getPropertyValue("dateTimeForNow");
assertThat(propertyValue, is(notNullValue()));
assertThat((String) propertyValue.getValue(), is(value));
}

private BeanDefinition getBeanDefinition(String configFile) {

DefaultListableBeanFactory factory = loadFactoryFrom(configFile);
return factory.getBeanDefinition(AuditingBeanDefinitionParser.AUDITING_ENTITY_LISTENER_CLASS_NAME);
}

private DefaultListableBeanFactory loadFactoryFrom(String configFile) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(factory);
xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource(configFile));
return factory;
}
}
14 changes: 14 additions & 0 deletions src/test/resources/auditing/auditing-namespace-context3.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<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"
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">

<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>

</beans>

0 comments on commit 9ae8caa

Please sign in to comment.