Skip to content

Commit 65e3dc6

Browse files
GH-2252 - Allow seeding and retrieval of Neo4j bookmarks from the transaction managers.
This change introduces the `Neo4jBookmarksUpdatedEvent`. It will be published by both the imperative and reactive transaction managers as soon as any of them receives a new bookmark from the cluster. Applications will be able to listen to it by implementation `ApplicationListener<Neo4jBookmarksUpdatedEvent>`. The event returns an unmodifiable view of new bookmarks. In addition a new constructor has been added to the transaction managers for allowing the configuration of the bookmark system. `org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager#create(java.util.function.Supplier<java.util.Set<org.neo4j.driver.Bookmark>>)` can be used to add a supplier of bookmarks as seeding mechanism. This closes #2252.
1 parent 2faba3a commit 65e3dc6

File tree

7 files changed

+289
-10
lines changed

7 files changed

+289
-10
lines changed

src/main/asciidoc/faq/faq.adoc

+70
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,76 @@ No, you don't.
5858
SDN uses Neo4j Causal Cluster bookmarks internally without any configuration on your side required.
5959
Transactions in the same thread or the same reactive stream following each other will be able to read their previously changed values as you would expect.
6060

61+
[[faq.bookmarks]]
62+
== Can I retrieve the latest Bookmarks or seed the transaction manager?
63+
64+
As mentioned briefly in <<migrating.bookmarks>>, there is no need to configure anything with regard to bookmarks.
65+
It may however be useful to retrieve the latest bookmark the SDN transaction system received from a database.
66+
You can add a `@Bean` like `BookmarkCapture` to do this:
67+
68+
[source,java,indent=0,tabsize=4]
69+
.BookmarkCapture.java
70+
----
71+
import java.util.Set;
72+
73+
import org.neo4j.driver.Bookmark;
74+
import org.springframework.context.ApplicationListener;
75+
76+
public final class BookmarkCapture
77+
implements ApplicationListener<Neo4jBookmarksUpdatedEvent> {
78+
79+
@Override
80+
public void onApplicationEvent(Neo4jBookmarksUpdatedEvent event) {
81+
// We make sure that this event is called only once,
82+
// the thread safe application of those bookmarks is up to your system.
83+
Set<Bookmark> latestBookmarks = event.getBookmarks();
84+
}
85+
}
86+
----
87+
88+
For seeding the transaction system, a customized transactionmanager like the the following is required:
89+
90+
[source,java,indent=0,tabsize=4]
91+
.BookmarkSeedingConfig.java
92+
----
93+
import java.util.Set;
94+
import java.util.function.Supplier;
95+
96+
import org.neo4j.driver.Bookmark;
97+
import org.neo4j.driver.Driver;
98+
import org.springframework.context.annotation.Bean;
99+
import org.springframework.context.annotation.Configuration;
100+
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
101+
import org.springframework.transaction.PlatformTransactionManager;
102+
103+
@Configuration
104+
public class BookmarkSeedingConfig {
105+
106+
@Bean
107+
public PlatformTransactionManager transactionManager(
108+
Driver driver, DatabaseSelectionProvider databaseNameProvider) { // <.>
109+
110+
Supplier<Set<Bookmark>> bookmarkSupplier = () -> { // <.>
111+
Bookmark a = null;
112+
Bookmark b = null;
113+
return Set.of(a, b);
114+
};
115+
116+
Neo4jBookmarkManager bookmarkManager =
117+
Neo4jBookmarkManager.create(bookmarkSupplier); // <.>
118+
return new Neo4jTransactionManager(
119+
driver, databaseNameProvider, bookmarkManager); // <.>
120+
}
121+
}
122+
----
123+
<.> Let Spring inject those
124+
<.> This supplier can be anything that holds the latest bookmarks you want to bring into the system
125+
<.> Create the bookmark manager with it
126+
<.> Pass it on to the customized transaction manager
127+
128+
WARNING: There is *no* need to do any of these things above, unless your application has the need to access or provide
129+
this data. If in doubt, don't do neither.
130+
61131
[[faq.annotations.specific]]
62132
== Do I need to use Neo4j specific annotations?
63133

src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jBookmarkManager.java

+48-4
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@
2121
import java.util.Set;
2222
import java.util.concurrent.locks.Lock;
2323
import java.util.concurrent.locks.ReentrantReadWriteLock;
24+
import java.util.function.Supplier;
2425

26+
import org.apiguardian.api.API;
2527
import org.neo4j.driver.Bookmark;
28+
import org.springframework.context.ApplicationEventPublisher;
29+
import org.springframework.lang.Nullable;
2630

2731
/**
2832
* Responsible for storing, updating and retrieving the bookmarks of Neo4j's transaction.
@@ -31,32 +35,72 @@
3135
* @soundtrack Metallica - Death Magnetic
3236
* @since 6.0
3337
*/
34-
final class Neo4jBookmarkManager {
38+
@API(status = API.Status.STABLE, since = "6.1.1")
39+
public final class Neo4jBookmarkManager {
3540

36-
private Set<Bookmark> bookmarks = new HashSet<>();
41+
/**
42+
* @return A default bookmark manager
43+
*/
44+
public static Neo4jBookmarkManager create() {
45+
return new Neo4jBookmarkManager(null);
46+
}
47+
48+
/**
49+
* Use this factory method to add supplier of initial "seeding" bookmarks to the transaction managers
50+
* <p>
51+
* While this class will make sure that the supplier will be accessed in a thread-safe manner,
52+
* it us the callers duty to provide thread safe supplier (not changing the seed in during a call etc).
53+
* <p>
54+
*
55+
* @param bookmarksSupplier A supplier for seeding bookmarks, can be null. The supplier is free to provide different
56+
* bookmarks on each call.
57+
* @return A bookmark manager
58+
*/
59+
public static Neo4jBookmarkManager create(@Nullable Supplier<Set<Bookmark>> bookmarksSupplier) {
60+
return new Neo4jBookmarkManager(bookmarksSupplier);
61+
}
62+
63+
private final Set<Bookmark> bookmarks = new HashSet<>();
3764

3865
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
3966
private final Lock read = lock.readLock();
4067
private final Lock write = lock.writeLock();
4168

69+
private final Supplier<Set<Bookmark>> bookmarksSupplier;
70+
71+
@Nullable
72+
private ApplicationEventPublisher applicationEventPublisher;
73+
74+
private Neo4jBookmarkManager(@Nullable Supplier<Set<Bookmark>> bookmarksSupplier) {
75+
this.bookmarksSupplier = bookmarksSupplier == null ? () -> Collections.emptySet() : bookmarksSupplier;
76+
}
77+
4278
Collection<Bookmark> getBookmarks() {
4379

4480
try {
4581
read.lock();
46-
return Collections.unmodifiableSet(new HashSet<>(bookmarks));
82+
HashSet<Bookmark> bookmarksToUse = new HashSet<>(this.bookmarks);
83+
bookmarksToUse.addAll(bookmarksSupplier.get());
84+
return Collections.unmodifiableSet(bookmarksToUse);
4785
} finally {
4886
read.unlock();
4987
}
5088
}
5189

5290
void updateBookmarks(Collection<Bookmark> usedBookmarks, Bookmark lastBookmark) {
53-
5491
try {
5592
write.lock();
5693
bookmarks.removeAll(usedBookmarks);
5794
bookmarks.add(lastBookmark);
95+
if (applicationEventPublisher != null) {
96+
applicationEventPublisher.publishEvent(new Neo4jBookmarksUpdatedEvent(new HashSet<>(bookmarks)));
97+
}
5898
} finally {
5999
write.unlock();
60100
}
61101
}
102+
103+
void setApplicationEventPublisher(@Nullable ApplicationEventPublisher applicationEventPublisher) {
104+
this.applicationEventPublisher = applicationEventPublisher;
105+
}
62106
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2011-2021 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.core.transaction;
17+
18+
import java.util.Collections;
19+
import java.util.Set;
20+
21+
import org.apiguardian.api.API;
22+
import org.neo4j.driver.Bookmark;
23+
import org.springframework.context.ApplicationEvent;
24+
25+
/**
26+
* This event will be published after a Neo4j transaction manager physically committed a transaction without errors
27+
* and reveiced a new set of bookmarks from the cluster.
28+
*
29+
* @author Michael J. Simons
30+
* @soundtrack Black Sabbath - Master Of Reality
31+
* @since 6.1.1
32+
*/
33+
@API(status = API.Status.STABLE, since = "6.1.1")
34+
public final class Neo4jBookmarksUpdatedEvent extends ApplicationEvent {
35+
36+
private final Set<Bookmark> bookmarks;
37+
38+
Neo4jBookmarksUpdatedEvent(Set<Bookmark> bookmarks) {
39+
super(bookmarks);
40+
this.bookmarks = bookmarks;
41+
}
42+
43+
/**
44+
* @return An unmodifiable views of the new bookmarks.
45+
*/
46+
public Set<Bookmark> getBookmarks() {
47+
48+
return Collections.unmodifiableSet(this.bookmarks);
49+
}
50+
}

src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java

+35-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import org.neo4j.driver.Session;
2222
import org.neo4j.driver.Transaction;
2323
import org.neo4j.driver.TransactionConfig;
24+
import org.springframework.beans.BeansException;
25+
import org.springframework.context.ApplicationContext;
26+
import org.springframework.context.ApplicationContextAware;
2427
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
2528
import org.springframework.data.neo4j.core.Neo4jClient;
2629
import org.springframework.lang.Nullable;
@@ -42,7 +45,7 @@
4245
* @since 6.0
4346
*/
4447
@API(status = API.Status.STABLE, since = "6.0")
45-
public class Neo4jTransactionManager extends AbstractPlatformTransactionManager {
48+
public final class Neo4jTransactionManager extends AbstractPlatformTransactionManager implements ApplicationContextAware {
4649

4750
/**
4851
* The underlying driver, which is also the synchronisation object.
@@ -56,16 +59,46 @@ public class Neo4jTransactionManager extends AbstractPlatformTransactionManager
5659

5760
private final Neo4jBookmarkManager bookmarkManager;
5861

62+
/**
63+
* This will create a transaction manager for the default database.
64+
*
65+
* @param driver A driver instance
66+
*/
5967
public Neo4jTransactionManager(Driver driver) {
6068

6169
this(driver, DatabaseSelectionProvider.getDefaultSelectionProvider());
6270
}
6371

72+
/**
73+
* This will create a transaction manager targeting whatever the database selection provider determines.
74+
*
75+
* @param driver A driver instance
76+
* @param databaseSelectionProvider The database selection provider to determine the database in which the transactions should happen
77+
*/
6478
public Neo4jTransactionManager(Driver driver, DatabaseSelectionProvider databaseSelectionProvider) {
6579

80+
this(driver, databaseSelectionProvider, Neo4jBookmarkManager.create());
81+
}
82+
83+
/**
84+
* This constructor can be used to configure the bookmark manager being used. It is useful when you need to seed
85+
* the bookmark manager or if you want to capture new bookmarks.
86+
*
87+
* @param driver A driver instance
88+
* @param databaseSelectionProvider The database selection provider to determine the database in which the transactions should happen
89+
* @param bookmarkManager A bookmark manager
90+
*/
91+
public Neo4jTransactionManager(Driver driver, DatabaseSelectionProvider databaseSelectionProvider, Neo4jBookmarkManager bookmarkManager) {
92+
6693
this.driver = driver;
6794
this.databaseSelectionProvider = databaseSelectionProvider;
68-
this.bookmarkManager = new Neo4jBookmarkManager();
95+
this.bookmarkManager = bookmarkManager;
96+
}
97+
98+
@Override
99+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
100+
101+
this.bookmarkManager.setApplicationEventPublisher(applicationContext);
69102
}
70103

71104
/**

src/main/java/org/springframework/data/neo4j/core/transaction/ReactiveNeo4jTransactionManager.java

+35-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
import org.neo4j.driver.TransactionConfig;
2424
import org.neo4j.driver.reactive.RxSession;
2525
import org.neo4j.driver.reactive.RxTransaction;
26+
import org.springframework.beans.BeansException;
27+
import org.springframework.context.ApplicationContext;
28+
import org.springframework.context.ApplicationContextAware;
2629
import org.springframework.data.neo4j.core.DatabaseSelection;
2730
import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider;
2831
import org.springframework.lang.Nullable;
@@ -42,7 +45,7 @@
4245
* @since 6.0
4346
*/
4447
@API(status = API.Status.STABLE, since = "6.0")
45-
public class ReactiveNeo4jTransactionManager extends AbstractReactiveTransactionManager {
48+
public final class ReactiveNeo4jTransactionManager extends AbstractReactiveTransactionManager implements ApplicationContextAware {
4649

4750
/**
4851
* The underlying driver, which is also the synchronisation object.
@@ -56,15 +59,45 @@ public class ReactiveNeo4jTransactionManager extends AbstractReactiveTransaction
5659

5760
private final Neo4jBookmarkManager bookmarkManager;
5861

62+
/**
63+
* This will create a transaction manager for the default database.
64+
*
65+
* @param driver A driver instance
66+
*/
5967
public ReactiveNeo4jTransactionManager(Driver driver) {
6068
this(driver, ReactiveDatabaseSelectionProvider.getDefaultSelectionProvider());
6169
}
6270

71+
/**
72+
* This will create a transaction manager targeting whatever the database selection provider determines.
73+
*
74+
* @param driver A driver instance
75+
* @param databaseSelectionProvider The database selection provider to determine the database in which the transactions should happen
76+
*/
6377
public ReactiveNeo4jTransactionManager(Driver driver, ReactiveDatabaseSelectionProvider databaseSelectionProvider) {
6478

79+
this(driver, databaseSelectionProvider, Neo4jBookmarkManager.create());
80+
}
81+
82+
/**
83+
* This constructor can be used to configure the bookmark manager being used. It is useful when you need to seed
84+
* the bookmark manager or if you want to capture new bookmarks.
85+
*
86+
* @param driver A driver instance
87+
* @param databaseSelectionProvider The database selection provider to determine the database in which the transactions should happen
88+
* @param bookmarkManager A bookmark manager
89+
*/
90+
public ReactiveNeo4jTransactionManager(Driver driver, ReactiveDatabaseSelectionProvider databaseSelectionProvider, Neo4jBookmarkManager bookmarkManager) {
91+
6592
this.driver = driver;
6693
this.databaseSelectionProvider = databaseSelectionProvider;
67-
this.bookmarkManager = new Neo4jBookmarkManager();
94+
this.bookmarkManager = bookmarkManager;
95+
}
96+
97+
@Override
98+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
99+
100+
this.bookmarkManager.setApplicationEventPublisher(applicationContext);
68101
}
69102

70103
public static Mono<RxTransaction> retrieveReactiveTransaction(final Driver driver, final String targetDatabase) {

0 commit comments

Comments
 (0)