diff --git a/src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfiguration.java b/src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfiguration.java new file mode 100644 index 000000000..82e8d4859 --- /dev/null +++ b/src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013 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.auditing.config; + +import org.springframework.data.auditing.DateTimeProvider; + +/** + * Configuration information for auditing. + * + * @author Ranie Jade Ramiso + * @author Thomas Darimont + */ +public interface AnnotationAuditingConfiguration { + + /** + * @return References a bean of type AuditorAware to represent the current principal. + */ + String getAuditorAwareRef(); + + /** + * @return Configures whether the creation and modification dates are set and defaults to {@literal true}. + */ + boolean isSetDates(); + + /** + * @return Configures whether the entity shall be marked as modified on creation and defaults to {@literal true}. + */ + boolean isModifyOnCreate(); + + /** + * @return Configures a {@link DateTimeProvider} that allows customizing which DateTime shall be used for setting + * creation and modification dates. + */ + String getDateTimeProviderRef(); +} diff --git a/src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfigurationSupport.java b/src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfigurationSupport.java new file mode 100644 index 000000000..7c79302ac --- /dev/null +++ b/src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfigurationSupport.java @@ -0,0 +1,74 @@ +/* + * Copyright 2013 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.auditing.config; + +import java.lang.annotation.Annotation; + +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.Assert; + +/** + * Default implementation for {@link AnnotationAuditingConfiguration}. + * + * @author Ranie Jade Ramiso + * @author Thomas Darimont + */ +public class AnnotationAuditingConfigurationSupport implements AnnotationAuditingConfiguration { + + private final AnnotationAttributes attributes; + + /** + * Creates a new instance of {@link AnnotationAuditingConfigurationSupport}. + * + * @param metadata, must not be {@literal null}. + * @param annotation, must not be {@literal null}. + */ + public AnnotationAuditingConfigurationSupport(AnnotationMetadata metadata, Class annotation) { + + Assert.notNull(metadata, "metadata must not be null!"); + Assert.notNull(annotation, "annotation must not be null!"); + attributes = new AnnotationAttributes(metadata.getAnnotationAttributes(annotation.getName())); + } + + /* (non-Javadoc) + * @see org.springframework.data.auditing.config.AnnotationAuditingConfiguration#getAuditorAwareRef() + */ + public String getAuditorAwareRef() { + return attributes.getString("auditorAwareRef"); + } + + /* (non-Javadoc) + * @see org.springframework.data.auditing.config.AnnotationAuditingConfiguration#isSetDates() + */ + public boolean isSetDates() { + return attributes.getBoolean("setDates"); + } + + /* (non-Javadoc) + * @see org.springframework.data.auditing.config.AnnotationAuditingConfiguration#getDateTimeProviderRef() + */ + public String getDateTimeProviderRef() { + return attributes.getString("dateTimeProviderRef"); + } + + /* (non-Javadoc) + * @see org.springframework.data.auditing.config.AnnotationAuditingConfiguration#isModifyOnCreate() + */ + public boolean isModifyOnCreate() { + return attributes.getBoolean("modifyOnCreate"); + } +} diff --git a/src/main/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupport.java b/src/main/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupport.java new file mode 100644 index 000000000..60cb340dd --- /dev/null +++ b/src/main/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupport.java @@ -0,0 +1,176 @@ +/* + * Copyright 2013 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.auditing.config; + +import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; + +import java.lang.annotation.Annotation; + +import org.springframework.aop.framework.ProxyFactoryBean; +import org.springframework.aop.target.LazyInitTargetSource; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.auditing.CurrentDateTimeProvider; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * A {@link ImportBeanDefinitionRegistrar} that serves as a base class for store specific implementations for + * configuring audit support. Registers a {@link AuditingHandler} based on the provided configuration( + * {@link AnnotationAuditingConfiguration}). + * + * @author Ranie Jade Ramiso + * @author Thomas Darimont + */ +public abstract class AuditingBeanDefinitionRegistrarSupport implements ImportBeanDefinitionRegistrar { + + private static final String AUDITOR_AWARE = "auditorAware"; + private static final String DATE_TIME_PROVIDER = "dateTimeProvider"; + private static final String MODIFY_ON_CREATE = "modifyOnCreation"; + private static final String SET_DATES = "dateTimeForNow"; + + /** + * @param annotationMetadata, must not be {@literal null}. + * @param registry, must not be {@literal null}. + * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, + * org.springframework.beans.factory.support.BeanDefinitionRegistry) + */ + public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { + + Assert.notNull(annotationMetadata, "annotationMetadata must not be null!"); + Assert.notNull(annotationMetadata, "registry must not be null!"); + + AbstractBeanDefinition ahbd = registerAuditHandlerBeanDefinition(registry, getConfiguration(annotationMetadata)); + registerAuditListenerBeanDefinition(ahbd, registry); + } + + /** + * Registers an appropriate BeanDefinition for an {@link AuditingHandler}. + * + * @param registry, must not be {@literal null}. + * @param configuration, must not be {@literal null}. + * @return + */ + private AbstractBeanDefinition registerAuditHandlerBeanDefinition(BeanDefinitionRegistry registry, + AnnotationAuditingConfiguration configuration) { + + Assert.notNull(registry, "registry must not be null!"); + Assert.notNull(configuration, "registry must not be null!"); + + AbstractBeanDefinition ahbd = getAuditHandlerBeanDefinitionBuilder(configuration).getBeanDefinition(); + registry.registerBeanDefinition(BeanDefinitionReaderUtils.generateBeanName(ahbd, registry), ahbd); + return ahbd; + } + + /** + * Creates a {@link BeanDefinitionBuilder} to ease the definition of store specific {@link AuditingHandler} + * implementations. + * + * @param configuration, must not be {@literal null}. + * @return + */ + protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AnnotationAuditingConfiguration configuration) { + + Assert.notNull(configuration, "configuration must not be null!"); + + return configureDefaultAuditHandlerAttributes(configuration, + BeanDefinitionBuilder.rootBeanDefinition(AuditingHandler.class)); + } + + /** + * Configures the given {@link BeanDefinitionBuilder} with the default attributes from the given + * {@link AnnotationAuditingConfiguration}. + * + * @param configuration + * @param builder + * @return the builder with the audit attributes configured. + */ + protected BeanDefinitionBuilder configureDefaultAuditHandlerAttributes(AnnotationAuditingConfiguration configuration, + BeanDefinitionBuilder builder) { + + if (StringUtils.hasText(configuration.getAuditorAwareRef())) { + builder.addPropertyValue(AUDITOR_AWARE, + createLazyInitTargetSourceBeanDefinition(configuration.getAuditorAwareRef())); + } else { + builder.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE); + } + + builder.addPropertyValue(SET_DATES, configuration.isSetDates()); + builder.addPropertyValue(MODIFY_ON_CREATE, configuration.isModifyOnCreate()); + + if (StringUtils.hasText(configuration.getDateTimeProviderRef())) { + builder.addPropertyReference(DATE_TIME_PROVIDER, configuration.getDateTimeProviderRef()); + } else { + builder.addPropertyValue(DATE_TIME_PROVIDER, CurrentDateTimeProvider.INSTANCE); + } + + return builder; + } + + /** + * Retrieve auditing configuration information. + * + * @param annotationMetadata + * @return + */ + protected AnnotationAuditingConfiguration getConfiguration(AnnotationMetadata annotationMetadata) { + return new AnnotationAuditingConfigurationSupport(annotationMetadata, getAnnotation()); + } + + /** + * @return the annotation to use for the configuration of the auditing feature. + */ + protected abstract Class getAnnotation(); + + /** + * @param auditingHandlerDefinition + * @param registry + */ + protected abstract void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition, + BeanDefinitionRegistry registry); + + /** + * Registers the given {@link AbstractBeanDefinition} as a singleton infrastructure bean under the given id. + * + * @param def + * @param id + * @param registry + */ + protected void registerInfrastructureBeanWithId(AbstractBeanDefinition def, String id, BeanDefinitionRegistry registry) { + + def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + def.setScope("singleton"); + registry.registerBeanDefinition(id, def); + } + + 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(); + } +} diff --git a/src/test/java/org/springframework/data/auditing/EnableAuditing.java b/src/test/java/org/springframework/data/auditing/EnableAuditing.java new file mode 100644 index 000000000..64543e18a --- /dev/null +++ b/src/test/java/org/springframework/data/auditing/EnableAuditing.java @@ -0,0 +1,52 @@ +/* + * Copyright 2013 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.auditing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Dummy Annotation to test auditing annotation configuration. + * + * @author Thomas Darimont + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface EnableAuditing { + + /** + * @return References a bean of type AuditorAware to represent the current principal. + */ + String auditorAwareRef() default ""; + + /** + * @return Configures whether the creation and modification dates are set and defaults to {@literal true}. + */ + boolean setDates() default true; + + /** + * @return Configures whether the entity shall be marked as modified on creation and defaults to {@literal true}. + */ + boolean modifyOnCreate() default true; + + /** + * @return Configures a {@link DateTimeProvider} that allows customizing which DateTime shall be used for setting + * creation and modification dates. + */ + String dateTimeProviderRef() default ""; +} diff --git a/src/test/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupportUnitTests.java b/src/test/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupportUnitTests.java new file mode 100644 index 000000000..fb63a228b --- /dev/null +++ b/src/test/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupportUnitTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2013 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.auditing.config; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.lang.annotation.Annotation; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.data.auditing.EnableAuditing; + +/** + * @author Ranie Jade Ramiso + * @author Thomas Darimont + */ +@RunWith(MockitoJUnitRunner.class) +public class AuditingBeanDefinitionRegistrarSupportUnitTests { + + @Mock private BeanDefinitionRegistry registry; + + @Test + public void testRegisterBeanDefinitions() { + + AuditingBeanDefinitionRegistrarSupport registrar = new DummyAuditingBeanDefinitionRegistrarSupport(); + AnnotationMetadata metadata = new StandardAnnotationMetadata(SampleConfig.class); + + registrar.registerBeanDefinitions(metadata, registry); + verify(registry, times(1)).registerBeanDefinition(anyString(), any(BeanDefinition.class)); + } + + static class SampleConfig {} + + static class DummyAuditingBeanDefinitionRegistrarSupport extends AuditingBeanDefinitionRegistrarSupport { + + @Override + protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition, + BeanDefinitionRegistry registry) {} + + @Override + protected Class getAnnotation() { + return EnableAuditing.class; + } + + @Override + protected AnnotationAuditingConfiguration getConfiguration(AnnotationMetadata annotationMetadata) { + + return new AnnotationAuditingConfiguration() { + public String getAuditorAwareRef() { + return "auditor"; + } + + public boolean isSetDates() { + return true; + } + + public String getDateTimeProviderRef() { + return "dateTimeProvider"; + } + + public boolean isModifyOnCreate() { + return true; + } + }; + } + } +}