Skip to content

Commit 300eb31

Browse files
authored
DATAES-68 - Add support for auditing annotations.
Original PR: #400
1 parent 0b0c802 commit 300eb31

31 files changed

+1863
-45
lines changed

src/main/asciidoc/index.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ include::reference/elasticsearch-clients.adoc[]
2424
include::reference/elasticsearch-object-mapping.adoc[]
2525
include::reference/elasticsearch-operations.adoc[]
2626
include::reference/elasticsearch-repositories.adoc[]
27+
include::{spring-data-commons-docs}/auditing.adoc[]
28+
include::reference/elasticsearch-auditing.adoc[]
2729
include::reference/elasticsearch-misc.adoc[]
2830
:leveloffset: -1
2931

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
[[elasticsearch.auditing]]
2+
== Elasticsearch Auditing
3+
4+
=== Preparing entities
5+
6+
In order for the auditing code to be able to decide wether an entity instance is new, the entity must implement the `Persistable<ID>` interface which is defined as follows:
7+
8+
[source,java]
9+
----
10+
package org.springframework.data.domain;
11+
12+
import org.springframework.lang.Nullable;
13+
14+
public interface Persistable<ID> {
15+
@Nullable
16+
ID getId();
17+
18+
boolean isNew();
19+
}
20+
----
21+
22+
As the existence of an Id is not a sufficient criterion to determine if an enitity is new in Elasticsearch, additional information is necessary. One way is to use the creation-relevant auditing fields for this decision:
23+
24+
A `Person` entity might look as follows - omitting getter and setter methods for brevity:
25+
26+
[source,java]
27+
----
28+
@Document(indexName = "person")
29+
public class Person implements Persistable<Long> {
30+
@Id private Long id;
31+
private String lastName;
32+
private String firstName;
33+
@Field(type = FieldType.Date, format = DateFormat.basic_date_time)
34+
private Instant createdDate;
35+
private String createdBy
36+
@Field(type = FieldType.Date, format = DateFormat.basic_date_time)
37+
private Instant lastModifiedDate;
38+
private String lastModifiedBy;
39+
40+
public Long getId() { <1>
41+
return id;
42+
}
43+
44+
@Override
45+
public boolean isNew() {
46+
return id == null || (createdDate == null && createdBy == null); <2>
47+
}
48+
}
49+
----
50+
<1> the getter also is the required implementation from the interface
51+
<2> an object is new if it either has no `id` or none of fields containing creation attributes are set.
52+
53+
=== Activating auditing
54+
55+
After the entities have been set up and providing the `AuditorAware` the Auditing must be activated by setting the `@EnableElasticsearchAuditing` on a configuration class:
56+
57+
[source,java]
58+
----
59+
@Configuration
60+
@EnableElasticsearchRepositories
61+
@EnableElasticsearchAuditing
62+
class MyConfiguration {
63+
// configuration code
64+
}
65+
----
66+
67+
If your code contains more than one `AuditorAware` bean for different types, you must provide the name of the bean to use as an argument to the `auditorAwareRef` parameter of the
68+
`@EnableElasticsearchAuditing` annotation.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.config;
17+
18+
import static org.springframework.data.config.ParsingUtils.*;
19+
20+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
21+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
22+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
23+
import org.springframework.beans.factory.support.RootBeanDefinition;
24+
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
25+
import org.springframework.beans.factory.xml.BeanDefinitionParser;
26+
import org.springframework.beans.factory.xml.ParserContext;
27+
import org.springframework.data.auditing.config.IsNewAwareAuditingHandlerBeanDefinitionParser;
28+
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
29+
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
30+
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
31+
import org.springframework.data.repository.util.ReactiveWrappers;
32+
import org.springframework.lang.Nullable;
33+
import org.springframework.util.StringUtils;
34+
import org.w3c.dom.Element;
35+
36+
/**
37+
* {@link BeanDefinitionParser} to register a {@link AuditingEntityCallback} to transparently set auditing information
38+
* on an entity.
39+
*
40+
* @author Peter-Josef Meisch
41+
*/
42+
public class ElasticsearchAuditingBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
43+
44+
private static String MAPPING_CONTEXT_BEAN_NAME = "simpleElasticsearchMappingContext";
45+
46+
/*
47+
* (non-Javadoc)
48+
* @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#getBeanClass(org.w3c.dom.Element)
49+
*/
50+
@Override
51+
protected Class<?> getBeanClass(Element element) {
52+
return AuditingEntityCallback.class;
53+
}
54+
55+
/*
56+
* (non-Javadoc)
57+
* @see org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#shouldGenerateId()
58+
*/
59+
@Override
60+
protected boolean shouldGenerateId() {
61+
return true;
62+
}
63+
64+
/*
65+
* (non-Javadoc)
66+
* @see org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#doParse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, org.springframework.beans.factory.support.BeanDefinitionBuilder)
67+
*/
68+
@Override
69+
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
70+
71+
String mappingContextRef = element.getAttribute("mapping-context-ref");
72+
73+
if (!StringUtils.hasText(mappingContextRef)) {
74+
75+
BeanDefinitionRegistry registry = parserContext.getRegistry();
76+
77+
if (!registry.containsBeanDefinition(MAPPING_CONTEXT_BEAN_NAME)) {
78+
registry.registerBeanDefinition(MAPPING_CONTEXT_BEAN_NAME,
79+
new RootBeanDefinition(SimpleElasticsearchMappingContext.class));
80+
}
81+
82+
mappingContextRef = MAPPING_CONTEXT_BEAN_NAME;
83+
}
84+
85+
IsNewAwareAuditingHandlerBeanDefinitionParser parser = new IsNewAwareAuditingHandlerBeanDefinitionParser(
86+
mappingContextRef);
87+
parser.parse(element, parserContext);
88+
89+
AbstractBeanDefinition isNewAwareAuditingHandler = getObjectFactoryBeanDefinition(parser.getResolvedBeanName(),
90+
parserContext.extractSource(element));
91+
builder.addConstructorArgValue(isNewAwareAuditingHandler);
92+
93+
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
94+
registerReactiveAuditingEntityCallback(parserContext.getRegistry(), isNewAwareAuditingHandler,
95+
parserContext.extractSource(element));
96+
}
97+
}
98+
99+
private void registerReactiveAuditingEntityCallback(BeanDefinitionRegistry registry,
100+
AbstractBeanDefinition isNewAwareAuditingHandler, @Nullable Object source) {
101+
102+
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
103+
104+
builder.addConstructorArgValue(isNewAwareAuditingHandler);
105+
builder.getRawBeanDefinition().setSource(source);
106+
107+
registry.registerBeanDefinition(ReactiveAuditingEntityCallback.class.getName(), builder.getBeanDefinition());
108+
}
109+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.config;
17+
18+
import java.lang.annotation.Annotation;
19+
20+
import org.springframework.beans.factory.FactoryBean;
21+
import org.springframework.beans.factory.config.BeanDefinition;
22+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
23+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
24+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
25+
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
26+
import org.springframework.core.type.AnnotationMetadata;
27+
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
28+
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
29+
import org.springframework.data.auditing.config.AuditingConfiguration;
30+
import org.springframework.data.config.ParsingUtils;
31+
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
32+
import org.springframework.data.elasticsearch.core.event.AuditingEntityCallback;
33+
import org.springframework.data.elasticsearch.core.event.ReactiveAuditingEntityCallback;
34+
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
35+
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
36+
import org.springframework.data.mapping.context.MappingContext;
37+
import org.springframework.data.repository.util.ReactiveWrappers;
38+
import org.springframework.util.Assert;
39+
40+
/**
41+
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableElasticsearchAuditing} annotation.
42+
*
43+
* @author Peter-Josef Meisch
44+
* @since 4.0
45+
*/
46+
class ElasticsearchAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
47+
48+
/*
49+
* (non-Javadoc)
50+
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
51+
*/
52+
@Override
53+
protected Class<? extends Annotation> getAnnotation() {
54+
return EnableElasticsearchAuditing.class;
55+
}
56+
57+
/*
58+
* (non-Javadoc)
59+
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
60+
*/
61+
@Override
62+
protected String getAuditingHandlerBeanName() {
63+
return "elasticsearchAuditingHandler";
64+
}
65+
66+
/*
67+
* (non-Javadoc)
68+
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
69+
*/
70+
@Override
71+
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
72+
73+
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
74+
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
75+
76+
super.registerBeanDefinitions(annotationMetadata, registry);
77+
}
78+
79+
/*
80+
* (non-Javadoc)
81+
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
82+
*/
83+
@Override
84+
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
85+
86+
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
87+
88+
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class);
89+
90+
BeanDefinitionBuilder definition = BeanDefinitionBuilder
91+
.genericBeanDefinition(ElasticsearchMappingContextLookup.class);
92+
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
93+
94+
builder.addConstructorArgValue(definition.getBeanDefinition());
95+
return configureDefaultAuditHandlerAttributes(configuration, builder);
96+
}
97+
98+
/*
99+
* (non-Javadoc)
100+
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
101+
*/
102+
@Override
103+
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
104+
BeanDefinitionRegistry registry) {
105+
106+
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
107+
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
108+
109+
BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder
110+
.rootBeanDefinition(AuditingEntityCallback.class);
111+
listenerBeanDefinitionBuilder
112+
.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
113+
114+
registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(),
115+
AuditingEntityCallback.class.getName(), registry);
116+
117+
if (ReactiveWrappers.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR)) {
118+
registerReactiveAuditingEntityCallback(registry, auditingHandlerDefinition.getSource());
119+
}
120+
}
121+
122+
private void registerReactiveAuditingEntityCallback(BeanDefinitionRegistry registry, Object source) {
123+
124+
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
125+
126+
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
127+
builder.getRawBeanDefinition().setSource(source);
128+
129+
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
130+
registry);
131+
}
132+
133+
/**
134+
* Simple helper to be able to wire the {@link MappingContext} from a {@link MappingElasticsearchConverter} bean
135+
* available in the application context.
136+
*
137+
* @author Oliver Gierke
138+
*/
139+
static class ElasticsearchMappingContextLookup implements
140+
FactoryBean<MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty>> {
141+
142+
private final MappingElasticsearchConverter converter;
143+
144+
/**
145+
* Creates a new {@link ElasticsearchMappingContextLookup} for the given {@link MappingElasticsearchConverter}.
146+
*
147+
* @param converter must not be {@literal null}.
148+
*/
149+
public ElasticsearchMappingContextLookup(MappingElasticsearchConverter converter) {
150+
this.converter = converter;
151+
}
152+
153+
/*
154+
* (non-Javadoc)
155+
* @see org.springframework.beans.factory.FactoryBean#getObject()
156+
*/
157+
@Override
158+
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getObject()
159+
throws Exception {
160+
return converter.getMappingContext();
161+
}
162+
163+
/*
164+
* (non-Javadoc)
165+
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
166+
*/
167+
@Override
168+
public Class<?> getObjectType() {
169+
return MappingContext.class;
170+
}
171+
172+
/*
173+
* (non-Javadoc)
174+
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
175+
*/
176+
@Override
177+
public boolean isSingleton() {
178+
return true;
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)