Make DynamoDBRefDatebase class implement ExtendedGlobalRefDatabase

ExtendedGlobalRefDatabase provides `put` function to update item
in the DynamoDB. Opposite to compareAndPut this method is not
checking current global-refdb value. `put` method should be used
to update value when atomicity doesn't have to be ensured.

Bug: Issue 297440085
Change-Id: Ic50b07d5aac0f533fd81d019b606848a522b2ed5
diff --git a/src/main/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/dynamodb/DynamoDBRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/dynamodb/DynamoDBRefDatabase.java
index f6bdddc..933fd3f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/dynamodb/DynamoDBRefDatabase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/dynamodb/DynamoDBRefDatabase.java
@@ -18,17 +18,20 @@
 import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
 import com.amazonaws.services.dynamodbv2.AmazonDynamoDBLockClient;
 import com.amazonaws.services.dynamodbv2.LockItem;
+import com.amazonaws.services.dynamodbv2.model.AttributeAction;
 import com.amazonaws.services.dynamodbv2.model.AttributeValue;
+import com.amazonaws.services.dynamodbv2.model.AttributeValueUpdate;
 import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
 import com.amazonaws.services.dynamodbv2.model.GetItemResult;
 import com.amazonaws.services.dynamodbv2.model.LockNotGrantedException;
 import com.amazonaws.services.dynamodbv2.model.UpdateItemRequest;
-import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
+import com.gerritforge.gerrit.globalrefdb.ExtendedGlobalRefDatabase;
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
 import com.google.inject.Inject;
 import java.util.Optional;
 import javax.inject.Singleton;
@@ -36,7 +39,7 @@
 import org.eclipse.jgit.lib.Ref;
 
 @Singleton
-public class DynamoDBRefDatabase implements GlobalRefDatabase {
+public class DynamoDBRefDatabase implements ExtendedGlobalRefDatabase {
 
   public static final String REF_DB_PRIMARY_KEY = "refPath";
   public static final String REF_DB_VALUE_KEY = "refValue";
@@ -146,6 +149,29 @@
   }
 
   @Override
+  public <T> void put(NameKey project, String refName, T value) throws GlobalRefDbSystemError {
+    String refPath = pathFor(project, refName);
+    String refValue =
+        Optional.ofNullable(value).map(Object::toString).orElse(ObjectId.zeroId().getName());
+    try {
+      dynamoDBClient.updateItem(
+          configuration.getRefsDbTableName(),
+          ImmutableMap.of(REF_DB_PRIMARY_KEY, new AttributeValue(refPath)),
+          ImmutableMap.of(
+              REF_DB_VALUE_KEY,
+              new AttributeValueUpdate(new AttributeValue(refValue), AttributeAction.PUT)));
+      logger.atFine().log(
+          "Updated path for project %s, path %s, value: %s", project.get(), refPath, refValue);
+    } catch (Exception e) {
+      throw new GlobalRefDbSystemError(
+          String.format(
+              "Error updating path for project %s, path %s. value: %s",
+              project.get(), refPath, refValue),
+          e);
+    }
+  }
+
+  @Override
   public AutoCloseable lockRef(Project.NameKey project, String refName)
       throws GlobalRefDbLockException {
     String refPath = pathFor(project, refName);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/dynamodb/DynamoDBRefDatabaseIT.java b/src/test/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/dynamodb/DynamoDBRefDatabaseIT.java
index b750c31..38b6dc8 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/dynamodb/DynamoDBRefDatabaseIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/validation/dfsrefdb/dynamodb/DynamoDBRefDatabaseIT.java
@@ -18,15 +18,9 @@
 import static com.google.common.truth.Truth8.assertThat;
 import static com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.Configuration.DEFAULT_LOCKS_TABLE_NAME;
 import static com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.Configuration.DEFAULT_REFS_DB_TABLE_NAME;
-import static com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.DynamoDBRefDatabase.REF_DB_PRIMARY_KEY;
-import static com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.DynamoDBRefDatabase.REF_DB_VALUE_KEY;
-import static com.googlesource.gerrit.plugins.validation.dfsrefdb.dynamodb.DynamoDBRefDatabase.pathFor;
 import static org.testcontainers.containers.localstack.LocalStackContainer.Service.DYNAMODB;
 
 import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
-import com.amazonaws.services.dynamodbv2.model.AttributeValue;
-import com.amazonaws.services.dynamodbv2.model.PutItemRequest;
-import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
 import com.google.gerrit.acceptance.TestPlugin;
 import com.google.gerrit.acceptance.WaitUtil;
@@ -167,6 +161,39 @@
   }
 
   @Test
+  public void putShouldBeSuccessfulWhenNoPreviousValueForRefExists() {
+    String refName = "refs/changes/01/01/meta";
+    String newRefValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
+
+    dynamoDBRefDatabase().put(project, refName, newRefValue);
+    Optional<String> result = dynamoDBRefDatabase().get(project, refName, String.class);
+    assertThat(result).hasValue(newRefValue);
+  }
+
+  @Test
+  public void putShouldSuccessfullyUpdateRemovedRef() {
+    String refName = "refs/changes/01/01/meta";
+    String newRefValue = null;
+
+    dynamoDBRefDatabase().put(project, refName, newRefValue);
+    Optional<String> result = dynamoDBRefDatabase().get(project, refName, String.class);
+    assertThat(result).hasValue(ObjectId.zeroId().getName());
+  }
+
+  @Test
+  public void putShouldBeSuccessfulWhenUpdatingRef() {
+    String refName = "refs/changes/01/01/meta";
+    String oldValue = "123";
+    String newValue = "345";
+    dynamoDBRefDatabase().put(project, refName, oldValue);
+
+    dynamoDBRefDatabase().put(project, refName, newValue);
+
+    Optional<String> result = dynamoDBRefDatabase().get(project, refName, String.class);
+    assertThat(result).hasValue(newValue);
+  }
+
+  @Test
   public void compareAndPutShouldSuccessfullyUpdateRemovedRef() throws Exception {
     String refName = "refs/changes/01/01/meta";
     String currentRefValue = "533d3ccf8a650fb26380faa732921a2c74924d5c";
@@ -223,16 +250,7 @@
   }
 
   private void createRefInDynamoDB(Project.NameKey project, String refPath, String refValue) {
-    dynamoDBClient()
-        .putItem(
-            new PutItemRequest()
-                .withTableName(DEFAULT_REFS_DB_TABLE_NAME)
-                .withItem(
-                    ImmutableMap.of(
-                        REF_DB_PRIMARY_KEY,
-                        new AttributeValue(pathFor(project, refPath)),
-                        REF_DB_VALUE_KEY,
-                        new AttributeValue(refValue))));
+    dynamoDBRefDatabase().put(project, refPath, refValue);
   }
 
   private Ref refOf(String refName, @Nullable String objectIdSha1) {