You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2023/04/28 19:12:29 UTC

[jackrabbit-filevault] 01/01: Fix restoring original principal ACLs

This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch bugfix/JCRVLT-683
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git

commit b5c99c7fe7e50b70075ffa661e0889ebe02eb95e
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Tue Apr 18 08:58:54 2023 +0200

    Fix restoring original principal ACLs
---
 .../vault/fs/impl/io/DocViewAdapter.java           |   7 +-
 .../vault/fs/impl/io/DocViewImporter.java          |  23 +-
 .../vault/fs/impl/io/ImportInfoImpl.java           |  41 ++
 .../vault/fs/impl/io/JackrabbitACLImporter.java    | 592 ++++++---------------
 .../vault/fs/impl/io/JcrSysViewTransformer.java    |   2 +-
 .../apache/jackrabbit/vault/fs/io/Importer.java    |  65 ++-
 .../jackrabbit/vault/fs/spi/ACLManagement.java     |   3 +-
 .../jackrabbit/vault/fs/spi/UserManagement.java    |  13 +-
 ...anagement.java => JackrabbitACLManagement.java} |  63 +--
 .../spi/impl/jcr20/JackrabbitServiceProvider.java  |   2 +-
 .../spi/impl/jcr20/JackrabbitUserManagement.java   |  49 +-
 .../vault/fs/spi/impl/jcr20/SimplePrincipal.java   |  32 ++
 .../accesscontrol/AbstractAccessControlEntry.java  |  96 ++++
 .../JackrabbitAccessControlEntryBuilder.java}      |  19 +-
 .../JackrabbitAccessControlPolicy.java             | 193 +++++++
 .../JackrabbitAccessControlPolicyBuilder.java}     |  14 +-
 .../PrincipalBasedAccessControlEntry.java          |  54 ++
 .../PrincipalBasedAccessControlList.java           | 108 ++++
 .../PrincipalSetAccessControlPolicy.java           |  91 ++++
 .../ResourceBasedAccessControlEntry.java           |  59 ++
 .../ResourceBasedAccessControlList.java            | 123 +++++
 .../jackrabbit/vault/fs/spi/package-info.java      |   2 +-
 .../jackrabbit/vault/util/DocViewProperty2.java    |  44 +-
 .../UncheckedValueFormatException.java}            |  24 +-
 .../packaging/integration/PrincipalBasedIT.java    | 148 ++++--
 .../spi/impl/AccessControlValidator.java           |   4 +-
 26 files changed, 1302 insertions(+), 569 deletions(-)

diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewAdapter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewAdapter.java
index e542f0a4..e50a4ddb 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewAdapter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewAdapter.java
@@ -16,6 +16,7 @@
  ************************************************************************/
 package org.apache.jackrabbit.vault.fs.impl.io;
 
+import java.io.IOException;
 import java.util.List;
 
 import javax.jcr.RepositoryException;
@@ -35,19 +36,19 @@ public interface DocViewAdapter {
      * @param node the node
      * @throws RepositoryException if a import exception occurs.
      */
-    void startNode(DocViewNode2 node) throws RepositoryException;
+    void startNode(DocViewNode2 node) throws IOException, RepositoryException;
 
     /**
      * Ends node is invoked when the importer ascends from an element.
      * @throws RepositoryException if a import exception occurs.
      */
-    void endNode() throws RepositoryException;
+    void endNode() throws IOException, RepositoryException;
 
     /**
      * Is called by the importer if the adapter is no longer used and must finalize the import.
      * @throws RepositoryException if a import exception occurs.
      * @return The paths that were created.
      */
-    List<String> close() throws RepositoryException;
+    List<String> close() throws IOException, RepositoryException;
 
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
index dfb92644..fdafce5f 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/DocViewImporter.java
@@ -54,7 +54,7 @@ import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.nodetype.NodeDefinition;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.PropertyDefinition;
-import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
 import javax.jcr.version.VersionException;
 
 import org.apache.jackrabbit.spi.Name;
@@ -482,11 +482,9 @@ public class DocViewImporter implements DocViewParserHandler {
                                     }
                                 }
                             } 
-                            
-                            // TODO: how to find authorizables in the quickest way?
-                            aclManagement.getPrincipalAcls(child);
 
                             if (shouldRemoveChild) {
+                                stashPrincipalAcls(child);
                                 importInfo.onDeleted(path);
                                 child.remove();
                             }
@@ -510,6 +508,14 @@ public class DocViewImporter implements DocViewParserHandler {
         }
     }
 
+    protected void stashPrincipalAcls(Node node) throws RepositoryException {
+        Map<String, List<? extends AccessControlPolicy>> principalAcls = aclManagement.getPrincipalAcls(node);
+        if (!principalAcls.isEmpty()) {
+            log.debug("Stashing {} principal ACLs below to be deleted node {}", principalAcls.size(), node.getPath());
+            importInfo.onDeletedPrincipalAcls(principalAcls);
+        }
+    }
+
     private boolean hasSiblingWithPrimaryTypesAndName(Node node, NodeType[] requiredPrimaryNodeTypes, String requiredName) throws RepositoryException {
         NodeIterator iter = node.getParent().getNodes();
         while (iter.hasNext()) {
@@ -728,9 +734,10 @@ public class DocViewImporter implements DocViewParserHandler {
      * @param node the parent node
      * @param docViewNode   doc view node of the authorizable
      * @throws RepositoryException if an error accessing the repository occurrs.
+     * @throws IOException 
      * @throws SAXException        if an XML parsing error occurrs.
      */
-    private void handleAuthorizable(Node node, DocViewNode2 docViewNode) throws RepositoryException {
+    private void handleAuthorizable(Node node, DocViewNode2 docViewNode) throws RepositoryException, IOException {
         String id = userManagement.getAuthorizableId(docViewNode);
         String newPath = node.getPath() + "/" + npResolver.getJCRName(docViewNode.getName());
         boolean isIncluded = wspFilter.contains(newPath);
@@ -744,8 +751,10 @@ public class DocViewImporter implements DocViewParserHandler {
             log.trace("Authorizable element detected. Starting sysview transformation {}", newPath);
             stack = stack.push();
             stack.adapter = new JcrSysViewTransformer(node, wspFilter.getImportMode(newPath));
+            stashPrincipalAcls(node);
             stack.adapter.startNode(docViewNode);
             importInfo.onCreated(newPath);
+            importInfo.onAuthorizableCreated(id);
             return;
         }
 
@@ -787,15 +796,18 @@ public class DocViewImporter implements DocViewParserHandler {
                 // just replace the entire subtree for now.
                 log.trace("Authorizable element detected. starting sysview transformation {}", newPath);
                 stack = stack.push();
+                stashPrincipalAcls(authNode);
                 stack.adapter = new JcrSysViewTransformer(node, mode);
                 stack.adapter.startNode(docViewNode);
                 importInfo.onReplaced(newPath);
+                importInfo.onAuthorizableCreated(id);
                 break;
 
             case UPDATE:
             case UPDATE_PROPERTIES:
                 log.trace("Authorizable element detected. starting sysview transformation {}", newPath);
                 stack = stack.push();
+                stashPrincipalAcls(authNode);
                 stack.adapter = new JcrSysViewTransformer(node, oldPath, mode);
                 // we need to tweak the ni.name so that the sysview import does not
                 // rename the authorizable node name
@@ -819,6 +831,7 @@ public class DocViewImporter implements DocViewParserHandler {
                 
                 stack.adapter.startNode(mapped);
                 importInfo.onReplaced(newPath);
+                importInfo.onAuthorizableCreated(id);
                 break;
         }
     }
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ImportInfoImpl.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ImportInfoImpl.java
index e09f7979..a223e188 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ImportInfoImpl.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/ImportInfoImpl.java
@@ -17,11 +17,13 @@
 
 package org.apache.jackrabbit.vault.fs.impl.io;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
@@ -29,8 +31,10 @@ import java.util.TreeMap;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.security.AccessControlPolicy;
 import javax.jcr.version.Version;
 
+import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.vault.fs.api.ImportInfo;
 import org.apache.jackrabbit.vault.fs.api.MultiPathMapping;
 import org.apache.jackrabbit.vault.fs.api.NodeNameList;
@@ -66,6 +70,29 @@ public class ImportInfoImpl implements ImportInfo {
 
     private Map<String, String[]> memberships;
 
+    /**
+     * The principal ACLs which have been deleted during the import (might need to be restored later on in case of moved authorizable)
+     */
+    private final Map<String, List<? extends AccessControlPolicy>> deletedPrincipalAcls;
+
+    public Map<String, List<? extends AccessControlPolicy>> getDeletedPrincipalAcls() {
+        return deletedPrincipalAcls;
+    }
+
+    public List<String> getCreatedAuthorizableIds() {
+        return createdAuthorizableIds;
+    }
+
+    /**
+     * All authorizable ids being deleted during the import
+     */
+    private final List<String> deletedAuthorizableIds;
+
+    /**
+     * All authorizable ids being created during the import
+     */
+    private final List<String> createdAuthorizableIds;
+
     public static ImportInfo create(ImportInfo base) {
         if (base == null) {
             return new ImportInfoImpl();
@@ -75,6 +102,9 @@ public class ImportInfoImpl implements ImportInfo {
     }
 
     public ImportInfoImpl() {
+        deletedPrincipalAcls = new HashMap<>();
+        createdAuthorizableIds = new ArrayList<>();
+        deletedAuthorizableIds = new ArrayList<>();
     }
 
     public ImportInfoImpl merge(ImportInfo base) {
@@ -94,6 +124,9 @@ public class ImportInfoImpl implements ImportInfo {
             } else {
                 memberships.putAll(baseImpl.getMemberships());
             }
+            deletedPrincipalAcls.putAll(baseImpl.deletedPrincipalAcls);
+            deletedAuthorizableIds.addAll(baseImpl.deletedAuthorizableIds);
+            createdAuthorizableIds.addAll(baseImpl.createdAuthorizableIds);
         }
         return this;
     }
@@ -158,6 +191,10 @@ public class ImportInfoImpl implements ImportInfo {
         numErrors++;
     }
 
+    public void onDeletedPrincipalAcls(Map<String, List<? extends AccessControlPolicy>> principalAcls) {
+        deletedPrincipalAcls.putAll(principalAcls);
+    }
+
     /**
      * remembers that a package path was remapped during import. e.g. when the importer follows and existing
      * authorizable for MERGE and UPDATE modes.
@@ -258,6 +295,10 @@ public class ImportInfoImpl implements ImportInfo {
         return memberships == null ? Collections.<String, String[]>emptyMap() : memberships;
     }
 
+    public void onAuthorizableCreated(String id) throws RepositoryException {
+        createdAuthorizableIds.add(id);
+    }
+
     static final class InfoImpl implements Info {
 
         private final String path;
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JackrabbitACLImporter.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JackrabbitACLImporter.java
index f8b5db69..329c3442 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JackrabbitACLImporter.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JackrabbitACLImporter.java
@@ -16,48 +16,43 @@
  ************************************************************************/
 package org.apache.jackrabbit.vault.fs.impl.io;
 
-import java.security.Principal;
-import java.util.ArrayList;
+import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Deque;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 
+import javax.jcr.NamespaceException;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
-import javax.jcr.ValueFactory;
-import javax.jcr.security.AccessControlEntry;
-import javax.jcr.security.AccessControlException;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.AccessControlPolicyIterator;
-import javax.jcr.security.Privilege;
+import javax.jcr.ValueFormatException;
 
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
-import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
-import org.apache.jackrabbit.api.security.authorization.PrincipalSetPolicy;
-import org.apache.jackrabbit.api.security.principal.PrincipalManager;
-import org.apache.jackrabbit.api.security.user.Authorizable;
-import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
-import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
 import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.AbstractAccessControlEntry;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.JackrabbitAccessControlEntryBuilder;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.JackrabbitAccessControlPolicy;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.JackrabbitAccessControlPolicyBuilder;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.PrincipalBasedAccessControlEntry;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.PrincipalBasedAccessControlList;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.PrincipalSetAccessControlPolicy;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.ResourceBasedAccessControlEntry;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.ResourceBasedAccessControlList;
 import org.apache.jackrabbit.vault.util.DocViewNode2;
 import org.apache.jackrabbit.vault.util.DocViewProperty2;
+import org.apache.jackrabbit.vault.util.UncheckedRepositoryException;
+import org.apache.jackrabbit.vault.util.UncheckedValueFormatException;
 import org.slf4j.Logger;
 
 /**
@@ -74,29 +69,39 @@ public class JackrabbitACLImporter implements DocViewAdapter {
      */
     private static final Logger log = DocViewImporter.log;
 
-    private final JackrabbitSession session;
+    private final Session session;
 
     private final AccessControlHandling aclHandling;
 
-    private final AccessControlManager acMgr;
-
-    private final PrincipalManager pMgr;
-
     private final String accessControlledPath;
 
     private final  NamePathResolver resolver;
 
-    private ImportedPolicy<? extends AccessControlPolicy> importPolicy;
-
+    /**
+     * The state representing the level of the last evaluated node (i.e. the parent)
+     *
+     */
     private enum State {
         INITIAL,
-        ACL,
-        ACE,
-        RESTRICTION,
-        ERROR,
-        PRINCIPAL_SET_POLICY
+        RESOURCE_BASED_ACL,
+        PRINCIPAL_BASED_ACL,
+        PRINCIPAL_SET_POLICY,
+        RESOURCE_BASED_ACE,
+        PRINCIPAL_BASED_ACE,
+        RESTRICTION
     }
 
+    /** all property names on either rep:GrantACE/rep:DenyACE or rep:Restrictions which don't represent an access control restriction */
+    private static final Set<Name> NON_RESTRICTION_PROPERTY_NAMES = new HashSet<>(Arrays.asList(
+            NameConstants.REP_PRINCIPAL_NAME,
+            NameConstants.JCR_PRIMARYTYPE,
+            NameConstants.JCR_MIXINTYPES,
+            NameConstants.REP_PRIVILEGES
+            ));
+
+    private JackrabbitAccessControlPolicyBuilder<?> policyBuilder;
+    private JackrabbitAccessControlEntryBuilder<? extends AbstractAccessControlEntry> entryBuilder;
+   
     private final Deque<State> states = new LinkedList<>();
 
     public JackrabbitACLImporter(Node accessControlledNode, AccessControlHandling aclHandling)
@@ -115,433 +120,156 @@ public class JackrabbitACLImporter implements DocViewAdapter {
             throw new RepositoryException("Error while reading access control content: unsupported AccessControlHandling: " + aclHandling);
         }
         this.accessControlledPath = path;
-        this.session = (JackrabbitSession) session;
-        this.acMgr = this.session.getAccessControlManager();
-        this.pMgr = this.session.getPrincipalManager();
+        this.session = session;
         this.aclHandling = aclHandling;
         this.states.push(State.INITIAL);
         this.resolver = new DefaultNamePathResolver(session);
     }
 
-    public void startNode(DocViewNode2 node) {
+    public void startNode(DocViewNode2 node) throws RepositoryException, IOException {
         State state = states.peek();
-        switch (state) {
-            case INITIAL:
-                String primaryType = node.getPrimaryType().orElseThrow(() -> new IllegalStateException("Error while reading access control content: Missing 'jcr:primaryType'"));
-                if ("rep:ACL".equals(primaryType)) {
-                    importPolicy = new ImportedAcList();
-                    state = State.ACL;
-                } else if ("rep:CugPolicy".equals(primaryType)) {
-                    importPolicy = new ImportedPrincipalSet(node);
-                    state = State.PRINCIPAL_SET_POLICY;
-                } else if ("rep:PrincipalPolicy".equals(primaryType)) {
-                    importPolicy = new ImportedPrincipalAcList(node);
-                    state = State.ACL;
-                } else {
-                    log.error("Error while reading access control content: Expected rep:ACL or rep:CugPolicy but was: {}", node.getPrimaryType());
-                    state = State.ERROR;
-                }
-                break;
-            case ACL:
-            case ACE:
-            case RESTRICTION:
-                state = importPolicy.append(state, node);
-                break;
-            case PRINCIPAL_SET_POLICY:
-                state = importPolicy.append(state, node);
-                break;
-            case ERROR:
-                // stay in error
-                break;
-        }
-        states.push(state);
-    }
-
-    public void endNode() {
-        State state = states.pop();
-        importPolicy.endNode(state);
-    }
-
-    public List<String> close() throws RepositoryException {
-        if (states.peek() != State.INITIAL) {
-            log.error("Unexpected end state: {}", states.peek());
-        }
-        List<String> paths = new ArrayList<>();
-        importPolicy.apply(paths, resolver);
-        return paths;
-    }
-
-    private void addPathIfExists(List<String> paths, String path) throws RepositoryException {
-        if (session.nodeExists(path)) {
-            paths.add(path);
-        }
-    }
-
-    private abstract class ImportedPolicy<T extends AccessControlPolicy> {
-
-        abstract State append(State state, DocViewNode2 node);
-
-        abstract void endNode(State state);
-
-        abstract void apply(List<String> paths, NameResolver resolver) throws RepositoryException;
-
-        Principal getPrincipal(final String principalName) {
-            Principal principal = new Principal() {
-                public String getName() {
-                    return principalName;
-                }
-            };
-            return principal;
-        }
-
-        T getPolicy(Class<T> clz) throws RepositoryException {
-            for (AccessControlPolicy p : acMgr.getPolicies(accessControlledPath)) {
-                if (clz.isAssignableFrom(p.getClass())) {
-                    return clz.cast(p);
-                }
-            }
-            return null;
-        }
-
-        T getPolicy(Class<T> clz, Principal principal) throws RepositoryException {
-            if (acMgr instanceof JackrabbitAccessControlManager) {
-                for (AccessControlPolicy p : ((JackrabbitAccessControlManager) acMgr).getPolicies(principal)) {
-                    if (clz.isAssignableFrom(p.getClass())) {
-                        return clz.cast(p);
-                    }
-                }
-            }
-            return null;
-        }
-
-        T getApplicablePolicy(Class<T> clz) throws RepositoryException {
-            AccessControlPolicyIterator iter = acMgr.getApplicablePolicies(accessControlledPath);
-            while (iter.hasNext()) {
-                AccessControlPolicy p = iter.nextAccessControlPolicy();
-                if (clz.isAssignableFrom(p.getClass())) {
-                    return clz.cast(p);
-                }
-            }
-
-            // no applicable policy
-            throw new RepositoryException("no applicable AccessControlPolicy of type "+ clz + " on " +
-                    (accessControlledPath == null ? "'root'" : accessControlledPath));
-        }
-
-        T getApplicablePolicy(Class<T> clz, Principal principal) throws RepositoryException {
-            if (acMgr instanceof JackrabbitAccessControlManager) {
-                for (AccessControlPolicy p : ((JackrabbitAccessControlManager) acMgr).getApplicablePolicies(principal)) {
-                    if (clz.isAssignableFrom(p.getClass())) {
-                        return clz.cast(p);
+        try {
+            switch (state) {
+                case INITIAL:
+                    String primaryType = node.getPrimaryType().orElseThrow(() -> new IllegalStateException("Error while reading access control content: Missing 'jcr:primaryType'"));
+                    if ("rep:ACL".equals(primaryType)) {
+                        policyBuilder = new ResourceBasedAccessControlList.Builder();
+                        state = State.RESOURCE_BASED_ACL;
+                    } else if ("rep:CugPolicy".equals(primaryType)) {
+                        // just collect the rep:principalNames property
+                        Collection<String> principalNames = node.getPropertyValues(NAME_REP_PRINCIPAL_NAMES);
+                        policyBuilder = new PrincipalSetAccessControlPolicy.Builder(principalNames);
+                        state = State.PRINCIPAL_SET_POLICY;
+                    } else if ("rep:PrincipalPolicy".equals(primaryType)) {
+                        String principalName = node.getPropertyValue(NameConstants.REP_PRINCIPAL_NAME).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:principalName' missing on principal policy node"));
+                        policyBuilder = new PrincipalBasedAccessControlList.Builder(principalName);
+                        state = State.PRINCIPAL_BASED_ACL;
+                    } else {
+                        throw new IOException("Error while reading access control content: Expected rep:ACL, rep:PrincipalPolicy or rep:CugPolicy primary type but found: " + node.getPrimaryType().toString());
                     }
-                }
+                    break;
+                case RESOURCE_BASED_ACL:
+                case PRINCIPAL_BASED_ACL:
+                case RESOURCE_BASED_ACE:
+                case PRINCIPAL_BASED_ACE:
+                case RESTRICTION:
+                    state = startEntryNode(node, state);
+                    break;
+                case PRINCIPAL_SET_POLICY:
+                    throw new IOException("Error while reading access control content: Unexpected node: " + node.getPrimaryType().orElse("") + " for state " + state);
             }
-
-            // no applicable policy
-            throw new AccessControlException("no applicable AccessControlPolicy of type "+ clz + " for " + principal.getName());
-        }
+        } catch (UncheckedRepositoryException e) {
+            throw e.getCause();
+        } 
+        states.push(state);
     }
 
-    private final class ImportedAcList extends ImportedPolicy<JackrabbitAccessControlList> {
-
-        private List<ACE> aceList = new ArrayList<>();
-        private ACE currentACE;
-
-        private ImportedAcList() {
-        }
-
-        @Override
-        State append(State state, DocViewNode2 childNode) {
-            if (state == State.ACL) {
-                try {
-                    currentACE = new ACE(childNode);
-                    aceList.add(currentACE);
-                    return State.ACE;
-                } catch (IllegalArgumentException e) {
-                    log.error("Error while reading access control content: {}", e);
-                    return State.ERROR;
+    /**
+     * Extracts all information from rep:GrantACE/rep:DenyACE and children.
+     * This is used for both resource-based and principal based access control entries.
+     * 
+     * @param node
+     * @param state
+     * @return
+     * @see <a href="https://jackrabbit.apache.org/oak/docs/security/accesscontrol/default.html#representation-in-the-repository">Oak Access Control Management : The Default Implementation</a>
+     * @see <a href="https://jackrabbit.apache.org/oak/docs/security/authorization/principalbased.html#representation-in-the-repository">Oak Principal Based Access Control Management</a>
+     * @see <a href="https://jackrabbit.apache.org/oak/docs/security/authorization/restriction.html#representation-in-the-repository">Oak Restrictions</a>
+     */
+    private State startEntryNode(DocViewNode2 node, State state) throws IOException {
+        final State newState;
+        switch(state) {
+            case RESOURCE_BASED_ACL: {
+                final boolean allow;
+                final String primaryType = node.getPrimaryType().orElseThrow(() -> new IllegalStateException("mandatory property 'jcr:primaryType' missing on ace node"));
+                if ("rep:GrantACE".equals(primaryType)) {
+                    allow = true;
+                } else if ("rep:DenyACE".equals(primaryType)) {
+                    allow = false;
+                } else {
+                    throw new IOException("Unexpected node ACE type inside resource based ACL: " + node.getPrimaryType());
                 }
-            } else if (state == State.ACE) {
-                currentACE.addRestrictions(childNode);
-                return State.RESTRICTION;
-            } else {
-                log.error("Error while reading access control content: Unexpected node: {} for state {}", childNode.getPrimaryType(), state);
-                return State.ERROR;
-            }
-        }
-
-        @Override
-        void endNode(State state) {
-            if (state == State.ACE) {
-                currentACE = null;
+                final String principalName = node.getPropertyValue(NameConstants.REP_PRINCIPAL_NAME).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:principalName' missing"));
+                Collection<String> privileges = node.getPropertyValues(NameConstants.REP_PRIVILEGES);
+                entryBuilder = new ResourceBasedAccessControlEntry.Builder(privileges, allow, principalName);
+                extractRestrictions(node).entrySet().stream().forEach( entry -> entryBuilder.addRestriction(entry.getKey(), entry.getValue()));
+                newState = State.RESOURCE_BASED_ACE;
+                break;
             }
-        }
-
-        @Override
-        void apply(List<String> paths, NameResolver resolver) throws RepositoryException {
-            // find principals of existing ACL
-            JackrabbitAccessControlList acl = getPolicy(JackrabbitAccessControlList.class);
-            Set<String> existingPrincipals = new HashSet<String>();
-            if (acl != null) {
-                for (AccessControlEntry ace: acl.getAccessControlEntries()) {
-                    existingPrincipals.add(ace.getPrincipal().getName());
-                }
-
-                // remove existing policy for 'overwrite'
-                if (aclHandling == AccessControlHandling.OVERWRITE) {
-                    acMgr.removePolicy(accessControlledPath, acl);
-                    acl = null;
+            case PRINCIPAL_BASED_ACL: {
+                if (!"rep:PrincipalEntry".equals(node.getPrimaryType().orElseThrow(() -> new IllegalStateException("mandatory property 'jcr:primaryType' missing on principal policy node")))) {
+                    throw new IOException("Unexpected node ACE type inside principal based ACL: " + node.getPrimaryType());
                 }
-            }
-
-            if (acl == null) {
-                acl = getApplicablePolicy(JackrabbitAccessControlList.class);
-            }
-
-            // clear all ACEs of the package principals for merge (VLT-94), otherwise the `acl.addEntry()` below
-            // might just combine the privileges.
-            if (aclHandling == AccessControlHandling.MERGE) {
-                for (ACE entry : aceList) {
-                    for (AccessControlEntry ace : acl.getAccessControlEntries()) {
-                        if (ace.getPrincipal().getName().equals(entry.principalName)) {
-                            acl.removeAccessControlEntry(ace);
-                        }
-                    }
+                Collection<String> privileges = node.getPropertyValues(NameConstants.REP_PRIVILEGES);
+                String v = node.getPropertyValue(NAME_REP_EFFECTIVE_PATH).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:effectivePath ' missing on principal entry node"));
+                final String effectivePath;
+                if (v.isEmpty()) {
+                    effectivePath = null;
+                } else {
+                    effectivePath = v;
                 }
+                entryBuilder = new PrincipalBasedAccessControlEntry.Builder(privileges, effectivePath);
+                newState = State.PRINCIPAL_BASED_ACE;
+                break;
             }
-
-            // apply ACEs of package
-            for (ACE ace : aceList) {
-                final String principalName = ace.principalName;
-                if (aclHandling == AccessControlHandling.MERGE_PRESERVE && existingPrincipals.contains(principalName)) {
-                    // skip principal if it already has an ACL
-                    continue;
+            case RESOURCE_BASED_ACE:
+            case PRINCIPAL_BASED_ACE: {
+                if (!"rep:Restrictions".equals(node.getPrimaryType().orElseThrow(() -> new IllegalStateException("mandatory property 'jcr:primaryType' missing on principal policy node")))) {
+                    throw new IllegalArgumentException("Unexpected restriction type inside principal or resource based ACE: " + node.getPrimaryType());
                 }
-                Principal principal = getPrincipal(principalName);
-
-                Map<String, Value> svRestrictions = new HashMap<String, Value>();
-                Map<String, Value[]> mvRestrictions = new HashMap<String, Value[]>();
-                ace.convertRestrictions(acl, session.getValueFactory(), resolver, svRestrictions, mvRestrictions);
-                acl.addEntry(principal, ace.getPrivileges(acMgr), ace.allow, svRestrictions, mvRestrictions);
-            }
-            acMgr.setPolicy(accessControlledPath, acl);
-
-            if (accessControlledPath == null) {
-                addPathIfExists(paths, "/rep:repoPolicy");
-            } else if ("/".equals(accessControlledPath)) {
-                addPathIfExists(paths, "/rep:policy");
-            } else {
-                addPathIfExists(paths, accessControlledPath + "/rep:policy");
+                extractRestrictions(node).entrySet().stream().forEach( entry -> entryBuilder.addRestriction(entry.getKey(), entry.getValue()));
+                newState = State.RESTRICTION;
+                break;
             }
+            case RESTRICTION:
+                throw new IOException("Restriction nodes are not supposed to have any children but found " + node.toString());
+            default:
+                throw new IllegalArgumentException("This method must not be called with state " + state);
         }
+        return newState;
     }
 
-    private final class ImportedPrincipalSet extends ImportedPolicy<PrincipalSetPolicy> {
-
-        private final Collection<String> principalNames;
-
-        private ImportedPrincipalSet(DocViewNode2 node) {
-            // don't change the status as a cug policy may not have child nodes.
-            // just collect the rep:principalNames property
-            // any subsequent state would indicate an error
-            principalNames = node.getPropertyValues(NAME_REP_PRINCIPAL_NAMES);
-        }
-
-        @Override
-        State append(State state, DocViewNode2 childNode) {
-            log.error("Error while reading access control content: Unexpected node: {} for state {}", childNode.getPrimaryType(), state);
-            return State.ERROR;
-        }
-
-        @Override
-        void endNode(State state) {
-            // nothing to do
-        }
-
-        @Override
-        void apply(List<String> paths, NameResolver resolver) throws RepositoryException {
-            PrincipalSetPolicy psPolicy = getPolicy(PrincipalSetPolicy.class);
-            if (psPolicy != null) {
-                Set<Principal> existingPrincipals = psPolicy.getPrincipals();
-                // remove existing policy for 'overwrite'
-                if (aclHandling == AccessControlHandling.OVERWRITE) {
-                    psPolicy.removePrincipals(existingPrincipals.toArray(new Principal[existingPrincipals.size()]));
-                }
-            } else {
-                psPolicy = getApplicablePolicy(PrincipalSetPolicy.class);
-            }
-
-            // TODO: correct behavior for MERGE and MERGE_PRESERVE?
-            Principal[] principals = principalNames.stream().map(name -> getPrincipal(name)).toArray(Principal[]::new);
-
-            psPolicy.addPrincipals(principals);
-            acMgr.setPolicy(accessControlledPath, psPolicy);
-
-            if ("/".equals(accessControlledPath)) {
-                addPathIfExists(paths, "/rep:cugPolicy");
-            } else {
-                addPathIfExists(paths, accessControlledPath + "/rep:cugPolicy");
-            }
-        }
+    private Map<String, Value[]> extractRestrictions(DocViewNode2 node) {
+       return node.getProperties().stream()
+                .filter(p -> (!NON_RESTRICTION_PROPERTY_NAMES.contains(p.getName())))
+                .collect(Collectors.<DocViewProperty2, String, Value[]>toMap(
+                        p -> {
+                            try {
+                                return resolver.getJCRName(p.getName());
+                            } catch (NamespaceException e) {
+                                // should not happen
+                                throw new IllegalStateException("Cannot retrieve qualified name for " + p.getName().toString(), e);
+                            }
+                        }, 
+                        p -> {
+                            try {
+                                return p.getValues(session.getValueFactory()).toArray(new Value[0]);
+                            } catch (ValueFormatException e) {
+                                throw new UncheckedValueFormatException(e);
+                            } catch (RepositoryException e) {
+                                throw new UncheckedRepositoryException(e);
+                            }
+                        }));
     }
 
-    private final class ImportedPrincipalAcList extends ImportedPolicy<PrincipalAccessControlList> {
-
-        private final Principal principal;
-        private final List<PrincipalEntry> entries = new ArrayList<>();
-        private PrincipalEntry currentEntry;
-
-        private ImportedPrincipalAcList(DocViewNode2 node) {
-             String principalName = node.getPropertyValue(NameConstants.REP_PRINCIPAL_NAME).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:principalName' missing on principal policy node"));
-             Principal p = pMgr.getPrincipal(principalName);
-             if (p == null) {
-                 try {
-                     Authorizable a = session.getUserManager().getAuthorizableByPath(accessControlledPath);
-                     if (a != null) {
-                         p = a.getPrincipal();
-                     }
-                 } catch (RepositoryException e) {
-                     log.debug("Error while trying to retrieve user/group from access controlled path {}, {}", accessControlledPath, e.getMessage());
-                 }
-                 if (p == null) {
-                     p = getPrincipal(principalName);
-                 }
-             }
-             principal = p;
-        }
-
-        @Override
-        State append(State state, DocViewNode2 childNode) {
-            if (state == State.ACL) {
-                if (!"rep:PrincipalEntry".equals(childNode.getPrimaryType().orElseThrow(() -> new IllegalStateException("mandatory property 'jcr:primaryType' missing on principal policy node")))) {
-                    log.error("Unexpected node type of access control entry: {}", childNode.getPrimaryType());
-                    return State.ERROR;
-                }
-                currentEntry = new PrincipalEntry(childNode);
-                entries.add(currentEntry);
-                return State.ACE;
-            } else if (state == State.ACE) {
-                currentEntry.addRestrictions(childNode);
-                return State.RESTRICTION;
-            } else {
-                log.error("Error while reading access control content: Unexpected node: {} for state {}", childNode.getPrimaryType(), state);
-                return State.ERROR;
-            }
-        }
-
-        @Override
-        void endNode(State state) {
-            if (state == State.ACE) {
-                currentEntry = null;
-            }
-        }
-
-        @Override
-        void apply(List<String> paths, NameResolver resolver) throws RepositoryException {
-            if (aclHandling == AccessControlHandling.MERGE_PRESERVE) {
-                log.debug("MERGE_PRESERVE for principal-based access control list is equivalent to IGNORE.");
-                return;
-            }
-
-            PrincipalAccessControlList acl = getPolicy(PrincipalAccessControlList.class, principal);
-            if (acl != null && aclHandling == AccessControlHandling.OVERWRITE) {
-                // remove existing policy for 'OVERWRITE'
-                acMgr.removePolicy(acl.getPath(), acl);
-                acl = null;
-            }
-
-            if (acl == null) {
-                acl = getApplicablePolicy(PrincipalAccessControlList.class, principal);
-            }
-
-            // apply ACEs of package for MERGE and OVERWRITE
-            for (PrincipalEntry entry : entries) {
-                Map<String, Value> svRestrictions = new HashMap<>();
-                Map<String, Value[]> mvRestrictions = new HashMap<String, Value[]>();
-                entry.convertRestrictions(acl, session.getValueFactory(), resolver, svRestrictions, mvRestrictions);
-                acl.addEntry(entry.effectivePath, entry.getPrivileges(acMgr), svRestrictions, mvRestrictions);
-            }
-            acMgr.setPolicy(acl.getPath(), acl);
-
-            if (accessControlledPath == null) {
-                addPathIfExists(paths, "/rep:repoPolicy");
-            } else if ("/".equals(accessControlledPath)) {
-                addPathIfExists(paths, "/rep:policy");
-            } else {
-                addPathIfExists(paths, accessControlledPath + "/rep:policy");
-            }
-        }
-    }
-
-    private static class AbstractEntry {
-
-        private final Collection<String> privileges;
-        private final Map<Name, DocViewProperty2> restrictions;
-
-        private AbstractEntry(DocViewNode2 node) {
-            privileges = node.getPropertyValues(NameConstants.REP_PRIVILEGES);
-            restrictions = new HashMap<>();
-            addRestrictions(node);
-        }
-
-        void addRestrictions(DocViewNode2 node) {
-            restrictions.putAll(node.getProperties().stream().collect(Collectors.toMap(DocViewProperty2::getName, Function.identity())));
-        }
-
-        void convertRestrictions(JackrabbitAccessControlList acl, ValueFactory vf, NameResolver resolver, Map<String, Value> svRestrictions, Map<String, Value[]> mvRestrictions) throws RepositoryException {
-            for (String restName : acl.getRestrictionNames()) {
-                DocViewProperty2 restriction = restrictions.get(resolver.getQName(restName));
-                if (restriction != null) {
-                    Value[] values = new Value[restriction.getStringValues().size()];
-                    int type = acl.getRestrictionType(restName);
-                    for (int i=0; i<values.length; i++) {
-                        values[i] = vf.createValue(restriction.getStringValues().get(i), type);
-                    }
-                    if (restriction.isMultiValue()) {
-                        mvRestrictions.put(restName, values);
-                    } else {
-                        svRestrictions.put(restName, values[0]);
-                    }
-                }
+    public void endNode() {
+        State state = states.pop();
+        switch(state) {
+            case RESOURCE_BASED_ACE:
+            case PRINCIPAL_BASED_ACE: {
+                policyBuilder.addEntry(entryBuilder.build());
+                break;
             }
-        }
-
-        Privilege[] getPrivileges(AccessControlManager acMgr) throws RepositoryException {
-            return AccessControlUtils.privilegesFromNames(acMgr, privileges.toArray(new String[0]));
+            default:
+                // nothing happens in all other states
         }
     }
 
-    private static class ACE extends AbstractEntry {
-
-        private final boolean allow;
-        private final String principalName;
-
-        private ACE(DocViewNode2 childNode) {
-            super(childNode);
-            String primaryType = childNode.getPrimaryType().orElseThrow(() -> new IllegalStateException("mandatory property 'jcr:primaryType' missing on ace node"));
-            if ("rep:GrantACE".equals(primaryType)) {
-                allow = true;
-            } else if ("rep:DenyACE".equals(primaryType)) {
-                allow = false;
-            } else {
-                throw new IllegalArgumentException("Unexpected node ACE type: " + childNode.getPrimaryType());
-            }
-            principalName = childNode.getPropertyValue(NameConstants.REP_PRINCIPAL_NAME).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:principalName' missing"));
+    public List<String> close() throws RepositoryException {
+        if (states.peek() != State.INITIAL) {
+            log.error("Unexpected end state: {}", states.peek());
         }
+        JackrabbitAccessControlPolicy policy = policyBuilder.build(accessControlledPath);
+        return policy.apply(session, aclHandling);
     }
 
-    private static class PrincipalEntry extends AbstractEntry {
-
-        private final String effectivePath;
-
-        private PrincipalEntry(DocViewNode2 node) {
-            super(node);
-            String v = node.getPropertyValue(NAME_REP_EFFECTIVE_PATH).orElseThrow(() -> new IllegalStateException("mandatory property 'rep:effectivePath ' missing on principal entry node"));
-            if (v.isEmpty()) {
-                effectivePath = null;
-            } else {
-                effectivePath = v;
-            }
-        }
-    }
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
index 22b75c86..c9d261f5 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/impl/io/JcrSysViewTransformer.java
@@ -109,7 +109,7 @@ public class JcrSysViewTransformer implements DocViewAdapter {
         this.existingPath = existingPath;
         if (existingPath != null) {
             // check if there is an existing node with the name
-            recovery = new NodeStash(session, existingPath).excludeName("rep:cache");
+            recovery = new NodeStash(session, existingPath).excludeName("rep:cache").excludeName("rep:principalPolicy");
             recovery.stash();
         }
         excludeNode(NAME_REP_CACHE);
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
index 81a0b3d8..7bac61b1 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/io/Importer.java
@@ -39,10 +39,12 @@ import java.util.Set;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.security.AccessControlPolicy;
 import javax.jcr.version.Version;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.api.security.authorization.PrincipalSetPolicy;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceMapping;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
 import org.apache.jackrabbit.spi.commons.namespace.SessionNamespaceResolver;
@@ -81,6 +83,7 @@ import org.apache.jackrabbit.vault.fs.spi.PrivilegeInstaller;
 import org.apache.jackrabbit.vault.fs.spi.ProgressTracker;
 import org.apache.jackrabbit.vault.fs.spi.ServiceProviderFactory;
 import org.apache.jackrabbit.vault.fs.spi.UserManagement;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol.JackrabbitAccessControlPolicy;
 import org.apache.jackrabbit.vault.packaging.PackageException;
 import org.apache.jackrabbit.vault.packaging.impl.ActivityLog;
 import org.apache.jackrabbit.vault.packaging.registry.impl.JcrPackageRegistry;
@@ -186,12 +189,16 @@ public class Importer {
     /**
      * set of paths to versionable nodes that need to be checked in after import
      */
-    private final Set<String> nodesToCheckin = new HashSet<String>();
+    private final Set<String> nodesToCheckin = new HashSet<>();
 
     /**
      * map of group memberships that need to be applied after import
      */
-    private final Map<String, String[]> memberships = new HashMap<String, String[]>();
+    private final Map<String, String[]> memberships = new HashMap<>();
+
+    private Map<String, List<? extends AccessControlPolicy>> deletedPrincipalAcls = new HashMap<>();
+
+    private List<String> createdAuthorizableIds = new LinkedList<>();
 
     /**
      * general flag that indicates if the import had (recoverable) errors
@@ -259,7 +266,7 @@ public class Importer {
     /**
      * the checkpoint import info.
      */
-    private ImportInfo cpImportInfo;
+    private ImportInfoImpl cpImportInfo;
 
     /**
      * retry counter for the batch auto recovery
@@ -280,6 +287,7 @@ public class Importer {
     private final boolean isStrictByDefault;
     private final boolean overwritePrimaryTypesOfFoldersByDefault;
 
+
     /**
      * Default constructor neither setting specific import options nor defaults.
      */
@@ -540,6 +548,7 @@ public class Importer {
         if (tracker != null) {
             tracker.setMode(ProgressTrackerListener.Mode.TEXT);
         }
+        restorePrincipalAcls(session);
         checkinNodes(session);
         applyMemberships(session);
         applyPatches();
@@ -565,6 +574,52 @@ public class Importer {
         }
     }
 
+    private void restorePrincipalAcls(Session session) throws RepositoryException {
+        for (String authorizableId : createdAuthorizableIds) {
+            String principalName = userManagement.getPrincipalName(session, authorizableId);
+            if (deletedPrincipalAcls.containsKey(principalName)) {
+                if (opts.isDryRun()) {
+                    track("Dry run: Would potentially restore principal ACLs of " + principalName + " ...", "");
+                } else {
+                    for (AccessControlPolicy policy : deletedPrincipalAcls.get(principalName)) {
+                        // CUG or ACL handling relevant?
+                        AccessControlHandling aclHandling;
+                        if (policy instanceof PrincipalSetPolicy) {
+                            aclHandling = opts.getCugHandling();
+                        } else {
+                            aclHandling = opts.getAccessControlHandling();
+                        }
+                        // convert aclHandling (as this was set for the imported ACLs, not the existing ones)
+                        final AccessControlHandling aclHandlingForRestoredPolicy;
+                        switch (aclHandling) {
+                            case OVERWRITE:
+                                aclHandlingForRestoredPolicy = AccessControlHandling.IGNORE;
+                                break;
+                            case IGNORE:
+                                aclHandlingForRestoredPolicy = AccessControlHandling.OVERWRITE;
+                                break;
+                            case CLEAR:
+                                aclHandlingForRestoredPolicy = AccessControlHandling.IGNORE;
+                                break;
+                            case MERGE:
+                                aclHandlingForRestoredPolicy = AccessControlHandling.MERGE;
+                                break;
+                            default:
+                                aclHandlingForRestoredPolicy = AccessControlHandling.MERGE;
+                           
+                        }
+                        List<String> paths = JackrabbitAccessControlPolicy.fromAccessControlPolicy(policy).apply(session, aclHandlingForRestoredPolicy);
+                        for (String path: paths) {
+                            track("Restored principal ACLs of " + principalName + " ...", path);
+                        }
+                    }
+                }
+            }
+        }
+        
+        
+    }
+
     /**
      * Returns a human-readable error message from the throwable including all its causes till the root.
      * Also the throwable class names are included in the message
@@ -894,8 +949,8 @@ public class Importer {
     }
 
     private void commit(Session session, TxInfo info, LinkedList<TxInfo> skipList) throws RepositoryException, IOException {
+        ImportInfoImpl imp = null;
         try {
-            ImportInfo imp = null;
             if (skipList.isEmpty()) {
                 if (info == cpTxInfo) {
                     // don't need to import again, just set import info
@@ -907,6 +962,8 @@ public class Importer {
                         nodesToCheckin.addAll(imp.getToVersion());
                         memberships.putAll(imp.getMemberships());
                         autoSave.modified(imp.numModified());
+                        deletedPrincipalAcls.putAll(imp.getDeletedPrincipalAcls());
+                        createdAuthorizableIds.addAll(imp.getCreatedAuthorizableIds());
                     }
                 }
             } else if (log.isDebugEnabled()) {
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java
index faa57c54..801b47f8 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/ACLManagement.java
@@ -84,6 +84,5 @@ public interface ACLManagement {
      * @see <a href="https://jackrabbit.apache.org/archive/wiki/JCR/AccessControl_115513330.html">Access Control</a>
      * @since 3.6.10
      */
-    @NotNull
-    Map<String, List<AccessControlPolicy>> getPrincipalAcls(Node node) throws RepositoryException;
+    @NotNull Map<String, List<? extends AccessControlPolicy>> getPrincipalAcls(Node node) throws RepositoryException;
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/UserManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/UserManagement.java
index b9490599..99331466 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/UserManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/UserManagement.java
@@ -39,13 +39,22 @@ public interface UserManagement {
 
     /**
      * Returns the path of the authorizable or {@code null} if not exists.
-     * @param name the authorizable name
+     * @param id the authorizable id
      * @param session the session to access the repository
      * @return path of authorizable
      *
      * @since 2.3.26
      */
-    String getAuthorizablePath(Session session, String name);
+    String getAuthorizablePath(Session session, String id);
+
+    /**
+     * 
+     * @param session the session to access the repository
+     * @param id the authorizable id
+     * @return the principal name corresponding to the given authorizable id or {@code null} if the authorizable id cannot be found
+     * @since 3.6.10
+     */
+    String getPrincipalName(Session session, String id);
 
     /**
      * Returns the id of the authorizable from the specified authorizable node
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JcrACLManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitACLManagement.java
similarity index 83%
rename from vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JcrACLManagement.java
rename to vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitACLManagement.java
index 0090cd0f..447064d6 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JcrACLManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitACLManagement.java
@@ -21,6 +21,7 @@ import java.security.Principal;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -33,7 +34,9 @@ import javax.jcr.security.AccessControlManager;
 import javax.jcr.security.AccessControlPolicy;
 
 import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.User;
@@ -42,21 +45,19 @@ import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.vault.fs.spi.ACLManagement;
 import org.apache.jackrabbit.vault.fs.spi.UserManagement;
+import org.apache.jackrabbit.vault.util.UncheckedRepositoryException;
 import org.jetbrains.annotations.NotNull;
 
 /**
- * {@code JcrACLManagement}...
  * This is Jackrabbit/Oak specific as it is not defined by JCR 2.0 how access control policies are persisted.
  */
-public class JcrACLManagement implements ACLManagement {
+public class JackrabbitACLManagement implements ACLManagement {
 
     private String groupsRootPath;
     private String usersRootPath;
     private final UserManagement userManagement;
 
-    public JcrACLManagement() {
-        // figure out the root paths of users and groups
-        // how to get the securityProvider in Oak (https://issues.apache.org/jira/browse/OAK-9416) was never implemented
+    public JackrabbitACLManagement() {
         userManagement = new JackrabbitUserManagement();
     }
 
@@ -159,7 +160,7 @@ public class JcrACLManagement implements ACLManagement {
     }
 
     @Override
-    public @NotNull Map<String, List<AccessControlPolicy>> getPrincipalAcls(Node node) throws RepositoryException {
+    public @NotNull Map<String, List<? extends AccessControlPolicy>> getPrincipalAcls(Node node) throws RepositoryException {
         // first do a quick check if path may contain principal ACLs at all before triggering expensive traversal
         if (!areAuthorizablesAllowedBelowPath(node.getSession(), node.getPath())) {
             // TODO: Oak does not allow principal based authorizables everywhere, so we may restrict further
@@ -172,42 +173,18 @@ public class JcrACLManagement implements ACLManagement {
         }
         JackrabbitAccessControlManager jrAcMgr = (JackrabbitAccessControlManager) acMgr;
         PrincipalAccessControlPolicyCollector policiesCollector = new PrincipalAccessControlPolicyCollector(jrAcMgr);
-        findPrincipalsRecursively(jrSession.getUserManager(), node, policiesCollector);
-        return policiesCollector.getPoliciesPerPrincipal();
-    }
-
-    private static final class SimplePrincipal implements Principal {
-        private final String name;
-
-        SimplePrincipal(String name) {
-            if(name == null) {
-                throw new IllegalArgumentException("Name cannot be null");
-            }
-            this.name = name;
-        }
-
-        @Override
-        public String getName() {
-            return name;
-        }
-
-        @Override
-        public boolean equals(Object other) {
-            return (
-                    other instanceof SimplePrincipal)
-                    && (this.name.equals(((SimplePrincipal)other).name));
-        }
-
-        @Override
-        public int hashCode() {
-            return name.hashCode();
+        try {
+            findPrincipalsRecursively(jrSession.getUserManager(), node, policiesCollector);
+            return policiesCollector.getPoliciesPerPrincipal();
+        } catch (UncheckedRepositoryException e) {
+            throw e.getCause();
         }
     }
 
     private static final class PrincipalAccessControlPolicyCollector implements Consumer<Principal> {
 
         private final JackrabbitAccessControlManager jrAcMgr;
-        private final Map<String, List<AccessControlPolicy>> policiesPerPrincipal;
+        private final Map<String, List<? extends AccessControlPolicy>> policiesPerPrincipal;
 
         public PrincipalAccessControlPolicyCollector(JackrabbitAccessControlManager jrAcMgr) {
             super();
@@ -215,31 +192,35 @@ public class JcrACLManagement implements ACLManagement {
             this.policiesPerPrincipal = new HashMap<>();
         }
 
-        public Map<String, List<AccessControlPolicy>> getPoliciesPerPrincipal() {
+        public Map<String, List<? extends AccessControlPolicy>> getPoliciesPerPrincipal() {
             return policiesPerPrincipal;
         }
 
         @Override
         public void accept(Principal principal) {
             try {
-                policiesPerPrincipal.put(principal.getName(), Arrays.asList(jrAcMgr.getPolicies(principal)));
+                List<JackrabbitAccessControlPolicy> policies = Arrays.asList(jrAcMgr.getPolicies(principal));
+                if (!policies.isEmpty()) {
+                    policiesPerPrincipal.put(principal.getName(), policies);
+                }
             } catch (RepositoryException e) {
-                //throw new UncheckedRepositoryException()
+                throw new UncheckedRepositoryException(e);
             }
         }
     }
 
     private void findPrincipalsRecursively(UserManager userMgr, Node node, Consumer<Principal> principalConsumer) throws RepositoryException {
+        // TODO: check if the additional check with UserManagement isAuthorizableNodeType really speeds things up...
         if (userManagement.isAuthorizableNodeType(node.getPrimaryNodeType().getName())) {
-            // what is this authorizable's name?
             Authorizable authorizable = userMgr.getAuthorizableByPath(node.getPath());
             if (authorizable != null) {
                 principalConsumer.accept(authorizable.getPrincipal());
             }
         } else {
-            for (Node child : JcrUtils.in(node.getNodes())) {
+            for (Node child : JcrUtils.in(((Iterator<Node>)node.getNodes()))) {
                 findPrincipalsRecursively(userMgr, child, principalConsumer);
             }
         }
     }
+
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitServiceProvider.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitServiceProvider.java
index 568b8f80..5eafbf13 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitServiceProvider.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitServiceProvider.java
@@ -88,7 +88,7 @@ public class JackrabbitServiceProvider implements ServiceProvider {
      */
     public ACLManagement getACLManagement() {
         if (aclManagement == null) {
-            aclManagement = new JcrACLManagement();
+            aclManagement = new JackrabbitACLManagement();
         }
         return aclManagement;
     }
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java
index 04c313c9..ba96e82d 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/JackrabbitUserManagement.java
@@ -19,10 +19,12 @@ package org.apache.jackrabbit.vault.fs.spi.impl.jcr20;
 
 import java.util.UUID;
 
+import javax.jcr.AccessDeniedException;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
 
 import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.user.Authorizable;
@@ -62,16 +64,9 @@ public class JackrabbitUserManagement implements UserManagement {
      * {@inheritDoc}
      */
     public String getAuthorizablePath(Session session, String id) {
-        UserManager uMgr;
-        try {
-            uMgr = ((JackrabbitSession) session).getUserManager();
-        } catch (RepositoryException e) {
-            log.warn("Unable to get authorizable path of {}. Error while retrieving user manager.", id, e);
-            return null;
-        }
         Authorizable authorizable;
         try {
-            authorizable = uMgr.getAuthorizable(id);
+            authorizable = getAuthorizable(session, id);
             if (authorizable == null) {
                 log.debug("No existing authorizable with id {} found", id);
                 return null;
@@ -116,19 +111,10 @@ public class JackrabbitUserManagement implements UserManagement {
      * {@inheritDoc}
      */
     public void addMembers(Session session, String id, String[] membersUUID) {
-        if (!(session instanceof JackrabbitSession)) {
-            log.warn("Unable to update membership. no jackrabbit session.");
-            return;
-        }
-        UserManager uMgr;
-        try {
-            uMgr = ((JackrabbitSession) session).getUserManager();
-        } catch (RepositoryException e) {
-            log.warn("Unable to update membership of {}. Error while retrieving user manager.", id, e);
-            return;
-        }
         Authorizable auth;
+        UserManager uMgr;
         try {
+            uMgr = getUserManager(session);
             auth = uMgr.getAuthorizable(id);
         } catch (RepositoryException e) {
             log.warn("Unable to update membership of {}. Error while retrieving authorizable.", id, e);
@@ -170,4 +156,29 @@ public class JackrabbitUserManagement implements UserManagement {
             }
         }
     }
+
+    private UserManager getUserManager(Session session) throws RepositoryException {
+        if (!(session instanceof JackrabbitSession)) {
+            throw new RepositoryException("no jackrabbit session.");
+        }
+        return ((JackrabbitSession) session).getUserManager();
+    }
+
+    private Authorizable getAuthorizable(Session session, String id) throws RepositoryException {
+        return getUserManager(session).getAuthorizable(id);
+    }
+
+    @Override
+    public String getPrincipalName(Session session, String id) {
+        try {
+            Authorizable auth = getAuthorizable(session, id);
+            if (auth != null) {
+                return auth.getPrincipal().getName();
+            }
+        } catch (RepositoryException e) {
+            log.warn("Unable to get principal name of {}. Error while retrieving user manager or authorizable.", id, e);
+            return null;
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/SimplePrincipal.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/SimplePrincipal.java
new file mode 100644
index 00000000..ec71e1e6
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/SimplePrincipal.java
@@ -0,0 +1,32 @@
+package org.apache.jackrabbit.vault.fs.spi.impl.jcr20;
+
+import java.security.Principal;
+
+/** Helper class to create users/groups with intermediate path */
+public final class SimplePrincipal implements Principal {
+    private final String name;
+
+    public SimplePrincipal(String name) {
+        if(name == null) {
+            throw new IllegalArgumentException("Name cannot be null");
+        }
+        this.name = name;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        return (
+                other instanceof SimplePrincipal)
+                && (this.name.equals(((SimplePrincipal)other).name));
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode();
+    }
+}
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/AbstractAccessControlEntry.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/AbstractAccessControlEntry.java
new file mode 100644
index 00000000..7afeccf5
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/AbstractAccessControlEntry.java
@@ -0,0 +1,96 @@
+/*************************************************************************
+ * 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.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.vault.util.UncheckedRepositoryException;
+
+public class AbstractAccessControlEntry {
+    final Collection<String> privileges;
+    final Map<String, Value[]> restrictions;
+
+    protected abstract static class Builder<T extends AbstractAccessControlEntry> implements JackrabbitAccessControlEntryBuilder<T> {
+        final Collection<String> privileges;
+        final Map<String, Value[]> restrictions;
+
+        Builder(Collection<String> privileges) {
+            this.privileges = new HashSet<>(privileges);
+            this.restrictions = new HashMap<>();
+        }
+
+        @Override
+        public void addRestriction(String restrictionName, Value[] values) {
+            restrictions.put(restrictionName, values);
+        }
+    }
+
+    protected AbstractAccessControlEntry(JackrabbitAccessControlEntry entry) throws RepositoryException {
+        this(Arrays.stream(entry.getPrivileges()).map(Privilege::getName).collect(Collectors.toList()), 
+             Arrays.stream(entry.getRestrictionNames())
+                .collect(Collectors.<String, String, Value[]>toMap(rn -> rn, rn -> {
+                    try {
+                        return entry.getRestrictions(rn);
+                    } catch (RepositoryException e) {
+                        throw new UncheckedRepositoryException(e);
+                    }
+                })));
+    }
+
+    Map.Entry<Map<String, Value>, Map<String, Value[]>> separateRestrictions(JackrabbitAccessControlList list) throws RepositoryException {
+        Map<String, Value> svRestrictions = new HashMap<>();
+        Map<String, Value[]> mvRestrictions = new HashMap<>();
+        try {
+            Map<Boolean, List<String>> restrictionNamesMap = restrictions.keySet().stream().collect(Collectors.partitioningBy(r -> {
+                try {
+                    return list.isMultiValueRestriction(r);
+                } catch (RepositoryException e) {
+                    throw new UncheckedRepositoryException(e);
+                }
+            }));
+            restrictionNamesMap.get(Boolean.TRUE).stream().forEach(restrictionName -> mvRestrictions.put(restrictionName, restrictions.get(restrictionName)));
+            restrictionNamesMap.get(Boolean.FALSE).stream().forEach(restrictionName -> svRestrictions.put(restrictionName, restrictions.get(restrictionName)[0]));
+        } catch (UncheckedRepositoryException e) {
+            throw e.getCause();
+        }
+        return new AbstractMap.SimpleEntry<>(svRestrictions, mvRestrictions);
+    }
+
+    protected AbstractAccessControlEntry(Collection<String> privileges, Map<String, Value[]> restrictions) {
+        this.privileges = new HashSet<>(privileges);
+        this.restrictions = new HashMap<>(restrictions);
+    }
+
+    Privilege[] getPrivileges(AccessControlManager acMgr) throws RepositoryException {
+        return AccessControlUtils.privilegesFromNames(acMgr, privileges.toArray(new String[0]));
+    }
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlEntryBuilder.java
similarity index 57%
copy from vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
copy to vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlEntryBuilder.java
index 9b735fd4..9c2f8cb0 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlEntryBuilder.java
@@ -1,4 +1,4 @@
-/*
+/*************************************************************************
  * 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.
@@ -13,9 +13,20 @@
  * 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.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import javax.jcr.Value;
+
+/**
+ * Builder for an entry to be used with {@link PrincipalBasedAccessControlList} or {@link ResourceBasedAccessControlList}.
+ *
+ * @param <T> the type of the entry
  */
+public interface JackrabbitAccessControlEntryBuilder<T extends AbstractAccessControlEntry> {
+
+    T build();
 
-@Version("2.5.0")
-package org.apache.jackrabbit.vault.fs.spi;
+    void addRestriction(String restrictionName, Value[] values);
 
-import org.osgi.annotation.versioning.Version;
\ No newline at end of file
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicy.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicy.java
new file mode 100644
index 00000000..1ace04fb
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicy.java
@@ -0,0 +1,193 @@
+/*************************************************************************
+ * 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.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
+import org.apache.jackrabbit.api.security.authorization.PrincipalSetPolicy;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.SimplePrincipal;
+import org.apache.jackrabbit.vault.util.UncheckedRepositoryException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** 
+ * Abstraction on top of a JCR Access control policies
+ */
+public abstract class JackrabbitAccessControlPolicy {
+
+    protected final String accessControlledPath;
+    /** default logger */
+    protected static final Logger log = LoggerFactory.getLogger(JackrabbitAccessControlPolicy.class);
+
+    public static JackrabbitAccessControlPolicy fromAccessControlPolicy(AccessControlPolicy policy) throws RepositoryException {
+        final JackrabbitAccessControlPolicyBuilder<? extends JackrabbitAccessControlPolicy> builder;
+        if (policy instanceof PrincipalAccessControlList) {
+            PrincipalAccessControlList principalAcl = (PrincipalAccessControlList)policy;
+            builder = new PrincipalBasedAccessControlList.Builder(principalAcl.getPrincipal().getName());
+            Arrays.stream(principalAcl.getAccessControlEntries()).map(PrincipalAccessControlList.Entry.class::cast).map(t -> {
+                try {
+                    return new PrincipalBasedAccessControlEntry(t);
+                } catch (RepositoryException e) {
+                    throw new UncheckedRepositoryException(e);
+                }
+            }).forEach(builder::addEntry);
+        } else if (policy instanceof PrincipalSetPolicy) {
+            PrincipalSetPolicy principalSetPolicy = (PrincipalSetPolicy)policy;
+            builder = new PrincipalSetAccessControlPolicy.Builder(principalSetPolicy.getPrincipals().stream().map(Principal::getName).collect(Collectors.toList()));
+        } else if (policy instanceof JackrabbitAccessControlList) {
+            JackrabbitAccessControlList acl = (JackrabbitAccessControlList)policy;
+            builder = new ResourceBasedAccessControlList.Builder();
+            Arrays.stream(acl.getAccessControlEntries()).map(JackrabbitAccessControlEntry.class::cast).map(t -> {
+                try {
+                    return new ResourceBasedAccessControlEntry(t);
+                } catch (RepositoryException e) {
+                    throw new UncheckedRepositoryException(e);
+                }
+            }).forEach(builder::addEntry);
+        } else {
+            throw new RepositoryException("Unsupported policy type " + policy);
+        }
+        String accessControlledPath = ((org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy)policy).getPath();
+        return builder.build(accessControlledPath);
+    }
+
+    JackrabbitAccessControlPolicy(String accessControlledPath) {
+        this.accessControlledPath = accessControlledPath;
+    }
+
+    Principal getPrincipal(Session session, final String principalName) throws RepositoryException {
+        PrincipalManager pMgr = getPrincipalManager(session);
+        Principal p = pMgr.getPrincipal(principalName);
+        if (p == null) {
+            try {
+                Authorizable a = getUserManager(session).getAuthorizableByPath(accessControlledPath);
+                if (a != null) {
+                    p = a.getPrincipal();
+                }
+            } catch (RepositoryException e) {
+                log.debug("Error while trying to retrieve user/group from access controlled path {}, {}", accessControlledPath, e.getMessage());
+            }
+            if (p == null) {
+                p = getPrincipal(principalName);
+            }
+        }
+        return p;
+    }
+
+    Principal getPrincipal(final String principalName) {
+        return new SimplePrincipal(principalName);
+    }
+
+    protected static final JackrabbitAccessControlManager getAccessControlManager(Session session) throws RepositoryException {
+        AccessControlManager acMgr = session.getAccessControlManager();
+        if (!(acMgr instanceof JackrabbitAccessControlManager)) {
+            throw new IllegalStateException("The access control manager exposed by the given session is no JackrabbitAccessControlManager");
+        }
+        return (JackrabbitAccessControlManager)acMgr;
+    }
+
+    protected static final PrincipalManager getPrincipalManager(Session session) throws RepositoryException {
+        if(!(session instanceof JackrabbitSession)) {
+            throw new IllegalStateException("This session is not a JackrabbitSession");
+        }
+        return ((JackrabbitSession)session).getPrincipalManager();
+    }
+
+    protected static final UserManager getUserManager(Session session) throws RepositoryException {
+        if(!(session instanceof JackrabbitSession)) {
+            throw new IllegalStateException("This session is not a JackrabbitSession");
+        }
+        return ((JackrabbitSession)session).getUserManager();
+    }
+
+    <T> T getPolicy(JackrabbitAccessControlManager acMgr, Class<T> clz) throws RepositoryException {
+        for (AccessControlPolicy p : acMgr.getPolicies(accessControlledPath)) {
+            if (clz.isAssignableFrom(p.getClass())) {
+                return clz.cast(p);
+            }
+        }
+        return null;
+    }
+
+    <T> T getPolicy(JackrabbitAccessControlManager acMgr, Class<T> clz, Principal principal) throws RepositoryException {
+        for (AccessControlPolicy p : acMgr.getPolicies(principal)) {
+            if (clz.isAssignableFrom(p.getClass())) {
+                return clz.cast(p);
+            }
+        }
+        return null;
+    }
+
+    <T> T getApplicablePolicy(JackrabbitAccessControlManager acMgr, Class<T> clz) throws RepositoryException {
+        AccessControlPolicyIterator iter = acMgr.getApplicablePolicies(accessControlledPath);
+        while (iter.hasNext()) {
+            AccessControlPolicy p = iter.nextAccessControlPolicy();
+            if (clz.isAssignableFrom(p.getClass())) {
+                return clz.cast(p);
+            }
+        }
+
+        // no applicable policy
+        throw new RepositoryException("no applicable AccessControlPolicy of type " + clz + " on " +
+                (accessControlledPath == null ? "'root'" : accessControlledPath));
+    }
+
+    <T> T getApplicablePolicy(JackrabbitAccessControlManager acMgr, Class<T> clz, Principal principal) throws RepositoryException {
+        for (AccessControlPolicy p : ((JackrabbitAccessControlManager) acMgr).getApplicablePolicies(principal)) {
+            if (clz.isAssignableFrom(p.getClass())) {
+                return clz.cast(p);
+            }
+        }
+
+        // no applicable policy
+        throw new AccessControlException("no applicable AccessControlPolicy of type " + clz + " for " + principal.getName());
+    }
+
+    protected void addPathIfExists(Session session, List<String> paths, String path) throws RepositoryException {
+        if (session.nodeExists(path)) {
+            paths.add(path);
+        }
+    }
+
+    /**
+     * Imports the policy into the repository according to the rules from {@code aclHandling}.
+     * @param session
+     * @param aclHandling
+     * @return the paths which have been modified or added
+     * @throws RepositoryException
+     */
+    public abstract List<String> apply(Session session, AccessControlHandling aclHandling) throws RepositoryException;
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicyBuilder.java
similarity index 65%
copy from vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
copy to vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicyBuilder.java
index 9b735fd4..789304d7 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/JackrabbitAccessControlPolicyBuilder.java
@@ -1,4 +1,4 @@
-/*
+/*************************************************************************
  * 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.
@@ -13,9 +13,13 @@
  * 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.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
 
-@Version("2.5.0")
-package org.apache.jackrabbit.vault.fs.spi;
+public interface JackrabbitAccessControlPolicyBuilder<T extends JackrabbitAccessControlPolicy> {
 
-import org.osgi.annotation.versioning.Version;
\ No newline at end of file
+    T build(String accessControlledPath);
+
+    void addEntry(AbstractAccessControlEntry entry);
+
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlEntry.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlEntry.java
new file mode 100644
index 00000000..7abf2f72
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlEntry.java
@@ -0,0 +1,54 @@
+/*************************************************************************
+ * 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.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
+
+public class PrincipalBasedAccessControlEntry extends AbstractAccessControlEntry {
+
+    public static class Builder extends AbstractAccessControlEntry.Builder<PrincipalBasedAccessControlEntry> {
+        final String effectivePath;
+
+        public Builder(Collection<String> privileges, String effectivePath) {
+            super(privileges);
+            this.effectivePath = effectivePath;
+        }
+
+        @Override
+        public PrincipalBasedAccessControlEntry build() {
+            return new PrincipalBasedAccessControlEntry(effectivePath, privileges, restrictions);
+        }
+    }
+
+    final String effectivePath;
+
+    public PrincipalBasedAccessControlEntry(PrincipalAccessControlList.Entry entry) throws RepositoryException {
+        super(entry);
+        this.effectivePath = entry.getEffectivePath();
+    }
+
+    public PrincipalBasedAccessControlEntry(String effectivePath, Collection<String> privileges, Map<String, Value[]> restrictions) {
+        super(privileges, restrictions);
+        this.effectivePath = effectivePath;
+    }
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlList.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlList.java
new file mode 100644
index 00000000..4709e73e
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalBasedAccessControlList.java
@@ -0,0 +1,108 @@
+/*************************************************************************
+ * 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.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+
+public class PrincipalBasedAccessControlList extends JackrabbitAccessControlPolicy {
+
+    public static final class Builder implements JackrabbitAccessControlPolicyBuilder<PrincipalBasedAccessControlList> {
+        private final List<PrincipalBasedAccessControlEntry> entries = new ArrayList<>();
+        private final String principalName;
+
+        public Builder(String principalName) {
+            this.principalName = principalName;
+        }
+
+        @Override
+        public void addEntry(AbstractAccessControlEntry entry) {
+            if (!(entry instanceof PrincipalBasedAccessControlEntry)) {
+                throw new IllegalStateException("Only entries of type PrincipalBasedAccessControlEntry are supported");
+            }
+            entries.add((PrincipalBasedAccessControlEntry)entry);
+        }
+
+        @Override
+        public PrincipalBasedAccessControlList build(String accessControlledPath) {
+            return new PrincipalBasedAccessControlList(accessControlledPath, principalName, entries);
+        }
+    }
+
+    private final String principalName;
+    private final List<PrincipalBasedAccessControlEntry> entries = new ArrayList<>();
+
+    private PrincipalBasedAccessControlList(String accessControlledPath, String principalName, List<PrincipalBasedAccessControlEntry> entries) {
+        super(accessControlledPath);
+        this.entries.addAll(entries);
+        this.principalName = principalName;
+    }
+
+    @Override
+    public List<String> apply(Session session, final AccessControlHandling aclHandling) throws RepositoryException {
+        if (aclHandling == AccessControlHandling.IGNORE) {
+            return Collections.emptyList();
+        }
+        if (aclHandling == AccessControlHandling.MERGE_PRESERVE) {
+            log.debug("MERGE_PRESERVE for principal-based access control list is equivalent to IGNORE.");
+            return Collections.emptyList();
+        }
+
+        JackrabbitAccessControlManager acMgr = getAccessControlManager(session);
+        Principal principal = getPrincipal(session, principalName);
+        PrincipalAccessControlList acl = getPolicy(acMgr, PrincipalAccessControlList.class, principal);
+        if (acl != null && aclHandling == AccessControlHandling.OVERWRITE) {
+            // remove existing policy for 'OVERWRITE'
+            acMgr.removePolicy(acl.getPath(), acl);
+            acl = null;
+        }
+
+        if (acl == null) {
+            acl = getApplicablePolicy(acMgr, PrincipalAccessControlList.class, principal);
+        }
+
+        // apply ACEs of package for MERGE and OVERWRITE
+        for (PrincipalBasedAccessControlEntry entry : entries) {
+            Entry<Map<String, Value>, Map<String, Value[]>> restrictions = entry.separateRestrictions(acl);
+            acl.addEntry(entry.effectivePath, entry.getPrivileges(acMgr), restrictions.getKey(), restrictions.getValue());
+        }
+        acMgr.setPolicy(acl.getPath(), acl);
+
+        List<String> paths = new LinkedList<>();
+        if (accessControlledPath == null) {
+            addPathIfExists(session, paths, "/rep:repoPolicy");
+        } else if ("/".equals(accessControlledPath)) {
+            addPathIfExists(session, paths, "/rep:policy");
+        } else {
+            addPathIfExists(session, paths, accessControlledPath + "/rep:policy");
+        }
+        return paths;
+    }
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalSetAccessControlPolicy.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalSetAccessControlPolicy.java
new file mode 100644
index 00000000..b728e0fc
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/PrincipalSetAccessControlPolicy.java
@@ -0,0 +1,91 @@
+/*************************************************************************
+ * 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.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.authorization.PrincipalSetPolicy;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+
+public class PrincipalSetAccessControlPolicy extends JackrabbitAccessControlPolicy {
+
+    public static final class Builder implements JackrabbitAccessControlPolicyBuilder<JackrabbitAccessControlPolicy> {
+        private final Collection<String> principalNames;
+
+        public Builder(Collection<String> principalNames) {
+            this.principalNames = new HashSet<>(principalNames);
+        }
+
+        @Override
+        public void addEntry(AbstractAccessControlEntry entry) {
+            throw new UnsupportedOperationException("This policy type does not support entries");
+        }
+
+        @Override
+        public PrincipalSetAccessControlPolicy build(String accessControlledPath) {
+            return new PrincipalSetAccessControlPolicy(accessControlledPath, principalNames);
+        }
+
+    }
+
+    private final Collection<String> principalNames;
+
+    public PrincipalSetAccessControlPolicy(String accessControlledPath, Collection<String> principalNames) {
+        super(accessControlledPath);
+        this.principalNames = new HashSet<>(principalNames);
+    }
+
+    @Override
+    public List<String> apply(Session session, AccessControlHandling aclHandling) throws RepositoryException {
+        JackrabbitAccessControlManager acMgr = getAccessControlManager(session);
+        PrincipalSetPolicy psPolicy = getPolicy(acMgr, PrincipalSetPolicy.class);
+        if (psPolicy != null) {
+            Set<Principal> existingPrincipals = psPolicy.getPrincipals();
+            // remove existing policy for 'overwrite'
+            if (aclHandling == AccessControlHandling.OVERWRITE) {
+                psPolicy.removePrincipals(existingPrincipals.toArray(new Principal[existingPrincipals.size()]));
+            }
+        } else {
+            psPolicy = getApplicablePolicy(acMgr, PrincipalSetPolicy.class);
+        }
+
+        // TODO: correct behavior for MERGE and MERGE_PRESERVE?
+        Principal[] principals = principalNames.stream().map(name -> getPrincipal(name)).toArray(Principal[]::new);
+
+        psPolicy.addPrincipals(principals);
+        acMgr.setPolicy(accessControlledPath, psPolicy);
+
+        List<String> paths = new LinkedList<>();
+        if ("/".equals(accessControlledPath)) {
+            addPathIfExists(session, paths, "/rep:cugPolicy");
+        } else {
+            addPathIfExists(session, paths, accessControlledPath + "/rep:cugPolicy");
+        }
+        return paths;
+    }
+    
+    
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlEntry.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlEntry.java
new file mode 100644
index 00000000..1a5efead
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlEntry.java
@@ -0,0 +1,59 @@
+/*************************************************************************
+ * 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.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.util.Collection;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+
+public class ResourceBasedAccessControlEntry extends AbstractAccessControlEntry {
+
+    public static class Builder extends AbstractAccessControlEntry.Builder<ResourceBasedAccessControlEntry> {
+        final boolean allow;
+        final String principalName;
+
+        public Builder(Collection<String> privileges, boolean allow, String principalName) {
+            super(privileges);
+            this.allow = allow;
+            this.principalName = principalName;
+        }
+
+        @Override
+        public ResourceBasedAccessControlEntry build() {
+            return new ResourceBasedAccessControlEntry(allow, principalName, privileges, restrictions);
+        }
+    }
+
+    final boolean allow;
+    final String principalName;
+
+    public ResourceBasedAccessControlEntry(JackrabbitAccessControlEntry entry) throws RepositoryException {
+        super(entry);
+        this.allow = entry.isAllow();
+        this.principalName = entry.getPrincipal().getName();
+    }
+
+    protected ResourceBasedAccessControlEntry(boolean allow, String principalName, Collection<String> privileges, Map<String, Value[]> restrictions) {
+        super(privileges, restrictions);
+        this.allow = allow;
+        this.principalName = principalName;
+    }
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlList.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlList.java
new file mode 100644
index 00000000..5be53f21
--- /dev/null
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/impl/jcr20/accesscontrol/ResourceBasedAccessControlList.java
@@ -0,0 +1,123 @@
+/*************************************************************************
+ * 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.jackrabbit.vault.fs.spi.impl.jcr20.accesscontrol;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.security.AccessControlEntry;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
+
+public class ResourceBasedAccessControlList extends JackrabbitAccessControlPolicy {
+
+    public static final class Builder implements JackrabbitAccessControlPolicyBuilder<ResourceBasedAccessControlList> {
+        private final List<ResourceBasedAccessControlEntry> entries;
+
+        public Builder() {
+            entries = new ArrayList<>();
+        }
+
+        @Override
+        public void addEntry(AbstractAccessControlEntry entry) {
+            if (!(entry instanceof ResourceBasedAccessControlEntry)) {
+                throw new IllegalStateException("Only entries of type ResourceBasedAccessControlEntry are supported");
+            }
+            entries.add((ResourceBasedAccessControlEntry)entry);
+        }
+
+        @Override
+        public ResourceBasedAccessControlList build(String accessControlledPath) {
+            return new ResourceBasedAccessControlList(accessControlledPath, entries);
+        }
+    }
+
+    private final List<ResourceBasedAccessControlEntry> entries = new ArrayList<>();
+
+    ResourceBasedAccessControlList(String accessControlledPath, List<ResourceBasedAccessControlEntry> aceList) {
+        super(accessControlledPath);
+        this.entries.addAll(aceList);
+    }
+
+    @Override
+    public List<String> apply(Session session, final AccessControlHandling aclHandling) throws RepositoryException {
+        JackrabbitAccessControlManager acMgr = getAccessControlManager(session);
+        // find principals of existing ACL
+        JackrabbitAccessControlList acl = getPolicy(acMgr, JackrabbitAccessControlList.class);
+        Set<String> existingPrincipals = new HashSet<>();
+        if (acl != null) {
+            for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+                existingPrincipals.add(ace.getPrincipal().getName());
+            }
+
+            // remove existing policy for 'overwrite'
+            if (aclHandling == AccessControlHandling.OVERWRITE) {
+                acMgr.removePolicy(accessControlledPath, acl);
+                acl = null;
+            }
+        }
+
+        if (acl == null) {
+            acl = getApplicablePolicy(acMgr, JackrabbitAccessControlList.class);
+        }
+
+        // clear all ACEs of the package principals for merge (VLT-94), otherwise the `acl.addEntry()` below
+        // might just combine the privileges.
+        if (aclHandling == AccessControlHandling.MERGE) {
+            for (ResourceBasedAccessControlEntry entry : entries) {
+                for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+                    if (ace.getPrincipal().getName().equals(entry.principalName)) {
+                        acl.removeAccessControlEntry(ace);
+                    }
+                }
+            }
+        }
+
+        // apply ACEs of package
+        for (ResourceBasedAccessControlEntry ace : entries) {
+            final String principalName = ace.principalName;
+            if (aclHandling == AccessControlHandling.MERGE_PRESERVE && existingPrincipals.contains(principalName)) {
+                // skip principal if it already has an ACL
+                continue;
+            }
+            Principal principal = getPrincipal(principalName);
+            Entry<Map<String, Value>, Map<String, Value[]>> restrictions = ace.separateRestrictions(acl);
+            acl.addEntry(principal, ace.getPrivileges(acMgr), ace.allow, restrictions.getKey(), restrictions.getValue());
+        }
+        acMgr.setPolicy(accessControlledPath, acl);
+        List<String> paths = new ArrayList<>();
+        if (accessControlledPath == null) {
+            addPathIfExists(session, paths, "/rep:repoPolicy");
+        } else if ("/".equals(accessControlledPath)) {
+            addPathIfExists(session, paths, "/rep:policy");
+        } else {
+            addPathIfExists(session, paths, accessControlledPath + "/rep:policy");
+        }
+        return paths;
+    }
+
+}
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
index 9b735fd4..6fae8abe 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-@Version("2.5.0")
+@Version("2.6.0")
 package org.apache.jackrabbit.vault.fs.spi;
 
 import org.osgi.annotation.versioning.Version;
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2.java
index 98ab931e..c5060ff2 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/DocViewProperty2.java
@@ -25,6 +25,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import javax.jcr.Binary;
 import javax.jcr.InvalidSerializedDataException;
@@ -35,6 +36,7 @@ import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
+import javax.jcr.ValueFactory;
 import javax.jcr.ValueFormatException;
 import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
@@ -49,13 +51,14 @@ import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.util.XMLChar;
+import org.apache.jackrabbit.value.ValueFactoryImpl;
 import org.apache.jackrabbit.value.ValueHelper;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 /**
- * Helper class that represents a JCR property in the FileVault (enhanced) document view format.
- * It contains formatting and parsing methods for writing/reading enhanced
+ * Immutable helper class that represents a JCR property in the FileVault (enhanced) document view format.
+ * It contains formatting and parsing methods for serializing/deserializing enhanced
  * docview properties.
  * <br>
  * The string representation adheres to the following grammar:
@@ -703,6 +706,10 @@ public class DocViewProperty2 {
         return type;
     }
 
+    private int getSafeType() {
+        return type == PropertyType.UNDEFINED ? PropertyType.STRING : type;
+    }
+
     public @NotNull Optional<String> getStringValue() {
         if (!values.isEmpty()) {
             return Optional.of(values.get(0));
@@ -713,4 +720,37 @@ public class DocViewProperty2 {
     public @NotNull List<String> getStringValues() {
         return values;
     }
+
+    /**
+     * @param valueFactory the value factory to use for converting the underlying string to the JCR value
+     * @return the value or empty if no value set. For multi value only the first item is returned
+     * @throws ValueFormatException
+     * @since 3.6.10
+     */ 
+    public @NotNull Optional<Value> getValue(@NotNull ValueFactory valueFactory) throws ValueFormatException {
+        if (!values.isEmpty()) {
+            return Optional.of(valueFactory.createValue(values.get(0), getSafeType()));
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * @param valueFactory the value factory to use for converting the underlying string to the JCR value
+     * @return the list of values, may be empty. In case of single value entry just a single value list.
+     * @throws ValueFormatException
+     * @since 3.6.10
+     */
+    public @NotNull List<Value> getValues(@NotNull ValueFactory valueFactory) throws ValueFormatException {
+        try {
+            return values.stream().map(v -> {
+                try {
+                    return valueFactory.createValue(v, getSafeType());
+                } catch (ValueFormatException e) {
+                    throw new UncheckedValueFormatException(e);
+                }
+            }).collect(Collectors.toList());
+        } catch (UncheckedValueFormatException e) {
+            throw e.getCause();
+        }
+    }
 }
\ No newline at end of file
diff --git a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/UncheckedValueFormatException.java
similarity index 55%
copy from vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
copy to vault-core/src/main/java/org/apache/jackrabbit/vault/util/UncheckedValueFormatException.java
index 9b735fd4..2a49e107 100644
--- a/vault-core/src/main/java/org/apache/jackrabbit/vault/fs/spi/package-info.java
+++ b/vault-core/src/main/java/org/apache/jackrabbit/vault/util/UncheckedValueFormatException.java
@@ -14,8 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.jackrabbit.vault.util;
 
-@Version("2.5.0")
-package org.apache.jackrabbit.vault.fs.spi;
+import javax.jcr.ValueFormatException;
 
-import org.osgi.annotation.versioning.Version;
\ No newline at end of file
+/**
+ * Wraps a {@link ValueFormatException} with an unchecked exception.
+ * Useful in {@link FunctionalInterface} methods/lambda expressions which must not throw checked exceptions.
+ * @since 3.6.10
+ *
+ */
+public class UncheckedValueFormatException extends RuntimeException {
+
+    private static final long serialVersionUID = 7179774059211440453L;
+
+    public UncheckedValueFormatException(ValueFormatException e) {
+        super(e);
+    }
+
+    @Override
+    public synchronized ValueFormatException getCause() {
+        return (ValueFormatException) super.getCause();
+    }
+}
diff --git a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/PrincipalBasedIT.java b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/PrincipalBasedIT.java
index 2431e5f8..7f22485e 100644
--- a/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/PrincipalBasedIT.java
+++ b/vault-core/src/test/java/org/apache/jackrabbit/vault/packaging/integration/PrincipalBasedIT.java
@@ -16,14 +16,38 @@
  */
 package org.apache.jackrabbit.vault.packaging.integration;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.Privilege;
+
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
 import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
+import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList.Entry;
 import org.apache.jackrabbit.api.security.authorization.PrivilegeCollection;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.User;
@@ -46,34 +70,24 @@ import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
 import org.apache.jackrabbit.vault.fs.api.ImportMode;
 import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
 import org.apache.jackrabbit.vault.fs.io.ImportOptions;
+import org.apache.jackrabbit.vault.util.UncheckedRepositoryException;
 import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.hamcrest.Description;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.hamcrest.TypeSafeMatcher;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Test;
 
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.ValueFactory;
-import javax.jcr.ValueFormatException;
-import javax.jcr.security.AccessControlEntry;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.Privilege;
-import java.security.Principal;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 
 public class PrincipalBasedIT extends IntegrationTestBase {
 
@@ -113,8 +127,8 @@ public class PrincipalBasedIT extends IntegrationTestBase {
                 Map<String, Value[]> mvRestrictions = ImmutableMap.of(AccessControlConstants.REP_ITEM_NAMES, new Value[]{vf.createValue(JcrConstants.JCR_CONTENT, PropertyType.NAME)});
                 pacl.addEntry(EFFECTIVE_PATH, AccessControlUtils.privilegesFromNames(acMgr, Privilege.JCR_READ), ImmutableMap.<String, Value>of(), mvRestrictions);
                 pacl.addEntry(null, AccessControlUtils.privilegesFromNames(acMgr, PrivilegeConstants.JCR_NAMESPACE_MANAGEMENT));
-                existingEntries = pacl.getAccessControlEntries();
                 acMgr.setPolicy(pacl.getPath(), pacl);
+                existingEntries = pacl.getAccessControlEntries();
                 break;
             }
         }
@@ -176,34 +190,81 @@ public class PrincipalBasedIT extends IntegrationTestBase {
 
     private void assertPolicy(@NotNull Principal principal, @NotNull AccessControlEntry... expectedEntries) throws RepositoryException {
         for (AccessControlPolicy policy : acMgr.getPolicies(principal)) {
+            // disregard the order
             if (policy instanceof PrincipalAccessControlList) {
                 PrincipalAccessControlList pacl = (PrincipalAccessControlList) policy;
                 AccessControlEntry[] aces = pacl.getAccessControlEntries();
-                assertEquals(expectedEntries.length, aces.length);
+                MatcherAssert.assertThat(Arrays.asList(aces), Matchers.containsInAnyOrder(Arrays.stream(aces).map(e -> new PrincipalAccessControlEntryMatcher(e, pacl)).collect(Collectors.toList())));
+                return;
+            }
+        }
+        fail("expected PrincipalAccessControlList for principal " + principal.getName());
+    }
 
-                for (int i = 0; i < expectedEntries.length; i++) {
-                    assertTrue(expectedEntries[i] instanceof PrincipalAccessControlList.Entry);
-                    assertTrue(aces[i] instanceof PrincipalAccessControlList.Entry);
+    static String toString(PrincipalAccessControlList.Entry entry) {
+        try {
+            return "PrincipalAccessControlList.Entry[effectivePath="+entry.getEffectivePath() +", privileges=" + Arrays.toString(entry.getPrivileges()) + ", restrictionNames = " + Arrays.toString(entry.getRestrictionNames()) + "]";
+        } catch (RepositoryException e) {
+            throw new UncheckedRepositoryException(e);
+        }
+    }
 
+    private static final class PrincipalAccessControlEntryMatcher extends TypeSafeMatcher<AccessControlEntry> {
 
-                    PrincipalAccessControlList.Entry entry = (PrincipalAccessControlList.Entry) aces[i];
-                    PrincipalAccessControlList.Entry expected = (PrincipalAccessControlList.Entry) expectedEntries[i];
+        private final PrincipalAccessControlList.Entry expectedEntry;
+        private final JackrabbitAccessControlList containerACL;
+        public PrincipalAccessControlEntryMatcher(AccessControlEntry accessControlEntry, JackrabbitAccessControlList containerACL) {
+            this.expectedEntry = PrincipalAccessControlList.Entry.class.cast(accessControlEntry);
+            this.containerACL = containerACL;
+        }
 
-                    assertEquals(expected.getEffectivePath(), entry.getEffectivePath());
-                    assertEquals(ImmutableSet.copyOf(expected.getPrivileges()), ImmutableSet.copyOf(entry.getPrivileges()));
-                    assertEquals(ImmutableSet.copyOf(expected.getRestrictionNames()), ImmutableSet.copyOf(entry.getRestrictionNames()));
-                    for (String rName : expected.getRestrictionNames()) {
-                        if (pacl.isMultiValueRestriction(rName)) {
-                            assertArrayEquals(expected.getRestrictions(rName), entry.getRestrictions(rName));
-                        } else {
-                            assertEquals(expected.getRestriction(rName), entry.getRestriction(rName));
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(PrincipalBasedIT.toString(expectedEntry));
+        }
+
+        @Override
+        protected void describeMismatchSafely(AccessControlEntry item, Description mismatchDescription) {
+            mismatchDescription.appendText(PrincipalBasedIT.toString(PrincipalAccessControlList.Entry.class.cast(item)));
+        }
+
+        @Override
+        protected boolean matchesSafely(AccessControlEntry item) {
+            if (!(item instanceof PrincipalAccessControlList.Entry)) {
+                return false;
+            }
+            Entry actualEntry = PrincipalAccessControlList.Entry.class.cast(item);
+            if (!Objects.equals(expectedEntry.getEffectivePath(), actualEntry.getEffectivePath())) {
+                return false;
+            }
+            if (!ImmutableSet.copyOf(expectedEntry.getPrivileges()).equals(ImmutableSet.copyOf(actualEntry.getPrivileges()))) {
+                return false;
+            }
+            try {
+                if (!ImmutableSet.copyOf(expectedEntry.getRestrictionNames()).equals(ImmutableSet.copyOf(actualEntry.getRestrictionNames()))) {
+                    return false;
+                }
+                for (String rName : expectedEntry.getRestrictionNames()) {
+                    if (containerACL.isMultiValueRestriction(rName)) {
+                        if (!Arrays.equals(expectedEntry.getRestrictions(rName), actualEntry.getRestrictions(rName))) {
+                            return false;
+                        }
+                    } else {
+                        if (!Objects.equals(expectedEntry.getRestriction(rName), actualEntry.getRestriction(rName))) {
+                            return false;
                         }
                     }
                 }
-                return;
+            } catch (RepositoryException e) {
+                throw new UncheckedRepositoryException(e);
             }
+            return true;
         }
-        fail("expected PrincipalAccessControlList for principal " + principal.getName());
+        
+    }
+
+    private void assertNoPolicy(@NotNull Principal principal) throws AccessDeniedException, AccessControlException, UnsupportedRepositoryOperationException, RepositoryException {
+        assertEquals("Expected no policy for principal " + principal.getName(), 0, acMgr.getPolicies(principal).length);
     }
 
     @Test
@@ -305,7 +366,8 @@ public class PrincipalBasedIT extends IntegrationTestBase {
         admin.save();
 
         extractVaultPackage("/test-packages/principalbased.zip");
-        assertPolicy(testUser.getPrincipal(), packageEntries);
+        Authorizable newUser = userManager.getAuthorizable(SYSTEM_USER_ID);
+        assertPolicy(newUser.getPrincipal(), packageEntries);
     }
 
     @Test
@@ -436,15 +498,17 @@ public class PrincipalBasedIT extends IntegrationTestBase {
     public void testNewUserHandlingMergePreserveModeReplace() throws Exception {
         assumeTrue(isOak());
         ImportOptions opts = getDefaultOptions();
+        // MERGE_PRESERVE for principal-based access control list is equivalent to IGNORE (compare with comment
         opts.setAccessControlHandling(AccessControlHandling.MERGE_PRESERVE);
         opts.setImportMode(ImportMode.REPLACE);
 
+        // This removes the test user's ACEs being added in setup
         admin.getNode(testUser.getPath()).remove();
         admin.save();
 
         extractVaultPackage("/test-packages/principalbased.zip", opts);
         Principal p = userManager.getAuthorizable(SYSTEM_USER_ID).getPrincipal();
-        assertPolicy(p, packageEntries);
+        assertNoPolicy(p);
     }
 
     @Test
diff --git a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java
index 9b93d6db..a70b9134 100644
--- a/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java
+++ b/vault-validation/src/main/java/org/apache/jackrabbit/vault/validation/spi/impl/AccessControlValidator.java
@@ -21,7 +21,7 @@ import java.util.Collections;
 
 import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
 import org.apache.jackrabbit.vault.fs.spi.ACLManagement;
-import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.JcrACLManagement;
+import org.apache.jackrabbit.vault.fs.spi.impl.jcr20.JackrabbitACLManagement;
 import org.apache.jackrabbit.vault.util.DocViewNode2;
 import org.apache.jackrabbit.vault.validation.spi.DocumentViewXmlValidator;
 import org.apache.jackrabbit.vault.validation.spi.NodeContext;
@@ -35,7 +35,7 @@ import org.jetbrains.annotations.Nullable;
  */
 public class AccessControlValidator implements DocumentViewXmlValidator {
 
-    protected static final ACLManagement ACL_MANAGEMENT = new JcrACLManagement();
+    protected static final ACLManagement ACL_MANAGEMENT = new JackrabbitACLManagement();
     protected static final String MESSAGE_IGNORED_ACCESS_CONTROL_LIST = "Found an access control list, but it is never considered during installation as the property 'acHandling' is set to '%s'!";
     protected static final String MESSAGE_INEFFECTIVE_ACCESS_CONTROL_LIST = "Found no access control list, but there is supposed to be one contained as the property 'acHandling' is set to '%s'!";
     private final ValidationMessageSeverity severity;