Merge branch 'stable-3.2' into stable-3.3

* stable-3.2:
  Introduce an optional configuration to define a regex for naming rules
  Upgrade bazlets to latest stable-3.1 to build with 3.1.12 API
  Upgrade bazlets to latest stable-3.2 to build with 3.2.6 API
  Upgrade bazlets to latest stable-3.1 to build with 3.1.11 API

Change-Id: Ic4e881927a5057b4209cac7d60d1bd8d9afccff4
diff --git a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/Configuration.java
new file mode 100644
index 0000000..6a3c40f
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/Configuration.java
@@ -0,0 +1,57 @@
+// Copyright (C) 2021 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.ericsson.gerrit.plugins.projectgroupstructure;
+
+import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+class Configuration {
+  private static final Logger log = LoggerFactory.getLogger(Configuration.class);
+
+  private static final String NAME_REGEX = "nameRegex";
+  private static final String DEFAULT_NAME_REGEX_VALUE = ".+";
+  private static final String DEFAULT_NAME_REGEX_MESSAGE = "The value of the regex is invalid.";
+
+  static final String SEE_DOCUMENTATION_MSG = "\n\nSee documentation for more info: %s";
+  static final String DOCUMENTATION_PATH = "Documentation/index.html";
+
+  private String regexNameFilter;
+
+  @Inject
+  Configuration(
+      PluginConfigFactory pluginConfigFactory,
+      @PluginName String pluginName,
+      @PluginCanonicalWebUrl String url) {
+    PluginConfig config = pluginConfigFactory.getFromGerritConfig(pluginName);
+    regexNameFilter = config.getString(NAME_REGEX, DEFAULT_NAME_REGEX_VALUE);
+    if (!"/".matches(regexNameFilter) || " ".matches(regexNameFilter)) {
+      log.warn(
+          String.format(
+              DEFAULT_NAME_REGEX_MESSAGE + SEE_DOCUMENTATION_MSG, url + DOCUMENTATION_PATH));
+      regexNameFilter = DEFAULT_NAME_REGEX_VALUE;
+    }
+  }
+
+  public String getRegexNameFilter() {
+    return regexNameFilter;
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidator.java b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidator.java
index 2c50e81..9a71293 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidator.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidator.java
@@ -14,6 +14,8 @@
 
 package com.ericsson.gerrit.plugins.projectgroupstructure;
 
+import static com.ericsson.gerrit.plugins.projectgroupstructure.Configuration.SEE_DOCUMENTATION_MSG;
+
 import com.google.common.base.Charsets;
 import com.google.common.hash.Hashing;
 import com.google.gerrit.entities.AccountGroup;
@@ -52,14 +54,12 @@
   private static final String AN_ERROR_OCCURRED_MSG =
       "An error occurred while creating project, please contact Gerrit support";
 
-  private static final String SEE_DOCUMENTATION_MSG = "\n\nSee documentation for more info: %s";
-
   private static final String MUST_BE_OWNER_TO_CREATE_PROJECT_MSG =
       "You must be owner of the parent project \"%s\" to create a nested project."
           + SEE_DOCUMENTATION_MSG;
 
   private static final String PROJECT_CANNOT_CONTAINS_SPACES_MSG =
-      "Project name cannot contains spaces." + SEE_DOCUMENTATION_MSG;
+      "Project name cannot contain spaces." + SEE_DOCUMENTATION_MSG;
 
   private static final String ROOT_PROJECT_CANNOT_CONTAINS_SLASHES_MSG =
       "Since the \"Rights Inherit From\" field is empty, "
@@ -81,6 +81,9 @@
   private static final String PROJECT_MUST_START_WITH_PARENT_NAME_MSG =
       "Project name must start with parent project name, e.g. %s." + SEE_DOCUMENTATION_MSG;
 
+  private static final String PROJECT_SHOULD_MATCH_REGEX_MSG =
+      "Project name should match the regex: %s." + SEE_DOCUMENTATION_MSG;
+
   static final String DELEGATE_PROJECT_CREATION_TO = "delegateProjectCreationTo";
 
   static final String DISABLE_GRANTING_PROJECT_OWNERSHIP = "disableGrantingProjectOwnership";
@@ -92,6 +95,7 @@
   private final PermissionBackend permissionBackend;
   private final PluginConfigFactory cfg;
   private final String pluginName;
+  private final Configuration config;
 
   @Inject
   public ProjectCreationValidator(
@@ -101,20 +105,33 @@
       Provider<CurrentUser> self,
       PermissionBackend permissionBackend,
       PluginConfigFactory cfg,
+      Configuration config,
       @PluginName String pluginName) {
     this.groups = groups;
-    this.documentationUrl = url + "Documentation/index.html";
+    this.documentationUrl = url + Configuration.DOCUMENTATION_PATH;
     this.allProjectsName = allProjectsName;
     this.self = self;
     this.permissionBackend = permissionBackend;
     this.cfg = cfg;
     this.pluginName = pluginName;
+    this.config = config;
   }
 
   @Override
   public void validateNewProject(CreateProjectArgs args) throws ValidationException {
     String name = args.getProjectName();
     log.debug("validating creation of {}", name);
+    String regex = config.getRegexNameFilter();
+    if (!(name.matches(regex))) {
+      throw new ValidationException(
+          String.format(PROJECT_SHOULD_MATCH_REGEX_MSG, regex, documentationUrl));
+    }
+    if (name.contains(" ")) {
+      throw new ValidationException(
+          String.format(PROJECT_CANNOT_CONTAINS_SPACES_MSG, documentationUrl));
+    }
+
+    Project.NameKey newParent = args.newParent;
 
     try {
       permissionBackend.user(self.get()).check(GlobalPermission.ADMINISTRATE_SERVER);
@@ -134,8 +151,6 @@
           String.format(PROJECT_CANNOT_CONTAINS_SPACES_MSG, documentationUrl));
     }
 
-    Project.NameKey newParent = args.newParent;
-
     if (allProjectsName.get().equals(newParent)) {
       validateRootProject(name, args.permissionsOnly);
     } else {
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index de4c600..b9eb8aa 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -26,3 +26,17 @@
 ```
 
 Note: default access rights configuration is bypassed for projects created by admins.
+
+Also, this plugin offers a way to restrict the new names of the projects to match an optionally
+configured regex in the gerrit.config. For example:
+
+```
+[plugin "@PLUGIN@"]
+  nameRegex = [a-z0-9/]+
+
+```
+
+In this example, the regex will limit the project created to non-capital letters, numbers
+and slashes. The regex must accept slash (/) to not disturb the functionality of the plugin.
+If the regex doesn't accept / or accepts spaces, it will be ignored and replaced with a default
+non-empty wildcard (.+) regex.
diff --git a/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorIT.java b/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorIT.java
index 226dc1b..d6394e5 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorIT.java
@@ -24,6 +24,8 @@
 import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.entities.AccountGroup;
@@ -47,6 +49,10 @@
   @Inject private ProjectOperations projectOperations;
 
   private static final String PLUGIN_NAME = "project-group-structure";
+  private static final String REGEX_INCLUDING_SLASH = "[a-z_/]+";
+  private static final String REGEX_NOT_INCLUDING_SLASH = "[a-z_]+";
+  private static final String REGEX_INCLUDING_SPACE = "[a-z_/ ]+";
+  private static final String PROJECT_WITH_SPACE = "project with space";
 
   @Override
   @Before
@@ -70,9 +76,44 @@
   public void shouldProjectWithASpaceInTheirName() throws Exception {
     ProjectInput in = new ProjectInput();
     in.permissionsOnly = true;
-    RestResponse r = userRestSession.put("/projects/" + Url.encode("project with space"), in);
+    RestResponse r = userRestSession.put("/projects/" + Url.encode(PROJECT_WITH_SPACE), in);
     r.assertConflict();
-    assertThat(r.getEntityContent()).contains("Project name cannot contains spaces");
+    assertThat(r.getEntityContent()).contains("Project name cannot contain spaces");
+  }
+
+  @Test
+  @UseLocalDisk
+  @GerritConfig(name = "project-group-structure.nameRegex", value = REGEX_INCLUDING_SPACE)
+  public void shouldRejectProjectWithSpaceInItsNameEvenWithRegex() throws Exception {
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    RestResponse r = userRestSession.put("/projects/" + Url.encode(PROJECT_WITH_SPACE), in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("Project name cannot contain spaces");
+  }
+
+  @Test
+  @UseLocalDisk
+  @GerritConfig(name = "plugin.project-group-structure.nameRegex", value = REGEX_INCLUDING_SLASH)
+  public void shouldMatchProjectNameIfRegexContainsSlash() throws Exception {
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    RestResponse r = userRestSession.put("/projects/" + Url.encode("project1"), in);
+    userRestSession.put("/projects/" + Url.encode("project"), in).assertCreated();
+    r.assertConflict();
+    assertThat(r.getEntityContent())
+        .contains(String.format("Project name should match the regex: %s", REGEX_INCLUDING_SLASH));
+  }
+
+  @Test
+  @UseLocalDisk
+  @GerritConfig(
+      name = "plugin.project-group-structure.nameRegex",
+      value = REGEX_NOT_INCLUDING_SLASH)
+  public void shouldNotMatchProjectNameIfRegexMissesSlash() throws Exception {
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    userRestSession.put("/projects/" + Url.encode("PROJECT1"), in).assertCreated();
   }
 
   @Test