Skip to content

Commit 534ff2f

Browse files
GH-2564 - Add example how to combine auditing with custom callbacks.
Closes #2564
1 parent c734078 commit 534ff2f

File tree

3 files changed

+339
-0
lines changed

3 files changed

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