Skip to content

Commit f16f005

Browse files
GH-2564 - Add example how to combine auditing with custom callbacks.
Closes #2564
1 parent 03be6a3 commit f16f005

File tree

3 files changed

+341
-0
lines changed

3 files changed

+341
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright 2011-2022 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.neo4j.integration.imperative;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import java.util.Collection;
21+
import java.util.Collections;
22+
import java.util.Optional;
23+
import java.util.UUID;
24+
import java.util.concurrent.atomic.AtomicInteger;
25+
import java.util.stream.Stream;
26+
27+
import org.junit.jupiter.api.BeforeEach;
28+
import org.junit.jupiter.api.Test;
29+
import org.neo4j.driver.Driver;
30+
import org.neo4j.driver.Session;
31+
import org.neo4j.driver.Transaction;
32+
import org.springframework.beans.factory.annotation.Autowired;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.context.annotation.Configuration;
35+
import org.springframework.core.Ordered;
36+
import org.springframework.data.domain.AuditorAware;
37+
import org.springframework.data.neo4j.config.EnableNeo4jAuditing;
38+
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
39+
import org.springframework.data.neo4j.core.mapping.callback.AuditingBeforeBindCallback;
40+
import org.springframework.data.neo4j.core.mapping.callback.BeforeBindCallback;
41+
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
42+
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
43+
import org.springframework.data.neo4j.integration.shared.common.Book;
44+
import org.springframework.data.neo4j.integration.shared.common.Editor;
45+
import org.springframework.data.neo4j.integration.shared.common.ImmutableAuditableThing;
46+
import org.springframework.data.neo4j.repository.Neo4jRepository;
47+
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
48+
import org.springframework.data.neo4j.test.BookmarkCapture;
49+
import org.springframework.data.neo4j.test.Neo4jExtension;
50+
import org.springframework.data.neo4j.test.Neo4jImperativeTestConfiguration;
51+
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
52+
import org.springframework.transaction.PlatformTransactionManager;
53+
import org.springframework.transaction.annotation.EnableTransactionManagement;
54+
55+
/**
56+
* @author Michael J. Simons
57+
*/
58+
@Neo4jIntegrationTest
59+
public class ChainedAuditingIT {
60+
61+
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
62+
63+
@BeforeEach
64+
protected void setupData(@Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture) {
65+
try (Session session = driver.session(bookmarkCapture.createSessionConfig());
66+
Transaction transaction = session.beginTransaction()) {
67+
transaction.run("MATCH (n) detach delete n");
68+
transaction.commit();
69+
bookmarkCapture.seedWith(session.lastBookmark());
70+
}
71+
}
72+
73+
@Test
74+
void auditingCallbacksShouldBeCombinableWithOtherCallbacks(@Autowired BookRepository bookRepository) {
75+
76+
Book book = new Book("Dune");
77+
book = bookRepository.save(book);
78+
79+
assertThat(book.getCreatedAt()).isNotNull();
80+
assertThat(book.getModifiedAt()).isNotNull();
81+
assertThat(book.getCreatedBy()).isNotNull();
82+
assertThat(book.getModifiedBy()).isNotNull();
83+
84+
for (int i = 1; i <= 5; ++i) {
85+
book.setContent(String.format("Content was edited %d times", i));
86+
book = bookRepository.save(book);
87+
}
88+
89+
assertThat(book.getModifiedBy()).isEqualTo("User 6");
90+
91+
String[] names = Stream.of(5, 3, 2, 1).map(i -> "User " + i).toArray(String[]::new);
92+
Editor editor = book.getPreviousEditor();
93+
for (int i = 0; i < names.length; i++) {
94+
assertThat(editor).isNotNull();
95+
assertThat(editor.getName()).isEqualTo(names[i]);
96+
editor = editor.getPredecessor();
97+
}
98+
}
99+
100+
interface BookRepository extends Neo4jRepository<Book, UUID> {
101+
}
102+
103+
static class BookEditorHistorian implements BeforeBindCallback<Book>, Ordered {
104+
105+
@Override
106+
public Book onBeforeBind(Book entity) {
107+
if (entity.getModifiedBy() != null) {
108+
Editor previousEditor = entity.getPreviousEditor();
109+
if (previousEditor == null || !previousEditor.getName().equals(entity.getModifiedBy())) {
110+
previousEditor = new Editor(entity.getModifiedBy(), previousEditor);
111+
}
112+
entity.setPreviousEditor(previousEditor);
113+
}
114+
return entity;
115+
}
116+
117+
@Override
118+
public int getOrder() {
119+
return AuditingBeforeBindCallback.NEO4J_AUDITING_ORDER - 50;
120+
}
121+
}
122+
123+
@Configuration
124+
@EnableTransactionManagement
125+
@EnableNeo4jRepositories(considerNestedRepositories = true)
126+
@EnableNeo4jAuditing(auditorAwareRef = "auditorProvider")
127+
static class Config extends Neo4jImperativeTestConfiguration {
128+
129+
@Bean
130+
public Driver driver() {
131+
return neo4jConnectionSupport.getDriver();
132+
}
133+
134+
@Override
135+
protected Collection<String> getMappingBasePackages() {
136+
return Collections.singleton(ImmutableAuditableThing.class.getPackage().getName());
137+
}
138+
139+
@Bean
140+
public BookmarkCapture bookmarkCapture() {
141+
return new BookmarkCapture();
142+
}
143+
144+
@Override
145+
public PlatformTransactionManager transactionManager(Driver driver,
146+
DatabaseSelectionProvider databaseNameProvider) {
147+
148+
BookmarkCapture bookmarkCapture = bookmarkCapture();
149+
return new Neo4jTransactionManager(driver, databaseNameProvider,
150+
Neo4jBookmarkManager.create(bookmarkCapture));
151+
}
152+
153+
@Override
154+
public boolean isCypher5Compatible() {
155+
return neo4jConnectionSupport.isCypher5SyntaxCompatible();
156+
}
157+
158+
@Bean
159+
public AuditorAware<String> auditorProvider() {
160+
AtomicInteger state = new AtomicInteger(0);
161+
return () -> {
162+
int i = state.compareAndSet(3, 4) ? 3 : state.incrementAndGet();
163+
return Optional.of("User " + i);
164+
};
165+
}
166+
167+
@Bean
168+
public BeforeBindCallback<Book> bookEditorHistorian() {
169+
return new BookEditorHistorian();
170+
}
171+
172+
}
173+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2011-2022 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.neo4j.integration.shared.common;
17+
18+
import java.time.LocalDateTime;
19+
import java.util.UUID;
20+
21+
import org.springframework.data.annotation.CreatedBy;
22+
import org.springframework.data.annotation.CreatedDate;
23+
import org.springframework.data.annotation.LastModifiedBy;
24+
import org.springframework.data.annotation.LastModifiedDate;
25+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
26+
import org.springframework.data.neo4j.core.schema.Id;
27+
import org.springframework.data.neo4j.core.schema.Node;
28+
import org.springframework.data.neo4j.core.schema.Relationship;
29+
30+
/**
31+
* @author Michael J. Simons
32+
*/
33+
@Node
34+
public class Book {
35+
36+
@Id @GeneratedValue
37+
private UUID id;
38+
39+
private String title;
40+
41+
private String content;
42+
43+
@CreatedDate
44+
private LocalDateTime createdAt;
45+
@CreatedBy
46+
private String createdBy;
47+
@LastModifiedDate
48+
private LocalDateTime modifiedAt;
49+
@LastModifiedBy
50+
private String modifiedBy;
51+
52+
@Relationship("PREVIOUSLY_EDITED_BY")
53+
private Editor previousEditor;
54+
55+
public Book(String title) {
56+
this.title = title;
57+
}
58+
59+
public UUID getId() {
60+
return id;
61+
}
62+
63+
public String getTitle() {
64+
return title;
65+
}
66+
67+
public String getContent() {
68+
return content;
69+
}
70+
71+
public void setContent(String content) {
72+
this.content = content;
73+
}
74+
75+
public LocalDateTime getCreatedAt() {
76+
return createdAt;
77+
}
78+
79+
public void setCreatedAt(LocalDateTime createdAt) {
80+
this.createdAt = createdAt;
81+
}
82+
83+
public String getCreatedBy() {
84+
return createdBy;
85+
}
86+
87+
public void setCreatedBy(String createdBy) {
88+
this.createdBy = createdBy;
89+
}
90+
91+
public LocalDateTime getModifiedAt() {
92+
return modifiedAt;
93+
}
94+
95+
public void setModifiedAt(LocalDateTime modifiedAt) {
96+
this.modifiedAt = modifiedAt;
97+
}
98+
99+
public String getModifiedBy() {
100+
return modifiedBy;
101+
}
102+
103+
public void setModifiedBy(String modifiedBy) {
104+
this.modifiedBy = modifiedBy;
105+
}
106+
107+
public void setTitle(String title) {
108+
this.title = title;
109+
}
110+
111+
public Editor getPreviousEditor() {
112+
return previousEditor;
113+
}
114+
115+
public void setPreviousEditor(Editor previousEditor) {
116+
this.previousEditor = previousEditor;
117+
}
118+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2011-2022 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.neo4j.integration.shared.common;
17+
18+
import java.util.UUID;
19+
20+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
21+
import org.springframework.data.neo4j.core.schema.Id;
22+
import org.springframework.data.neo4j.core.schema.Node;
23+
import org.springframework.data.neo4j.core.schema.Relationship;
24+
25+
/**
26+
* @author Michael J. Simons
27+
*/
28+
@Node
29+
public class Editor {
30+
@Id @GeneratedValue
31+
private UUID id;
32+
33+
String name;
34+
35+
@Relationship("HAS_PREDECESSOR")
36+
private Editor predecessor;
37+
38+
public Editor(String name, Editor predecessor) {
39+
this.name = name;
40+
this.predecessor = predecessor;
41+
}
42+
43+
public String getName() {
44+
return name;
45+
}
46+
47+
public Editor getPredecessor() {
48+
return predecessor;
49+
}
50+
}

0 commit comments

Comments
 (0)