Merge branch 'stable-3.1'

* stable-3.1:
  Set version to 3.1.1
  Set version to 3.0.1
  Allow write of generic value in global-refdb
  Expose getter to return values stored in the SharedRefDB
  Add bazel build
  Set version to 3.1.0-rc1

Change-Id: Ied09c05097b6374311bd6c5f43dc83649badbf3d
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java
index ecaa14b..3b75019 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabase.java
@@ -15,6 +15,7 @@
 package com.gerritforge.gerrit.globalrefdb;
 
 import com.google.gerrit.entities.Project;
+import java.util.Optional;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 
@@ -56,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
@@ -81,4 +97,14 @@
    * @throws GlobalRefDbSystemError project cannot be removed due to a system error.
    */
   void remove(Project.NameKey project) throws GlobalRefDbSystemError;
+
+  /**
+   * Return value for a specific project and ref name
+   *
+   * @param project project name
+   * @param refName reference name
+   * @return {@link java.util.Optional} of the value
+   * @throws GlobalRefDbSystemError value cannot be returned due to a system error.
+   */
+  <T> Optional<T> get(Project.NameKey project, String refName) throws GlobalRefDbSystemError;
 }
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/FakeGlobalRefDatabase.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/FakeGlobalRefDatabase.java
index deed53b..40d7a8e 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/FakeGlobalRefDatabase.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/FakeGlobalRefDatabase.java
@@ -16,6 +16,7 @@
 
 import com.google.common.collect.MapMaker;
 import com.google.gerrit.entities.Project;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.Lock;
@@ -29,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
@@ -60,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);
@@ -86,6 +103,14 @@
     keyValueStore.remove(project);
   }
 
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T> Optional<T> get(Project.NameKey project, String refName)
+      throws GlobalRefDbSystemError {
+    return Optional.ofNullable((T) projectRefDb(project).get(refName))
+        .map(v -> ((AtomicReference<T>) v).get());
+  }
+
   private ConcurrentMap<String, AtomicReference<ObjectId>> projectRefDb(Project.NameKey project) {
     ConcurrentMap<String, AtomicReference<ObjectId>> projectRefDb = keyValueStore.get(project);
     if (projectRefDb == null) {
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabaseTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabaseTest.java
index f26484c..b2656bb 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabaseTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/GlobalRefDatabaseTest.java
@@ -19,6 +19,7 @@
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.entities.RefNames;
+import java.util.Optional;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import org.eclipse.jgit.lib.ObjectId;
@@ -153,6 +154,48 @@
     }
   }
 
+  @Test
+  public void shouldReturnValueInTheGlobalRefDB() {
+    objectUnderTest.compareAndPut(project, initialRef, objectId1);
+    Optional<ObjectId> o = objectUnderTest.get(project, initialRef.getName());
+    assertThat(o.isPresent()).isTrue();
+    assertThat(o.get()).isEqualTo(objectId1);
+  }
+
+  @Test
+  public void shouldReturnEmptyIfValueIsNotInTheGlobalRefDB() {
+    Optional<ObjectId> o = objectUnderTest.get(project, "nonExistentRef");
+    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);
   }