Skip to content

Commit 60880b2

Browse files
GH-2515 - Add a Noop bookmark manager.
Closes #2515.
1 parent 2995811 commit 60880b2

File tree

10 files changed

+327
-66
lines changed

10 files changed

+327
-66
lines changed

src/main/asciidoc/appendix/migrating.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ interface and its only implementation `org.springframework.data.neo4j.bookmark.C
141141
SDN uses bookmarks for all transactions, without configuration.
142142
You can remove the bean declaration of `CaffeineBookmarkManager` as well as the dependency to `com.github.ben-manes.caffeine:caffeine`.
143143

144+
If you absolutely must, you can disable the automatic bookmark management by following <<faq.bookmarks.noop, these instructions>>.
145+
144146
[[migrating.autoindex]]
145147
=== Automatic creation of constraints and indexes
146148

src/main/asciidoc/faq/faq.adoc

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ No, you don't.
458458
SDN uses Neo4j Causal Cluster bookmarks internally without any configuration on your side required.
459459
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.
460460

461-
[[faq.bookmarks]]
461+
[[faq.bookmarks.seeding]]
462462
== Can I retrieve the latest Bookmarks or seed the transaction manager?
463463

464464
As mentioned briefly in <<migrating.bookmarks>>, there is no need to configure anything with regard to bookmarks.
@@ -498,6 +498,8 @@ import org.neo4j.driver.Driver;
498498
import org.springframework.context.annotation.Bean;
499499
import org.springframework.context.annotation.Configuration;
500500
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
501+
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
502+
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
501503
import org.springframework.transaction.PlatformTransactionManager;
502504
503505
@Configuration
@@ -528,6 +530,46 @@ public class BookmarkSeedingConfig {
528530
WARNING: There is *no* need to do any of these things above, unless your application has the need to access or provide
529531
this data. If in doubt, don't do either.
530532

533+
[[faq.bookmarks.noop]]
534+
== Can I disable bookmark management?
535+
536+
We provide a Noop bookmark manager that effectively disables bookmark management.
537+
538+
WARNING: Use this bookmark manager at your own risk, it will effectively disable any bookmark management by dropping all
539+
bookmarks and never supplying any. In a cluster you will be at a high risk of experiencing stale reads. In a single
540+
instance it will most likely not make any difference.
541+
+
542+
In a cluster this can be a sensible approach only and if only you can tolerate stale reads and are not in danger of
543+
overwriting old data.
544+
545+
You need to provide the following configuration in your system and make sure that SDN uses the transaction manager:
546+
547+
[source,java,indent=0,tabsize=4]
548+
.BookmarksDisabledConfig.java
549+
----
550+
import org.neo4j.driver.Driver;
551+
import org.springframework.context.annotation.Bean;
552+
import org.springframework.context.annotation.Configuration;
553+
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
554+
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
555+
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
556+
import org.springframework.transaction.PlatformTransactionManager;
557+
558+
@Configuration
559+
public class BookmarksDisabledConfig {
560+
561+
@Bean
562+
public PlatformTransactionManager transactionManager(
563+
Driver driver, DatabaseSelectionProvider databaseNameProvider) {
564+
565+
Neo4jBookmarkManager bookmarkManager = Neo4jBookmarkManager.noop(); // <.>
566+
return new Neo4jTransactionManager(
567+
driver, databaseNameProvider, bookmarkManager);
568+
}
569+
}
570+
----
571+
<.> Get an instance of the Noop bookmark manager
572+
531573
[[faq.annotations.specific]]
532574
== Do I need to use Neo4j specific annotations?
533575

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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.core.transaction;
17+
18+
/**
19+
* A loophole for test implementations.
20+
*
21+
* @author Michael J. Simons
22+
*/
23+
non-sealed abstract class AbstractBookmarkManager implements Neo4jBookmarkManager {
24+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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.core.transaction;
17+
18+
import java.util.Collection;
19+
import java.util.Collections;
20+
import java.util.HashSet;
21+
import java.util.Set;
22+
import java.util.concurrent.locks.Lock;
23+
import java.util.concurrent.locks.ReentrantReadWriteLock;
24+
import java.util.function.Supplier;
25+
26+
import org.neo4j.driver.Bookmark;
27+
import org.springframework.context.ApplicationEventPublisher;
28+
import org.springframework.lang.Nullable;
29+
30+
/**
31+
* Default bookmark manager.
32+
*
33+
* @author Michael J. Simons
34+
* @soundtrack Helge Schneider - The Last Jazz
35+
* @since 7.0
36+
*/
37+
final class DefaultBookmarkManager extends AbstractBookmarkManager {
38+
39+
private final Set<Bookmark> bookmarks = new HashSet<>();
40+
41+
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
42+
private final Lock read = lock.readLock();
43+
private final Lock write = lock.writeLock();
44+
45+
private final Supplier<Set<Bookmark>> bookmarksSupplier;
46+
47+
@Nullable
48+
private ApplicationEventPublisher applicationEventPublisher;
49+
50+
DefaultBookmarkManager(@Nullable Supplier<Set<Bookmark>> bookmarksSupplier) {
51+
this.bookmarksSupplier = bookmarksSupplier == null ? Collections::emptySet : bookmarksSupplier;
52+
}
53+
54+
@Override
55+
public Collection<Bookmark> getBookmarks() {
56+
57+
try {
58+
read.lock();
59+
HashSet<Bookmark> bookmarksToUse = new HashSet<>(this.bookmarks);
60+
bookmarksToUse.addAll(bookmarksSupplier.get());
61+
return Collections.unmodifiableSet(bookmarksToUse);
62+
} finally {
63+
read.unlock();
64+
}
65+
}
66+
67+
@Override
68+
public void updateBookmarks(Collection<Bookmark> usedBookmarks, Bookmark lastBookmark) {
69+
70+
try {
71+
write.lock();
72+
bookmarks.removeAll(usedBookmarks);
73+
bookmarks.add(lastBookmark);
74+
if (applicationEventPublisher != null) {
75+
applicationEventPublisher.publishEvent(new Neo4jBookmarksUpdatedEvent(new HashSet<>(bookmarks)));
76+
}
77+
} finally {
78+
write.unlock();
79+
}
80+
}
81+
82+
@Override
83+
public void setApplicationEventPublisher(@Nullable ApplicationEventPublisher applicationEventPublisher) {
84+
this.applicationEventPublisher = applicationEventPublisher;
85+
}
86+
}

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

Lines changed: 43 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,7 @@
1616
package org.springframework.data.neo4j.core.transaction;
1717

1818
import java.util.Collection;
19-
import java.util.Collections;
20-
import java.util.HashSet;
2119
import java.util.Set;
22-
import java.util.concurrent.locks.Lock;
23-
import java.util.concurrent.locks.ReentrantReadWriteLock;
2420
import java.util.function.Supplier;
2521

2622
import org.apiguardian.api.API;
@@ -36,13 +32,13 @@
3632
* @since 6.0
3733
*/
3834
@API(status = API.Status.STABLE, since = "6.1.1")
39-
public final class Neo4jBookmarkManager {
35+
public sealed interface Neo4jBookmarkManager permits AbstractBookmarkManager, NoopBookmarkManager {
4036

4137
/**
42-
* @return A default bookmark manager
38+
* {@return the default bookmark manager}
4339
*/
44-
public static Neo4jBookmarkManager create() {
45-
return new Neo4jBookmarkManager(null);
40+
static Neo4jBookmarkManager create() {
41+
return new DefaultBookmarkManager(null);
4642
}
4743

4844
/**
@@ -51,55 +47,52 @@ public static Neo4jBookmarkManager create() {
5147
* While this class will make sure that the supplier will be accessed in a thread-safe manner,
5248
* it is the caller's duty to provide a thread safe supplier (not changing the seed during a call, etc.).
5349
*
54-
* @param bookmarksSupplier A supplier for seeding bookmarks, can be null. The supplier is free to provide different
55-
* bookmarks on each call.
50+
* @param bookmarksSupplier A supplier for seeding bookmarks, can be null. The supplier is free to provide different
51+
* bookmarks on each call.
5652
* @return A bookmark manager
5753
*/
58-
public static Neo4jBookmarkManager create(@Nullable Supplier<Set<Bookmark>> bookmarksSupplier) {
59-
return new Neo4jBookmarkManager(bookmarksSupplier);
54+
static Neo4jBookmarkManager create(@Nullable Supplier<Set<Bookmark>> bookmarksSupplier) {
55+
return new DefaultBookmarkManager(bookmarksSupplier);
6056
}
6157

62-
private final Set<Bookmark> bookmarks = new HashSet<>();
63-
64-
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
65-
private final Lock read = lock.readLock();
66-
private final Lock write = lock.writeLock();
67-
68-
private final Supplier<Set<Bookmark>> bookmarksSupplier;
69-
70-
@Nullable
71-
private ApplicationEventPublisher applicationEventPublisher;
72-
73-
private Neo4jBookmarkManager(@Nullable Supplier<Set<Bookmark>> bookmarksSupplier) {
74-
this.bookmarksSupplier = bookmarksSupplier == null ? () -> Collections.emptySet() : bookmarksSupplier;
58+
/**
59+
* Use this bookmark manager at your own risk, it will effectively disable any bookmark management by dropping all
60+
* bookmarks and never supplying any. In a cluster you will be at a high risk of experiencing stale reads. In a single
61+
* instance it will most likely not make any difference.
62+
* <p>
63+
* In a cluster this can be a sensible approach only and if only you can tolerate stale reads and are not in danger of
64+
* overwriting old data.
65+
*
66+
* @return A noop bookmark manager, dropping new bookmarks immediately, never supplying bookmarks.
67+
* @since 6.1.11
68+
*/
69+
@API(status = API.Status.STABLE, since = "6.1.11")
70+
static Neo4jBookmarkManager noop() {
71+
return NoopBookmarkManager.INSTANCE;
7572
}
7673

77-
Collection<Bookmark> getBookmarks() {
78-
79-
try {
80-
read.lock();
81-
HashSet<Bookmark> bookmarksToUse = new HashSet<>(this.bookmarks);
82-
bookmarksToUse.addAll(bookmarksSupplier.get());
83-
return Collections.unmodifiableSet(bookmarksToUse);
84-
} finally {
85-
read.unlock();
86-
}
87-
}
74+
/**
75+
* No need to introspect this collection ever. The Neo4j driver will together with the cluster figure out which of
76+
* the bookmarks is the most recent one.
77+
*
78+
* @return a collection of currently known bookmarks
79+
*/
80+
Collection<Bookmark> getBookmarks();
8881

89-
void updateBookmarks(Collection<Bookmark> usedBookmarks, Bookmark lastBookmark) {
90-
try {
91-
write.lock();
92-
bookmarks.removeAll(usedBookmarks);
93-
bookmarks.add(lastBookmark);
94-
if (applicationEventPublisher != null) {
95-
applicationEventPublisher.publishEvent(new Neo4jBookmarksUpdatedEvent(new HashSet<>(bookmarks)));
96-
}
97-
} finally {
98-
write.unlock();
99-
}
100-
}
82+
/**
83+
* Refreshes the bookmark manager with the {@code lastBookmark} received after the last transaction committed. The
84+
* collection of {@code usedBookmarks} should be removed from the list of known bookmarks.
85+
*
86+
* @param usedBookmarks The collection of bookmarks known prior to the end of a transaction
87+
* @param lastBookmark The bookmark received after the end of a transaction
88+
*/
89+
void updateBookmarks(Collection<Bookmark> usedBookmarks, Bookmark lastBookmark);
10190

102-
void setApplicationEventPublisher(@Nullable ApplicationEventPublisher applicationEventPublisher) {
103-
this.applicationEventPublisher = applicationEventPublisher;
91+
/**
92+
* A hook for bookmark managers supporting events.
93+
*
94+
* @param applicationEventPublisher An event publisher. If null, no events will be published.
95+
*/
96+
default void setApplicationEventPublisher(@Nullable ApplicationEventPublisher applicationEventPublisher) {
10497
}
10598
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.core.transaction;
17+
18+
import java.util.Collection;
19+
import java.util.Collections;
20+
21+
import org.neo4j.driver.Bookmark;
22+
23+
/**
24+
* A bookmark manager that drops all bookmarks and never provides any bookmarks.
25+
*
26+
* @author Michael J. Simons
27+
* @soundtrack Helge Schneider - The Last Jazz
28+
* @since 7.0
29+
*/
30+
enum NoopBookmarkManager implements Neo4jBookmarkManager {
31+
32+
INSTANCE;
33+
34+
@Override
35+
public Collection<Bookmark> getBookmarks() {
36+
return Collections.emptyList();
37+
}
38+
39+
@Override
40+
public void updateBookmarks(Collection<Bookmark> usedBookmarks, Bookmark lastBookmark) {
41+
}
42+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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.core.transaction;
17+
18+
import java.util.Collection;
19+
import java.util.Collections;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
import org.neo4j.driver.Bookmark;
24+
25+
/**
26+
* Avoids mocking / spying the thing.
27+
*
28+
* @author Michael J. Simons
29+
*/
30+
final class AssertableBookmarkManager extends AbstractBookmarkManager {
31+
32+
boolean getBookmarksCalled = false;
33+
final Map<Bookmark, Boolean> updateBookmarksCalled = new HashMap<>();
34+
35+
@Override
36+
public Collection<Bookmark> getBookmarks() {
37+
getBookmarksCalled = true;
38+
return Collections.emptyList();
39+
}
40+
41+
@Override
42+
public void updateBookmarks(Collection<Bookmark> usedBookmarks, Bookmark lastBookmark) {
43+
updateBookmarksCalled.put(lastBookmark, true);
44+
}
45+
}

0 commit comments

Comments
 (0)