Store version value in shared db

Without storing the version values the consumers wouldn't be able
to determine the timestamp of the producer in case they are behind
since they would require the object id of the lastest version.

Change-Id: I5500a7540f20868ad45b1da2441759a29d324963
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java
index 4a844f7..95202b5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java
@@ -101,6 +101,19 @@
   }
 
   @Override
+  public <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue) {
+    if (newRefValue != null) {
+      sharedRefDBLog.info(
+          gson.toJson(
+              new SharedRefLogEntry.UpdateRef(
+                  project, refName, safeToString(currRef), safeToString(newRefValue), null, null)));
+    } else {
+      sharedRefDBLog.info(
+          gson.toJson(new SharedRefLogEntry.DeleteRef(project, refName, safeToString(currRef))));
+    }
+  }
+
+  @Override
   public void logProjectDelete(String project) {
     sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.DeleteProject(project)));
   }
@@ -119,4 +132,11 @@
   public void setLogger(Logger logger) {
     this.sharedRefDBLog = logger;
   }
+
+  private <T> String safeToString(T currRef) {
+    if (currRef == null) {
+      return "<null>";
+    }
+    return currRef.toString();
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
index 356bd69..0ea78a9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
@@ -62,7 +62,11 @@
   @Override
   public <T> boolean compareAndPut(Project.NameKey project, String refName, T currValue, T newValue)
       throws GlobalRefDbSystemError {
-    throw new UnsupportedOperationException();
+    boolean succeeded = sharedRefDb().compareAndPut(project, refName, currValue, newValue);
+    if (succeeded) {
+      sharedRefLogger.logRefUpdate(project.get(), refName, currValue, newValue);
+    }
+    return succeeded;
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
index 51f9ff0..3853af6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
@@ -21,6 +21,8 @@
 
   void logRefUpdate(String project, Ref currRef, ObjectId newRefValue);
 
+  <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue);
+
   void logProjectDelete(String project);
 
   void logLockAcquisition(String project, String refName);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
index af778ab..74dcc6e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
@@ -55,6 +55,7 @@
       ImmutableSet.of(RefUpdate.Result.NEW, RefUpdate.Result.FORCED, RefUpdate.Result.NO_CHANGE);
 
   public static final String MULTI_SITE_VERSIONING_REF = "refs/multi-site/version";
+  public static final String MULTI_SITE_VERSIONING_VALUE_REF = "refs/multi-site/version/value";
   public static final Ref NULL_PROJECT_VERSION_REF =
       new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId());
 
@@ -93,7 +94,8 @@
       return;
     }
     try {
-      updateLocalProjectVersion(projectNameKey, refReplicationDoneEvent.getRefName());
+      updateLocalProjectVersion(
+          projectNameKey, getLastRefUpdatedTimestamp(projectNameKey, refName));
     } catch (LocalProjectVersionUpdateException e) {
       logger.atSevere().withCause(e).log(
           "Issue encountered when updating version for project " + projectNameKey);
@@ -117,12 +119,20 @@
     try {
       Project.NameKey projectNameKey = refUpdatedEvent.getProjectNameKey();
       Ref currentProjectVersionRef = getLocalProjectVersionRef(refUpdatedEvent.getProjectNameKey());
+      Optional<Long> currentProjectVersionValue =
+          getLongFromObjectId(projectNameKey.get(), currentProjectVersionRef.getObjectId());
+
+      Optional<Long> lastRefUpdatedTimestamp = getLastRefUpdatedTimestamp(projectNameKey, refName);
       Optional<ObjectId> newProjectVersionObjectId =
-          updateLocalProjectVersion(projectNameKey, refUpdatedEvent.getRefName());
+          updateLocalProjectVersion(projectNameKey, lastRefUpdatedTimestamp);
 
       if (newProjectVersionObjectId.isPresent()) {
         updateSharedProjectVersion(
-            projectNameKey, currentProjectVersionRef, newProjectVersionObjectId.get());
+            projectNameKey,
+            currentProjectVersionRef,
+            newProjectVersionObjectId.get(),
+            currentProjectVersionValue,
+            lastRefUpdatedTimestamp);
       } else {
         logger.atWarning().log(
             "Ref %s not found on projet %s: skipping project version update",
@@ -157,7 +167,7 @@
       return ref != null ? ref : NULL_PROJECT_VERSION_REF;
     } catch (IOException e) {
       String message =
-          String.format("Error while getting current version for %s", projectNameKey.get());
+          String.format("Error while getting current version ref for %s", projectNameKey.get());
       logger.atSevere().withCause(e).log(message);
       throw new LocalProjectVersionUpdateException(message);
     }
@@ -189,19 +199,38 @@
   }
 
   private void updateSharedProjectVersion(
-      Project.NameKey projectNameKey, Ref currentRef, ObjectId newObjectId)
+      Project.NameKey projectNameKey,
+      Ref currentRef,
+      ObjectId newObjectId,
+      Optional<Long> currentVersion,
+      Optional<Long> newVersion)
       throws SharedProjectVersionUpdateException {
+
     logger.atFine().log(
         String.format(
             "Updating shared project version for %s. Current value %s, new value: %s",
             projectNameKey.get(), currentRef.getObjectId(), newObjectId));
     try {
       boolean success = sharedRefDb.compareAndPut(projectNameKey, currentRef, newObjectId);
-      String message =
-          String.format(
-              "Project version update failed for %s. Current value %s, new value: %s",
-              projectNameKey.get(), currentRef.getObjectId(), newObjectId);
       if (!success) {
+        String message =
+            String.format(
+                "Project version blob update failed for %s. Current value %s, new value: %s",
+                projectNameKey.get(), currentRef.getObjectId(), newObjectId);
+        logger.atSevere().log(message);
+        throw new SharedProjectVersionUpdateException(message);
+      }
+      success =
+          sharedRefDb.compareAndPut(
+              projectNameKey,
+              MULTI_SITE_VERSIONING_VALUE_REF,
+              currentVersion.map(Object::toString).orElse(null),
+              newVersion.map(Object::toString).orElse(null));
+      if (!success) {
+        String message =
+            String.format(
+                "Project version update failed for %s. Current value %s, new value: %s",
+                projectNameKey.get(), currentRef.getObjectId(), newObjectId);
         logger.atSevere().log(message);
         throw new SharedProjectVersionUpdateException(message);
       }
@@ -234,17 +263,26 @@
   }
 
   public Optional<Long> getProjectRemoteVersion(String projectName) {
-    Optional<ObjectId> remoteObjectId =
+    Optional<String> globalVersion =
         sharedRefDb.get(
-            Project.NameKey.parse(projectName), MULTI_SITE_VERSIONING_REF, ObjectId.class);
-    if (remoteObjectId.isPresent()) {
-      return getLongFromObjectId(projectName, remoteObjectId.get());
+            Project.NameKey.parse(projectName), MULTI_SITE_VERSIONING_VALUE_REF, String.class);
+    return globalVersion.flatMap(longString -> getLongValueOf(longString));
+  }
+
+  private Optional<Long> getLongValueOf(String longString) {
+    try {
+      return Optional.ofNullable(Long.parseLong(longString));
+    } catch (NumberFormatException e) {
+      logger.atSevere().withCause(e).log(
+          "Unable to parse timestamp value %s into Long", longString);
+      return Optional.empty();
     }
-    logger.atFine().log("Didn't find remote version for %s", projectName);
-    return Optional.empty();
   }
 
   private Optional<Long> getLongFromObjectId(String projectName, ObjectId objectId) {
+    if (objectId.equals(ObjectId.zeroId())) {
+      return Optional.empty();
+    }
     try (Repository repository =
         gitRepositoryManager.openRepository(Project.NameKey.parse(projectName))) {
       ObjectReader or = repository.newObjectReader();
@@ -268,8 +306,8 @@
   }
 
   private Optional<ObjectId> updateLocalProjectVersion(
-      Project.NameKey projectNameKey, String refName) throws LocalProjectVersionUpdateException {
-    Optional<Long> lastRefUpdatedTimestamp = getLastRefUpdatedTimestamp(projectNameKey, refName);
+      Project.NameKey projectNameKey, Optional<Long> lastRefUpdatedTimestamp)
+      throws LocalProjectVersionUpdateException {
     if (!lastRefUpdatedTimestamp.isPresent()) {
       return Optional.empty();
     }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
index 37d5008..047a1c5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
@@ -33,4 +33,7 @@
 
   @Override
   public void logLockRelease(String project, String refName) {}
+
+  @Override
+  public <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue) {}
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
index 2feb052..597b241 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF;
+import static com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.never;
@@ -188,18 +189,16 @@
   }
 
   @Test
-  public void getRemoteProjectVersionShouldReturnCorrectValue() throws IOException {
-    updateLocalVersion();
-    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
-    when(sharedRefDb.get(A_TEST_PROJECT_NAME_KEY, MULTI_SITE_VERSIONING_REF, ObjectId.class))
-        .thenReturn(Optional.of(ref.getObjectId()));
+  public void getRemoteProjectVersionShouldReturnCorrectValue() {
+    when(sharedRefDb.get(A_TEST_PROJECT_NAME_KEY, MULTI_SITE_VERSIONING_VALUE_REF, String.class))
+        .thenReturn(Optional.of("123"));
 
     Optional<Long> version =
         new ProjectVersionRefUpdate(repoManager, sharedRefDb)
             .getProjectRemoteVersion(A_TEST_PROJECT_NAME);
 
     assertThat(version.isPresent()).isTrue();
-    assertThat(version.get()).isEqualTo(masterCommit.getCommitTime());
+    assertThat(version.get()).isEqualTo(123L);
   }
 
   @Test