From a7d909846ca599685098548906fae501c40e9a4d Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Thu, 7 Mar 2013 22:24:11 +0800 Subject: [PATCH] DATACMNS-389 - Support for JavaConfig in auditing. Basic infrastructure for supporting annotation based auditing configuration. Introduces AuditingBeanDefinitionRegistrarSupport for store specific auditing configuration enhancements. An AuditorAware instance is wired into the Auditing handler automatically if it's present in the ApplicationContext. The auditorAwareRef attribute of the enabling annotation can be used to disambiguate in case multiple AuditorAware instances are present in the configuration. Original pull requests: #26, #53. --- .../AnnotationAuditingConfiguration.java | 48 +++++ ...nnotationAuditingConfigurationSupport.java | 74 ++++++++ ...uditingBeanDefinitionRegistrarSupport.java | 176 ++++++++++++++++++ .../data/auditing/EnableAuditing.java | 52 ++++++ ...anDefinitionRegistrarSupportUnitTests.java | 86 +++++++++ 5 files changed, 436 insertions(+) create mode 100644 src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfiguration.java create mode 100644 src/main/java/org/springframework/data/auditing/config/AnnotationAuditingConfigurationSupport.java create mode 100644 src/main/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupport.java create mode 100644 src/test/java/org/springframework/data/auditing/EnableAuditing.java create mode 100644 src/test/java/org/springframework/data/auditing/config/AuditingBeanDefinitionRegistrarSupportUnitTests.java 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; + } + }; + } + } +}