Merge branch 'stable-2.14' into stable-2.15

* stable-2.14:
  Upgrade bazlets to latest stable-2.14 to build with 2.14.21 API

Change-Id: Icf7724472e86f97bc24eab60b9f0c4dde078bc77
diff --git a/.gitignore b/.gitignore
index 72f041f..92c26ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
+# `LC_COLLATE=C sort`
+/.apt_generated/
 /.classpath
 /.primary_build_tool
 /.project
 /.settings/
 /bazel-*
+/bin/
 /eclipse-out/
diff --git a/WORKSPACE b/WORKSPACE
index 688fb8a..617504a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,7 +3,7 @@
 load("//:bazlets.bzl", "load_bazlets")
 
 load_bazlets(
-    commit = "78c35a7eb33ee5ea0980923e246c7dba37347193",
+    commit = "f53f51fb660552d0581aa0ba52c3836ed63d56a3",
     #local_path = "/home/<user>/projects/bazlets",
 )
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/DefaultAccessRights.java b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/DefaultAccessRights.java
index 17dedb6..4df4f25 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/DefaultAccessRights.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/DefaultAccessRights.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.project.RefPattern;
@@ -34,6 +35,7 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
+import java.util.Optional;
 import java.util.Set;
 import java.util.regex.Pattern;
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -143,7 +145,10 @@
   private String getOwnerGroupName(ProjectState project) {
     Set<AccountGroup.UUID> owners = project.getAllOwners();
     if (!owners.isEmpty()) {
-      return groupCache.get(owners.iterator().next()).getName();
+      Optional<InternalGroup> owner = groupCache.get(owners.iterator().next());
+      if (owner.isPresent()) {
+        return owner.get().getName();
+      }
     }
     return String.format("no owners for project %s", project.getProject().getName());
   }
@@ -183,13 +188,14 @@
         // this means that group is not already in the groups file, so
         // we need to check if group exist if if it does, get its
         // uuid.
-        AccountGroup group = groupCache.get(new AccountGroup.NameKey(rule.getGroup().getName()));
+        Optional<InternalGroup> group =
+            groupCache.get(new AccountGroup.NameKey(rule.getGroup().getName()));
 
-        if (group == null) {
+        if (!group.isPresent()) {
           log.error("Group {} not found", rule.getGroup().getName());
           continue;
         }
-        rule.getGroup().setUUID(group.getGroupUUID());
+        rule.getGroup().setUUID(group.get().getGroupUUID());
       }
       perm.add(rule);
     }
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 278987c..347244b 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidator.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidator.java
@@ -21,15 +21,20 @@
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.api.groups.GroupInput;
 import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.group.CreateGroup;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.CreateProjectArgs;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
@@ -37,8 +42,10 @@
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -59,7 +66,10 @@
       "Project name cannot contains spaces." + SEE_DOCUMENTATION_MSG;
 
   private static final String ROOT_PROJECT_CANNOT_CONTAINS_SLASHES_MSG =
-      "Root project name cannot contains slashes." + SEE_DOCUMENTATION_MSG;
+      "Since the \"Rights Inherit From\" field is empty, "
+          + "\"%s\" is considered a root project whose parent is \"%s\". "
+          + "Root project names cannot contain slashes."
+          + SEE_DOCUMENTATION_MSG;
 
   private static final String REGULAR_PROJECT_NOT_ALLOWED_AS_ROOT_MSG =
       "Regular projects are not allowed as root.\n\n"
@@ -82,21 +92,30 @@
   private final CreateGroup.Factory createGroupFactory;
   private final String documentationUrl;
   private final AllProjectsNameProvider allProjectsName;
+  private final Provider<CurrentUser> self;
+  private final PermissionBackend permissionBackend;
   private final PluginConfigFactory cfg;
   private final String pluginName;
+  private final ProjectControl.GenericFactory projectControlFactory;
 
   @Inject
   public ProjectCreationValidator(
       CreateGroup.Factory createGroupFactory,
       @PluginCanonicalWebUrl String url,
       AllProjectsNameProvider allProjectsName,
+      Provider<CurrentUser> self,
+      PermissionBackend permissionBackend,
       PluginConfigFactory cfg,
-      @PluginName String pluginName) {
+      @PluginName String pluginName,
+      ProjectControl.GenericFactory projectControlFactory) {
     this.createGroupFactory = createGroupFactory;
     this.documentationUrl = url + "Documentation/index.html";
     this.allProjectsName = allProjectsName;
+    this.self = self;
+    this.permissionBackend = permissionBackend;
     this.cfg = cfg;
     this.pluginName = pluginName;
+    this.projectControlFactory = projectControlFactory;
   }
 
   @Override
@@ -108,14 +127,30 @@
           String.format(PROJECT_CANNOT_CONTAINS_SPACES_MSG, documentationUrl));
     }
 
-    ProjectControl parentCtrl = args.newParent;
-    if (parentCtrl.getUser().getCapabilities().canAdministrateServer()) {
+    ProjectControl parentCtrl;
+    try {
+      parentCtrl = projectControlFactory.controlFor(args.newParent, self.get());
+    } catch (NoSuchProjectException | IOException e) {
+      log.error(
+          "Failed to create project {}; Cannot retrieve info about parent project {}: {}",
+          name,
+          args.newParent.get(),
+          e.getMessage(),
+          e);
+      throw new ValidationException(AN_ERROR_OCCURRED_MSG);
+    }
+
+    try {
+      permissionBackend.user(self).check(GlobalPermission.ADMINISTRATE_SERVER);
+
       // Admins can bypass any rules to support creating projects that doesn't
       // comply with the new naming rules. New projects structures have to
       // comply but we need to be able to add new project to an existing non
       // compliant structure.
       log.debug("admin is creating project, bypassing all rules");
       return;
+    } catch (AuthException | PermissionBackendException e) {
+      // continuing
     }
 
     if (allProjectsName.get().equals(parentCtrl.getProject().getNameKey())) {
@@ -171,7 +206,7 @@
                 .apply(TopLevelResource.INSTANCE, new GroupInput());
       }
       return AccountGroup.UUID.parse(groupInfo.id);
-    } catch (RestApiException | OrmException | IOException e) {
+    } catch (RestApiException | OrmException | IOException | ConfigInvalidException e) {
       log.error("Failed to create project {}: {}", name, e.getMessage(), e);
       throw new ValidationException(AN_ERROR_OCCURRED_MSG);
     }
@@ -182,7 +217,11 @@
     if (name.contains("/")) {
       log.debug("rejecting creation of {}: name contains slashes", name);
       throw new ValidationException(
-          String.format(ROOT_PROJECT_CANNOT_CONTAINS_SLASHES_MSG, documentationUrl));
+          String.format(
+              ROOT_PROJECT_CANNOT_CONTAINS_SLASHES_MSG,
+              name,
+              allProjectsName.get(),
+              documentationUrl));
     }
     if (!permissionOnly) {
       log.debug("rejecting creation of {}: missing permissions only option", name);
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 8ea7dc6..8f53648 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorIT.java
@@ -81,7 +81,7 @@
     // Creation is rejected when root project name contains slashes
     RestResponse r = userRestSession.put("/projects/" + Url.encode("a/parentProject"), in);
     r.assertConflict();
-    assertThat(r.getEntityContent()).contains("Root project name cannot contains slashes");
+    assertThat(r.getEntityContent()).contains("Root project names cannot contain slashes");
   }
 
   @Test
@@ -162,7 +162,8 @@
     ProjectState projectState = projectCache.get(new Project.NameKey(rootProject));
     assertThat(projectState.getOwners().size()).isEqualTo(1);
     assertThat(projectState.getOwners())
-        .contains(groupCache.get(new AccountGroup.NameKey(rootProject + "-admins")).getGroupUUID());
+        .contains(
+            groupCache.get(new AccountGroup.NameKey(rootProject + "-admins")).get().getGroupUUID());
 
     // case when <project-name>-admins group already exists
     rootProject = name("rootProject2");
@@ -179,7 +180,8 @@
                 .toString()
                 .substring(0, 7);
     assertThat(projectState.getOwners())
-        .contains(groupCache.get(new AccountGroup.NameKey(expectedOwnerGroup)).getGroupUUID());
+        .contains(
+            groupCache.get(new AccountGroup.NameKey(expectedOwnerGroup)).get().getGroupUUID());
   }
 
   @Test
@@ -250,7 +252,10 @@
     assertThat(projectState.getOwners().size()).isEqualTo(1);
     assertThat(projectState.getOwners())
         .contains(
-            groupCache.get(new AccountGroup.NameKey(childProject + "-admins")).getGroupUUID());
+            groupCache
+                .get(new AccountGroup.NameKey(childProject + "-admins"))
+                .get()
+                .getGroupUUID());
 
     // case when <project-name>-admins group already exists
     String childProject2 = parent + "/childProject2";
@@ -267,7 +272,8 @@
                 .toString()
                 .substring(0, 7);
     assertThat(projectState.getOwners())
-        .contains(groupCache.get(new AccountGroup.NameKey(expectedOwnerGroup)).getGroupUUID());
+        .contains(
+            groupCache.get(new AccountGroup.NameKey(expectedOwnerGroup)).get().getGroupUUID());
   }
 
   @Test
diff --git a/tools/BUILD b/tools/BUILD
new file mode 100644
index 0000000..c5ed0b7
--- /dev/null
+++ b/tools/BUILD
@@ -0,0 +1 @@
+# Empty file required by Bazel
diff --git a/tools/eclipse/project.sh b/tools/eclipse/project.sh
index be0fd7a..9cdbc95 100755
--- a/tools/eclipse/project.sh
+++ b/tools/eclipse/project.sh
@@ -12,4 +12,7 @@
 # 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.
-`bazel query @com_googlesource_gerrit_bazlets//tools/eclipse:project --output location | sed s/BUILD:.*//`project.py -n project-group-structure -r .
+
+path=$(bazel query @com_googlesource_gerrit_bazlets//tools/eclipse:project \
+    --output location | sed s/BUILD:.*//)
+${path}project.py -n project-group-structure -r . "$@"