Merge branch 'stable-2.16' into stable-3.0

* stable-2.16:
  Format unformatted files
  Use SharedRefDatabaseWrapper in MultisiteReplicationPushFilter
  Add Singleton scope to SharedRefDatabase
  Bind SharedRefDB as DynamicItem to allow other implementations
  Log project deletion in sharedref_log
  Log acquiring and releasing sharedref locks
  Log blob updates in sharedref_log
  Do no store refs/cache-automerge in the shared ref-db
  Stop replication when instance is not aligned with SharedRefDb
  Move DisabledMessageLogger to tests
  Clean up unused SharedRefDatabase methods
  Introduce sharedRef database log file
  Avoid direct KafkaConsumer creation

Inject SharedRefDatabaseWrapper at startup:
Since stable-3.0, validation module is installed in dbInjector
while the DynamicItem<SharedRefDatabase> is in sysInjector.
This would make DynamicItem.bind() tricky because should be defined
in sysInjector which isn't related to dbInjector.

Workaround the problem by defining optional the injector of
the DynamicItem<SharedRefDatabase> and delegate a new PluginStartup
lifecycle listener to complete the injectors once both dbInjector
and sysInjector are fully loaded.

Change-Id: I5be1200f9745805667657d376496bd064e43b37b
diff --git a/BUILD b/BUILD
index 1de15db..0e88858 100644
--- a/BUILD
+++ b/BUILD
@@ -11,7 +11,7 @@
     srcs = glob(["src/main/java/**/*.java"]),
     manifest_entries = [
         "Gerrit-PluginName: multi-site",
-        "Gerrit-Module: com.googlesource.gerrit.plugins.multisite.Module",
+        "Gerrit-Module: com.googlesource.gerrit.plugins.multisite.PluginModule",
         "Implementation-Title: multi-site plugin",
         "Implementation-URL: https://review.gerrithub.io/admin/repos/GerritForge/plugins_multi-site",
     ],
@@ -22,6 +22,7 @@
         "@curator-recipes//jar",
         "@kafka-client//jar",
         "@zookeeper//jar",
+        "//plugins/replication",
     ],
 )
 
diff --git a/README.md b/README.md
index 6e55d86..0f526a9 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,8 @@
 
 Install the multi-site plugin into the `$GERRIT_SITE/lib` directory of all
 the Gerrit servers that are part of the multi-site cluster.
+Create a symbolic link from `$GERRIT_SITE/lib/multi-site.jar` into the
+`$GERRIT_SITE/plugins`.
 
 Add the multi-site module to `$GERRIT_SITE/etc/gerrit.config` as follows:
 
diff --git a/setup_local_env/setup.sh b/setup_local_env/setup.sh
index 31b5ebf..ff75d7e 100755
--- a/setup_local_env/setup.sh
+++ b/setup_local_env/setup.sh
@@ -355,7 +355,7 @@
 	# Deploying TLS certificates
 	if [ "$HTTPS_ENABLED" = "true" ];then deploy_tls_certificates;fi
 
-	echo "Copy multi-site library"
+	echo "Copy multi-site library to lib directory"
 	cp -f $DEPLOYMENT_LOCATION/multi-site.jar $LOCATION_TEST_SITE_1/lib/multi-site.jar
 
 	echo "Copy websession-flatfile plugin"
@@ -369,6 +369,14 @@
 	# Replicating environment
 	echo "Replicating environment"
 	cp -fR $LOCATION_TEST_SITE_1/* $LOCATION_TEST_SITE_2
+
+	echo "Link replication plugin"
+	ln -s $LOCATION_TEST_SITE_1/plugins/replication.jar $LOCATION_TEST_SITE_1/lib/replication.jar
+	ln -s $LOCATION_TEST_SITE_2/plugins/replication.jar $LOCATION_TEST_SITE_2/lib/replication.jar
+
+	echo "Link multi-site library to plugin directory"
+	ln -s $LOCATION_TEST_SITE_1/lib/multi-site.jar $LOCATION_TEST_SITE_1/plugins/multi-site.jar
+	ln -s $LOCATION_TEST_SITE_2/lib/multi-site.jar $LOCATION_TEST_SITE_2/plugins/multi-site.jar
 fi
 
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
index e861fd5..4f7205d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
@@ -17,7 +17,6 @@
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.validation.ValidationModule;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.ZkValidationModule;
 
 public class GitModule extends AbstractModule {
   private final Configuration config;
@@ -31,7 +30,6 @@
   protected void configure() {
     if (config.getSharedRefDb().isEnabled()) {
       install(new ValidationModule(config));
-      install(new ZkValidationModule(config));
     }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/LibModuleLogFile.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/LibModuleLogFile.java
new file mode 100644
index 0000000..106bcde
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/LibModuleLogFile.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite;
+
+import com.google.gerrit.server.util.SystemLog;
+import org.apache.log4j.AsyncAppender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+public abstract class LibModuleLogFile {
+
+  public LibModuleLogFile(SystemLog systemLog, String logName, Layout layout) {
+    AsyncAppender asyncAppender = systemLog.createAsyncAppender(logName, layout, true, true);
+    Logger logger = LogManager.getLogger(logName);
+    logger.removeAppender(logName);
+    logger.addAppender(asyncAppender);
+    logger.setAdditivity(false);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/LockWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/LockWrapper.java
new file mode 100644
index 0000000..0e018d3
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/LockWrapper.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+public class LockWrapper implements AutoCloseable {
+  public interface Factory {
+    LockWrapper create(
+        @Assisted("project") String project,
+        @Assisted("refName") String refName,
+        @Assisted("lock") AutoCloseable lock);
+  }
+
+  private final String project;
+  private final String refName;
+  private final AutoCloseable lock;
+  private final SharedRefLogger sharedRefLogger;
+
+  @Inject
+  public LockWrapper(
+      SharedRefLogger sharedRefLogger,
+      @Assisted("project") String project,
+      @Assisted("refName") String refName,
+      @Assisted("lock") AutoCloseable lock) {
+    this.lock = lock;
+    this.sharedRefLogger = sharedRefLogger;
+    this.project = project;
+    this.refName = refName;
+  }
+
+  @Override
+  public void close() throws Exception {
+    lock.close();
+    sharedRefLogger.logLockRelease(project, refName);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java
new file mode 100644
index 0000000..b5d20e0
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java
@@ -0,0 +1,120 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.common.GitPerson;
+import com.google.gerrit.json.OutputFormat;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CommonConverters;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.util.SystemLog;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.apache.log4j.PatternLayout;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class Log4jSharedRefLogger extends LibModuleLogFile implements SharedRefLogger {
+  private static final String LOG_NAME = "sharedref_log";
+  private Logger sharedRefDBLog;
+  private final GitRepositoryManager gitRepositoryManager;
+  private static final Gson gson = OutputFormat.JSON_COMPACT.newGson();
+
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  @Inject
+  public Log4jSharedRefLogger(SystemLog systemLog, GitRepositoryManager gitRepositoryManager) {
+    super(systemLog, LOG_NAME, new PatternLayout("[%d{ISO8601}] [%t] %-5p : %m%n"));
+    this.gitRepositoryManager = gitRepositoryManager;
+    sharedRefDBLog = LoggerFactory.getLogger(LOG_NAME);
+  }
+
+  @Override
+  public void logRefUpdate(String project, Ref currRef, ObjectId newRefValue) {
+    if (!ObjectId.zeroId().equals(newRefValue)) {
+      try (Repository repository =
+              gitRepositoryManager.openRepository(new Project.NameKey(project));
+          RevWalk walk = new RevWalk(repository)) {
+        GitPerson committer = null;
+        String commitMessage = null;
+        int objectType = walk.parseAny(newRefValue).getType();
+        switch (objectType) {
+          case OBJ_COMMIT:
+            RevCommit commit = walk.parseCommit(newRefValue);
+            committer = CommonConverters.toGitPerson(commit.getCommitterIdent());
+            commitMessage = commit.getShortMessage();
+            break;
+          case OBJ_BLOB:
+            break;
+          default:
+            throw new IncorrectObjectTypeException(newRefValue, Constants.typeString(objectType));
+        }
+        sharedRefDBLog.info(
+            gson.toJson(
+                new SharedRefLogEntry.UpdateRef(
+                    project,
+                    currRef.getName(),
+                    currRef.getObjectId().getName(),
+                    newRefValue.getName(),
+                    committer,
+                    commitMessage)));
+      } catch (IOException e) {
+        logger.atSevere().withCause(e).log(
+            "Cannot log sharedRefDB interaction for ref %s on project %s",
+            currRef.getName(), project);
+      }
+    } else {
+      sharedRefDBLog.info(
+          gson.toJson(
+              new SharedRefLogEntry.DeleteRef(
+                  project, currRef.getName(), currRef.getObjectId().getName())));
+    }
+  }
+
+  @Override
+  public void logProjectDelete(String project) {
+    sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.DeleteProject(project)));
+  }
+
+  @Override
+  public void logLockAcquisition(String project, String refName) {
+    sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.LockAcquire(project, refName)));
+  }
+
+  @Override
+  public void logLockRelease(String project, String refName) {
+    sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.LockRelease(project, refName)));
+  }
+
+  @VisibleForTesting
+  public void setLogger(Logger logger) {
+    this.sharedRefDBLog = logger;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
index 00af046b..1ec499c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
@@ -16,6 +16,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.SitePaths;
@@ -23,6 +24,7 @@
 import com.google.inject.CreationException;
 import com.google.inject.Inject;
 import com.google.inject.Provides;
+import com.google.inject.Scopes;
 import com.google.inject.Singleton;
 import com.google.inject.spi.Message;
 import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
@@ -34,6 +36,8 @@
 import com.googlesource.gerrit.plugins.multisite.index.IndexModule;
 import com.googlesource.gerrit.plugins.multisite.kafka.router.KafkaForwardedEventRouterModule;
 import com.googlesource.gerrit.plugins.multisite.validation.ProjectDeletedSharedDbCleanup;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.NoopSharedRefDatabase;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.IOException;
@@ -91,6 +95,12 @@
       throw new CreationException(validationErrors);
     }
 
+    DynamicItem.itemOf(binder(), SharedRefDatabase.class);
+    DynamicItem.bind(binder(), SharedRefDatabase.class)
+        .to(NoopSharedRefDatabase.class)
+        .in(Scopes.SINGLETON);
+    log.info("Shared ref-db engine: none");
+
     listener().to(Log4jMessageLogger.class);
     bind(MessageLogger.class).to(Log4jMessageLogger.class);
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
new file mode 100644
index 0000000..22a8d2d
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.ZkValidationModule;
+
+public class PluginModule extends LifecycleModule {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private Configuration config;
+  private ZkValidationModule zkValidationModule;
+
+  @Inject
+  public PluginModule(Configuration config, ZkValidationModule zkValidationModule) {
+    this.config = config;
+    this.zkValidationModule = zkValidationModule;
+  }
+
+  @Override
+  protected void configure() {
+    listener().to(PluginStartup.class);
+
+    if (config.getSharedRefDb().isEnabled()) {
+      logger.atInfo().log("Shared ref-db engine: Zookeeper");
+      install(zkValidationModule);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginStartup.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginStartup.java
new file mode 100644
index 0000000..33b54d2
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginStartup.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+public class PluginStartup implements LifecycleListener {
+  private SharedRefDatabaseWrapper sharedRefDb;
+  private Injector injector;
+
+  @Inject
+  public PluginStartup(SharedRefDatabaseWrapper sharedRefDb, Injector injector) {
+    this.sharedRefDb = sharedRefDb;
+    this.injector = injector;
+  }
+
+  @Override
+  public void start() {
+    injector.injectMembers(sharedRefDb);
+  }
+
+  @Override
+  public void stop() {}
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
new file mode 100644
index 0000000..eda8d7c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
@@ -0,0 +1,81 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedLockException;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
+import java.io.IOException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+
+public class SharedRefDatabaseWrapper implements SharedRefDatabase {
+
+  @Inject(optional = true)
+  private DynamicItem<SharedRefDatabase> sharedRefDbDynamicItem;
+
+  private final SharedRefLogger sharedRefLogger;
+
+  @Inject
+  public SharedRefDatabaseWrapper(SharedRefLogger sharedRefLogger) {
+    this.sharedRefLogger = sharedRefLogger;
+  }
+
+  @VisibleForTesting
+  public SharedRefDatabaseWrapper(
+      DynamicItem<SharedRefDatabase> sharedRefDbDynamicItem, SharedRefLogger sharedRefLogger) {
+    this.sharedRefLogger = sharedRefLogger;
+    this.sharedRefDbDynamicItem = sharedRefDbDynamicItem;
+  }
+
+  @Override
+  public boolean isUpToDate(String project, Ref ref) throws SharedLockException {
+    return sharedRefDb().isUpToDate(project, ref);
+  }
+
+  @Override
+  public boolean compareAndPut(String project, Ref currRef, ObjectId newRefValue)
+      throws IOException {
+    boolean succeeded = sharedRefDb().compareAndPut(project, currRef, newRefValue);
+    if (succeeded) {
+      sharedRefLogger.logRefUpdate(project, currRef, newRefValue);
+    }
+    return succeeded;
+  }
+
+  @Override
+  public AutoCloseable lockRef(String project, String refName) throws SharedLockException {
+    AutoCloseable locker = sharedRefDb().lockRef(project, refName);
+    sharedRefLogger.logLockAcquisition(project, refName);
+    return locker;
+  }
+
+  @Override
+  public boolean exists(String project, String refName) {
+    return sharedRefDb().exists(project, refName);
+  }
+
+  @Override
+  public void removeProject(String project) throws IOException {
+    sharedRefDb().removeProject(project);
+    sharedRefLogger.logProjectDelete(project);
+  }
+
+  private SharedRefDatabase sharedRefDb() {
+    return sharedRefDbDynamicItem.get();
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogEntry.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogEntry.java
new file mode 100644
index 0000000..d9e762f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogEntry.java
@@ -0,0 +1,100 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.common.GitPerson;
+
+public class SharedRefLogEntry {
+
+  public enum Type {
+    UPDATE_REF,
+    DELETE_REF,
+    DELETE_PROJECT,
+    LOCK_ACQUIRE,
+    LOCK_RELEASE
+  }
+
+  public String projectName;
+  public Type type;
+
+  public static class UpdateRef extends SharedRefLogEntry {
+
+    public String refName;
+    public String oldId;
+    public String newId;
+    public GitPerson committer;
+    public String comment;
+
+    UpdateRef(
+        String projectName,
+        String refName,
+        String oldId,
+        String newId,
+        @Nullable GitPerson committer,
+        @Nullable String comment) {
+      this.type = Type.UPDATE_REF;
+      this.projectName = projectName;
+      this.refName = refName;
+      this.oldId = oldId;
+      this.newId = newId;
+      this.committer = committer;
+      this.comment = comment;
+    }
+  }
+
+  public static class DeleteProject extends SharedRefLogEntry {
+
+    DeleteProject(String projectName) {
+      this.type = Type.DELETE_PROJECT;
+      this.projectName = projectName;
+    }
+  }
+
+  public static class DeleteRef extends SharedRefLogEntry {
+
+    public String refName;
+    public String oldId;
+
+    DeleteRef(String projectName, String refName, String oldId) {
+      this.type = Type.DELETE_REF;
+      this.projectName = projectName;
+      this.refName = refName;
+      this.oldId = oldId;
+    }
+  }
+
+  public static class LockAcquire extends SharedRefLogEntry {
+
+    public String refName;
+
+    LockAcquire(String projectName, String refName) {
+      this.type = Type.LOCK_ACQUIRE;
+      this.projectName = projectName;
+      this.refName = refName;
+    }
+  }
+
+  public static class LockRelease extends SharedRefLogEntry {
+
+    public String refName;
+
+    LockRelease(String projectName, String refName) {
+      this.type = Type.LOCK_RELEASE;
+      this.projectName = projectName;
+      this.refName = refName;
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/DisabledMessageLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
similarity index 66%
copy from src/main/java/com/googlesource/gerrit/plugins/multisite/DisabledMessageLogger.java
copy to src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
index 258314f..51f9ff0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/DisabledMessageLogger.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
@@ -14,10 +14,16 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
-import com.googlesource.gerrit.plugins.multisite.kafka.consumer.SourceAwareEventWrapper;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
 
-public class DisabledMessageLogger implements MessageLogger {
+public interface SharedRefLogger {
 
-  @Override
-  public void log(Direction direction, SourceAwareEventWrapper event) {}
+  void logRefUpdate(String project, Ref currRef, ObjectId newRefValue);
+
+  void logProjectDelete(String project);
+
+  void logLockAcquisition(String project, String refName);
+
+  void logLockRelease(String project, String refName);
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/AbstractKafkaSubcriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/AbstractKafkaSubcriber.java
index 23d26ca..9a97a14 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/AbstractKafkaSubcriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/AbstractKafkaSubcriber.java
@@ -35,17 +35,16 @@
 import java.util.Collections;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.kafka.clients.consumer.Consumer;
 import org.apache.kafka.clients.consumer.ConsumerRecord;
 import org.apache.kafka.clients.consumer.ConsumerRecords;
-import org.apache.kafka.clients.consumer.KafkaConsumer;
 import org.apache.kafka.common.errors.WakeupException;
-import org.apache.kafka.common.serialization.ByteArrayDeserializer;
 import org.apache.kafka.common.serialization.Deserializer;
 
 public abstract class AbstractKafkaSubcriber implements Runnable {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  private final KafkaConsumer<byte[], byte[]> consumer;
+  private final Consumer<byte[], byte[]> consumer;
   private final ForwardedEventRouter eventRouter;
   private final DynamicSet<DroppedEventListener> droppedEventListeners;
   private final Gson gson;
@@ -58,6 +57,7 @@
 
   public AbstractKafkaSubcriber(
       KafkaConfiguration configuration,
+      KafkaConsumerFactory consumerFactory,
       Deserializer<byte[]> keyDeserializer,
       Deserializer<SourceAwareEventWrapper> valueDeserializer,
       ForwardedEventRouter eventRouter,
@@ -76,11 +76,7 @@
     final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
     try {
       Thread.currentThread().setContextClassLoader(AbstractKafkaSubcriber.class.getClassLoader());
-      this.consumer =
-          new KafkaConsumer<>(
-              configuration.kafkaSubscriber().initPropsWith(instanceId),
-              keyDeserializer,
-              new ByteArrayDeserializer());
+      this.consumer = consumerFactory.create(keyDeserializer, instanceId);
     } finally {
       Thread.currentThread().setContextClassLoader(previousClassLoader);
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventSubscriber.java
index 09938db..8c0f124 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventSubscriber.java
@@ -33,6 +33,7 @@
   @Inject
   public IndexEventSubscriber(
       KafkaConfiguration configuration,
+      KafkaConsumerFactory consumerFactory,
       Deserializer<byte[]> keyDeserializer,
       Deserializer<SourceAwareEventWrapper> valueDeserializer,
       IndexEventRouter eventRouter,
@@ -43,6 +44,7 @@
       MessageLogger msgLog) {
     super(
         configuration,
+        consumerFactory,
         keyDeserializer,
         valueDeserializer,
         eventRouter,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaCacheEvictionEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaCacheEvictionEventSubscriber.java
index ca07e78..8adc836 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaCacheEvictionEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaCacheEvictionEventSubscriber.java
@@ -33,6 +33,7 @@
   @Inject
   public KafkaCacheEvictionEventSubscriber(
       KafkaConfiguration configuration,
+      KafkaConsumerFactory consumerFactory,
       Deserializer<byte[]> keyDeserializer,
       Deserializer<SourceAwareEventWrapper> valueDeserializer,
       StreamEventRouter eventRouter,
@@ -43,6 +44,7 @@
       MessageLogger msgLog) {
     super(
         configuration,
+        consumerFactory,
         keyDeserializer,
         valueDeserializer,
         eventRouter,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaConsumerFactory.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaConsumerFactory.java
new file mode 100644
index 0000000..9a5e19f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaConsumerFactory.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.kafka.KafkaConfiguration;
+import java.util.UUID;
+import org.apache.kafka.clients.consumer.Consumer;
+import org.apache.kafka.clients.consumer.KafkaConsumer;
+import org.apache.kafka.common.serialization.ByteArrayDeserializer;
+import org.apache.kafka.common.serialization.Deserializer;
+
+@Singleton
+public class KafkaConsumerFactory {
+  private KafkaConfiguration config;
+
+  @Inject
+  public KafkaConsumerFactory(KafkaConfiguration configuration) {
+    this.config = configuration;
+  }
+
+  public Consumer<byte[], byte[]> create(Deserializer<byte[]> keyDeserializer, UUID instanceId) {
+    return new KafkaConsumer<>(
+        config.kafkaSubscriber().initPropsWith(instanceId),
+        keyDeserializer,
+        new ByteArrayDeserializer());
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectUpdateEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectUpdateEventSubscriber.java
index 2ba33e4..2c00441 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectUpdateEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectUpdateEventSubscriber.java
@@ -33,6 +33,7 @@
   @Inject
   public ProjectUpdateEventSubscriber(
       KafkaConfiguration configuration,
+      KafkaConsumerFactory consumerFactory,
       Deserializer<byte[]> keyDeserializer,
       Deserializer<SourceAwareEventWrapper> valueDeserializer,
       ProjectListUpdateRouter eventRouter,
@@ -43,6 +44,7 @@
       MessageLogger msgLog) {
     super(
         configuration,
+        consumerFactory,
         keyDeserializer,
         valueDeserializer,
         eventRouter,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventSubscriber.java
index 77f3c85..b5f2e43 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventSubscriber.java
@@ -33,6 +33,7 @@
   @Inject
   public StreamEventSubscriber(
       KafkaConfiguration configuration,
+      KafkaConsumerFactory consumerFactory,
       Deserializer<byte[]> keyDeserializer,
       Deserializer<SourceAwareEventWrapper> valueDeserializer,
       StreamEventRouter eventRouter,
@@ -43,6 +44,7 @@
       MessageLogger msgLog) {
     super(
         configuration,
+        consumerFactory,
         keyDeserializer,
         valueDeserializer,
         eventRouter,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
index 33be190..4e6bef5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
@@ -17,6 +17,8 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
+import com.googlesource.gerrit.plugins.multisite.LockWrapper;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
@@ -43,12 +45,13 @@
 
   @Inject
   public BatchRefUpdateValidator(
-      SharedRefDatabase sharedRefDb,
+      SharedRefDatabaseWrapper sharedRefDb,
       ValidationMetrics validationMetrics,
       SharedRefEnforcement refEnforcement,
+      LockWrapper.Factory lockWrapperFactory,
       @Assisted String projectName,
       @Assisted RefDatabase refDb) {
-    super(sharedRefDb, validationMetrics, refEnforcement, projectName, refDb);
+    super(sharedRefDb, validationMetrics, refEnforcement, lockWrapperFactory, projectName, refDb);
   }
 
   public void executeBatchUpdateWithValidation(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java
new file mode 100644
index 0000000..94a0a9b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java
@@ -0,0 +1,105 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.validation;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedLockException;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
+import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class MultisiteReplicationPushFilter implements ReplicationPushFilter {
+  private static final String REF_META_SUFFIX = "/meta";
+  static final String REPLICATION_LOG_NAME = "replication_log";
+  static final Logger repLog = LoggerFactory.getLogger(REPLICATION_LOG_NAME);
+
+  private final SharedRefDatabaseWrapper sharedRefDb;
+
+  @Inject
+  public MultisiteReplicationPushFilter(SharedRefDatabaseWrapper sharedRefDb) {
+    this.sharedRefDb = sharedRefDb;
+  }
+
+  @Override
+  public List<RemoteRefUpdate> filter(String projectName, List<RemoteRefUpdate> remoteUpdatesList) {
+    Set<String> outdatedChanges = new HashSet<>();
+
+    List<RemoteRefUpdate> filteredRefUpdates =
+        remoteUpdatesList.stream()
+            .filter(
+                refUpdate -> {
+                  String ref = refUpdate.getSrcRef();
+                  try {
+                    if (sharedRefDb.isUpToDate(
+                        projectName, SharedRefDatabase.newRef(ref, refUpdate.getNewObjectId()))) {
+                      return true;
+                    }
+                    repLog.warn(
+                        "{} is not up-to-date with the shared-refdb and thus will NOT BE replicated",
+                        refUpdate);
+                  } catch (SharedLockException e) {
+                    repLog.warn(
+                        "{} is locked on shared-refdb and thus will NOT BE replicated", refUpdate);
+                  }
+                  if (ref.endsWith(REF_META_SUFFIX)) {
+                    outdatedChanges.add(getRootChangeRefPrefix(ref));
+                  }
+                  return false;
+                })
+            .collect(Collectors.toList());
+
+    return filteredRefUpdates.stream()
+        .filter(
+            refUpdate -> {
+              if (outdatedChanges.contains(changePrefix(refUpdate.getSrcRef()))) {
+                repLog.warn(
+                    "{} belongs to an outdated /meta ref and thus will NOT BE replicated",
+                    refUpdate);
+                return false;
+              }
+              return true;
+            })
+        .collect(Collectors.toList());
+  }
+
+  private String changePrefix(String changeRef) {
+    if (!changeRef.startsWith("refs/changes")) {
+      return changeRef;
+    }
+    if (changeRef.endsWith(REF_META_SUFFIX)) {
+      return getRootChangeRefPrefix(changeRef);
+    }
+
+    // changeRef has the form '/refs/changes/NN/NNNN/P'
+    return changeRef.substring(0, changeRef.lastIndexOf('/'));
+  }
+
+  private String getRootChangeRefPrefix(String changeMetaRef) {
+    if (changeMetaRef.endsWith(REF_META_SUFFIX)) {
+      return changeMetaRef.substring(0, changeMetaRef.length() - REF_META_SUFFIX.length());
+    }
+
+    return changeMetaRef;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
index 7f58d39..78e1cea 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
@@ -17,19 +17,19 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import java.io.IOException;
 
 public class ProjectDeletedSharedDbCleanup implements ProjectDeletedListener {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  private final SharedRefDatabase sharedDb;
+  private final SharedRefDatabaseWrapper sharedDb;
 
   private final ValidationMetrics validationMetrics;
 
   @Inject
   public ProjectDeletedSharedDbCleanup(
-      SharedRefDatabase sharedDb, ValidationMetrics validationMetrics) {
+      SharedRefDatabaseWrapper sharedDb, ValidationMetrics validationMetrics) {
     this.sharedDb = sharedDb;
     this.validationMetrics = validationMetrics;
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
index 15e8602..82d4ff0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
@@ -20,6 +20,8 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
+import com.googlesource.gerrit.plugins.multisite.LockWrapper;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedDbSplitBrainException;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedLockException;
@@ -36,10 +38,11 @@
 public class RefUpdateValidator {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  protected final SharedRefDatabase sharedRefDb;
+  protected final SharedRefDatabaseWrapper sharedRefDb;
   protected final ValidationMetrics validationMetrics;
 
   protected final String projectName;
+  private final LockWrapper.Factory lockWrapperFactory;
   protected final RefDatabase refDb;
   protected final SharedRefEnforcement refEnforcement;
 
@@ -66,13 +69,15 @@
 
   @Inject
   public RefUpdateValidator(
-      SharedRefDatabase sharedRefDb,
+      SharedRefDatabaseWrapper sharedRefDb,
       ValidationMetrics validationMetrics,
       SharedRefEnforcement refEnforcement,
+      LockWrapper.Factory lockWrapperFactory,
       @Assisted String projectName,
       @Assisted RefDatabase refDb) {
     this.sharedRefDb = sharedRefDb;
     this.validationMetrics = validationMetrics;
+    this.lockWrapperFactory = lockWrapperFactory;
     this.refDb = refDb;
     this.projectName = projectName;
     this.refEnforcement = refEnforcement;
@@ -159,7 +164,9 @@
 
     locks.addResourceIfNotExist(
         String.format("%s-%s", projectName, refName),
-        () -> sharedRefDb.lockRef(projectName, refName));
+        () ->
+            lockWrapperFactory.create(
+                projectName, refName, sharedRefDb.lockRef(projectName, refName)));
 
     RefPair latestRefPair = getLatestLocalRef(refPair);
     if (sharedRefDb.isUpToDate(projectName, latestRefPair.compareRef)) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
index c47b4d4..1719f38 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
@@ -14,15 +14,25 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation;
 
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Scopes;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
+import com.googlesource.gerrit.plugins.multisite.LockWrapper;
+import com.googlesource.gerrit.plugins.multisite.Log4jSharedRefLogger;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
+import com.googlesource.gerrit.plugins.multisite.SharedRefLogger;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
+import com.googlesource.gerrit.plugins.replication.ReplicationExtensionPointModule;
+import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter;
 
 public class ValidationModule extends FactoryModule {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
   private final Configuration cfg;
 
   public ValidationModule(Configuration cfg) {
@@ -31,6 +41,12 @@
 
   @Override
   protected void configure() {
+    install(new ReplicationExtensionPointModule());
+
+    bind(SharedRefDatabaseWrapper.class).in(Scopes.SINGLETON);
+    bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
+    factory(LockWrapper.Factory.class);
+
     factory(MultiSiteRepository.Factory.class);
     factory(MultiSiteRefDatabase.Factory.class);
     factory(MultiSiteRefUpdate.Factory.class);
@@ -39,6 +55,9 @@
     factory(BatchRefUpdateValidator.Factory.class);
 
     bind(GitRepositoryManager.class).to(MultiSiteGitRepositoryManager.class);
+    DynamicItem.bind(binder(), ReplicationPushFilter.class)
+        .to(MultisiteReplicationPushFilter.class);
+
     if (cfg.getSharedRefDb().getEnforcementRules().isEmpty()) {
       bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON);
     } else {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabase.java
new file mode 100644
index 0000000..e40688f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabase.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+
+public class NoopSharedRefDatabase implements SharedRefDatabase {
+  @Override
+  public boolean isUpToDate(String project, Ref ref) {
+    return true;
+  }
+
+  @Override
+  public boolean compareAndPut(String project, Ref currRef, ObjectId newRefValue) {
+    return true;
+  }
+
+  @Override
+  public AutoCloseable lockRef(String project, String refName) {
+    return () -> {};
+  }
+
+  @Override
+  public boolean exists(String project, String refName) {
+    return false;
+  }
+
+  @Override
+  public void removeProject(String project) {}
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java
index cfbd783..a93efcf 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java
@@ -86,18 +86,6 @@
   }
 
   /**
-   * Utility method for new refs.
-   *
-   * @param project project name of the ref
-   * @param newRef new reference to store.
-   * @return true if the operation was successful; false otherwise.
-   * @throws IOException
-   */
-  default boolean compareAndCreate(String project, Ref newRef) throws IOException {
-    return compareAndPut(project, nullRef(newRef.getName()), newRef.getObjectId());
-  }
-
-  /**
    * Verify in shared db if Ref is the most recent
    *
    * @param project project name of the ref
@@ -129,16 +117,6 @@
   boolean compareAndPut(String project, Ref currRef, ObjectId newRefValue) throws IOException;
 
   /**
-   * Compare a reference, and delete if it matches.
-   *
-   * @param project project name of the ref
-   * @param oldRef the old reference information that was previously read.
-   * @return true if the remove was successful; false otherwise.
-   * @throws java.io.IOException the reference could not be removed due to a system error.
-   */
-  boolean compareAndRemove(String project, Ref oldRef) throws IOException;
-
-  /**
    * Lock a reference for writing.
    *
    * @param project project name
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefEnforcement.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefEnforcement.java
index a5f87b5..4de6dea 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefEnforcement.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefEnforcement.java
@@ -48,6 +48,7 @@
   default boolean isRefToBeIgnoredBySharedRefDb(String refName) {
     return refName == null
         || refName.startsWith("refs/draft-comments")
-        || (refName.startsWith("refs/changes") && !refName.endsWith("/meta"));
+        || (refName.startsWith("refs/changes") && !refName.endsWith("/meta"))
+        || refName.startsWith("refs/cache-automerge");
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java
index efbf390..1179045 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java
@@ -80,11 +80,6 @@
   }
 
   @Override
-  public boolean compareAndRemove(String project, Ref oldRef) throws IOException {
-    return compareAndPut(project, oldRef, ObjectId.zeroId());
-  }
-
-  @Override
   public void removeProject(String project) throws IOException {
     try {
       client.delete().deletingChildrenIfNeeded().forPath("/" + project);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java
index 1f41b1f..360beea 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java
@@ -14,7 +14,10 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
 
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Scopes;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.ZookeeperConfig;
 import com.googlesource.gerrit.plugins.multisite.validation.ZkConnectionConfig;
@@ -25,15 +28,17 @@
 
   private ZookeeperConfig cfg;
 
+  @Inject
   public ZkValidationModule(Configuration cfg) {
     this.cfg = new ZookeeperConfig(cfg.getMultiSiteConfig());
   }
 
   @Override
   protected void configure() {
-    bind(SharedRefDatabase.class).to(ZkSharedRefDatabase.class);
+    DynamicItem.bind(binder(), SharedRefDatabase.class)
+        .to(ZkSharedRefDatabase.class)
+        .in(Scopes.SINGLETON);
     bind(CuratorFramework.class).toInstance(cfg.buildCurator());
-
     bind(ZkConnectionConfig.class)
         .toInstance(
             new ZkConnectionConfig(cfg.buildCasRetryPolicy(), cfg.getZkInterProcessLockTimeOut()));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/DisabledMessageLogger.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/DisabledMessageLogger.java
similarity index 96%
rename from src/main/java/com/googlesource/gerrit/plugins/multisite/DisabledMessageLogger.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/DisabledMessageLogger.java
index 258314f..a558be9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/DisabledMessageLogger.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/DisabledMessageLogger.java
@@ -15,7 +15,9 @@
 package com.googlesource.gerrit.plugins.multisite;
 
 import com.googlesource.gerrit.plugins.multisite.kafka.consumer.SourceAwareEventWrapper;
+import org.junit.Ignore;
 
+@Ignore
 public class DisabledMessageLogger implements MessageLogger {
 
   @Override
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
index bf5fc44..5c18e4f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
@@ -18,7 +18,9 @@
 import static junit.framework.TestCase.assertFalse;
 import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
 
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.metrics.DisabledMetricMaker;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
@@ -54,7 +56,7 @@
   private RevCommit B;
 
   ZookeeperTestContainerSupport zookeeperContainer;
-  ZkSharedRefDatabase zkSharedRefDatabase;
+  SharedRefDatabaseWrapper zkSharedRefDatabase;
 
   @Before
   public void setup() throws Exception {
@@ -79,11 +81,15 @@
     int NUMBER_OF_RETRIES = 5;
 
     zkSharedRefDatabase =
-        new ZkSharedRefDatabase(
-            zookeeperContainer.getCurator(),
-            new ZkConnectionConfig(
-                new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
-                TRANSACTION_LOCK_TIMEOUT));
+        new SharedRefDatabaseWrapper(
+            DynamicItem.itemOf(
+                SharedRefDatabase.class,
+                new ZkSharedRefDatabase(
+                    zookeeperContainer.getCurator(),
+                    new ZkConnectionConfig(
+                        new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
+                        TRANSACTION_LOCK_TIMEOUT))),
+            new DisabledSharedRefLogger());
   }
 
   @Test
@@ -145,6 +151,7 @@
         zkSharedRefDatabase,
         new ValidationMetrics(new DisabledMetricMaker()),
         sharedRefEnforcement,
+        new DummyLockWrapper(),
         projectName,
         diskRepo.getRefDatabase());
   }
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
new file mode 100644
index 0000000..37d5008
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.validation;
+
+import com.googlesource.gerrit.plugins.multisite.SharedRefLogger;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.junit.Ignore;
+
+@Ignore
+public class DisabledSharedRefLogger implements SharedRefLogger {
+
+  @Override
+  public void logRefUpdate(String project, Ref currRef, ObjectId newRefValue) {}
+
+  @Override
+  public void logProjectDelete(String project) {}
+
+  @Override
+  public void logLockAcquisition(String project, String refName) {}
+
+  @Override
+  public void logLockRelease(String project, String refName) {}
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DummyLockWrapper.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DummyLockWrapper.java
new file mode 100644
index 0000000..1ff0429
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DummyLockWrapper.java
@@ -0,0 +1,13 @@
+package com.googlesource.gerrit.plugins.multisite.validation;
+
+import com.googlesource.gerrit.plugins.multisite.LockWrapper;
+import org.junit.Ignore;
+
+@Ignore
+public class DummyLockWrapper implements LockWrapper.Factory {
+
+  @Override
+  public LockWrapper create(String project, String refName, AutoCloseable lock) {
+    return new LockWrapper(new DisabledSharedRefLogger(), project, refName, lock);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/Log4jSharedRefLoggerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/Log4jSharedRefLoggerTest.java
new file mode 100644
index 0000000..c6f0fc1
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/Log4jSharedRefLoggerTest.java
@@ -0,0 +1,167 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.validation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.json.OutputFormat;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.notedb.Sequences;
+import com.google.gerrit.server.util.SystemLog;
+import com.google.gson.Gson;
+import com.googlesource.gerrit.plugins.multisite.Log4jSharedRefLogger;
+import com.googlesource.gerrit.plugins.multisite.SharedRefLogEntry;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.WriterAppender;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Log4jSharedRefLoggerTest extends AbstractDaemonTest {
+
+  private static final Gson gson = OutputFormat.JSON_COMPACT.newGson();
+  private StringWriter logWriter;
+  private Log4jSharedRefLogger log4jSharedRefLogger;
+
+  @Before
+  public void setUp() throws IOException {
+    this.logWriter = new StringWriter();
+    this.log4jSharedRefLogger = newLog4jSharedRefLogger();
+  }
+
+  @Test
+  public void shouldLogProjectDeletion() {
+    log4jSharedRefLogger.logProjectDelete(project.get());
+
+    SharedRefLogEntry.DeleteProject gotLogEntry =
+        gson.fromJson(logWriter.toString(), SharedRefLogEntry.DeleteProject.class);
+
+    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.DELETE_PROJECT);
+    assertThat(gotLogEntry.projectName).isEqualTo(project.get());
+  }
+
+  @Test
+  public void shouldLogUpdateRef() throws Exception {
+    final String refName = "refs/remotes/origin/master";
+    Ref currRef = repo().exactRef(refName);
+    PushOneCommit.Result result = pushTo(refName);
+    ObjectId newRefValue = result.getCommit().toObjectId();
+
+    log4jSharedRefLogger.logRefUpdate(project.get(), currRef, newRefValue);
+
+    SharedRefLogEntry.UpdateRef gotLogEntry =
+        gson.fromJson(logWriter.toString(), SharedRefLogEntry.UpdateRef.class);
+
+    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.UPDATE_REF);
+    assertThat(gotLogEntry.projectName).isEqualTo(project.get());
+    assertThat(gotLogEntry.refName).isEqualTo(refName);
+    assertThat(gotLogEntry.oldId).isEqualTo(currRef.getObjectId().getName());
+    assertThat(gotLogEntry.newId).isEqualTo(newRefValue.getName());
+    assertThat(gotLogEntry.comment).isNotNull();
+    assertThat(gotLogEntry.committer).isNotNull();
+  }
+
+  @Test
+  public void shouldLogDeleteRef() throws Exception {
+    final String refName = "refs/remotes/origin/master";
+    Ref currRef = repo().exactRef(refName);
+
+    log4jSharedRefLogger.logRefUpdate(project.get(), currRef, ObjectId.zeroId());
+
+    SharedRefLogEntry.DeleteRef gotLogEntry =
+        gson.fromJson(logWriter.toString(), SharedRefLogEntry.DeleteRef.class);
+
+    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.DELETE_REF);
+    assertThat(gotLogEntry.projectName).isEqualTo(project.get());
+    assertThat(gotLogEntry.refName).isEqualTo(refName);
+    assertThat(gotLogEntry.oldId).isEqualTo(currRef.getObjectId().getName());
+  }
+
+  @Test
+  public void shouldLogBlobRefs() throws Exception {
+    Repository allUsersRepo = repoManager.openRepository(allUsers);
+    String blobRefName = RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS;
+    Ref currRef = allUsersRepo.exactRef(blobRefName);
+    log4jSharedRefLogger.logRefUpdate(allUsers.get(), currRef, currRef.getObjectId());
+
+    SharedRefLogEntry.UpdateRef gotLogEntry =
+        gson.fromJson(logWriter.toString(), SharedRefLogEntry.UpdateRef.class);
+
+    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.UPDATE_REF);
+    assertThat(gotLogEntry.projectName).isEqualTo(allUsers.get());
+    assertThat(gotLogEntry.refName).isEqualTo(blobRefName);
+    assertThat(gotLogEntry.oldId).isEqualTo(currRef.getObjectId().getName());
+    assertThat(gotLogEntry.newId).isEqualTo(currRef.getObjectId().getName());
+    assertThat(gotLogEntry.comment).isNull();
+    assertThat(gotLogEntry.committer).isNull();
+  }
+
+  @Test
+  public void shouldLogLockAcquisition() {
+    String refName = "refs/foo/bar";
+    log4jSharedRefLogger.logLockAcquisition(project.get(), refName);
+
+    SharedRefLogEntry.LockAcquire gotLogEntry =
+        gson.fromJson(logWriter.toString(), SharedRefLogEntry.LockAcquire.class);
+
+    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.LOCK_ACQUIRE);
+    assertThat(gotLogEntry.projectName).isEqualTo(project.get());
+    assertThat(gotLogEntry.refName).isEqualTo(refName);
+  }
+
+  @Test
+  public void shouldLogLockRelease() {
+    String refName = "refs/foo/bar";
+    log4jSharedRefLogger.logLockRelease(project.get(), refName);
+
+    SharedRefLogEntry.LockAcquire gotLogEntry =
+        gson.fromJson(logWriter.toString(), SharedRefLogEntry.LockAcquire.class);
+
+    assertThat(gotLogEntry.type).isEqualTo(SharedRefLogEntry.Type.LOCK_RELEASE);
+    assertThat(gotLogEntry.projectName).isEqualTo(project.get());
+    assertThat(gotLogEntry.refName).isEqualTo(refName);
+  }
+
+  private Log4jSharedRefLogger newLog4jSharedRefLogger() throws IOException {
+    final Log4jSharedRefLogger log4jSharedRefLogger =
+        new Log4jSharedRefLogger(new SystemLog(new SitePaths(newPath()), baseConfig), repoManager);
+    log4jSharedRefLogger.setLogger(logWriterLogger());
+    return log4jSharedRefLogger;
+  }
+
+  private Logger logWriterLogger() {
+    org.apache.log4j.Logger logger = LogManager.getLogger("logWriterLogger");
+    logger.addAppender(new WriterAppender(new PatternLayout("%m"), logWriter));
+    return LoggerFactory.getLogger("logWriterLogger");
+  }
+
+  private static Path newPath() throws IOException {
+    Path tmp = Files.createTempFile("gerrit_test_", "_site");
+    Files.deleteIfExists(tmp);
+    return tmp;
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
index 9acedef..730e558 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
@@ -23,8 +23,8 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
 import java.io.IOException;
 import java.util.Collections;
@@ -48,7 +48,7 @@
 @RunWith(MockitoJUnitRunner.class)
 public class MultiSiteBatchRefUpdateTest implements RefFixture {
 
-  @Mock SharedRefDatabase sharedRefDb;
+  @Mock SharedRefDatabaseWrapper sharedRefDb;
   @Mock BatchRefUpdate batchRefUpdate;
   @Mock RefDatabase refDatabase;
   @Mock RevWalk revWalk;
@@ -155,6 +155,7 @@
                 sharedRefDb,
                 validationMetrics,
                 new DefaultSharedRefEnforcement(),
+                new DummyLockWrapper(),
                 projectName,
                 refDb);
           }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
index 8dcaf3a..a9d8e76 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
@@ -21,9 +21,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.Factory;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefUpdateStub;
 import java.io.IOException;
@@ -46,7 +46,7 @@
 // working, and the other way around
 public class MultiSiteRefUpdateTest implements RefFixture {
 
-  @Mock SharedRefDatabase sharedRefDb;
+  @Mock SharedRefDatabaseWrapper sharedRefDb;
   @Mock ValidationMetrics validationMetrics;
   @Mock RefDatabase refDb;
 
@@ -197,6 +197,7 @@
                     sharedRefDb,
                     validationMetrics,
                     new DefaultSharedRefEnforcement(),
+                    new DummyLockWrapper(),
                     projectName,
                     refDb);
             return RefUpdateValidator;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
index 6fa7ed4..f0677b8 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedDbSplitBrainException;
@@ -44,7 +45,7 @@
   private static final DefaultSharedRefEnforcement defaultRefEnforcement =
       new DefaultSharedRefEnforcement();
 
-  @Mock SharedRefDatabase sharedRefDb;
+  @Mock SharedRefDatabaseWrapper sharedRefDb;
 
   @Mock RefDatabase localRefDb;
 
@@ -75,7 +76,12 @@
 
     refUpdateValidator =
         new RefUpdateValidator(
-            sharedRefDb, validationMetrics, defaultRefEnforcement, A_TEST_PROJECT_NAME, localRefDb);
+            sharedRefDb,
+            validationMetrics,
+            defaultRefEnforcement,
+            new DummyLockWrapper(),
+            A_TEST_PROJECT_NAME,
+            localRefDb);
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
new file mode 100644
index 0000000..caf0d69
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
@@ -0,0 +1,132 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
+import com.googlesource.gerrit.plugins.multisite.validation.DisabledSharedRefLogger;
+import com.googlesource.gerrit.plugins.multisite.validation.MultisiteReplicationPushFilter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MultisiteReplicationPushFilterTest {
+
+  @Mock SharedRefDatabaseWrapper sharedRefDatabaseMock;
+
+  String project = "fooProject";
+
+  @Test
+  public void shouldReturnAllRefUpdatesWhenAllUpToDate() throws Exception {
+    List<RemoteRefUpdate> refUpdates =
+        Arrays.asList(refUpdate("refs/heads/foo"), refUpdate("refs/heads/bar"));
+    doReturn(true).when(sharedRefDatabaseMock).isUpToDate(eq(project), any());
+
+    MultisiteReplicationPushFilter pushFilter =
+        new MultisiteReplicationPushFilter(sharedRefDatabaseMock);
+    List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates);
+
+    assertThat(filteredRefUpdates).containsExactlyElementsIn(refUpdates);
+  }
+
+  @Test
+  public void shouldFilterOutOneOutdatedRef() throws Exception {
+    RemoteRefUpdate refUpToDate = refUpdate("refs/heads/uptodate");
+    RemoteRefUpdate outdatedRef = refUpdate("refs/heads/outdated");
+    List<RemoteRefUpdate> refUpdates = Arrays.asList(refUpToDate, outdatedRef);
+    SharedRefDatabaseWrapper sharedRefDatabase = newSharedRefDatabase(outdatedRef.getSrcRef());
+
+    MultisiteReplicationPushFilter pushFilter =
+        new MultisiteReplicationPushFilter(sharedRefDatabase);
+    List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates);
+
+    assertThat(filteredRefUpdates).containsExactly(refUpToDate);
+  }
+
+  @Test
+  public void shouldFilterOutAllOutdatedChangesRef() throws Exception {
+    RemoteRefUpdate refUpToDate = refUpdate("refs/heads/uptodate");
+    RemoteRefUpdate refChangeUpToDate = refUpdate("refs/changes/25/1225/2");
+    RemoteRefUpdate changeMetaRef = refUpdate("refs/changes/12/4512/meta");
+    RemoteRefUpdate changeRef = refUpdate("refs/changes/12/4512/1");
+    List<RemoteRefUpdate> refUpdates =
+        Arrays.asList(refUpToDate, refChangeUpToDate, changeMetaRef, changeRef);
+    SharedRefDatabaseWrapper sharedRefDatabase = newSharedRefDatabase(changeMetaRef.getSrcRef());
+
+    MultisiteReplicationPushFilter pushFilter =
+        new MultisiteReplicationPushFilter(sharedRefDatabase);
+    List<RemoteRefUpdate> filteredRefUpdates = pushFilter.filter(project, refUpdates);
+
+    assertThat(filteredRefUpdates).containsExactly(refUpToDate, refChangeUpToDate);
+  }
+
+  private SharedRefDatabaseWrapper newSharedRefDatabase(String... rejectedRefs) {
+    Set<String> rejectedSet = new HashSet<>();
+    rejectedSet.addAll(Arrays.asList(rejectedRefs));
+
+    SharedRefDatabase sharedRefDatabase =
+        new SharedRefDatabase() {
+
+          @Override
+          public void removeProject(String project) throws IOException {}
+
+          @Override
+          public AutoCloseable lockRef(String project, String refName) throws SharedLockException {
+            return null;
+          }
+
+          @Override
+          public boolean isUpToDate(String project, Ref ref) throws SharedLockException {
+            return !rejectedSet.contains(ref.getName());
+          }
+
+          @Override
+          public boolean exists(String project, String refName) {
+            return true;
+          }
+
+          @Override
+          public boolean compareAndPut(String project, Ref currRef, ObjectId newRefValue)
+              throws IOException {
+            return false;
+          }
+        };
+    return new SharedRefDatabaseWrapper(
+        DynamicItem.itemOf(SharedRefDatabase.class, sharedRefDatabase),
+        new DisabledSharedRefLogger());
+  }
+
+  private RemoteRefUpdate refUpdate(String refName) throws IOException {
+    ObjectId srcObjId = ObjectId.fromString("0000000000000000000000000000000000000001");
+    Ref srcRef = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, refName, srcObjId);
+    return new RemoteRefUpdate(null, srcRef, "origin", false, "origin", srcObjId);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/DefaultSharedRefEnforcementTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/DefaultSharedRefEnforcementTest.java
index c0be21e..83fcf52 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/DefaultSharedRefEnforcementTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/DefaultSharedRefEnforcementTest.java
@@ -42,6 +42,13 @@
   }
 
   @Test
+  public void aCacheAutomergeShouldBeIgnored() {
+    Ref immutableChangeRef = newRef("refs/cache-automerge/01/1/1000000", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
+        .isEqualTo(EnforcePolicy.IGNORED);
+  }
+
+  @Test
   public void aDraftCommentsShouldBeIgnored() {
     Ref immutableChangeRef = newRef("refs/draft-comments/01/1/1000000", AN_OBJECT_ID_1);
     assertThat(refEnforcement.getPolicy(A_TEST_PROJECT_NAME, immutableChangeRef.getName()))
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseIT.java
index cfa5476..e2bdfbc 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseIT.java
@@ -19,12 +19,17 @@
 import static org.junit.Assert.assertTrue;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.metrics.DisabledMetricMaker;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.BatchRefUpdateValidator;
+import com.googlesource.gerrit.plugins.multisite.validation.DisabledSharedRefLogger;
+import com.googlesource.gerrit.plugins.multisite.validation.DummyLockWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.MultiSiteBatchRefUpdate;
 import com.googlesource.gerrit.plugins.multisite.validation.ValidationMetrics;
 import com.googlesource.gerrit.plugins.multisite.validation.ZkConnectionConfig;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
 import org.apache.curator.retry.RetryNTimes;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
@@ -46,7 +51,7 @@
   @Rule public TestName nameRule = new TestName();
 
   ZookeeperTestContainerSupport zookeeperContainer;
-  ZkSharedRefDatabase zkSharedRefDatabase;
+  SharedRefDatabaseWrapper zkSharedRefDatabase;
   SharedRefEnforcement refEnforcement;
 
   int SLEEP_BETWEEN_RETRIES_MS = 30;
@@ -58,11 +63,15 @@
     refEnforcement = new DefaultSharedRefEnforcement();
     zookeeperContainer = new ZookeeperTestContainerSupport(false);
     zkSharedRefDatabase =
-        new ZkSharedRefDatabase(
-            zookeeperContainer.getCurator(),
-            new ZkConnectionConfig(
-                new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
-                TRANSACTION_LOCK_TIMEOUT));
+        new SharedRefDatabaseWrapper(
+            DynamicItem.itemOf(
+                SharedRefDatabase.class,
+                new ZkSharedRefDatabase(
+                    zookeeperContainer.getCurator(),
+                    new ZkConnectionConfig(
+                        new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
+                        TRANSACTION_LOCK_TIMEOUT))),
+            new DisabledSharedRefLogger());
   }
 
   @After
@@ -179,6 +188,7 @@
                 zkSharedRefDatabase,
                 new ValidationMetrics(new DisabledMetricMaker()),
                 new DefaultSharedRefEnforcement(),
+                new DummyLockWrapper(),
                 projectName,
                 refDb);
           }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java
index 8819b8b..bff41f1 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java
@@ -19,6 +19,9 @@
 
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
+import com.googlesource.gerrit.plugins.multisite.validation.DisabledSharedRefLogger;
 import com.googlesource.gerrit.plugins.multisite.validation.ProjectDeletedSharedDbCleanup;
 import com.googlesource.gerrit.plugins.multisite.validation.ValidationMetrics;
 import com.googlesource.gerrit.plugins.multisite.validation.ZkConnectionConfig;
@@ -38,7 +41,7 @@
   @Rule public TestName nameRule = new TestName();
 
   ZookeeperTestContainerSupport zookeeperContainer;
-  ZkSharedRefDatabase zkSharedRefDatabase;
+  SharedRefDatabaseWrapper zkSharedRefDatabase;
   SharedRefEnforcement refEnforcement;
 
   ValidationMetrics mockValidationMetrics;
@@ -52,11 +55,15 @@
     int NUMBER_OF_RETRIES = 5;
 
     zkSharedRefDatabase =
-        new ZkSharedRefDatabase(
-            zookeeperContainer.getCurator(),
-            new ZkConnectionConfig(
-                new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
-                TRANSACTION_LOCK_TIMEOUT));
+        new SharedRefDatabaseWrapper(
+            DynamicItem.itemOf(
+                SharedRefDatabase.class,
+                new ZkSharedRefDatabase(
+                    zookeeperContainer.getCurator(),
+                    new ZkConnectionConfig(
+                        new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
+                        TRANSACTION_LOCK_TIMEOUT))),
+            new DisabledSharedRefLogger());
 
     mockValidationMetrics = mock(ValidationMetrics.class);
   }
@@ -67,16 +74,6 @@
   }
 
   @Test
-  public void shouldCompareAndCreateSuccessfully() throws Exception {
-    Ref ref = refOf(AN_OBJECT_ID_1);
-
-    assertThat(zkSharedRefDatabase.compareAndCreate(A_TEST_PROJECT_NAME, ref)).isTrue();
-
-    assertThat(zookeeperContainer.readRefValueFromZk(A_TEST_PROJECT_NAME, ref))
-        .isEqualTo(ref.getObjectId());
-  }
-
-  @Test
   public void shouldCompareAndPutSuccessfully() throws Exception {
     Ref oldRef = refOf(AN_OBJECT_ID_1);
     Ref newRef = refOf(AN_OBJECT_ID_2);
@@ -128,39 +125,6 @@
         .isFalse();
   }
 
-  @Test
-  public void shouldCompareAndRemoveSuccessfully() throws Exception {
-    Ref oldRef = refOf(AN_OBJECT_ID_1);
-    String projectName = A_TEST_PROJECT_NAME;
-
-    zookeeperContainer.createRefInZk(projectName, oldRef);
-
-    assertThat(zkSharedRefDatabase.compareAndRemove(projectName, oldRef)).isTrue();
-  }
-
-  @Test
-  public void shouldReplaceTheRefWithATombstoneAfterCompareAndPutRemove() throws Exception {
-    Ref oldRef = refOf(AN_OBJECT_ID_1);
-
-    zookeeperContainer.createRefInZk(A_TEST_PROJECT_NAME, oldRef);
-
-    assertThat(zkSharedRefDatabase.compareAndRemove(A_TEST_PROJECT_NAME, oldRef)).isTrue();
-
-    assertThat(zookeeperContainer.readRefValueFromZk(A_TEST_PROJECT_NAME, oldRef))
-        .isEqualTo(ObjectId.zeroId());
-  }
-
-  @Test
-  public void shouldNotCompareAndPutSuccessfullyAfterACompareAndRemove() throws Exception {
-    Ref oldRef = refOf(AN_OBJECT_ID_1);
-    String projectName = A_TEST_PROJECT_NAME;
-
-    zookeeperContainer.createRefInZk(projectName, oldRef);
-
-    zkSharedRefDatabase.compareAndRemove(projectName, oldRef);
-    assertThat(zkSharedRefDatabase.compareAndPut(projectName, oldRef, AN_OBJECT_ID_2)).isFalse();
-  }
-
   private Ref refOf(ObjectId objectId) {
     return SharedRefDatabase.newRef(aBranchRef(), objectId);
   }