Allow write of generic value in global-refdb Currently it is only possible to write ObjectId. The possibility of writing, for example, Long is needed to version the repositories. Feature: Issue 12256 Change-Id: Id782729b8d7e786211ddc95c944dbb7b00c1e027
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java index 4217751..24f86a7 100644 --- a/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java +++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java
@@ -57,6 +57,21 @@ throws GlobalRefDbSystemError; /** + * Compare a value of generic type T, and put if it is up-to-date with the current. + * + * <p>Compare and put are executed as an atomic operation. + * + * @param project project name of the ref. + * @param refName to store the value for. + * @param currValue current expected value in the DB. + * @param newValue new value to store. + * @return true if the put was successful; false otherwise. + * @throws GlobalRefDbSystemError the reference cannot be put due to a system error. + */ + <T> boolean compareAndPut(Project.NameKey project, String refName, T currValue, T newValue) + throws GlobalRefDbSystemError; + + /** * Lock a reference. * * @param project project name
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/FakeGlobalRefDatabase.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/FakeGlobalRefDatabase.java index d96f532..4d2e0b6 100644 --- a/src/test/java/com/gerritforge/gerrit/globalrefdb/FakeGlobalRefDatabase.java +++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/FakeGlobalRefDatabase.java
@@ -30,12 +30,14 @@ private ConcurrentMap<Project.NameKey, ConcurrentMap<String, AtomicReference<ObjectId>>> keyValueStore; + private ConcurrentMap<String, AtomicReference<?>> genericKeyValueStore; private ConcurrentMap<Project.NameKey, ConcurrentMap<String, AtomicReference<Lock>>> refLockStore; public FakeGlobalRefDatabase() { keyValueStore = new MapMaker().concurrencyLevel(1).makeMap(); refLockStore = new MapMaker().concurrencyLevel(1).makeMap(); + genericKeyValueStore = new MapMaker().concurrencyLevel(1).makeMap(); } @Override @@ -61,6 +63,20 @@ } @Override + @SuppressWarnings("unchecked") + public <T> boolean compareAndPut(Project.NameKey project, String refName, T currValue, T newValue) + throws GlobalRefDbSystemError { + String key = String.format("%s/%s", project.get(), refName); + AtomicReference<T> storedValue = (AtomicReference<T>) genericKeyValueStore.get(key); + if (storedValue == null) { + genericKeyValueStore.put(key, new AtomicReference<>(newValue)); + return true; + } + + return storedValue.compareAndSet(currValue, newValue); + } + + @Override public AutoCloseable lockRef(Project.NameKey project, String refName) throws GlobalRefDbLockException { ConcurrentMap<String, AtomicReference<Lock>> projectRefLock = projectRefLock(project);
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabaseTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabaseTest.java index 9e2ee81..02d060b 100644 --- a/src/test/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabaseTest.java +++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabaseTest.java
@@ -167,6 +167,34 @@ assertThat(o.isPresent()).isFalse(); } + @Test + public void shouldCreateGenericEntryInTheGlobalRefDBWhenFirstValue() { + assertThat(objectUnderTest.compareAndPut(project, refName, null, new Object())).isTrue(); + } + + @Test + public void shouldUpdateGenericEntryWithNewRef() throws Exception { + createChange(refName); + + Object object1 = new Object(); + objectUnderTest.compareAndPut(project, refName, null, object1); + + Object object2 = new Object(); + assertThat(objectUnderTest.compareAndPut(project, refName, object1, object2)).isTrue(); + } + + @Test + public void shouldRejectGenericUpdateWhenLocalRepoIsOutdated() throws Exception { + createChange(refName); + + Object object1 = new Object(); + objectUnderTest.compareAndPut(project, refName, null, object1); + + Object object2 = new Object(); + Object object3 = new Object(); + assertThat(objectUnderTest.compareAndPut(project, refName, object2, object3)).isFalse(); + } + private Ref ref(String refName, ObjectId objectId) { return new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, refName, objectId); }