DATAMONGO-792 - Add support to configure auditing via JavaConfig.

Introduces the necessary infrastructure in form of MongoAuditingRegistrar to configure auditing with MongoDB via Annotation config. The MongoDB auditing feature can be enabled by annotating a configuration class with the EnableMongoAuditing annotation. Added section to reference documentation.

Original pull request: #94.
This commit is contained in:
Thomas Darimont
2013-11-05 19:35:08 +01:00
committed by Oliver Gierke
parent ea33e8b8c6
commit e2d0220cea
5 changed files with 383 additions and 2 deletions

View File

@@ -0,0 +1,60 @@
/*
* 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.mongodb.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.data.auditing.DateTimeProvider;
/**
* Annotation to enable auditing in MongoDB via annotation configuration.
*
* @author Thomas Darimont
*/
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MongoAuditingRegistrar.class)
public @interface EnableMongoAuditing {
/**
* @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 "";
}

View File

@@ -0,0 +1,100 @@
/*
* 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.mongodb.config;
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.auditing.config.AnnotationAuditingConfiguration;
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory;
import org.springframework.data.mongodb.core.mapping.event.AuditingEventListener;
import org.springframework.data.support.IsNewStrategyFactory;
import org.springframework.util.Assert;
/**
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableMongoAuditing} annotation.
*
* @author Thomas Darimont
*/
class MongoAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
/* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
*/
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableMongoAuditing.class;
}
/* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
Assert.notNull(annotationMetadata, "annotationMetadata must not be null!");
Assert.notNull(annotationMetadata, "registry must not be null!");
registerIsNewStrategyFactoryIfNecessary(registry);
super.registerBeanDefinitions(annotationMetadata, registry);
}
/* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AnnotationAuditingConfiguration)
*/
@Override
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AnnotationAuditingConfiguration configuration) {
Assert.notNull(configuration, "configuration must not be null!");
return configureDefaultAuditHandlerAttributes(configuration,
BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class)).addConstructorArgReference(
BeanNames.IS_NEW_STRATEGY_FACTORY);
}
/* (non-Javadoc)
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
BeanDefinitionRegistry registry) {
Assert.notNull(auditingHandlerDefinition, "auditingHandlerDefinition must not be null!");
Assert.notNull(registry, "registry must not be null!");
registerInfrastructureBeanWithId(BeanDefinitionBuilder.rootBeanDefinition(AuditingEventListener.class)
.addConstructorArgValue(auditingHandlerDefinition).getRawBeanDefinition(),
AuditingEventListener.class.getName(), registry);
}
/**
* @param registry, the {@link BeanDefinitionRegistry} to use to register an {@link IsNewStrategyFactory} to.
*/
private void registerIsNewStrategyFactoryIfNecessary(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(BeanNames.IS_NEW_STRATEGY_FACTORY)) {
registry.registerBeanDefinition(BeanNames.IS_NEW_STRATEGY_FACTORY,
BeanDefinitionBuilder.rootBeanDefinition(MappingContextIsNewStrategyFactory.class)
.addConstructorArgReference(BeanNames.MAPPING_CONTEXT).getBeanDefinition());
}
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.mongodb.config;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.mongodb.core.AuditablePerson;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.stereotype.Repository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.mongodb.MongoClient;
/**
* Integration tests for auditing via Java config.
*
* @author Thomas Darimont
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class AuditingViaJavaConfigRepositoriesTests {
@Autowired AuditablePersonRepository auditablePersonRepository;
@Autowired AuditorAware<AuditablePerson> auditorAware;
AuditablePerson auditor;
@Configuration
@EnableMongoAuditing(auditorAwareRef = "auditorProvider")
@EnableMongoRepositories(basePackageClasses = AuditablePersonRepository.class, considerNestedRepositories = true)
static class Config {
@Bean
public MongoOperations mongoTemplate() throws Exception {
return new MongoTemplate(new SimpleMongoDbFactory(new MongoClient(), "database"));
}
@Bean
public MongoMappingContext mappingContext() {
return new MongoMappingContext();
}
@Bean
public AuditorAware<AuditablePerson> auditorProvider() {
return mock(AuditorAware.class);
}
}
@Before
public void setup() {
auditablePersonRepository.deleteAll();
this.auditor = auditablePersonRepository.save(new AuditablePerson("auditor"));
}
@Test
public void basicAuditing() {
doReturn(this.auditor).when(this.auditorAware).getCurrentAuditor();
AuditablePerson user = new AuditablePerson("user");
AuditablePerson savedUser = auditablePersonRepository.save(user);
System.out.println(savedUser);
AuditablePerson createdBy = savedUser.getCreatedBy();
assertThat(createdBy, is(notNullValue()));
assertThat(createdBy.getFirstname(), is(this.auditor.getFirstname()));
}
@Repository
static interface AuditablePersonRepository extends MongoRepository<AuditablePerson, String> {}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.mongodb.core;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;
/**
* Domain class for auditing functionality testing.
*
* @author Thomas Darimont
*/
public class AuditablePerson {
@Id private String id;
private String firstname;
@DBRef @CreatedBy private AuditablePerson createdBy;
public AuditablePerson() {}
public AuditablePerson(String firstName) {
this.firstname = firstName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstName) {
this.firstname = firstName;
}
public AuditablePerson getCreatedBy() {
return createdBy;
}
public void setCreatedBy(AuditablePerson createdBy) {
this.createdBy = createdBy;
}
}

View File

@@ -571,6 +571,66 @@ public class MongoConfiguration {
</section>
</section>
<section id="mongo.auditing">
<title>General auditing configuration</title>
<para>Activating auditing functionality is just a matter of adding the
Spring Data Mongo <literal>auditing</literal> namespace element to your
configuration:</para>
<example>
<title>Activating auditing in the Spring configuration</title>
<programlisting language="xml">&lt;mongo:auditing mapping-context-ref="customMappingContext" auditor-aware-ref="yourAuditorAwareImpl"/&gt;</programlisting>
</example>
<example>
<title>Auditing via Java Config</title>
<para>Since Spring Data MongoDB 1.4 Auditing can be enabled by
annotating a configuration class with the
<classname>EnableMongoAuditing</classname> annotation.</para>
<programlisting language="java">@Configuration
@EnableMongoAuditing(auditorAwareRef="myAuditorProvider")
@EnableMongoRepositories
class Config {
@Bean
public AuditorAware&lt;AuditableUser&gt; myAuditorProvider() {
return new AuditorAwareImpl();
}
}</programlisting>
<para>Note that if you want to record information about the create-User
or modify-User, you can delcare a bean that implements the
<classname>AuditorAware</classname> interface. The bean name can also be
configured explicitly via the <code>auditorAwareRef</code> attribute of
the <classname>EnableMongoAuditing</classname> annotation, this can be
used to resolve an ambiguity. If no <code>auditorAwareRef</code> is
specified explicitly we try to discover and autowire an
<classname>AuditorAware</classname> implementation.</para>
</example>
<para>As you can see you have to provide a bean that implements the
<interfacename>AuditorAware</interfacename> interface which looks as
follows:</para>
<example>
<title><interfacename>AuditorAware</interfacename> interface</title>
<programlisting language="java">public interface AuditorAware&lt;T, ID extends Serializable&gt; {
T getCurrentAuditor();
}</programlisting>
</example>
<para>Usually you will have some kind of authentication component in your
application that tracks the user currently working with the system. This
component should be <interfacename>AuditorAware</interfacename> and thus
allow seamless tracking of the auditor.</para>
</section>
<section id="mongo-template">
<title>Introduction to MongoTemplate</title>
@@ -2465,8 +2525,8 @@ project("a","b").and("foo").as("bar") // will generate {$project: {a: 1, b: 1, b
<para>In this introductory example we want to aggregate a list of tags
to get the occurence count of a particular tag from a MongoDB
collection called <code>"tags"</code> sorted by the occurrence count in
descending order. This example demonstrates the usage of grouping,
collection called <code>"tags"</code> sorted by the occurrence count
in descending order. This example demonstrates the usage of grouping,
sorting, projections (selection) and unwinding (result
splitting).</para>