Merge branch 'stable-3.9'

* stable-3.9:
  Fix tests broken with the upgrade of Mockito framework
  Do not swallow the cause exception when repository is not found
  Fix ordering of parameters for apply objects log
  Use `ProjectCache.onCreateProject` after project creation
  Control replication `mirror` option from Docker Compose
  Update base Docker image to v3.9.0
  Respect `remote.mirror` option in DeleteRefCommand

Change-Id: I04eede3087b313fbc831beff51b4845f9ad4d5e5
diff --git a/example-setup/broker/Dockerfile b/example-setup/broker/Dockerfile
index d615273..9ed7944 100644
--- a/example-setup/broker/Dockerfile
+++ b/example-setup/broker/Dockerfile
@@ -1,4 +1,4 @@
-FROM gerritcodereview/gerrit:3.8.1-almalinux9
+FROM gerritcodereview/gerrit:3.9.0-rc4-almalinux9
 
 USER root
 
diff --git a/example-setup/broker/configs/replication.config.template b/example-setup/broker/configs/replication.config.template
index e77edeb..92047f8 100644
--- a/example-setup/broker/configs/replication.config.template
+++ b/example-setup/broker/configs/replication.config.template
@@ -14,7 +14,7 @@
     url = http://$REMOTE_URL:8080/#{name}#.git
     apiUrl = http://$REMOTE_URL:8080/
     fetch = +refs/*:refs/*
-    mirror = true
+    mirror = $MIRROR
     timeout = 60 # In seconds
     connectionTimeout = 120000 # In mseconds
     rescheduleDelay = 15
diff --git a/example-setup/broker/docker-compose.yaml b/example-setup/broker/docker-compose.yaml
index 705aea6..5d2e68a 100644
--- a/example-setup/broker/docker-compose.yaml
+++ b/example-setup/broker/docker-compose.yaml
@@ -11,6 +11,7 @@
       - BROKER_HOST=broker
       - BROKER_PORT=9092
       - REPLICATE_ON_STARTUP=false
+      - MIRROR=true
     ports:
       - "8080:8080"
       - "29418:29418"
@@ -28,6 +29,7 @@
       - BROKER_HOST=broker
       - BROKER_PORT=9092
       - REPLICATE_ON_STARTUP=true
+      - MIRROR=true
     ports:
       - "8081:8080"
       - "29419:29418"
diff --git a/example-setup/http/Dockerfile b/example-setup/http/Dockerfile
index 6f1d1f9..32a475e 100644
--- a/example-setup/http/Dockerfile
+++ b/example-setup/http/Dockerfile
@@ -1,4 +1,4 @@
-FROM gerritcodereview/gerrit:3.8.1-almalinux9
+FROM gerritcodereview/gerrit:3.9.0-rc4-almalinux9
 
 USER root
 
diff --git a/example-setup/http/configs/replication.config.template b/example-setup/http/configs/replication.config.template
index 6768146..53809ca 100644
--- a/example-setup/http/configs/replication.config.template
+++ b/example-setup/http/configs/replication.config.template
@@ -13,7 +13,7 @@
     url = http://$REMOTE_URL:8080/#{name}#.git
     apiUrl = http://$REMOTE_URL:8080
     fetch = +refs/*:refs/*
-    mirror = true
+    mirror = $MIRROR
     timeout = 60 # In seconds
     connectionTimeout = 120000 # In mseconds
     rescheduleDelay = 15
diff --git a/example-setup/http/docker-compose.yaml b/example-setup/http/docker-compose.yaml
index ccb6b86..62fed3c 100644
--- a/example-setup/http/docker-compose.yaml
+++ b/example-setup/http/docker-compose.yaml
@@ -9,6 +9,7 @@
       - REMOTE_URL=gerrit2
       - DEBUG_PORT=5005
       - REPLICATE_ON_STARTUP=false
+      - MIRROR=true
     ports:
       - "8080:8080"
       - "29418:29418"
@@ -22,6 +23,7 @@
       - REMOTE_URL=gerrit1
       - DEBUG_PORT=5006
       - REPLICATE_ON_STARTUP=true
+      - MIRROR=true
     ports:
       - "8081:8080"
       - "29419:29418"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java
index 0f7af40..9984a03 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/Source.java
@@ -906,6 +906,10 @@
     return config.replicateProjectDeletions();
   }
 
+  public boolean isMirror() {
+    return config.getRemoteConfig().isMirror();
+  }
+
   public boolean enableBatchedRefs() {
     return config.enableBatchedRefs();
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java
index d535ac9..9911070 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java
@@ -67,8 +67,8 @@
     try {
       repLog.info(
           "Apply object API from {} for {}:{} - {}",
-          resource.getNameKey(),
           input.getLabel(),
+          resource.getNameKey(),
           input.getRefName(),
           input.getRevisionData());
 
@@ -76,8 +76,8 @@
         deleteRefCommand.deleteRef(resource.getNameKey(), input.getRefName(), input.getLabel());
         repLog.info(
             "Apply object API - REF DELETED - from {} for {}:{} - {}",
-            resource.getNameKey(),
             input.getLabel(),
+            resource.getNameKey(),
             input.getRefName(),
             input.getRevisionData());
         return Response.withStatusCode(HttpServletResponse.SC_NO_CONTENT, "");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectsAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectsAction.java
index 817ea00..ffd5bfc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectsAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectsAction.java
@@ -66,8 +66,8 @@
 
       repLog.info(
           "Apply object API from {} for {}:{} - {}",
-          resource.getNameKey(),
           input.getLabel(),
+          resource.getNameKey(),
           input.getRefName(),
           Arrays.toString(input.getRevisionsData()));
 
@@ -75,8 +75,8 @@
         deleteRefCommand.deleteRef(resource.getNameKey(), input.getRefName(), input.getLabel());
         repLog.info(
             "Apply object API - REF DELETED - from {} for {}:{}",
-            resource.getNameKey(),
             input.getLabel(),
+            resource.getNameKey(),
             input.getRefName());
         return Response.withStatusCode(HttpServletResponse.SC_NO_CONTENT, "");
       }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java
index f204f89..3150fe1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java
@@ -68,6 +68,18 @@
 
   public void deleteRef(Project.NameKey name, String refName, String sourceLabel)
       throws IOException, RestApiException {
+    Source source =
+        sourcesCollection
+            .getByRemoteName(sourceLabel)
+            .orElseThrow(
+                () ->
+                    new IllegalStateException(
+                        String.format("Could not find URI for %s remote", sourceLabel)));
+    if (!source.isMirror()) {
+      repLog.info(
+          "Ignoring ref {} deletion from project {}, as mirror option is false", refName, name);
+      return;
+    }
     try {
       repLog.info("Delete ref from {} for project {}, ref name {}", sourceLabel, name, refName);
       Optional<ProjectState> projectState = projectCache.get(name);
@@ -81,13 +93,6 @@
         return;
       }
 
-      Source source =
-          sourcesCollection
-              .getByRemoteName(sourceLabel)
-              .orElseThrow(
-                  () ->
-                      new IllegalStateException(
-                          String.format("Could not find URI for %s remote", sourceLabel)));
       URIish sourceUri = source.getURI(name);
 
       try {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
index 9f48441..9afae71 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ProjectInitializationAction.java
@@ -140,7 +140,7 @@
   }
 
   public boolean initProject(String gitRepositoryName)
-      throws AuthException, PermissionBackendException {
+      throws AuthException, PermissionBackendException, IOException {
     if (initProject(gitRepositoryName, true)) {
       repLog.info("Init project API from {}", gitRepositoryName);
       return true;
@@ -166,9 +166,6 @@
         input.getRevisionsData(),
         input.getLabel(),
         input.getEventCreatedOn());
-    // XXX: The cache eviction is needed temporarily until Issue 308448333 won't be fixed.
-    // Once the fix will be in place, `onCreateProject` will take care of evicting the cache.
-    projectCache.evict(Project.nameKey(projectName));
     projectCache.onCreateProject(Project.nameKey(projectName));
     repLog.info(
         "Init project API from {} for {}:{} - {}",
@@ -180,7 +177,7 @@
   }
 
   private boolean initProject(String gitRepositoryName, boolean needsProjectReindexing)
-      throws AuthException, PermissionBackendException {
+      throws AuthException, PermissionBackendException, IOException {
     // When triggered internally(for example by consuming stream events) user is not provided
     // and internal user is returned. Project creation should be always allowed for internal user.
     if (!userProvider.get().isInternalUser()) {
@@ -195,7 +192,7 @@
     Project.NameKey projectNameKey = Project.NameKey.parse(gitRepositoryName);
     if (localFS.createProject(projectNameKey, RefNames.HEAD)) {
       if (needsProjectReindexing) {
-        projectCache.evictAndReindex(projectNameKey);
+        projectCache.onCreateProject(projectNameKey);
       }
       return true;
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/EventsBrokerMessageConsumer.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/EventsBrokerMessageConsumer.java
index a9b0a5e..a9b4945 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/EventsBrokerMessageConsumer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/EventsBrokerMessageConsumer.java
@@ -28,6 +28,7 @@
 import com.google.inject.Inject;
 import com.google.inject.name.Named;
 import com.googlesource.gerrit.plugins.replication.pull.ShutdownState;
+import java.io.IOException;
 import java.util.function.Consumer;
 
 public class EventsBrokerMessageConsumer implements Consumer<Event>, LifecycleListener {
@@ -57,7 +58,7 @@
     try {
       eventListener.fetchRefsForEvent(event);
       if (shutdownState.isShuttingDown()) stop();
-    } catch (AuthException | PermissionBackendException e) {
+    } catch (AuthException | PermissionBackendException | IOException e) {
       throw new EventRejectedException(event, e);
     }
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
index b14d4a1..4c621ac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/event/StreamEventListener.java
@@ -95,7 +95,7 @@
   public void onEvent(Event event) {
     try {
       fetchRefsForEvent(event);
-    } catch (AuthException | PermissionBackendException e) {
+    } catch (AuthException | PermissionBackendException | IOException e) {
       logger.atSevere().withCause(e).log(
           "This is the event handler of Gerrit's event-bus. It isn't"
               + "supposed to throw any exception, otherwise the other handlers "
@@ -103,7 +103,8 @@
     }
   }
 
-  public void fetchRefsForEvent(Event event) throws AuthException, PermissionBackendException {
+  public void fetchRefsForEvent(Event event)
+      throws AuthException, PermissionBackendException, IOException {
     if (instanceId.equals(event.instanceId) || !shouldReplicateProject(event)) {
       return;
     }
@@ -153,7 +154,7 @@
             projectCreatedEvent.instanceId,
             projectCreatedEvent.getProjectNameKey(),
             metrics);
-      } catch (AuthException | PermissionBackendException e) {
+      } catch (AuthException | PermissionBackendException | IOException e) {
         logger.atSevere().withCause(e).log(
             "Cannot initialise project:%s", projectCreatedEvent.projectName);
         throw e;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObject.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObject.java
index 5a4c92f..dc90c7f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObject.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObject.java
@@ -94,7 +94,7 @@
         return new RefUpdateState(refSpec.getSource(), result);
       }
     } catch (RepositoryNotFoundException e) {
-      throw new ResourceNotFoundException(IdString.fromDecoded(name.get()));
+      throw new ResourceNotFoundException(IdString.fromDecoded(name.get()), e);
     }
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
index 14447a9..90c9b9c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
@@ -26,7 +26,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 import com.google.common.base.Suppliers;
@@ -527,7 +527,7 @@
     Event event = generateBatchRefUpdateEvent("refs/multi-site/version");
     objectUnderTest.onEvent(event);
 
-    verifyZeroInteractions(wq, rd, dis, sl, fetchClientFactory, accountAttribute);
+    verifyNoInteractions(wq, rd, dis, sl, fetchClientFactory, accountAttribute);
   }
 
   @Test
@@ -541,7 +541,7 @@
     Event event = generateBatchRefUpdateEvent("refs/starred-changes/41/2941/1000000");
     objectUnderTest.onEvent(event);
 
-    verifyZeroInteractions(wq, rd, dis, sl, fetchClientFactory, accountAttribute);
+    verifyNoInteractions(wq, rd, dis, sl, fetchClientFactory, accountAttribute);
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java
index 6e40226..40912ac 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 import com.google.gerrit.entities.Project;
@@ -40,7 +41,6 @@
 import com.googlesource.gerrit.plugins.replication.pull.fetch.ApplyObject;
 import java.util.Optional;
 import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.RefUpdate.Result;
 import org.eclipse.jgit.lib.Repository;
@@ -77,7 +77,6 @@
   @Mock private RefUpdate refUpdate;
   @Mock private Repository repository;
   @Mock private Ref currentRef;
-  @Mock private RefDatabase refDb;
   @Captor ArgumentCaptor<Event> eventCaptor;
 
   private DeleteRefCommand objectUnderTest;
@@ -91,8 +90,7 @@
     when(source.getURI(TEST_PROJECT_NAME)).thenReturn(TEST_REMOTE_URI);
     when(gitManager.openRepository(any())).thenReturn(repository);
     when(repository.updateRef(any())).thenReturn(refUpdate);
-    when(repository.getRefDatabase()).thenReturn(refDb);
-    when(refDb.exactRef(anyString())).thenReturn(currentRef);
+    when(repository.exactRef(anyString())).thenReturn(currentRef);
     when(refUpdate.delete()).thenReturn(Result.FORCED);
 
     objectUnderTest =
@@ -106,6 +104,8 @@
 
   @Test
   public void shouldSendEventWhenDeletingRef() throws Exception {
+    when(source.isMirror()).thenReturn(true);
+
     objectUnderTest.deleteRef(TEST_PROJECT_NAME, TEST_REF_NAME, TEST_SOURCE_LABEL);
 
     verify(eventDispatcher).postEvent(eventCaptor.capture());
@@ -117,9 +117,18 @@
   }
 
   @Test
-  public void shouldHandleNonExistingRef() throws Exception {
-    when(refDb.exactRef(anyString())).thenReturn(null);
+  public void shouldNotSendNotSendEventWhenMirroringIsDisabled() throws Exception {
+    when(source.isMirror()).thenReturn(false);
 
+    objectUnderTest.deleteRef(TEST_PROJECT_NAME, TEST_REF_NAME, TEST_SOURCE_LABEL);
+
+    verifyNoInteractions(eventDispatcher);
+  }
+
+  @Test
+  public void shouldHandleNonExistingRef() throws Exception {
+    when(source.isMirror()).thenReturn(true);
+    when(repository.exactRef(anyString())).thenReturn(null);
     objectUnderTest.deleteRef(TEST_PROJECT_NAME, NON_EXISTING_REF_NAME, TEST_SOURCE_LABEL);
 
     verify(eventDispatcher, never()).postEvent(any());
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientWithBasicAuthenticationTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientWithBasicAuthenticationTest.java
index ebac729..165ed80 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientWithBasicAuthenticationTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientWithBasicAuthenticationTest.java
@@ -39,7 +39,7 @@
   @Before
   public void setup() throws Exception {
     when(bearerTokenProvider.get()).thenReturn(Optional.empty());
-    when(credentialProvider.supports(any()))
+    when(credentialProvider.supports(any(CredentialItem[].class)))
         .thenAnswer(
             new Answer<Boolean>() {
 
@@ -54,7 +54,7 @@
               }
             });
 
-    when(credentialProvider.get(any(), any(CredentialItem.class))).thenReturn(true);
+    when(credentialProvider.get(any(), any(CredentialItem[].class))).thenReturn(true);
     when(credentials.create(anyString())).thenReturn(credentialProvider);
     when(replicationConfig.getConfig()).thenReturn(config);
     when(config.getStringList("replication", null, "syncRefs")).thenReturn(new String[0]);