Merge branch 'master' into avoid_subpackages_flattering

# Conflicts:
#	src/main/java/org/apache/sling/feature/cpconverter/acl/DefaultAclManager.java
#	src/test/java/org/apache/sling/feature/cpconverter/acl/AclManagerTest.java
#	src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/acl/Acl.java b/src/main/java/org/apache/sling/feature/cpconverter/acl/Acl.java
index 0e21ead..23cd723 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/acl/Acl.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/acl/Acl.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sling.feature.cpconverter.acl;
 
+import java.nio.file.Path;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -28,11 +29,11 @@
 
     private final String privileges;
 
-    private final String path;
+    private final Path path;
 
     private final List<String> restrictions = new LinkedList<>();
 
-    protected Acl(String operation, String privileges, String path) {
+    public Acl(String operation, String privileges, Path path) {
         this.operation = operation;
         this.privileges = privileges;
         this.path = path;
@@ -52,7 +53,7 @@
         return privileges;
     }
 
-    public String getPath() {
+    public Path getPath() {
         return path;
     }
 
@@ -60,4 +61,17 @@
         return restrictions;
     }
 
+    @Override
+    public String toString() {
+        return "Acl [operation="
+               + operation
+               + ", privileges="
+               + privileges
+               + ", path="
+               + path
+               + ", restrictions="
+               + restrictions
+               + "]";
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/acl/AclManager.java b/src/main/java/org/apache/sling/feature/cpconverter/acl/AclManager.java
index a3c15bd..80ac1be 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/acl/AclManager.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/acl/AclManager.java
@@ -26,9 +26,9 @@
  */
 public interface AclManager {
 
-    boolean addSystemUser(String systemUser);
+    boolean addSystemUser(SystemUser systemUser);
 
-    Acl addAcl(String systemUser, String operation, String privileges, String path);
+    Acl addAcl(String systemUser, Acl acl);
 
     void addRepoinitExtension(List<VaultPackageAssembler> packageAssemblers, Feature feature);
 
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/acl/DefaultAclManager.java b/src/main/java/org/apache/sling/feature/cpconverter/acl/DefaultAclManager.java
index 4f72cb2..11c8c4c 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/acl/DefaultAclManager.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/acl/DefaultAclManager.java
@@ -18,6 +18,7 @@
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.nio.file.Path;
 import java.util.Formatter;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -41,11 +42,11 @@
 
     private static final String DEFAULT_TYPE = "sling:Folder";
 
-    private final Set<String> preProvidedSystemUsers = new LinkedHashSet<>();
+    private final Set<SystemUser> preProvidedSystemUsers = new LinkedHashSet<>();
 
-    private final Set<String> preProvidedPaths = new HashSet<String>();
+    private final Set<Path> preProvidedPaths = new HashSet<>();
 
-    private final Set<String> systemUsers = new LinkedHashSet<>();
+    private final Set<SystemUser> systemUsers = new LinkedHashSet<>();
 
     private final Map<String, List<Acl>> acls = new HashMap<>();
 
@@ -53,27 +54,26 @@
 
     private Set<String> privileges = new LinkedHashSet<>();
 
-    public boolean addSystemUser(String systemUser) {
-        if (systemUser != null && !systemUser.isEmpty() && preProvidedSystemUsers.add(systemUser)) {
+    public boolean addSystemUser(SystemUser systemUser) {
+        if (preProvidedSystemUsers.add(systemUser)) {
             return systemUsers.add(systemUser);
         }
         return false;
     }
 
-    public Acl addAcl(String systemUser, String operation, String privileges, String path) {
-        Acl acl = new Acl(operation, privileges, path);
+    public Acl addAcl(String systemUser, Acl acl) {
         acls.computeIfAbsent(systemUser, k -> new LinkedList<>()).add(acl);
         return acl;
     }
 
-    private void addPath(String path, Set<String> paths) {
+    private void addPath(Path path, Set<Path> paths) {
         if (preProvidedPaths.add(path)) {
             paths.add(path);
         }
 
-        int endIndex = path.lastIndexOf('/');
-        if (endIndex > 0) {
-            addPath(path.substring(0, endIndex), paths);
+        Path parent = path.getParent();
+        if (parent != null && parent.getNameCount() > 0) {
+            addPath(parent, paths);
         }
     }
 
@@ -105,20 +105,20 @@
 
             // system users
 
-            for (String systemUser : systemUsers) {
-                List<Acl> authorizations = acls.remove(systemUser);
+            for (SystemUser systemUser : systemUsers) {
+                List<Acl> authorizations = acls.remove(systemUser.getId());
 
-                // make sure all paths are created first
+                // make sure all users are created first
+
+                formatter.format("create service user %s with path %s%n", systemUser.getId(), systemUser.getPath().getFileName());
+
+                // create then the paths
 
                 addPaths(authorizations, packageAssemblers, formatter);
 
-                // create then the users
-
-                formatter.format("create service user %s%n", systemUser);
-
                 // finally add ACLs
 
-                addAclStatement(formatter, systemUser, authorizations);
+                addAclStatement(formatter, systemUser.getId(), authorizations);
             }
 
             // all the resting ACLs can now be set
@@ -126,7 +126,7 @@
             for (Entry<String, List<Acl>> currentAcls : acls.entrySet()) {
                 String systemUser = currentAcls.getKey();
 
-                if (preProvidedSystemUsers.contains(systemUser)) {
+                if (isKnownSystemUser(systemUser)) {
                     List<Acl> authorizations = currentAcls.getValue();
 
                     // make sure all paths are created first
@@ -153,6 +153,15 @@
         }
     }
 
+    private boolean isKnownSystemUser(String id) {
+        for (SystemUser systemUser : preProvidedSystemUsers) {
+            if (id.equals(systemUser.getId())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public void addNodetypeRegistrationSentence(String nodetypeRegistrationSentence) {
         if (nodetypeRegistrationSentence != null) {
@@ -177,19 +186,19 @@
             return;
         }
 
-        Set<String> paths = new TreeSet<String>();
+        Set<Path> paths = new TreeSet<>();
         for (Acl authorization : authorizations) {
             addPath(authorization.getPath(), paths);
         }
 
-        Set<String> visitedPaths = new HashSet<>();
+        Set<Path> visitedPaths = new HashSet<>();
         for (VaultPackageAssembler packageAssembler: packageAssemblers) {
-            for (String path : paths) {
+            for (Path path : paths) {
                 if (!visitedPaths.add(path)) {
                     continue;
                 }
 
-                File currentDir = packageAssembler.getEntry(path);
+                File currentDir = packageAssembler.getEntry(path.toString());
                 String type = DEFAULT_TYPE;
 
                 if (currentDir.exists()) {
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/acl/SystemUser.java b/src/main/java/org/apache/sling/feature/cpconverter/acl/SystemUser.java
new file mode 100644
index 0000000..0c3c3c6
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/cpconverter/acl/SystemUser.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You 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 org.apache.sling.feature.cpconverter.acl;
+
+import java.nio.file.Path;
+import java.util.Objects;
+
+public class SystemUser {
+
+    private final String id;
+
+    private final Path path;
+
+    public SystemUser(String id, Path path) {
+        this.id = id;
+        this.path = path;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public Path getPath() {
+        return path;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + Objects.hash(id);
+        result = prime * result + Objects.hash(path);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        SystemUser other = (SystemUser) obj;
+        return Objects.equals(id, other.getId()) && Objects.equals(path, other.getPath());
+    }
+
+    
+
+}
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandler.java
index 0ea5aa3..f377ca1 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandler.java
@@ -19,6 +19,7 @@
 import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 
 import java.io.InputStream;
+import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Stack;
@@ -36,7 +37,7 @@
 public final class RepPolicyEntryHandler extends AbstractRegexEntryHandler {
 
     public RepPolicyEntryHandler() {
-        super("/jcr_root(/.+)/_rep_policy.xml");
+        super("/jcr_root(.*/)_rep_policy.xml");
     }
 
     @Override
@@ -112,7 +113,9 @@
                     int endIndex = privileges.indexOf(']');
                     privileges = privileges.substring(beginIndex, endIndex);
 
-                    acls.add(aclManager.addAcl(principalName, operation, privileges, path));
+                    Acl acl = new Acl(operation, privileges, Paths.get(path));
+
+                    acls.add(aclManager.addAcl(principalName, acl));
                 } else if (REP_RESTRICTIONS.equals(primaryType) && !acls.isEmpty()) {
                     for (String restriction : RESTRICTIONS) {
                         String path = attributes.getValue(restriction);
diff --git a/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java b/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
index 74ce9c0..1188c01 100644
--- a/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
+++ b/src/main/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandler.java
@@ -17,10 +17,13 @@
 package org.apache.sling.feature.cpconverter.handlers;
 
 import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 
 import org.apache.jackrabbit.vault.fs.io.Archive;
 import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
 import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
+import org.apache.sling.feature.cpconverter.acl.SystemUser;
 import org.apache.sling.feature.cpconverter.shared.AbstractJcrNodeParser;
 import org.xml.sax.Attributes;
 
@@ -33,7 +36,7 @@
     @Override
     public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter)
             throws Exception {
-        SystemUserParser systemUserParser = new SystemUserParser(converter);
+        SystemUserParser systemUserParser = new SystemUserParser(converter, Paths.get(path).getParent());
         try (InputStream input = archive.openInputStream(entry)) {
             systemUserParser.parse(input);
         }
@@ -47,16 +50,19 @@
 
         private final ContentPackage2FeatureModelConverter converter;
 
-        public SystemUserParser(ContentPackage2FeatureModelConverter converter) {
+        private final Path path;
+
+        public SystemUserParser(ContentPackage2FeatureModelConverter converter, Path path) {
             super(REP_SYSTEM_USER);
             this.converter = converter;
+            this.path = path;
         }
 
         @Override
         protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) {
             String authorizableId = attributes.getValue(REP_AUTHORIZABLE_ID);
             if (authorizableId != null && !authorizableId.isEmpty()) {
-                converter.getAclManager().addSystemUser(authorizableId);
+                converter.getAclManager().addSystemUser(new SystemUser(authorizableId, path));
             }
         }
 
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/acl/AclManagerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/acl/AclManagerTest.java
index 1b835e1..2fc239b 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/acl/AclManagerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/acl/AclManagerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.when;
 
 import java.io.File;
+import java.nio.file.Paths;
 import java.util.Arrays;
 
 import org.apache.sling.feature.ArtifactId;
@@ -49,18 +50,18 @@
 
     @Test
     public void makeSureAclsAreCreatedOnlyForKnownUsers() {
-        aclManager.addSystemUser("acs-commons-ensure-oak-index-service");
+        aclManager.addSystemUser(new SystemUser("acs-commons-ensure-oak-index-service", Paths.get("/asd/public")));
 
         // emulate a second iteration of conversion
         aclManager.reset();
 
-        aclManager.addSystemUser("acs-commons-package-replication-status-event-service");
+        aclManager.addSystemUser(new SystemUser("acs-commons-package-replication-status-event-service", Paths.get("/asd/public")));
 
-        aclManager.addAcl("acs-commons-ensure-oak-index-service", "allow", "jcr:read,rep:write,rep:indexDefinitionManagement", "/asd/public");
-        aclManager.addAcl("acs-commons-package-replication-status-event-service", "allow", "jcr:read,crx:replicate,jcr:removeNode", "/asd/public");
+        aclManager.addAcl("acs-commons-ensure-oak-index-service", new Acl("allow", "jcr:read,rep:write,rep:indexDefinitionManagement", Paths.get("/asd/public")));
+        aclManager.addAcl("acs-commons-package-replication-status-event-service", new Acl("allow", "jcr:read,crx:replicate,jcr:removeNode", Paths.get("/asd/public")));
 
         // add an ACL for unknown user
-        aclManager.addAcl("acs-commons-on-deploy-scripts-service", "allow", "jcr:read,crx:replicate,jcr:removeNode", "/asd/public");
+        aclManager.addAcl("acs-commons-on-deploy-scripts-service", new Acl("allow", "jcr:read,crx:replicate,jcr:removeNode", Paths.get("/asd/public")));
 
         VaultPackageAssembler assembler = mock(VaultPackageAssembler.class);
         when(assembler.getEntry(anyString())).thenReturn(new File(System.getProperty("java.io.tmpdir")));
@@ -72,9 +73,9 @@
         assertNotNull(repoinitExtension);
 
         // acs-commons-on-deploy-scripts-service will be missed
-        String expected = "create path (sling:Folder) /asd\n" + 
-                "create path (sling:Folder) /asd/public\n" + 
-                "create service user acs-commons-package-replication-status-event-service\n" + 
+        String expected = "create service user acs-commons-package-replication-status-event-service with path public\n" + 
+                "create path (sling:Folder) /asd\n" + 
+                "create path (sling:Folder) /asd/public\n" +
                 "set ACL for acs-commons-package-replication-status-event-service\n" + 
                 "allow jcr:read,crx:replicate,jcr:removeNode on /asd/public\n" + 
                 "end\n" + 
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
index 1d8f63c..a94cf52 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/RepPolicyEntryHandlerTest.java
@@ -16,17 +16,18 @@
  */
 package org.apache.sling.feature.cpconverter.handlers;
 
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import java.io.File;
+import java.nio.file.Paths;
 import java.util.Arrays;
 
 import org.apache.jackrabbit.vault.fs.io.Archive;
@@ -37,6 +38,7 @@
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
 import org.apache.sling.feature.cpconverter.acl.DefaultAclManager;
+import org.apache.sling.feature.cpconverter.acl.SystemUser;
 import org.apache.sling.feature.cpconverter.features.DefaultFeaturesManager;
 import org.apache.sling.feature.cpconverter.features.FeaturesManager;
 import org.apache.sling.feature.cpconverter.vltpkg.VaultPackageAssembler;
@@ -70,6 +72,11 @@
     }
 
     @Test
+    public void matchesRootPolicies() {
+        assertTrue(handler.matches("/jcr_root/_rep_policy.xml"));
+    }
+
+    @Test
     public void parseAcl() throws Exception {
         Extension repoinitExtension = parseAndSetRepoinit("acs-commons-ensure-oak-index-service",
                                                           "acs-commons-dispatcher-flush-service",
@@ -80,29 +87,29 @@
         assertNotNull(repoinitExtension);
         assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
 
-        String expected = "create path (sling:Folder) /asd\n" + 
+        String expected = "create service user acs-commons-ensure-oak-index-service with path public\n" + 
+                "create path (sling:Folder) /asd\n" + 
                 "create path (sling:Folder) /asd/public\n" + 
-                "create service user acs-commons-ensure-oak-index-service\n" + 
                 "set ACL for acs-commons-ensure-oak-index-service\n" + 
                 "allow jcr:read,rep:write,rep:indexDefinitionManagement on /asd/public restriction(rep:glob,*/oak:index/*)\n" + 
                 "end\n" + 
-                "create service user acs-commons-dispatcher-flush-service\n" + 
+                "create service user acs-commons-dispatcher-flush-service with path public\n" + 
                 "set ACL for acs-commons-dispatcher-flush-service\n" + 
                 "allow jcr:read,crx:replicate,jcr:removeNode on /asd/public\n" + 
                 "end\n" + 
-                "create service user acs-commons-package-replication-status-event-service\n" + 
+                "create service user acs-commons-package-replication-status-event-service with path public\n" + 
                 "set ACL for acs-commons-package-replication-status-event-service\n" + 
                 "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
                 "end\n" + 
-                "create service user acs-commons-ensure-service-user-service\n" + 
+                "create service user acs-commons-ensure-service-user-service with path public\n" + 
                 "set ACL for acs-commons-ensure-service-user-service\n" + 
                 "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
                 "end\n" + 
-                "create service user acs-commons-automatic-package-replicator-service\n" + 
+                "create service user acs-commons-automatic-package-replicator-service with path public\n" + 
                 "set ACL for acs-commons-automatic-package-replicator-service\n" + 
                 "allow jcr:read on /asd/public\n" + 
                 "end\n" + 
-                "create service user acs-commons-on-deploy-scripts-service\n" + 
+                "create service user acs-commons-on-deploy-scripts-service with path public\n" + 
                 "set ACL for acs-commons-on-deploy-scripts-service\n" + 
                 "allow jcr:read on /asd/public\n" + 
                 "end\n";
@@ -119,21 +126,21 @@
         assertNotNull(repoinitExtension);
         assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
 
-        String expected = "create path (sling:Folder) /asd\n" + 
+        String expected = "create service user acs-commons-package-replication-status-event-service with path public\n" + 
+                "create path (sling:Folder) /asd\n" + 
                 "create path (sling:Folder) /asd/public\n" + 
-                "create service user acs-commons-package-replication-status-event-service\n" + 
                 "set ACL for acs-commons-package-replication-status-event-service\n" + 
                 "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
                 "end\n" + 
-                "create service user acs-commons-ensure-service-user-service\n" + 
+                "create service user acs-commons-ensure-service-user-service with path public\n" + 
                 "set ACL for acs-commons-ensure-service-user-service\n" + 
                 "allow jcr:read,rep:write,jcr:readAccessControl,jcr:modifyAccessControl on /asd/public\n" + 
                 "end\n" + 
-                "create service user acs-commons-automatic-package-replicator-service\n" + 
+                "create service user acs-commons-automatic-package-replicator-service with path public\n" + 
                 "set ACL for acs-commons-automatic-package-replicator-service\n" + 
                 "allow jcr:read on /asd/public\n" + 
                 "end\n" + 
-                "create service user acs-commons-on-deploy-scripts-service\n" + 
+                "create service user acs-commons-on-deploy-scripts-service with path public\n" + 
                 "set ACL for acs-commons-on-deploy-scripts-service\n" + 
                 "allow jcr:read on /asd/public\n" + 
                 "end\n";
@@ -166,7 +173,7 @@
 
         if (systemUsers != null) {
             for (String systemUser : systemUsers) {
-                converter.getAclManager().addSystemUser(systemUser);
+                converter.getAclManager().addSystemUser(new SystemUser(systemUser, Paths.get("/asd/public")));
             }
         }
 
diff --git a/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java b/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
index 5fa5034..a4f133a 100644
--- a/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
+++ b/src/test/java/org/apache/sling/feature/cpconverter/handlers/SystemUsersEntryHandlerTest.java
@@ -75,7 +75,10 @@
         assertNotNull(repoinitExtension);
         assertEquals(ExtensionType.TEXT, repoinitExtension.getType());
         assertTrue(repoinitExtension.isRequired());
-        assertEquals("create service user asd-share-commons-asd-index-definition-reader-service\n", repoinitExtension.getText());
+
+        String expected = "create service user asd-share-commons-asd-index-definition-reader-service with path asd-index-definition-reader\n";
+        String actual = repoinitExtension.getText();
+        assertEquals(expected, actual);
     }
 
     @Test