Merge branch 'stable-3.0'

* stable-3.0:
  Adapt to JGit 5.3.x for Gerrit v3.0
  Add project custom enforcement policy
  Clean up projects from the SharedRef Db
  Adapt the local env. setup to work with stable-3.0

Change-Id: I9a42af314c6137f9432dd2edbe457fbc5911a582
diff --git a/setup_local_env/configs/gerrit.config b/setup_local_env/configs/gerrit.config
index 348b90e..f0f56dc 100644
--- a/setup_local_env/configs/gerrit.config
+++ b/setup_local_env/configs/gerrit.config
@@ -3,6 +3,7 @@
     serverId = 69ec38f0-350e-4d9c-96d4-bc956f2faaac
     canonicalWebUrl = $GERRIT_CANONICAL_WEB_URL
     installModule = com.googlesource.gerrit.plugins.multisite.Module # multi-site needs to be a gerrit lib
+    installDbModule = com.googlesource.gerrit.plugins.multisite.GitModule
 [database]
     type = h2
     database = $LOCATION_TEST_SITE/db/ReviewDB
diff --git a/setup_local_env/setup.sh b/setup_local_env/setup.sh
index 5f4d076..94fc972 100755
--- a/setup_local_env/setup.sh
+++ b/setup_local_env/setup.sh
@@ -317,11 +317,11 @@
 	cp -f $MULTISITE_LIB_LOCATION $DEPLOYMENT_LOCATION/multi-site.jar  >/dev/null 2>&1 || { echo >&2 "$MULTISITE_LIB_LOCATION: Not able to copy the file. Aborting"; exit 1; }
 fi
 if [ $DOWNLOAD_WEBSESSION_FLATFILE = "true" ];then
-	echo "Downloading websession-flatfile plugin stable 2.16"
-	wget https://gerrit-ci.gerritforge.com/view/Plugins-stable-2.16/job/plugin-websession-flatfile-bazel-master-stable-2.16/lastSuccessfulBuild/artifact/bazel-genfiles/plugins/websession-flatfile/websession-flatfile.jar \
+	echo "Downloading websession-flatfile plugin stable 3.0"
+	wget https://gerrit-ci.gerritforge.com/view/Plugins-stable-3.0/job/plugin-websession-flatfile-bazel-master-stable-3.0/lastSuccessfulBuild/artifact/bazel-genfiles/plugins/websession-flatfile/websession-flatfile.jar \
 	-O $DEPLOYMENT_LOCATION/websession-flatfile.jar || { echo >&2 "Cannot download websession-flatfile plugin: Check internet connection. Abort\
 ing"; exit 1; }
-	wget https://gerrit-ci.gerritforge.com/view/Plugins-stable-2.16/job/plugin-healthcheck-bazel-stable-2.16/lastSuccessfulBuild/artifact/bazel-genfiles/plugins/healthcheck/healthcheck.jar \
+	wget https://gerrit-ci.gerritforge.com/view/Plugins-stable-3.0/job/plugin-healthcheck-bazel-stable-3.0/lastSuccessfulBuild/artifact/bazel-genfiles/plugins/healthcheck/healthcheck.jar \
 	-O $DEPLOYMENT_LOCATION/healthcheck.jar || { echo >&2 "Cannot download healthcheck plugin: Check internet connection. Abort\
 ing"; exit 1; }
 else
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
index 3756097..72fec47 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
@@ -22,12 +22,16 @@
 import com.google.common.base.CaseFormat;
 import com.google.common.base.Strings;
 import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.google.inject.spi.Message;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventFamily;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
@@ -517,6 +521,8 @@
     public static final String KEY_MIGRATE = "migrate";
     public final String TRANSACTION_LOCK_TIMEOUT_KEY = "transactionLockTimeoutMs";
 
+    public static final String SUBSECTION_ENFORCEMENT_RULES = "enforcementRules";
+
     private final String connectionString;
     private final String root;
     private final int sessionTimeoutMs;
@@ -529,6 +535,8 @@
     private final int casMaxRetries;
     private final boolean enabled;
 
+    private final Multimap<EnforcePolicy, String> enforcementRules;
+
     private final Long transactionLockTimeOut;
 
     private CuratorFramework build;
@@ -600,7 +608,14 @@
 
       checkArgument(StringUtils.isNotEmpty(connectionString), "zookeeper.%s contains no servers");
 
-      enabled = Configuration.getBoolean(cfg, SECTION, SUBSECTION, ENABLE_KEY, true);
+      enabled = Configuration.getBoolean(cfg, SECTION, null, ENABLE_KEY, true);
+
+      enforcementRules = MultimapBuilder.hashKeys().arrayListValues().build();
+      for (EnforcePolicy policy : EnforcePolicy.values()) {
+        enforcementRules.putAll(
+            policy,
+            Configuration.getList(cfg, SECTION, SUBSECTION_ENFORCEMENT_RULES, policy.name()));
+      }
     }
 
     public CuratorFramework buildCurator() {
@@ -632,6 +647,15 @@
     public boolean isEnabled() {
       return enabled;
     }
+
+    public Multimap<EnforcePolicy, String> getEnforcementRules() {
+      return enforcementRules;
+    }
+  }
+
+  static List<String> getList(
+      Supplier<Config> cfg, String section, String subsection, String name) {
+    return ImmutableList.copyOf(cfg.get().getStringList(section, subsection, name));
   }
 
   static boolean getBoolean(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java
index 630b091..e1d1c65 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java
@@ -99,11 +99,6 @@
   }
 
   @Override
-  public Ref getRef(String name) throws IOException {
-    return refDatabase.getRef(name);
-  }
-
-  @Override
   public String toString() {
     return refDatabase.toString();
   }
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
new file mode 100644
index 0000000..7f58d39
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
@@ -0,0 +1,53 @@
+// 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.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 java.io.IOException;
+
+public class ProjectDeletedSharedDbCleanup implements ProjectDeletedListener {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private final SharedRefDatabase sharedDb;
+
+  private final ValidationMetrics validationMetrics;
+
+  @Inject
+  public ProjectDeletedSharedDbCleanup(
+      SharedRefDatabase sharedDb, ValidationMetrics validationMetrics) {
+    this.sharedDb = sharedDb;
+    this.validationMetrics = validationMetrics;
+  }
+
+  @Override
+  public void onProjectDeleted(Event event) {
+    String projectName = event.getProjectName();
+    logger.atInfo().log(
+        "Deleting project '%s'. Will perform a cleanup in Shared-Ref database.", projectName);
+
+    try {
+      sharedDb.removeProject(projectName);
+    } catch (IOException e) {
+      validationMetrics.incrementSplitBrain();
+      logger.atSevere().withCause(e).log(
+          "Project '%s' deleted from GIT but it was not able to cleanup"
+              + " from Shared-Ref database",
+          projectName);
+    }
+  }
+}
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 e10af32..001fe1b 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
@@ -18,6 +18,7 @@
 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.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.multisite.validation.dfsrefdb.zookeeper.ZkValidationModule;
@@ -39,7 +40,14 @@
     factory(BatchRefUpdateValidator.Factory.class);
 
     bind(GitRepositoryManager.class).to(MultiSiteGitRepositoryManager.class);
-    bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON);
+    if (cfg.getZookeeperConfig().getEnforcementRules().isEmpty()) {
+      bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON);
+    } else {
+      bind(SharedRefEnforcement.class)
+          .to(CustomSharedRefEnforcementByProject.class)
+          .in(Scopes.SINGLETON);
+    }
+
     install(new ZkValidationModule(cfg));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java
new file mode 100644
index 0000000..7a806a8
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProject.java
@@ -0,0 +1,103 @@
+// 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.base.Suppliers.memoize;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Splitter;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class CustomSharedRefEnforcementByProject implements SharedRefEnforcement {
+  private static final String ALL = ".*";
+
+  private final Supplier<Map<String, Map<String, EnforcePolicy>>> predefEnforcements;
+
+  @Inject
+  public CustomSharedRefEnforcementByProject(Configuration config) {
+    this.predefEnforcements = memoize(() -> parseDryRunEnforcementsToMap(config));
+  }
+
+  private static Map<String, Map<String, EnforcePolicy>> parseDryRunEnforcementsToMap(
+      Configuration config) {
+    Map<String, Map<String, EnforcePolicy>> enforcementMap = new HashMap<>();
+
+    for (Map.Entry<EnforcePolicy, String> enforcementEntry :
+        config.getZookeeperConfig().getEnforcementRules().entries()) {
+      parseEnforcementEntry(enforcementMap, enforcementEntry);
+    }
+
+    return enforcementMap;
+  }
+
+  private static void parseEnforcementEntry(
+      Map<String, Map<String, EnforcePolicy>> enforcementMap,
+      Map.Entry<EnforcePolicy, String> enforcementEntry) {
+    Iterator<String> projectAndRef = Splitter.on(':').split(enforcementEntry.getValue()).iterator();
+    EnforcePolicy enforcementPolicy = enforcementEntry.getKey();
+
+    if (projectAndRef.hasNext()) {
+      String projectName = emptyToAll(projectAndRef.next());
+      String refName = emptyToAll(projectAndRef.hasNext() ? projectAndRef.next() : ALL);
+
+      Map<String, EnforcePolicy> existingOrDefaultRef =
+          enforcementMap.getOrDefault(projectName, new HashMap<>());
+
+      existingOrDefaultRef.put(refName, enforcementPolicy);
+
+      enforcementMap.put(projectName, existingOrDefaultRef);
+    }
+  }
+
+  private static String emptyToAll(String value) {
+    return value.trim().isEmpty() ? ALL : value;
+  }
+
+  @Override
+  public EnforcePolicy getPolicy(String projectName, String refName) {
+    if (isRefToBeIgnoredBySharedRefDb(refName)) {
+      return EnforcePolicy.IGNORED;
+    }
+
+    return getRefEnforcePolicy(projectName, refName);
+  }
+
+  private EnforcePolicy getRefEnforcePolicy(String projectName, String refName) {
+    Map<String, EnforcePolicy> orDefault =
+        predefEnforcements
+            .get()
+            .getOrDefault(
+                projectName, predefEnforcements.get().getOrDefault(ALL, ImmutableMap.of()));
+
+    return MoreObjects.firstNonNull(
+        orDefault.getOrDefault(refName, orDefault.get(ALL)), EnforcePolicy.REQUIRED);
+  }
+
+  @Override
+  public EnforcePolicy getPolicy(String projectName) {
+    Map<String, EnforcePolicy> policiesForProject =
+        predefEnforcements
+            .get()
+            .getOrDefault(
+                projectName, predefEnforcements.get().getOrDefault(ALL, ImmutableMap.of()));
+    return policiesForProject.getOrDefault(ALL, EnforcePolicy.REQUIRED);
+  }
+}
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 790935b..cfbd783 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
@@ -156,4 +156,12 @@
    * @return true if the ref exists on the project
    */
   boolean exists(String project, String refName);
+
+  /**
+   * Clean project path from SharedRefDatabase
+   *
+   * @param project project name
+   * @throws IOException
+   */
+  void removeProject(String project) throws IOException;
 }
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 321ad8a..afd3766 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
@@ -73,6 +73,15 @@
   }
 
   @Override
+  public void removeProject(String project) throws IOException {
+    try {
+      client.delete().deletingChildrenIfNeeded().forPath("/" + project);
+    } catch (Exception e) {
+      throw new IOException(String.format("Not able to delete project '%s'", project), e);
+    }
+  }
+
+  @Override
   public boolean exists(String project, String refName) throws ZookeeperRuntimeException {
     try {
       return client.checkExists().forPath(pathFor(project, refName)) != null;
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 927591e..3e8f75a 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,8 +14,11 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
 
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.AbstractModule;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectDeletedSharedDbCleanup;
 import com.googlesource.gerrit.plugins.multisite.validation.ZkConnectionConfig;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
 import org.apache.curator.framework.CuratorFramework;
@@ -38,5 +41,7 @@
             new ZkConnectionConfig(
                 cfg.getZookeeperConfig().buildCasRetryPolicy(),
                 cfg.getZookeeperConfig().getZkInterProcessLockTimeOut()));
+
+    DynamicSet.bind(binder(), ProjectDeletedListener.class).to(ProjectDeletedSharedDbCleanup.class);
   }
 }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index e3abe59..be32c27 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -187,6 +187,34 @@
 :   Enable the use of a shared ref-database
     Defaults: true
 
+```ref-database.enforcementRules.<policy>```
+:   Level of consistency enforcement across sites on a project:refs basis.
+    Supports multiple values for enforcing the policy on multiple projects or refs.
+    If the project or ref is omitted, apply the policy to all projects or all refs.
+
+    The <policy> can be one of the following values:
+
+    1. REQUIRED - Throw an exception if a git ref-update is processed again
+    a local ref not yet in sync with the shared ref-database.
+    The user transaction is cancelled. The Gerrit GUI (or the Git client)
+    receives an HTTP 500 - Internal Server Error.
+
+    2. DESIRED - Validate the git ref-update against the shared ref-database.
+    Any misaligned is logged in errors_log file but the user operation is allowed
+    to continue successfully.
+
+    3. IGNORED - Ignore any validation against the shared ref-database.
+
+    *Example:*
+    ```
+    [ref-database "enforcementRules"]
+       DESIRED = AProject:/refs/heads/feature
+    ```
+
+    Relax the alignment with the shared ref-database for AProject on refs/heads/feature.
+
+    Defaults: No rules = All projects are REQUIRED to be consistent on all refs.
+
 ```ref-database.zookeeper.connectString```
 :   Connection string to Zookeeper
 
@@ -230,14 +258,14 @@
     operations on Zookeeper
 
     Defaults: 1000
-    
+
 ```ref-database.zookeeper.casRetryPolicyMaxSleepTimeMs```
 :   Configuration for the maximum sleep timeout in milliseconds of the
     BoundedExponentialBackoffRetry policy used for the Compare and Swap
     operations on Zookeeper
 
     Defaults: 3000
-    
+
 ```ref-database.zookeeper.casRetryPolicyMaxRetries```
 :   Configuration for the maximum number of retries of the
     BoundedExponentialBackoffRetry policy used for the Compare and Swap
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/CustomSharedRefEnforcementByProjectTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/CustomSharedRefEnforcementByProjectTest.java
new file mode 100644
index 0000000..e4d7861
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/CustomSharedRefEnforcementByProjectTest.java
@@ -0,0 +1,159 @@
+// 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.zookeeper;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase.newRef;
+
+import com.googlesource.gerrit.plugins.multisite.Configuration;
+import com.googlesource.gerrit.plugins.multisite.Configuration.ZookeeperConfig;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
+import java.util.Arrays;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Ref;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CustomSharedRefEnforcementByProjectTest implements RefFixture {
+
+  SharedRefEnforcement refEnforcement;
+
+  @Before
+  public void setUp() {
+    Config multiSiteConfig = new Config();
+    multiSiteConfig.setStringList(
+        ZookeeperConfig.SECTION,
+        ZookeeperConfig.SUBSECTION_ENFORCEMENT_RULES,
+        EnforcePolicy.DESIRED.name(),
+        Arrays.asList(
+            "ProjectOne",
+            "ProjectTwo:refs/heads/master/test",
+            "ProjectTwo:refs/heads/master/test2"));
+    multiSiteConfig.setString(
+        ZookeeperConfig.SECTION,
+        ZookeeperConfig.SUBSECTION_ENFORCEMENT_RULES,
+        EnforcePolicy.IGNORED.name(),
+        ":refs/heads/master/test");
+
+    refEnforcement = newCustomRefEnforcement(multiSiteConfig);
+  }
+
+  @Test
+  public void projectOneShouldReturnDesiredForAllRefs() {
+    Ref aRef = newRef("refs/heads/master/2", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy("ProjectOne", aRef.getName()))
+        .isEqualTo(EnforcePolicy.DESIRED);
+  }
+
+  @Test
+  public void projectOneEnforcementShouldAlwaysPrevail() {
+    Ref aRef = newRef("refs/heads/master/test", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy("ProjectOne", aRef.getName()))
+        .isEqualTo(EnforcePolicy.DESIRED);
+  }
+
+  @Test
+  public void aNonListedProjectShouldIgnoreRefForMasterTest() {
+    Ref aRef = newRef("refs/heads/master/test", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy("NonListedProject", aRef.getName()))
+        .isEqualTo(EnforcePolicy.IGNORED);
+  }
+
+  @Test
+  public void projectTwoSpecificRefShouldReturnDesiredPolicy() {
+    Ref refOne = newRef("refs/heads/master/test", AN_OBJECT_ID_1);
+    Ref refTwo = newRef("refs/heads/master/test2", AN_OBJECT_ID_1);
+
+    assertThat(refEnforcement.getPolicy("ProjectTwo", refOne.getName()))
+        .isEqualTo(EnforcePolicy.DESIRED);
+    assertThat(refEnforcement.getPolicy("ProjectTwo", refTwo.getName()))
+        .isEqualTo(EnforcePolicy.DESIRED);
+  }
+
+  @Test
+  public void aNonListedProjectShouldReturnRequired() {
+    Ref refOne = newRef("refs/heads/master/newChange", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy("NonListedProject", refOne.getName()))
+        .isEqualTo(EnforcePolicy.REQUIRED);
+  }
+
+  @Test
+  public void aNonListedRefInProjectShouldReturnRequired() {
+    Ref refOne = newRef("refs/heads/master/test3", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy("ProjectTwo", refOne.getName()))
+        .isEqualTo(EnforcePolicy.REQUIRED);
+  }
+
+  @Test
+  public void aNonListedProjectAndRefShouldReturnRequired() {
+    Ref refOne = newRef("refs/heads/master/test3", AN_OBJECT_ID_1);
+    assertThat(refEnforcement.getPolicy("NonListedProject", refOne.getName()))
+        .isEqualTo(EnforcePolicy.REQUIRED);
+  }
+
+  @Test
+  public void getProjectPolicyForProjectOneShouldRetrunDesired() {
+    assertThat(refEnforcement.getPolicy("ProjectOne")).isEqualTo(EnforcePolicy.DESIRED);
+  }
+
+  @Test
+  public void getProjectPolicyForProjectTwoShouldReturnRequired() {
+    assertThat(refEnforcement.getPolicy("ProjectTwo")).isEqualTo(EnforcePolicy.REQUIRED);
+  }
+
+  @Test
+  public void getProjectPolicyForNonListedProjectShouldReturnRequired() {
+    assertThat(refEnforcement.getPolicy("NonListedProject")).isEqualTo(EnforcePolicy.REQUIRED);
+  }
+
+  @Test
+  public void getProjectPolicyForNonListedProjectWhenSingleProject() {
+    SharedRefEnforcement customEnforcement =
+        newCustomRefEnforcementWithValue(EnforcePolicy.DESIRED, ":refs/heads/master");
+
+    assertThat(customEnforcement.getPolicy("NonListedProject")).isEqualTo(EnforcePolicy.REQUIRED);
+  }
+
+  @Test
+  public void getANonListedProjectWhenOnlyOneProjectIsListedShouldReturnRequired() {
+    SharedRefEnforcement customEnforcement =
+        newCustomRefEnforcementWithValue(EnforcePolicy.DESIRED, "AProject:");
+    assertThat(customEnforcement.getPolicy("NonListedProject", "refs/heads/master"))
+        .isEqualTo(EnforcePolicy.REQUIRED);
+  }
+
+  private SharedRefEnforcement newCustomRefEnforcementWithValue(
+      EnforcePolicy policy, String... projectAndRefs) {
+    Config multiSiteConfig = new Config();
+    multiSiteConfig.setStringList(
+        ZookeeperConfig.SECTION,
+        ZookeeperConfig.SUBSECTION_ENFORCEMENT_RULES,
+        policy.name(),
+        Arrays.asList(projectAndRefs));
+    return newCustomRefEnforcement(multiSiteConfig);
+  }
+
+  private SharedRefEnforcement newCustomRefEnforcement(Config multiSiteConfig) {
+    return new CustomSharedRefEnforcementByProject(
+        new Configuration(multiSiteConfig, new Config()));
+  }
+
+  @Override
+  public String testBranch() {
+    return "fooBranch";
+  }
+}
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 9e85563..8819b8b 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
@@ -15,7 +15,12 @@
 package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
 
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectDeletedSharedDbCleanup;
+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;
@@ -36,6 +41,8 @@
   ZkSharedRefDatabase zkSharedRefDatabase;
   SharedRefEnforcement refEnforcement;
 
+  ValidationMetrics mockValidationMetrics;
+
   @Before
   public void setup() {
     refEnforcement = new DefaultSharedRefEnforcement();
@@ -50,6 +57,8 @@
             new ZkConnectionConfig(
                 new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
                 TRANSACTION_LOCK_TIMEOUT));
+
+    mockValidationMetrics = mock(ValidationMetrics.class);
   }
 
   @After
@@ -156,8 +165,62 @@
     return SharedRefDatabase.newRef(aBranchRef(), objectId);
   }
 
+  @Test
+  public void removeProjectShouldRemoveTheWholePathInZk() throws Exception {
+    String projectName = A_TEST_PROJECT_NAME;
+    Ref someRef = refOf(AN_OBJECT_ID_1);
+
+    zookeeperContainer.createRefInZk(projectName, someRef);
+
+    assertThat(zookeeperContainer.readRefValueFromZk(projectName, someRef))
+        .isEqualTo(AN_OBJECT_ID_1);
+
+    assertThat(getNumChildrenForPath("/")).isEqualTo(1);
+
+    zkSharedRefDatabase.removeProject(projectName);
+
+    assertThat(getNumChildrenForPath("/")).isEqualTo(0);
+  }
+
+  @Test
+  public void aDeleteProjectEventShouldCleanupProjectFromZk() throws Exception {
+    String projectName = A_TEST_PROJECT_NAME;
+    Ref someRef = refOf(AN_OBJECT_ID_1);
+    ProjectDeletedSharedDbCleanup projectDeletedSharedDbCleanup =
+        new ProjectDeletedSharedDbCleanup(zkSharedRefDatabase, mockValidationMetrics);
+
+    ProjectDeletedListener.Event event =
+        new ProjectDeletedListener.Event() {
+          @Override
+          public String getProjectName() {
+            return projectName;
+          }
+
+          @Override
+          public NotifyHandling getNotify() {
+            return NotifyHandling.NONE;
+          }
+        };
+
+    zookeeperContainer.createRefInZk(projectName, someRef);
+
+    assertThat(getNumChildrenForPath("/")).isEqualTo(1);
+
+    projectDeletedSharedDbCleanup.onProjectDeleted(event);
+
+    assertThat(getNumChildrenForPath("/")).isEqualTo(0);
+  }
+
   @Override
   public String testBranch() {
     return "branch_" + nameRule.getMethodName();
   }
+
+  private int getNumChildrenForPath(String path) throws Exception {
+    return zookeeperContainer
+        .getCurator()
+        .checkExists()
+        .forPath(String.format(path))
+        .getNumChildren();
+  }
 }