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

enorman pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-contentloader.git


The following commit(s) were added to refs/heads/master by this push:
     new 3dd6a2d  SLING-11713 Change ACL json input structure (#17)
3dd6a2d is described below

commit 3dd6a2dee2ac5c4c0d9432161552796188eb1361
Author: Eric Norman <[email protected]>
AuthorDate: Sat Dec 10 12:22:37 2022 -0800

    SLING-11713 Change ACL json input structure (#17)
    
    to be less ambiguous for restrictions
---
 .../sling/jcr/contentloader/ContentCreator.java    |  25 +
 .../sling/jcr/contentloader/LocalPrivilege.java    | 144 +++++
 .../sling/jcr/contentloader/LocalRestriction.java  | 107 ++++
 .../internal/DefaultContentCreator.java            | 426 ++++++++++++-
 .../contentloader/internal/readers/JsonReader.java | 261 +++++---
 .../jcr/contentloader/ContentCreatorTest.java      | 118 ++++
 .../jcr/contentloader/LocalPrivilegeTest.java      | 328 ++++++++++
 .../jcr/contentloader/LocalRestrictionTest.java    | 183 ++++++
 .../internal/DefaultContentCreatorTest.java        | 674 ++++++++++++++++++++-
 .../jcr/contentloader/internal/JsonReaderTest.java |  53 +-
 .../contentloader/it/ContentloaderTestSupport.java |  16 +
 .../it/SLING11713InitialContentIT.java             | 289 +++++++++
 .../resources/initial-content/SLING-11713.json     |  80 +++
 13 files changed, 2588 insertions(+), 116 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/jcr/contentloader/ContentCreator.java 
b/src/main/java/org/apache/sling/jcr/contentloader/ContentCreator.java
index 47195fd..6ebc3e4 100644
--- a/src/main/java/org/apache/sling/jcr/contentloader/ContentCreator.java
+++ b/src/main/java/org/apache/sling/jcr/contentloader/ContentCreator.java
@@ -19,6 +19,7 @@
 package org.apache.sling.jcr.contentloader;
 
 import java.io.InputStream;
+import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 
@@ -197,12 +198,36 @@ public interface ContentCreator {
      * @param mvRestrictions    specifies additional Map of multi-value 
restrictions to apply. (optional)
      * @param removedRestrictionNames optional set of restriction names that 
should be removed (if they already exist).
      * @throws RepositoryException If anything goes wrong.
+     * @deprecated use {@link #createAce(String, Collection, String)} instead
      */
+    @Deprecated
     default void createAce(String principal, String[] grantedPrivileges, 
String[] deniedPrivileges, String order, 
                Map<String, Value> restrictions, Map<String, Value[]> 
mvRestrictions, Set<String> removedRestrictionNames) throws RepositoryException 
{
        throw new UnsupportedOperationException();
        }
     
+    /**
+     * Creates an Access Control Entry for the current node for the specified
+     * principal and privileges.
+     *
+     * @param principal         the user or group id for the ACE
+     * @param privileges        the set of privileges to allow or deny for the 
principal
+     * @param order             specifies the position of the ACE in the 
containing ACL. (may be null)
+     *                          Value should be one of these:
+     *                          <table>
+     *                          <caption>Values</caption>
+     *                          <tr><td>first</td><td>Place the target ACE as 
the first amongst its siblings</td></tr>
+     *                          <tr><td>last</td><td>Place the target ACE as 
the last amongst its siblings</td></tr>
+     *                          <tr><td>before xyz</td><td>Place the target 
ACE immediately before the sibling whose name is xyz</td></tr>
+     *                          <tr><td>after xyz</td><td>Place the target ACE 
immediately after the sibling whose name is xyz</td></tr>
+     *                          <tr><td>numeric</td><td>Place the target ACE 
at the specified index</td></tr>
+     *                          </table>
+     * @throws RepositoryException If anything goes wrong.
+     */
+    default void createAce(String principal, Collection<LocalPrivilege> 
privileges, String order) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * Gets the current parent Node
      * @return the current parent node or null
diff --git 
a/src/main/java/org/apache/sling/jcr/contentloader/LocalPrivilege.java 
b/src/main/java/org/apache/sling/jcr/contentloader/LocalPrivilege.java
new file mode 100644
index 0000000..51cae09
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/LocalPrivilege.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.jcr.contentloader;
+
+import java.util.Collections;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+public class LocalPrivilege {
+    private String privilegeName;
+    private boolean allow;
+    private boolean deny;
+    private Set<LocalRestriction> allowRestrictions = Collections.emptySet();
+    private Set<LocalRestriction> denyRestrictions = Collections.emptySet();
+    private Privilege privilege;
+
+    public LocalPrivilege(String privilege) {
+        this.privilegeName = privilege;
+    }
+
+    public void checkPrivilege(AccessControlManager acm) throws 
RepositoryException {
+        this.privilege = acm.privilegeFromName(this.privilegeName);
+    }
+
+    public Privilege getPrivilege() {
+        return this.privilege;
+    }
+
+    public String getName() {
+        return privilegeName;
+    }
+
+    public boolean isAllow() {
+        return allow;
+    }
+
+    public boolean isDeny() {
+        return deny;
+    }
+
+    public void setAllow(boolean allow) {
+        this.allow = allow;
+    }
+
+    public void setDeny(boolean deny) {
+        this.deny = deny;
+    }
+
+    public Set<LocalRestriction> getAllowRestrictions() {
+        return allowRestrictions;
+    }
+
+    public void setAllowRestrictions(Set<LocalRestriction> allowRestrictions) {
+        this.allowRestrictions = allowRestrictions;
+    }
+
+    public Set<LocalRestriction> getDenyRestrictions() {
+        return denyRestrictions;
+    }
+
+    public void setDenyRestrictions(Set<LocalRestriction> denyRestrictions) {
+        this.denyRestrictions = denyRestrictions;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("LocalPrivilege [privilege=");
+        builder.append(privilegeName);
+        builder.append(", allow=");
+        builder.append(allow);
+        builder.append(", deny=");
+        builder.append(deny);
+        builder.append(", allowRestrictions=");
+        builder.append(allowRestrictions);
+        builder.append(", denyRestrictions=");
+        builder.append(denyRestrictions);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (allow ? 1231 : 1237);
+        result = prime * result + ((allowRestrictions == null) ? 0 : 
allowRestrictions.hashCode());
+        result = prime * result + (deny ? 1231 : 1237);
+        result = prime * result + ((denyRestrictions == null) ? 0 : 
denyRestrictions.hashCode());
+        result = prime * result + ((privilegeName == null) ? 0 : 
privilegeName.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        LocalPrivilege other = (LocalPrivilege) obj;
+        if (allow != other.allow)
+            return false;
+        if (allowRestrictions == null) {
+            if (other.allowRestrictions != null)
+                return false;
+        } else if (!allowRestrictions.equals(other.allowRestrictions))
+            return false;
+        if (deny != other.deny)
+            return false;
+        if (denyRestrictions == null) {
+            if (other.denyRestrictions != null)
+                return false;
+        } else if (!denyRestrictions.equals(other.denyRestrictions))
+            return false;
+        if (privilegeName == null) {
+            if (other.privilegeName != null)
+                return false;
+        } else if (!privilegeName.equals(other.privilegeName))
+            return false;
+        return true;
+    }
+
+}
diff --git 
a/src/main/java/org/apache/sling/jcr/contentloader/LocalRestriction.java 
b/src/main/java/org/apache/sling/jcr/contentloader/LocalRestriction.java
new file mode 100644
index 0000000..26a886b
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/contentloader/LocalRestriction.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.jcr.contentloader;
+
+import java.util.Arrays;
+
+import javax.jcr.Value;
+
+import org.jetbrains.annotations.NotNull;
+
+public class LocalRestriction {
+    private String restriction;
+    private boolean multival;
+    private Value[] values;
+
+    public LocalRestriction(@NotNull String restriction, Value[] values) {
+        this.restriction = restriction;
+        this.multival = true;
+        this.values = values;
+    }
+
+    public LocalRestriction(@NotNull String restriction, Value value) {
+        super();
+        this.restriction = restriction;
+        this.multival = false;
+        this.values = value == null ? null : new Value[] {value};
+    }
+
+    public String getName() {
+        return restriction;
+    }
+
+    public boolean isMultiValue() {
+        return multival;
+    }
+
+    public Value getValue() {
+        Value v = null;
+        if (values != null && values.length > 0) {
+            v = values[0];
+        }
+        return v;
+    }
+
+    public Value[] getValues() {
+        return values;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("LocalRestriction [restriction=");
+        builder.append(restriction);
+        builder.append(", multival=");
+        builder.append(multival);
+        builder.append(", values=");
+        builder.append(Arrays.toString(values));
+        builder.append("]");
+        return builder.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (multival ? 1231 : 1237);
+        result = prime * result + ((restriction == null) ? 0 : 
restriction.hashCode());
+        result = prime * result + Arrays.hashCode(values);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        LocalRestriction other = (LocalRestriction) obj;
+        if (multival != other.multival)
+            return false;
+        if (restriction == null) {
+            if (other.restriction != null)
+                return false;
+        } else if (!restriction.equals(other.restriction))
+            return false;
+        return Arrays.equals(values, other.values);
+    }
+
+}
diff --git 
a/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
 
b/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
index 8d2e7b0..ec1eb8f 100644
--- 
a/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
+++ 
b/src/main/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreator.java
@@ -26,10 +26,13 @@ import java.security.Principal;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Deque;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -50,21 +53,35 @@ import javax.jcr.PropertyIterator;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Value;
 import javax.jcr.ValueFactory;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
 import javax.jcr.version.VersionManager;
 
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
 import org.apache.jackrabbit.util.ISO8601;
 import org.apache.sling.jcr.base.util.AccessControlUtil;
 import org.apache.sling.jcr.contentloader.ContentCreator;
 import org.apache.sling.jcr.contentloader.ContentImportListener;
 import org.apache.sling.jcr.contentloader.ContentReader;
 import org.apache.sling.jcr.contentloader.ImportOptions;
+import org.apache.sling.jcr.contentloader.LocalPrivilege;
+import org.apache.sling.jcr.contentloader.LocalRestriction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -842,26 +859,88 @@ public class DefaultContentCreator implements 
ContentCreator {
      */
     public void createAce(String principalId, String[] grantedPrivilegeNames, 
String[] deniedPrivilegeNames,
             String order) throws RepositoryException {
-        createAce(principalId, grantedPrivilegeNames, deniedPrivilegeNames, 
order, null, null, null);
+        Map<String, LocalPrivilege> privilegeToLocalPrivilegesMap = 
toLocalPrivileges(grantedPrivilegeNames,
+                deniedPrivilegeNames);
+
+        createAce(principalId, new 
ArrayList<>(privilegeToLocalPrivilegesMap.values()), order);
     }
 
-    /*
-     * (non-Javadoc)
+    /**
+     * Convert the privilege names to LocalPrivileges
      * 
-     * @see
-     * 
org.apache.sling.jcr.contentloader.ContentCreator#createAce(java.lang.String,
-     * java.lang.String[], java.lang.String[], java.lang.String, java.util.Map,
-     * java.util.Map, java.util.Set)
+     * @param grantedPrivilegeNames the granted privileges
+     * @param deniedPrivilegeNames the denied privileges
+     * @return map of privilege names to LocalPrivilege data
+     */
+    protected Map<String, LocalPrivilege> toLocalPrivileges(String[] 
grantedPrivilegeNames,
+            String[] deniedPrivilegeNames) {
+        // first start with an empty map
+        Map<String, LocalPrivilege> privilegeToLocalPrivilegesMap = new 
LinkedHashMap<>();
+
+        if (grantedPrivilegeNames != null) {
+            for (String pn: grantedPrivilegeNames) {
+                LocalPrivilege lp = 
privilegeToLocalPrivilegesMap.computeIfAbsent(pn, LocalPrivilege::new);
+                lp.setAllow(true);
+            }
+        }
+
+        if (deniedPrivilegeNames != null) {
+            for (String pn: deniedPrivilegeNames) {
+                LocalPrivilege lp = 
privilegeToLocalPrivilegesMap.computeIfAbsent(pn, LocalPrivilege::new);
+                lp.setDeny(true);
+            }
+        }
+        return privilegeToLocalPrivilegesMap;
+    }
+
+    /**
+     * @deprecated use {@link #createAce(String, Collection, String)} instead
      */
+    @Deprecated
     @Override
     public void createAce(String principalId, String[] grantedPrivilegeNames, 
String[] deniedPrivilegeNames,
             String order, Map<String, Value> restrictions, Map<String, 
Value[]> mvRestrictions,
             Set<String> removedRestrictionNames) throws RepositoryException {
+        Map<String, LocalPrivilege> privilegeToLocalPrivilegesMap = 
toLocalPrivileges(grantedPrivilegeNames,
+                deniedPrivilegeNames);
+
+        Set<LocalRestriction> restrictionsSet = new HashSet<>();
+        if (restrictions != null) {
+            for (Entry<String, Value> entry: restrictions.entrySet()) {
+                LocalRestriction lr = new LocalRestriction(entry.getKey(), 
entry.getValue());
+                restrictionsSet.add(lr);
+            }
+        }
+        if (mvRestrictions != null) {
+            for (Entry<String, Value[]> entry: mvRestrictions.entrySet()) {
+                LocalRestriction lr = new LocalRestriction(entry.getKey(), 
entry.getValue());
+                restrictionsSet.add(lr);
+            }
+        }
+
+        if (!restrictionsSet.isEmpty()) {
+            for (LocalPrivilege entry: privilegeToLocalPrivilegesMap.values()) 
{
+                if (entry.isAllow()) {
+                    entry.setAllowRestrictions(restrictionsSet);
+                }
+                if (entry.isDeny()) {
+                    entry.setDenyRestrictions(restrictionsSet);
+                }
+            }
+        }
+
+        createAce(principalId, new 
ArrayList<>(privilegeToLocalPrivilegesMap.values()), order);
+    }
+
+    @Override
+    public void createAce(String principalId, Collection<LocalPrivilege> 
privileges, String order)
+            throws RepositoryException {
         final Node parentNode = this.parentNodeStack.peek();
-        Session session = parentNode.getSession();
+        Session jcrSession = parentNode.getSession();
 
-        PrincipalManager principalManager = 
AccessControlUtil.getPrincipalManager(session);
-        Principal principal = principalManager.getPrincipal(principalId);
+        // validate that the principal name is valid
+        PrincipalManager principalManager = 
AccessControlUtil.getPrincipalManager(jcrSession);
+        Principal principal = principalId == null ? null : 
principalManager.getPrincipal(principalId);
         if (principal == null) {
             // SLING-7268 - as pointed out in OAK-5496, we cannot successfully 
use
             // PrincipalManager#getPrincipal in oak
@@ -869,7 +948,7 @@ public class DefaultContentCreator implements 
ContentCreator {
             // subsequent index update).
             // Workaround by trying the UserManager#getAuthorizable API to 
locate the
             // principal.
-            UserManager userManager = 
AccessControlUtil.getUserManager(session);
+            UserManager userManager = 
AccessControlUtil.getUserManager(jcrSession);
             final Authorizable authorizable = 
userManager.getAuthorizable(principalId);
             if (authorizable != null) {
                 principal = authorizable.getPrincipal();
@@ -879,11 +958,330 @@ public class DefaultContentCreator implements 
ContentCreator {
         if (principal == null) {
             throw new RepositoryException("No principal found for id: " + 
principalId);
         }
+
+        // validate that the privilege names are valid
+        AccessControlManager acm = 
AccessControlUtil.getAccessControlManager(jcrSession);
+        for (LocalPrivilege localPrivilege: privileges) {
+            localPrivilege.checkPrivilege(acm);
+        }
+
         String resourcePath = parentNode.getPath();
 
-        if ((grantedPrivilegeNames != null) || (deniedPrivilegeNames != null)) 
{
-            AccessControlUtil.replaceAccessControlEntry(session, resourcePath, 
principal, grantedPrivilegeNames, deniedPrivilegeNames, null, order, 
-                       restrictions, mvRestrictions, removedRestrictionNames);
+        // build a list of each of the LocalPrivileges that have the same 
restrictions
+        Map<Set<LocalRestriction>, List<LocalPrivilege>> 
allowRestrictionsToLocalPrivilegesMap = new HashMap<>();
+        Map<Set<LocalRestriction>, List<LocalPrivilege>> 
denyRestrictionsToLocalPrivilegesMap = new HashMap<>();
+        for (LocalPrivilege localPrivilege: privileges) {
+            if (localPrivilege.isAllow()) {
+                List<LocalPrivilege> list = 
allowRestrictionsToLocalPrivilegesMap.computeIfAbsent(localPrivilege.getAllowRestrictions(),
 key -> new ArrayList<>());
+                list.add(localPrivilege);
+            }
+            if (localPrivilege.isDeny()) {
+                List<LocalPrivilege> list = 
denyRestrictionsToLocalPrivilegesMap.computeIfAbsent(localPrivilege.getDenyRestrictions(),
 key -> new ArrayList<>());
+                list.add(localPrivilege);
+            }
+        }
+
+        try {
+            // Get or create the ACL for the node.
+            JackrabbitAccessControlList acl = getAcl(acm, resourcePath, 
principal);
+
+            // remove all the old aces for the principal
+            order = removeAces(resourcePath, order, principal, acl);
+
+            // now add all the new aces that we have collected
+            Map<Privilege, Integer> privilegeLongestDepthMap = 
buildPrivilegeLongestDepthMap(acm.privilegeFromName(PrivilegeConstants.JCR_ALL));
+            addAces(resourcePath, principal, 
denyRestrictionsToLocalPrivilegesMap, false, acl, privilegeLongestDepthMap);
+            addAces(resourcePath, principal, 
allowRestrictionsToLocalPrivilegesMap, true, acl, privilegeLongestDepthMap);
+
+            // reorder the aces
+            reorderAccessControlEntries(acl, principal, order);
+
+            // Store the actual changes.
+            acm.setPolicy(acl.getPath(), acl);
+        } catch (RepositoryException re) {
+            throw new RepositoryException("Failed to create ace.", re);
+        }
+    }
+
+    /**
+     * If the privilege is contained in multiple aggregate privileges, then
+     * calculate the instance with the greatest depth.
+     */
+    private static void toLongestDepth(int parentDepth, Privilege 
parentPrivilege, Map<Privilege, Integer> privilegeToLongestDepth) {
+        Privilege[] declaredAggregatePrivileges = 
parentPrivilege.getDeclaredAggregatePrivileges();
+        for (Privilege privilege : declaredAggregatePrivileges) {
+            Integer oldValue = privilegeToLongestDepth.get(privilege);
+            int candidateDepth = parentDepth + 1;
+            if (oldValue == null || oldValue.intValue() < candidateDepth) {
+                privilegeToLongestDepth.put(privilege, candidateDepth);
+
+                // continue drilling down to the leaf privileges
+                toLongestDepth(candidateDepth, privilege, 
privilegeToLongestDepth);
+            }
+        }
+    }
+
+    /**
+     * Calculate the longest path for each of the possible privileges
+     * 
+     * @param jcrSession the current users JCR session
+     * @return map where the key is the privilege and the value is the longest 
path
+     */
+    public static Map<Privilege, Integer> 
buildPrivilegeLongestDepthMap(Privilege jcrAll) {
+        Map<Privilege, Integer> privilegeToLongestPath = new HashMap<>();
+        privilegeToLongestPath.put(jcrAll, 1);
+        toLongestDepth(1, jcrAll, privilegeToLongestPath);
+        return privilegeToLongestPath;
+    }
+
+    /**
+     * Lookup the ACL for the given resource
+     * 
+     * @param acm the access control manager
+     * @param resourcePath the resource path
+     * @param principal the principal for principalbased ACL
+     * @return the found ACL object
+     */
+    protected JackrabbitAccessControlList getAcl(@NotNull AccessControlManager 
acm, String resourcePath, Principal principal)
+            throws RepositoryException {
+        AccessControlPolicy[] policies = acm.getPolicies(resourcePath);
+        JackrabbitAccessControlList acl = null;
+        for (AccessControlPolicy policy : policies) {
+            if (policy instanceof JackrabbitAccessControlList) {
+                acl = (JackrabbitAccessControlList) policy;
+                break;
+            }
+        }
+        if (acl == null) {
+            AccessControlPolicyIterator applicablePolicies = 
acm.getApplicablePolicies(resourcePath);
+            while (applicablePolicies.hasNext()) {
+                AccessControlPolicy policy = 
applicablePolicies.nextAccessControlPolicy();
+                if (policy instanceof JackrabbitAccessControlList) {
+                    acl = (JackrabbitAccessControlList) policy;
+                    break;
+                }
+            }
+        }
+        return acl;
+    }
+
+    /**
+     * Remove all of the ACEs for the specified principal from the ACL
+     * 
+     * @param order the requested order (may be null)
+     * @param principal the principal whose aces should be removed
+     * @param acl the access control list to update
+     * @return the original order if it was supplied, otherwise the order of 
the first ACE 
+     */
+    protected String removeAces(@NotNull String resourcePath, @Nullable String 
order, @NotNull Principal principal, @NotNull JackrabbitAccessControlList acl) 
// NOSONAR
+            throws RepositoryException {
+        AccessControlEntry[] existingAccessControlEntries = 
acl.getAccessControlEntries();
+
+        if (order == null || order.length() == 0) {
+            //order not specified, so keep track of the original ACE position.
+            Set<Principal> processedPrincipals = new HashSet<>();
+            for (int j = 0; j < existingAccessControlEntries.length; j++) {
+                AccessControlEntry ace = existingAccessControlEntries[j];
+                Principal principal2 = ace.getPrincipal();
+                if (principal2.equals(principal)) {
+                    order = String.valueOf(processedPrincipals.size());
+                    break;
+                } else {
+                    processedPrincipals.add(principal2);
+                }
+            }
+        }
+
+        for (int j = 0; j < existingAccessControlEntries.length; j++) {
+            AccessControlEntry ace = existingAccessControlEntries[j];
+            if (ace.getPrincipal().equals(principal)) {
+                acl.removeAccessControlEntry(ace);
+            }
+        }
+        return order;
+    }
+
+    /**
+     * Add ACEs for the specified principal to the ACL.  One ACE is added for 
each unique
+     * restriction set.
+     * 
+     * @param resourcePath the path of the resource
+     * @param principal the principal whose aces should be added
+     * @param restrictionsToLocalPrivilegesMap the map containing the 
restrictions mapped to the LocalPrivlege items with those resrictions
+     * @param isAllow true for 'allow' ACE, false for 'deny' ACE
+     * @param acl the access control list to update
+     */
+    protected void addAces(@NotNull String resourcePath, @NotNull Principal 
principal,
+            @NotNull Map<Set<LocalRestriction>, List<LocalPrivilege>> 
restrictionsToLocalPrivilegesMap,
+            boolean isAllow,
+            @NotNull JackrabbitAccessControlList acl,
+            Map<Privilege, Integer> privilegeLongestDepthMap) throws 
RepositoryException {
+
+        List<Entry<Set<LocalRestriction>, List<LocalPrivilege>>> sortedEntries 
= new ArrayList<>(restrictionsToLocalPrivilegesMap.entrySet());
+        // sort the entries by the most shallow depth of the contained 
privileges
+        Collections.sort(sortedEntries, (e1, e2) -> {
+                        int shallowestDepth1 = Integer.MAX_VALUE;
+                        for (LocalPrivilege lp : e1.getValue()) {
+                            Integer depth = 
privilegeLongestDepthMap.get(lp.getPrivilege());
+                            if (depth != null && depth.intValue() < 
shallowestDepth1) {
+                                shallowestDepth1 = depth.intValue();
+                            }
+                        }
+                        int shallowestDepth2 = Integer.MAX_VALUE;
+                        for (LocalPrivilege lp : e2.getValue()) {
+                            Integer depth = 
privilegeLongestDepthMap.get(lp.getPrivilege());
+                            if (depth != null && depth.intValue() < 
shallowestDepth2) {
+                                shallowestDepth2 = depth.intValue();
+                            }
+                        }
+                        return Integer.compare(shallowestDepth1, 
shallowestDepth2);
+                    });
+
+        for (Entry<Set<LocalRestriction>, List<LocalPrivilege>> entry: 
sortedEntries) {
+            Set<Privilege> privilegesSet = new HashSet<>();
+            Map<String, Value> restrictions = new HashMap<>(); 
+            Map<String, Value[]> mvRestrictions = new HashMap<>();
+
+            Set<LocalRestriction> localRestrictions = entry.getKey();
+            for (LocalRestriction localRestriction : localRestrictions) {
+                if (localRestriction.isMultiValue()) {
+                    mvRestrictions.put(localRestriction.getName(), 
localRestriction.getValues());
+                } else {
+                    restrictions.put(localRestriction.getName(), 
localRestriction.getValue());
+                }
+            }
+
+            for (LocalPrivilege localPrivilege : entry.getValue()) {
+                privilegesSet.add(localPrivilege.getPrivilege());
+            }
+
+            if (!privilegesSet.isEmpty()) {
+                acl.addEntry(principal, privilegesSet.toArray(new 
Privilege[privilegesSet.size()]), isAllow, restrictions, mvRestrictions);
+            }
+        }
+    }
+
+    /**
+     * Move the ACE(s) for the specified principal to the position specified 
by the 'order'
+     * parameter. This is a copy of the private 
AccessControlUtil.reorderAccessControlEntries method.
+     *
+     * @param acl the acl of the node containing the ACE to position
+     * @param principal the user or group of the ACE to position
+     * @param order where the access control entry should go in the list.
+     *         Value should be one of these:
+     *         <table>
+     *          <caption>Values</caption>
+     *          <tr><td>first</td><td>Place the target ACE as the first 
amongst its siblings</td></tr>
+     *          <tr><td>last</td><td>Place the target ACE as the last amongst 
its siblings</td></tr>
+     *          <tr><td>before xyz</td><td>Place the target ACE immediately 
before the sibling whose name is xyz</td></tr>
+     *          <tr><td>after xyz</td><td>Place the target ACE immediately 
after the sibling whose name is xyz</td></tr>
+     *          <tr><td>numeric</td><td>Place the target ACE at the specified 
index</td></tr>
+     *         </table>
+     * @throws RepositoryException
+     * @throws UnsupportedRepositoryOperationException
+     * @throws AccessControlException
+     */
+    private static void reorderAccessControlEntries(AccessControlList acl,
+            Principal principal, String order) throws RepositoryException {
+        if (order == null || order.length() == 0) {
+            return; //nothing to do
+        }
+        if (acl instanceof JackrabbitAccessControlList) {
+            JackrabbitAccessControlList jacl = 
(JackrabbitAccessControlList)acl;
+
+            AccessControlEntry[] accessControlEntries = 
jacl.getAccessControlEntries();
+            if (accessControlEntries.length <= 1) {
+                return; //only one ACE, so nothing to reorder.
+            }
+
+            AccessControlEntry beforeEntry = null;
+            if ("first".equals(order)) {
+                beforeEntry = accessControlEntries[0];
+            } else if ("last".equals(order)) {
+                // add to the end is the same as default
+            } else if (order.startsWith("before ")) {
+                String beforePrincipalName = order.substring(7);
+
+                //find the index of the ACE of the 'before' principal
+                for (int i=0; i < accessControlEntries.length; i++) {
+                    if 
(beforePrincipalName.equals(accessControlEntries[i].getPrincipal().getName())) {
+                        //found it!
+                        beforeEntry = accessControlEntries[i];
+                        break;
+                    }
+                }
+
+                if (beforeEntry == null) {
+                    //didn't find an ACE that matched the 'before' principal
+                    throw new IllegalArgumentException("No ACE was found for 
the specified principal: " + beforePrincipalName);
+                }
+            } else if (order.startsWith("after ")) {
+                String afterPrincipalName = order.substring(6);
+
+                boolean foundPrincipal = false;
+                //find the index of the ACE of the 'after' principal
+                for (int i = accessControlEntries.length - 1; i >= 0; i--) {
+                    if 
(afterPrincipalName.equals(accessControlEntries[i].getPrincipal().getName())) {
+                        //found it!
+                        foundPrincipal = true;
+
+                        // the 'before' ACE is the next one after the 'after' 
ACE
+                        if (i >= accessControlEntries.length - 1) {
+                            //the after is the last one in the list
+                            beforeEntry = null;
+                        } else {
+                            beforeEntry = accessControlEntries[i + 1];
+                        }
+                        break;
+                    }
+                }
+
+                if (!foundPrincipal) {
+                    //didn't find an ACE that matched the 'after' principal
+                    throw new IllegalArgumentException("No ACE was found for 
the specified principal: " + afterPrincipalName);
+                }
+            } else {
+                int index = -1;
+                try {
+                    index = Integer.parseInt(order);
+                } catch (NumberFormatException nfe) {
+                    //not a number.
+                    throw new IllegalArgumentException("Illegal value for the 
order parameter: " + order);
+                }
+                if (index > accessControlEntries.length) {
+                    //invalid index
+                    throw new IndexOutOfBoundsException("Index value is too 
large: " + index);
+                }
+
+                //the index value is the index of the principal.  A principal 
may have more
+                // than one ACEs (deny + grant), so we need to compensate.
+                Map<Principal, Integer> principalToIndex = new HashMap<>();
+                for (int i = 0; i < accessControlEntries.length; i++) {
+                    Principal principal2 = 
accessControlEntries[i].getPrincipal();
+                    Integer idx = i;
+                    principalToIndex.computeIfAbsent(principal2, key -> idx);
+                }
+                Integer[] sortedIndexes = principalToIndex.values().stream()
+                        .sorted()
+                        .toArray(size -> new Integer[size]);
+                if (index >= 0 && index < sortedIndexes.length - 1) {
+                    int idx = sortedIndexes[index];
+                    beforeEntry = accessControlEntries[idx];
+                }
+            }
+
+            if (beforeEntry != null) {
+                //now loop through the entries to move the affected ACEs to 
the specified
+                // position.
+                for (AccessControlEntry ace : accessControlEntries) {
+                    if (principal.equals(ace.getPrincipal())) {
+                        //this ACE is for the specified principal.
+                        jacl.orderBefore(ace, beforeEntry);
+                    }
+                }
+            }
+        } else {
+            throw new IllegalArgumentException("The acl must be an instance of 
JackrabbitAccessControlList");
         }
     }
 
diff --git 
a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
 
b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
index 7bd0617..47494b0 100644
--- 
a/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
+++ 
b/src/main/java/org/apache/sling/jcr/contentloader/internal/readers/JsonReader.java
@@ -25,10 +25,14 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.regex.Pattern;
 
@@ -47,14 +51,18 @@ import jakarta.json.JsonString;
 import jakarta.json.JsonValue;
 import jakarta.json.JsonValue.ValueType;
 
+import 
org.apache.jackrabbit.oak.spi.security.authorization.restriction.CompositeRestrictionProvider;
 import 
org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinition;
 import 
org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
 import org.apache.sling.jcr.contentloader.ContentCreator;
 import org.apache.sling.jcr.contentloader.ContentReader;
+import org.apache.sling.jcr.contentloader.LocalPrivilege;
+import org.apache.sling.jcr.contentloader.LocalRestriction;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.component.annotations.Component;
 
@@ -465,128 +473,199 @@ public class JsonReader implements ContentReader {
      */
     private void createAce(JsonObject ace, ContentCreator contentCreator) 
throws RepositoryException {
         String principalID = ace.getString("principal");
+        String order = ace.getString("order", null);
+
+        // first start with an empty map
+        Map<String, LocalPrivilege> privilegeToLocalPrivilegesMap = new 
LinkedHashMap<>();
+
+        Node parentNode = contentCreator.getParent();
+        ValueFactory vf = parentNode.getSession().getValueFactory();
+
+        // Calculate a map of restriction names to the restriction definition.
+        // Use for fast lookup during the calls below.
+        Map<String, RestrictionDefinition> srMap = toSrMap(parentNode);
 
-        String[] grantedPrivileges = null;
+        // for backward compatibility, process the older syntax
         JsonArray granted = (JsonArray) ace.get("granted");
-        if (granted != null) {
-            grantedPrivileges = new String[granted.size()];
-            for (int a = 0; a < grantedPrivileges.length; a++) {
-                grantedPrivileges[a] = granted.getString(a);
+        JsonArray denied = (JsonArray) ace.get("denied");
+        if (granted != null || denied != null) {
+            JsonValue restrictions = ace.get("restrictions");
+            Set<LocalRestriction> restrictionsSet = Collections.emptySet();
+            if (restrictions instanceof JsonObject) {
+                restrictionsSet = 
toLocalRestrictions((JsonObject)restrictions, srMap, vf);
+            }
+
+            if (granted != null) {
+                for (int a = 0; a < granted.size(); a++) {
+                    String privilegeName = granted.getString(a);
+                    LocalPrivilege lp = 
privilegeToLocalPrivilegesMap.computeIfAbsent(privilegeName, 
LocalPrivilege::new);
+                    lp.setAllow(true);
+                    lp.setAllowRestrictions(restrictionsSet);
+                }
+            }
+
+            if (denied != null) {
+                for (int a = 0; a < denied.size(); a++) {
+                    String privilegeName = denied.getString(a);
+                    LocalPrivilege lp = 
privilegeToLocalPrivilegesMap.computeIfAbsent(privilegeName, 
LocalPrivilege::new);
+                    lp.setDeny(true);
+                    lp.setDenyRestrictions(restrictionsSet);
+                }
             }
         }
 
-        String[] deniedPrivileges = null;
-        JsonArray denied = (JsonArray) ace.get("denied");
-        if (denied != null) {
-            deniedPrivileges = new String[denied.size()];
-            for (int a = 0; a < deniedPrivileges.length; a++) {
-                deniedPrivileges[a] = denied.getString(a);
+        // now process the newer syntax
+        JsonValue privileges = ace.get("privileges");
+        if (privileges instanceof JsonObject) {
+            JsonObject privilegesObj = (JsonObject)privileges;
+            for (Entry<String, JsonValue> entry : privilegesObj.entrySet()) {
+                String privilegeName = entry.getKey();
+                JsonValue privilegeValue = entry.getValue();
+                if (privilegeValue instanceof JsonObject) {
+                    JsonObject privilegeValueObj = (JsonObject)privilegeValue;
+                    JsonValue allow = privilegeValueObj.get("allow");
+                    boolean isAllow = false;
+                    Set<LocalRestriction> allowRestrictions = 
Collections.emptySet(); 
+                    if (allow instanceof JsonObject) {
+                        isAllow = true;
+                        allowRestrictions = 
toLocalRestrictions((JsonObject)allow, srMap, vf);
+                    } else if (JsonValue.TRUE.equals(allow)) {
+                        isAllow = true;
+                    }
+
+                    JsonValue deny = privilegeValueObj.get("deny");
+                    boolean isDeny = false;
+                    Set<LocalRestriction> denyRestrictions = 
Collections.emptySet(); 
+                    if (deny instanceof JsonObject) {
+                        isDeny = true;
+                        denyRestrictions = 
toLocalRestrictions((JsonObject)deny, srMap, vf);
+                    } else if (JsonValue.TRUE.equals(deny)) {
+                        isDeny = true;
+                    }
+
+                    if (isAllow || isDeny) {
+                        LocalPrivilege lp = 
privilegeToLocalPrivilegesMap.computeIfAbsent(privilegeName, 
LocalPrivilege::new);
+                        if (isAllow) {
+                            lp.setAllow(true);
+                            lp.setAllowRestrictions(allowRestrictions);
+                        }
+                        if (isDeny) {
+                            lp.setDeny(true);
+                            lp.setDenyRestrictions(denyRestrictions);
+                        }
+                    }
+                }
             }
         }
 
-        String order = ace.getString("order", null);
+        // do the work.
+        contentCreator.createAce(principalID, new 
ArrayList<>(privilegeToLocalPrivilegesMap.values()), order);
+    }
 
-        Map<String, Value> restrictionsMap = null;
-        Map<String, Value[]> mvRestrictionsMap = null;
-        Set<String> removedRestrictionNames = null;
-        JsonObject restrictions = (JsonObject) ace.get("restrictions");
-        if (restrictions != null) {
-            // lazy initialized map for quick lookup when processing 
restrictions
-            Map<String, RestrictionDefinition> supportedRestrictionsMap = new 
HashMap<>();
+    /**
+     * Calculate a map of restriction names to the restriction definition
+     * 
+     * @param parentNode the node the restrictions are for
+     */
+    protected Map<String, RestrictionDefinition> toSrMap(Node parentNode)
+            throws RepositoryException {
+        // lazy initialized map for quick lookup when processing restrictions
+        Map<String, RestrictionDefinition> supportedRestrictionsMap = new 
HashMap<>();
 
-            Node parentNode = contentCreator.getParent();
+        RestrictionProvider compositeRestrictionProvider = null;
+        Set<RestrictionProvider> restrictionProviders = new HashSet<>();
 
-            RestrictionProvider restrictionProvider = null;
-            Bundle bundle = FrameworkUtil.getBundle(getClass());
+        Bundle bundle = FrameworkUtil.getBundle(getClass());
+        if (bundle != null) {
             BundleContext bundleContext = bundle.getBundleContext();
-            ServiceReference<RestrictionProvider> serviceReference = null;
+            Collection<ServiceReference<RestrictionProvider>> 
serviceReferences = null;
             try {
-                serviceReference = 
bundleContext.getServiceReference(RestrictionProvider.class);
-                restrictionProvider = 
bundleContext.getService(serviceReference);
-
-                if (restrictionProvider == null) {
-                    throw new JsonException(
-                            "No restriction provider is available so unable to 
process restriction values");
+                serviceReferences = 
bundleContext.getServiceReferences(RestrictionProvider.class, null);
+                for (ServiceReference<RestrictionProvider> serviceReference : 
serviceReferences) {
+                    RestrictionProvider service = 
bundleContext.getService(serviceReference);
+                    restrictionProviders.add(service);
                 }
+                compositeRestrictionProvider = 
CompositeRestrictionProvider.newInstance(restrictionProviders);
 
                 // populate the map
-                Set<RestrictionDefinition> supportedRestrictions = 
restrictionProvider
+                Set<RestrictionDefinition> supportedRestrictions = 
compositeRestrictionProvider
                         .getSupportedRestrictions(parentNode.getPath());
                 for (RestrictionDefinition restrictionDefinition : 
supportedRestrictions) {
                     
supportedRestrictionsMap.put(restrictionDefinition.getName(), 
restrictionDefinition);
                 }
+            } catch (InvalidSyntaxException e) {
+                throw new RepositoryException(e);
             } finally {
-                if (serviceReference != null) {
-                    bundleContext.ungetService(serviceReference);
+                if (serviceReferences != null) {
+                    for (ServiceReference<RestrictionProvider> 
serviceReference : serviceReferences) {
+                        bundleContext.ungetService(serviceReference);
+                    }
                 }
             }
+        }
+        return supportedRestrictionsMap;
+    }
 
-            restrictionsMap = new HashMap<>();
-            mvRestrictionsMap = new HashMap<>();
-            removedRestrictionNames = new HashSet<>();
+    /**
+     * Construct a LocalRestriction using data from the json object
+     * 
+     * @param allowOrDenyObj the json object
+     * @param srMap map of restriction names to the restriction definition
+     * @param vf the ValueFactory
+     */
+    protected Set<LocalRestriction> toLocalRestrictions(JsonObject 
allowOrDenyObj,
+            Map<String, RestrictionDefinition> srMap,
+            ValueFactory vf) throws RepositoryException {
+        Set<LocalRestriction> restrictions = new HashSet<>();
+        for (Entry<String, JsonValue> restrictionEntry : 
allowOrDenyObj.entrySet()) {
+            String restrictionName = restrictionEntry.getKey();
+            RestrictionDefinition rd = srMap.get(restrictionName);
+            if (rd == null) {
+                // illegal restriction name?
+                throw new JsonException("Invalid or not supported restriction 
name was supplied: " + restrictionName);
+            }
 
-            ValueFactory factory = parentNode.getSession().getValueFactory();
+            boolean multival = rd.getRequiredType().isArray();
+            int restrictionType = rd.getRequiredType().tag();
 
-            Set<String> keySet = restrictions.keySet();
-            for (String rname : keySet) {
-                if (rname.endsWith("@Delete")) {
-                    // add the key to the 'remove' set. the value doesn't 
matter and is ignored.
-                    String rname2 = rname.substring(9, rname.length() - 7);
-                    removedRestrictionNames.add(rname2);
-                } else {
-                    RestrictionDefinition rd = 
supportedRestrictionsMap.get(rname);
-                    if (rd == null) {
-                        // illegal restriction name?
-                        throw new JsonException("Invalid or not supported 
restriction name was supplied: " + rname);
-                    }
+            LocalRestriction lr = null;
+            JsonValue jsonValue = restrictionEntry.getValue();
 
-                    boolean multival = rd.getRequiredType().isArray();
-                    int restrictionType = rd.getRequiredType().tag();
-
-                    // read the requested restriction value and apply it
-                    JsonValue jsonValue = restrictions.get(rname);
-
-                    if (multival) {
-                        if (jsonValue.getValueType() == ValueType.ARRAY) {
-                            JsonArray jsonArray = (JsonArray) jsonValue;
-                            int size = jsonArray.size();
-                            Value[] values = new Value[size];
-                            for (int i = 0; i < size; i++) {
-                                values[i] = toValue(factory, jsonArray.get(i), 
restrictionType);
-                            }
-                            mvRestrictionsMap.put(rname, values);
-                        } else {
-                            Value v = toValue(factory, jsonValue, 
restrictionType);
-                            mvRestrictionsMap.put(rname, new Value[] { v });
-                        }
-                    } else {
-                        if (jsonValue.getValueType() == ValueType.ARRAY) {
-                            JsonArray jsonArray = (JsonArray) jsonValue;
-                            int size = jsonArray.size();
-                            if (size == 1) {
-                                Value v = toValue(factory, jsonArray.get(0), 
restrictionType);
-                                restrictionsMap.put(rname, v);
-                            } else if (size > 1) {
-                                throw new JsonException(
-                                        "Unexpected multi value array data 
found for single-value restriction value for name: "
-                                                + rname);
-                            }
-                        } else {
-                            Value v = toValue(factory, jsonValue, 
restrictionType);
-                            restrictionsMap.put(rname, v);
-                        }
+            if (multival) {
+                if (jsonValue.getValueType() == ValueType.ARRAY) {
+                    JsonArray jsonArray = (JsonArray) jsonValue;
+                    int size = jsonArray.size();
+                    Value[] values = new Value[size];
+                    for (int i = 0; i < size; i++) {
+                        values[i] = toValue(vf, jsonArray.get(i), 
restrictionType);
+                    }
+                    lr = new LocalRestriction(restrictionName, values);
+                } else {
+                    Value v = toValue(vf, jsonValue, restrictionType);
+                    lr = new LocalRestriction(restrictionName, new Value[] { v 
});
+                }
+            } else {
+                if (jsonValue.getValueType() == ValueType.ARRAY) {
+                    JsonArray jsonArray = (JsonArray) jsonValue;
+                    int size = jsonArray.size();
+                    if (size == 1) {
+                        Value v = toValue(vf, jsonArray.get(0), 
restrictionType);
+                        lr = new LocalRestriction(restrictionName, v);
+                    } else if (size > 1) {
+                        throw new JsonException(
+                                "Unexpected multi value array data found for 
single-value restriction value for name: "
+                                        + restrictionName);
                     }
+                } else {
+                    Value v = toValue(vf, jsonValue, restrictionType);
+                    lr = new LocalRestriction(restrictionName, v);
                 }
             }
+            if (lr != null) {
+                restrictions.add(lr);
+            }
         }
-
-        // do the work.
-        if (restrictionsMap == null && mvRestrictionsMap == null && 
removedRestrictionNames == null) {
-            contentCreator.createAce(principalID, grantedPrivileges, 
deniedPrivileges, order);
-        } else {
-            contentCreator.createAce(principalID, grantedPrivileges, 
deniedPrivileges, order, restrictionsMap,
-                    mvRestrictionsMap, removedRestrictionNames == null ? null 
: removedRestrictionNames);
-        }
+        return restrictions;
     }
 
     /**
diff --git 
a/src/test/java/org/apache/sling/jcr/contentloader/ContentCreatorTest.java 
b/src/test/java/org/apache/sling/jcr/contentloader/ContentCreatorTest.java
new file mode 100644
index 0000000..95427ab
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/contentloader/ContentCreatorTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations 
under
+ * the License.
+ */
+package org.apache.sling.jcr.contentloader;
+
+import java.io.InputStream;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+
+import org.junit.Test;
+
+/**
+ * Tests to verify the ContentCreator default methods
+ * for an old impl that does not provide an implementation
+ * for those methods
+ */
+public class ContentCreatorTest {
+
+    private ContentCreator contentCreator = new ContentCreatorOldImpl();
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testCreateAce1() throws RepositoryException {
+        contentCreator.createAce(null, null, null);
+    }
+
+    @Deprecated
+    @Test(expected = UnsupportedOperationException.class)
+    public void testCreateAce2() throws RepositoryException{
+        contentCreator.createAce(null, null, null, null, null, null, null);
+    }
+
+    /**
+     * An impl that doesn't provide implementations for the default methods
+     */
+    protected static class ContentCreatorOldImpl implements ContentCreator {
+
+        @Override
+        public void createNode(String name, String primaryNodeType, String[] 
mixinNodeTypes)
+                throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void finishNode() throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void finish() throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void createProperty(String name, int propertyType, String 
value) throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void createProperty(String name, int propertyType, String[] 
values) throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void createProperty(String name, Object value) throws 
RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void createProperty(String name, Object[] values) throws 
RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void createFileAndResourceNode(String name, InputStream data, 
String mimeType, long lastModified)
+                throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean switchCurrentNode(String subPath, String newNodeType) 
throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void createUser(String name, String password, Map<String, 
Object> extraProperties)
+                throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void createGroup(String name, String[] members, Map<String, 
Object> extraProperties)
+                throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void createAce(String principal, String[] grantedPrivileges, 
String[] deniedPrivileges, String order)
+                throws RepositoryException {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+}
diff --git 
a/src/test/java/org/apache/sling/jcr/contentloader/LocalPrivilegeTest.java 
b/src/test/java/org/apache/sling/jcr/contentloader/LocalPrivilegeTest.java
new file mode 100644
index 0000000..ac7a307
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/contentloader/LocalPrivilegeTest.java
@@ -0,0 +1,328 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.jcr.contentloader;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import 
org.apache.jackrabbit.oak.security.authorization.restriction.RestrictionProviderImpl;
+import 
org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
+import org.apache.jackrabbit.value.ValueFactoryImpl;
+import org.apache.sling.jcr.base.util.AccessControlUtil;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class LocalPrivilegeTest {
+
+    @Rule
+    public final SlingContext context = new 
SlingContext(ResourceResolverType.JCR_OAK);
+
+    private AccessControlManager acm;
+
+    @Before
+    public void setup() throws RepositoryException {
+        Session session = context.resourceResolver().adaptTo(Session.class);
+        acm = AccessControlUtil.getAccessControlManager(session);
+        context.registerService(new RestrictionProviderImpl());
+    }
+
+    private Privilege priv(String privilegeName) throws RepositoryException {
+        return acm.privilegeFromName(privilegeName);
+    }
+
+    private Value val(String value) {
+        return ValueFactoryImpl.getInstance().createValue(value);
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#hashCode()}.
+     */
+    @Test
+    public void testHashCode() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        LocalPrivilege lp2 = new LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        assertNotEquals(lp1.hashCode(), lp2.hashCode());
+
+        LocalPrivilege lp3 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertEquals(lp1.hashCode(), lp3.hashCode());
+
+        LocalPrivilege lp4 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp4.setAllow(true);
+        assertNotEquals(lp1.hashCode(), lp4.hashCode());
+        lp4.setDeny(true);
+        assertNotEquals(lp1.hashCode(), lp4.hashCode());
+        lp4.setAllowRestrictions(null);
+        assertNotEquals(lp1.hashCode(), lp4.hashCode());
+        lp4.setDenyRestrictions(null);
+        assertNotEquals(lp1.hashCode(), lp4.hashCode());
+
+        LocalPrivilege lp5 = new LocalPrivilege(null);
+        assertNotEquals(lp1.hashCode(), lp5.hashCode());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#checkPrivilege(javax.jcr.security.AccessControlManager)}.
+     * @throws RepositoryException 
+     */
+    @Test
+    public void testCheckPrivilege() throws RepositoryException {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp1.checkPrivilege(acm);
+        assertNotNull(lp1.getPrivilege());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#getPrivilege()}.
+     * @throws RepositoryException 
+     */
+    @Test
+    public void testGetPrivilege() throws RepositoryException {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp1.checkPrivilege(acm);
+        assertEquals(priv(PrivilegeConstants.JCR_READ), lp1.getPrivilege());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#getName()}.
+     */
+    @Test
+    public void testGetName() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertEquals(PrivilegeConstants.JCR_READ, lp1.getName());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#isAllow()}.
+     */
+    @Test
+    public void testIsAllow() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertFalse(lp1.isAllow());
+
+        lp1.setAllow(true);
+        assertTrue(lp1.isAllow());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#isDeny()}.
+     */
+    @Test
+    public void testIsDeny() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertFalse(lp1.isDeny());
+
+        lp1.setDeny(true);
+        assertTrue(lp1.isDeny());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#setAllow(boolean)}.
+     */
+    @Test
+    public void testSetAllow() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp1.setAllow(true);
+        assertTrue(lp1.isAllow());
+        lp1.setAllow(false);
+        assertFalse(lp1.isAllow());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#setDeny(boolean)}.
+     */
+    @Test
+    public void testSetDeny() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp1.setDeny(true);
+        assertTrue(lp1.isDeny());
+        lp1.setDeny(false);
+        assertFalse(lp1.isDeny());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#getAllowRestrictions()}.
+     */
+    @Test
+    public void testGetAllowRestrictions() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        Set<LocalRestriction> allowRestrictions = lp1.getAllowRestrictions();
+        assertNotNull(allowRestrictions);
+        assertTrue(allowRestrictions.isEmpty());
+
+        Set<LocalRestriction> newAllowRestrictions = new HashSet<>();
+        newAllowRestrictions.add(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello")));
+        lp1.setAllowRestrictions(newAllowRestrictions);
+        Set<LocalRestriction> allowRestrictions2 = lp1.getAllowRestrictions();
+        assertNotNull(allowRestrictions2);
+        assertFalse(allowRestrictions2.isEmpty());
+        assertEquals(newAllowRestrictions, allowRestrictions2);
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#setAllowRestrictions(java.util.Set)}.
+     */
+    @Test
+    public void testSetAllowRestrictions() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertTrue(lp1.getAllowRestrictions().isEmpty());
+
+        Set<LocalRestriction> newAllowRestrictions = new HashSet<>();
+        newAllowRestrictions.add(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello")));
+        lp1.setAllowRestrictions(newAllowRestrictions);
+        assertEquals(newAllowRestrictions, lp1.getAllowRestrictions());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#getDenyRestrictions()}.
+     */
+    @Test
+    public void testGetDenyRestrictions() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        Set<LocalRestriction> denyRestrictions = lp1.getDenyRestrictions();
+        assertNotNull(denyRestrictions);
+        assertTrue(denyRestrictions.isEmpty());
+
+        Set<LocalRestriction> newDenyRestrictions = new HashSet<>();
+        newDenyRestrictions.add(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello")));
+        lp1.setDenyRestrictions(newDenyRestrictions);
+        Set<LocalRestriction> denyRestrictions2 = lp1.getDenyRestrictions();
+        assertNotNull(denyRestrictions2);
+        assertFalse(denyRestrictions2.isEmpty());
+        assertEquals(newDenyRestrictions, denyRestrictions2);
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#setDenyRestrictions(java.util.Set)}.
+     */
+    @Test
+    public void testSetDenyRestrictions() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertTrue(lp1.getDenyRestrictions().isEmpty());
+
+        Set<LocalRestriction> newDenyRestrictions = new HashSet<>();
+        newDenyRestrictions.add(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello")));
+        lp1.setDenyRestrictions(newDenyRestrictions);
+        assertEquals(newDenyRestrictions, lp1.getDenyRestrictions());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#toString()}.
+     */
+    @Test
+    public void testToString() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertNotNull(lp1.toString());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalPrivilege#equals(java.lang.Object)}.
+     */
+    @Test
+    public void testEqualsObject() {
+        LocalPrivilege lp1 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertEquals(lp1, lp1);
+        assertNotEquals(lp1, null);
+        assertNotEquals(lp1, this);
+
+        LocalPrivilege lp2 = new LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        assertNotEquals(lp1, lp2);
+
+        LocalPrivilege lp3 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertEquals(lp1, lp3);
+
+        LocalPrivilege lp4 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp4.setAllow(true);
+        assertNotEquals(lp1, lp4);
+
+        LocalPrivilege lp5 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp5.setDeny(true);
+        assertNotEquals(lp1, lp5);
+
+        LocalPrivilege lp6 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp6.setAllowRestrictions(null);
+        assertNotEquals(lp1, lp6);
+
+        LocalPrivilege lp7 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp7.setDenyRestrictions(null);
+        assertNotEquals(lp1, lp7);
+
+        LocalPrivilege lp8 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp8.setAllowRestrictions(null);
+        LocalPrivilege lp9 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertNotEquals(lp8, lp9);
+
+        LocalPrivilege lp10 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp10.setDenyRestrictions(null);
+        LocalPrivilege lp11 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertNotEquals(lp10, lp11);
+
+        LocalPrivilege lp12 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp12.setAllowRestrictions(new HashSet<>(Arrays.asList(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello")))));
+        LocalPrivilege lp13 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertNotEquals(lp12, lp13);
+
+        LocalPrivilege lp14 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp14.setDenyRestrictions(new HashSet<>(Arrays.asList(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello")))));
+        LocalPrivilege lp15 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertNotEquals(lp14, lp15);
+
+        LocalPrivilege lp16 = new LocalPrivilege(null);
+        LocalPrivilege lp17 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        assertNotEquals(lp16, lp17);
+
+        LocalPrivilege lp18 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        LocalPrivilege lp19 = new LocalPrivilege(null);
+        assertNotEquals(lp18, lp19);
+
+        LocalPrivilege lp20 = new LocalPrivilege(null);
+        LocalPrivilege lp21 = new LocalPrivilege(null);
+        assertEquals(lp20, lp21);
+
+        LocalPrivilege lp22 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp22.setAllowRestrictions(null);
+        LocalPrivilege lp23 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp23.setAllowRestrictions(null);
+        assertEquals(lp22, lp23);
+
+        LocalPrivilege lp24 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp24.setDenyRestrictions(null);
+        LocalPrivilege lp25 = new LocalPrivilege(PrivilegeConstants.JCR_READ);
+        lp25.setDenyRestrictions(null);
+        assertEquals(lp24, lp25);
+    }
+
+}
diff --git 
a/src/test/java/org/apache/sling/jcr/contentloader/LocalRestrictionTest.java 
b/src/test/java/org/apache/sling/jcr/contentloader/LocalRestrictionTest.java
new file mode 100644
index 0000000..e3be41b
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/contentloader/LocalRestrictionTest.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.jcr.contentloader;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import javax.jcr.PropertyType;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+
+import 
org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
+import org.apache.jackrabbit.value.ValueFactoryImpl;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class LocalRestrictionTest {
+
+    public static Value val(String value) throws ValueFormatException {
+        return val(ValueFactoryImpl.getInstance(), PropertyType.STRING, value);
+    }
+    public static Value val(ValueFactory vf, int type, String value) throws 
ValueFormatException {
+        return vf.createValue(value, type);
+    }
+    public static Value[] vals(String ... value) throws ValueFormatException {
+        return vals(ValueFactoryImpl.getInstance(), PropertyType.STRING, 
value);
+    }
+    public static Value[] vals(ValueFactory vf, int type, String ... value) 
throws ValueFormatException {
+        Value[] values = new Value[value.length];
+        for (int i = 0; i < value.length; i++) {
+            values[i] = vf.createValue(value[i], type);
+        }
+        return values;
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalRestriction#hashCode()}.
+     */
+    @Test
+    public void testHashCode() throws ValueFormatException {
+        LocalRestriction lr1 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        LocalRestriction lr2 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello2"));
+        assertNotSame(lr1.hashCode(), lr2.hashCode());
+
+        LocalRestriction lr3 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        assertEquals(lr1.hashCode(), lr3.hashCode());
+
+        LocalRestriction lr4 = new LocalRestriction(null, (Value)null);
+        LocalRestriction lr5 = new LocalRestriction(null, (Value[])null);
+        assertNotSame(lr4.hashCode(), lr5.hashCode());
+        LocalRestriction lr6 = new LocalRestriction(null, (Value)null);
+        assertEquals(lr4.hashCode(), lr6.hashCode());
+        LocalRestriction lr7 = new LocalRestriction(null, (Value[])null);
+        assertEquals(lr5.hashCode(), lr7.hashCode());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalRestriction#getName()}.
+     */
+    @Test
+    public void testGetName() throws ValueFormatException {
+        LocalRestriction lr1 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        assertEquals(AccessControlConstants.REP_GLOB, lr1.getName());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalRestriction#isMultiValue()}.
+     */
+    @Test
+    public void testIsMultiValue() throws ValueFormatException {
+        LocalRestriction lr1 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        assertFalse(lr1.isMultiValue());
+
+        LocalRestriction lr2 = new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, vals("item1", "item2"));
+        assertTrue(lr2.isMultiValue());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalRestriction#getValue()}.
+     */
+    @Test
+    public void testGetValue() throws ValueFormatException {
+        LocalRestriction lr1 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        assertEquals(val("/hello1"), lr1.getValue());
+
+        LocalRestriction lr2 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, (Value)null);
+        assertNull(lr2.getValue());
+
+        LocalRestriction lr3 = new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, vals("item1", "item2"));
+        assertEquals(val("item1"), lr3.getValue());
+
+        LocalRestriction lr4 = new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, (Value[])new Value[0]);
+        assertNull(lr4.getValue());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalRestriction#getValues()}.
+     */
+    @Test
+    public void testGetValues() throws ValueFormatException {
+        LocalRestriction lr1 = new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, vals("item1", "item2"));
+        assertArrayEquals(vals("item1", "item2"), lr1.getValues());
+
+        LocalRestriction lr2 = new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, (Value[])null);
+        assertNull(lr2.getValues());
+
+        LocalRestriction lr3 = new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, new Value[0]);
+        assertArrayEquals(new Value[0], lr3.getValues());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalRestriction#toString()}.
+     */
+    @Test
+    public void testToString() throws ValueFormatException {
+        LocalRestriction lr1 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        assertNotNull(lr1.toString());
+
+        LocalRestriction lr2 = new LocalRestriction(null, val("/hello1"));
+        assertNotNull(lr2.toString());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.jcr.contentloader.LocalRestriction#equals(java.lang.Object)}.
+     */
+    @Test
+    public void testEqualsObject() throws ValueFormatException {
+        LocalRestriction lr1 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        assertEquals(lr1, lr1);
+        assertNotEquals(lr1, null);
+        assertNotEquals(lr1, this);
+
+        LocalRestriction lr2 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello2"));
+        assertNotEquals(lr1, lr2);
+
+        LocalRestriction lr3 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        assertEquals(lr1, lr3);
+
+        LocalRestriction lr4 = new LocalRestriction(null, val("/hello1"));
+        LocalRestriction lr5 = new LocalRestriction(null, val("/hello1"));
+        assertEquals(lr4, lr5);
+
+        LocalRestriction lr6 = new LocalRestriction(null, val("/hello1"));
+        LocalRestriction lr7 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        assertNotEquals(lr6, lr7);
+
+        LocalRestriction lr8 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        LocalRestriction lr9 = new LocalRestriction(null, val("/hello1"));
+        assertNotEquals(lr8, lr9);
+
+        LocalRestriction lr10 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        LocalRestriction lr11 = new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, vals("/hello1"));
+        assertNotEquals(lr10, lr11);
+
+        LocalRestriction lr12 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello1"));
+        LocalRestriction lr13 = new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("/hello2"));
+        assertNotEquals(lr12, lr13);
+    }
+
+}
diff --git 
a/src/test/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreatorTest.java
 
b/src/test/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreatorTest.java
index c316a46..5ee76fc 100644
--- 
a/src/test/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreatorTest.java
+++ 
b/src/test/java/org/apache/sling/jcr/contentloader/internal/DefaultContentCreatorTest.java
@@ -22,29 +22,48 @@ import static 
org.apache.sling.jcr.contentloader.ImportOptionsFactory.NO_OPTIONS
 import static 
org.apache.sling.jcr.contentloader.ImportOptionsFactory.OVERWRITE_NODE;
 import static 
org.apache.sling.jcr.contentloader.ImportOptionsFactory.OVERWRITE_PROPERTIES;
 import static 
org.apache.sling.jcr.contentloader.ImportOptionsFactory.createImportOptions;
+import static org.apache.sling.jcr.contentloader.LocalRestrictionTest.val;
+import static org.apache.sling.jcr.contentloader.LocalRestrictionTest.vals;
+import static 
org.apache.sling.jcr.contentloader.it.SLING11713InitialContentIT.assertAce;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 import java.text.ParseException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
+import javax.jcr.AccessDeniedException;
 import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
 import javax.jcr.Property;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.ValueFormatException;
 import javax.jcr.nodetype.NodeType;
-
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+
+import 
org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
+import org.apache.sling.jcr.base.util.AccessControlUtil;
 import org.apache.sling.jcr.contentloader.ContentImportListener;
 import org.apache.sling.jcr.contentloader.ContentReader;
+import org.apache.sling.jcr.contentloader.LocalPrivilege;
+import org.apache.sling.jcr.contentloader.LocalRestriction;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.jmock.Expectations;
@@ -547,6 +566,659 @@ public class DefaultContentCreatorTest {
         assertFalse(parentNode.hasProperty(propName));
     }
 
+    @Test
+    public void createAceWithoutRestrictionsSpecified() throws 
RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        contentCreator.createAce(userName, new String[] 
{PrivilegeConstants.JCR_READ}, 
+                new String[] {PrivilegeConstants.JCR_WRITE}, null);
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(2, allEntries.size());
+        assertAce(allEntries.get(0), userName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {}, // RestrictionNames
+                new String[][] {}); // RestrictionValues
+        assertAce(allEntries.get(1), userName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {}, // RestrictionNames
+                new String[][] {}); // RestrictionValues
+    }
+
+    @Deprecated
+    @Test
+    public void createAceWithNonSpecificRestrictions() throws 
RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        contentCreator.createAce(userName, new String[] 
{PrivilegeConstants.JCR_READ}, 
+                new String[] {PrivilegeConstants.JCR_WRITE}, null,
+                Collections.singletonMap(AccessControlConstants.REP_GLOB, 
val("glob1allow")),  // restrictions
+                
Collections.singletonMap(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2")),  // 
mvRestrictions
+                Collections.emptySet()); // removedRestrictionNames
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(2, allEntries.size());
+        assertAce(allEntries.get(0), userName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB, 
AccessControlConstants.REP_ITEM_NAMES}, // RestrictionNames
+                new String[][] {new String[]{"glob1allow"}, new String[] 
{"name1", "name2"}}); // RestrictionValues
+        assertAce(allEntries.get(1), userName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB, 
AccessControlConstants.REP_ITEM_NAMES}, // RestrictionNames
+                new String[][] {new String[]{"glob1allow"}, new String[] 
{"name1", "name2"}}); // RestrictionValues
+    }
+
+    @Deprecated
+    @Test
+    public void createAceWithNullSpecificRestrictions() throws 
RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        contentCreator.createAce(userName, new String[] 
{PrivilegeConstants.JCR_READ}, 
+                new String[] {PrivilegeConstants.JCR_WRITE}, null,
+                null,  // restrictions
+                null,  // mvRestrictions
+                null); // removedRestrictionNames
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(2, allEntries.size());
+        assertAce(allEntries.get(0), userName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {}, // RestrictionNames
+                new String[][] {}); // RestrictionValues
+        assertAce(allEntries.get(1), userName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {}, // RestrictionNames
+                new String[][] {}); // RestrictionValues
+    }
+
+    @Test
+    public void createAceWithSpecificRestrictions() throws RepositoryException 
{
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        contentCreator.createAce(userName, list, null);
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(2, allEntries.size());
+        assertAce(allEntries.get(0), userName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(1), userName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+    }
+
+    @Test
+    public void createAceWithSpecificRestrictionsTwice() throws 
RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        contentCreator.createAce(userName, list, null);
+        // call again to verify the old aces are replaced with the new ones
+        contentCreator.createAce(userName, list, null);
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(2, allEntries.size());
+        assertAce(allEntries.get(0), userName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(1), userName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+    }
+
+    @Test
+    public void createAceWithNullPrivileges() throws RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        // try all null privilege args
+        contentCreator.createAce(userName, null, null, null);
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(0, allEntries.size());
+    }
+
+    @Test
+    public void createAceWithNullPrincipal() throws RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        // try null principal
+        assertThrows("No principal found for id: null", 
RepositoryException.class, () -> {
+            contentCreator.createAce(null, new String[] 
{PrivilegeConstants.JCR_READ}, 
+                    new String[] {PrivilegeConstants.JCR_WRITE}, null);
+        });
+
+        contentCreator.finishNode();
+
+        //verify ACE was not set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(0, allEntries.size());
+    }
+
+    @Test
+    public void createAceWithInvalidPrincipal() throws RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+
+        // try non-existing principal
+        assertThrows(String.format("No principal found for id: %s", userName), 
RepositoryException.class, () -> {
+            contentCreator.createAce(userName, new String[] 
{PrivilegeConstants.JCR_READ}, 
+                    new String[] {PrivilegeConstants.JCR_WRITE}, null);
+        });
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(0, allEntries.size());
+    }
+
+    @Test
+    public void createAceWithOrderByNull() throws RepositoryException {
+        createAceWithOrderBy(null);
+    }
+
+    protected void createAceWithOrderBy(String orderBy) throws 
RepositoryException, ValueFormatException,
+            UnsupportedRepositoryOperationException, PathNotFoundException, 
AccessDeniedException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        final String groupName = uniqueId();
+        contentCreator.createGroup(groupName, null, null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        contentCreator.createAce(userName, list, null);
+        contentCreator.createAce(groupName, list, orderBy);
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(4, allEntries.size());
+        assertAce(allEntries.get(0), userName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(1), userName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+        assertAce(allEntries.get(2), groupName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(3), groupName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+    }
+
+    @Test
+    public void createAceWithOrderByEmpty() throws RepositoryException {
+        createAceWithOrderBy("");
+    }
+
+    @Test
+    public void createAceWithOrderByFirst() throws RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        final String groupName = uniqueId();
+        contentCreator.createGroup(groupName, null, null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        contentCreator.createAce(userName, list, null);
+        contentCreator.createAce(groupName, list, "first");
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(4, allEntries.size());
+        assertAce(allEntries.get(0), groupName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(1), groupName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+        assertAce(allEntries.get(2), userName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(3), userName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+    }
+
+    @Test
+    public void createAceWithOrderByLast() throws RepositoryException {
+        createAceWithOrderBy("last");
+    }
+
+    @Test
+    public void createAceWithOrderByAfter() throws RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        final String userName2 = uniqueId();
+        contentCreator.createUser(userName2, "Test", null);
+
+        final String groupName = uniqueId();
+        contentCreator.createGroup(groupName, null, null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        contentCreator.createAce(userName, list, null);
+        contentCreator.createAce(userName2, list, null);
+        contentCreator.createAce(groupName, list, String.format("after %s", 
userName));
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(6, allEntries.size());
+        assertAce(allEntries.get(0), userName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(1), userName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+        assertAce(allEntries.get(2), groupName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(3), groupName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+        assertAce(allEntries.get(4), userName2,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(5), userName2,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+    }
+
+    @Test
+    public void createAceWithOrderByBefore() throws RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        final String groupName = uniqueId();
+        contentCreator.createGroup(groupName, null, null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        contentCreator.createAce(userName, list, null);
+        contentCreator.createAce(groupName, list, String.format("before %s", 
userName));
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(4, allEntries.size());
+        assertAce(allEntries.get(0), groupName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(1), groupName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+        assertAce(allEntries.get(2), userName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(3), userName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+    }
+
+    @Test
+    public void createAceWithOrderByAfterInvalid() throws RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        assertThrows("No ACE was found for the specified principal: invalid", 
IllegalArgumentException.class, () -> {
+            contentCreator.createAce(userName, list, "after invalid");
+        });
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(0, allEntries.size());
+    }
+
+    @Test
+    public void createAceWithOrderByBeforeInvalid() throws RepositoryException 
{
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        assertThrows("No ACE was found for the specified principal: invalid", 
IllegalArgumentException.class, () -> {
+            contentCreator.createAce(userName, list, "before invalid");
+        });
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(0, allEntries.size());
+    }
+
+    @Test
+    public void createAceWithOrderByIndex() throws RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        final String userName2 = uniqueId();
+        contentCreator.createUser(userName2, "Test", null);
+
+        final String groupName = uniqueId();
+        contentCreator.createGroup(groupName, null, null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        contentCreator.createAce(userName, list, null);
+        contentCreator.createAce(userName2, list, null);
+        contentCreator.createAce(groupName, list, "1");
+
+        contentCreator.finishNode();
+
+        //verify ACE was set.
+        List<AccessControlEntry> allEntries = allEntries();
+        assertEquals(6, allEntries.size());
+        assertAce(allEntries.get(0), userName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(1), userName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+        assertAce(allEntries.get(2), groupName,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(3), groupName,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+        assertAce(allEntries.get(4), userName2,
+                false, // isAllow
+                new String[] {PrivilegeConstants.JCR_WRITE}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_ITEM_NAMES}, // 
RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+        assertAce(allEntries.get(5), userName2,
+                true, // isAllow
+                new String[] {PrivilegeConstants.JCR_READ}, // PrivilegeNames
+                new String[] {AccessControlConstants.REP_GLOB}, // 
RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+    }
+
+    @Test
+    public void createAceWithOrderByInvalid() throws RepositoryException {
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        assertThrows("Illegal value for the order parameter: invalid", 
IllegalArgumentException.class, () -> {
+            contentCreator.createAce(userName, list, "invalid");
+        });
+
+        contentCreator.finishNode();
+    }
+
+    @Test
+    public void createAceWithOrderByIndexTooLarge() throws RepositoryException 
{
+        contentCreator.init(createImportOptions(NO_OPTIONS),
+                new HashMap<String, ContentReader>(), null, null);
+        contentCreator.prepareParsing(parentNode, null);
+
+        final String userName = uniqueId();
+        contentCreator.createUser(userName, "Test", null);
+
+        List<LocalPrivilege> list = new ArrayList<>();
+        LocalPrivilege readLp = new 
LocalPrivilege(PrivilegeConstants.JCR_READ);
+        readLp.setAllow(true);
+        readLp.setAllowRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_GLOB, val("glob1allow"))));
+        list.add(readLp);
+        LocalPrivilege writeLp = new 
LocalPrivilege(PrivilegeConstants.JCR_WRITE);
+        writeLp.setDeny(true);
+        writeLp.setDenyRestrictions(Collections.singleton(new 
LocalRestriction(AccessControlConstants.REP_ITEM_NAMES, 
vals(session.getValueFactory(), PropertyType.NAME, "name1", "name2"))));
+        list.add(writeLp);
+        assertThrows("Index value is too large: 100", 
IndexOutOfBoundsException.class, () -> {
+            contentCreator.createAce(userName, list, "100");
+        });
+
+        contentCreator.finishNode();
+    }
+
+    protected List<AccessControlEntry> allEntries() throws 
UnsupportedRepositoryOperationException, RepositoryException,
+            PathNotFoundException, AccessDeniedException {
+        AccessControlManager accessControlManager = 
AccessControlUtil.getAccessControlManager(session);
+        AccessControlPolicy[] policies = 
accessControlManager.getPolicies(parentNode.getPath());
+        List<AccessControlEntry> allEntries = new 
ArrayList<AccessControlEntry>();
+        for (AccessControlPolicy accessControlPolicy : policies) {
+            if (accessControlPolicy instanceof AccessControlList) {
+                AccessControlEntry[] accessControlEntries = 
((AccessControlList) accessControlPolicy).getAccessControlEntries();
+                allEntries.addAll(Arrays.asList(accessControlEntries));
+            }
+        }
+        return allEntries;
+    }
+
     private final String uniqueId() {
         return getClass().getSimpleName() + UUID.randomUUID();
     }
diff --git 
a/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java 
b/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
index 3ee80ec..753ede8 100644
--- 
a/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
+++ 
b/src/test/java/org/apache/sling/jcr/contentloader/internal/JsonReaderTest.java
@@ -20,7 +20,10 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.LinkedHashMap;
+import java.util.List;
 
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
@@ -29,6 +32,7 @@ import jakarta.json.JsonArrayBuilder;
 import jakarta.json.JsonWriter;
 
 import org.apache.sling.jcr.contentloader.ContentCreator;
+import org.apache.sling.jcr.contentloader.LocalPrivilege;
 import org.apache.sling.jcr.contentloader.internal.readers.JsonReader;
 import org.jmock.Expectations;
 import org.jmock.Sequence;
@@ -446,6 +450,19 @@ public class JsonReaderTest {
         this.parse(json);
     }
 
+    /**
+     * Return a collection that will pass the equals checking by mockit
+     * @param localPrivileges the privileges to make a collection
+     * @return collection of local privileges
+     */
+    private Collection<LocalPrivilege> toCollection(LocalPrivilege ... 
localPrivileges) {
+        List<LocalPrivilege> list = new ArrayList<>();
+        for (LocalPrivilege lp: localPrivileges) {
+            list.add(lp);
+        }
+        return list;
+    }
+
     @org.junit.Test
     public void testCreateAcl() throws Exception {
         String json = " { " + "\"security:acl\" : [ " + "  { " + "    
\"principal\" : \"username1\","
@@ -453,17 +470,25 @@ public class JsonReaderTest {
                 + "    \"principal\" : \"groupname1\"," + "    \"granted\" : 
[\"jcr:read\",\"jcr:write\"]" + "  },"
                 + "  {" + "    \"principal\" : \"groupname2\"," + "    
\"granted\" : [\"jcr:read\"],"
                 + "    \"denied\" : [\"jcr:write\"]," + "    \"order\" : 
\"first\"" + "  }" + "]" + "}";
+
+        LocalPrivilege readAllow = new LocalPrivilege("jcr:read");
+        readAllow.setAllow(true);
+        LocalPrivilege writeAllow = new LocalPrivilege("jcr:write");
+        writeAllow.setAllow(true);
+        LocalPrivilege writeDeny = new LocalPrivilege("jcr:write");
+        writeDeny.setDeny(true);
+
         this.mockery.checking(new Expectations() {
             {
                 allowing(creator).createNode(null, null, null);
                 inSequence(mySequence);
-                allowing(creator).createAce("username1", new String[] { 
"jcr:read", "jcr:write" }, new String[] {},
-                        null);
+                allowing(creator).getParent();
                 inSequence(mySequence);
-                allowing(creator).createAce("groupname1", new String[] { 
"jcr:read", "jcr:write" }, null, null);
+                allowing(creator).createAce("username1", 
toCollection(readAllow, writeAllow), null);
                 inSequence(mySequence);
-                allowing(creator).createAce("groupname2", new String[] { 
"jcr:read" }, new String[] { "jcr:write" },
-                        "first");
+                allowing(creator).createAce("groupname1", 
toCollection(readAllow, writeAllow), null);
+                inSequence(mySequence);
+                allowing(creator).createAce("groupname2", 
toCollection(readAllow, writeDeny), "first");
                 inSequence(mySequence);
                 allowing(creator).finishNode();
                 inSequence(mySequence);
@@ -481,17 +506,25 @@ public class JsonReaderTest {
                 + "    'principal' : 'groupname1'," + "    'granted' : 
['jcr:read','jcr:write']" + "  }," + "  {"
                 + "    'principal' : \"\\\"'groupname2'\"," + "    'granted' : 
['jcr:read'],"
                 + "    'denied' : ['jcr:write']," + "    'order' : 'first'" + 
"  }" + "]" + "}";
+
+        LocalPrivilege readAllow = new LocalPrivilege("jcr:read");
+        readAllow.setAllow(true);
+        LocalPrivilege writeAllow = new LocalPrivilege("jcr:write");
+        writeAllow.setAllow(true);
+        LocalPrivilege writeDeny = new LocalPrivilege("jcr:write");
+        writeDeny.setDeny(true);
+
         this.mockery.checking(new Expectations() {
             {
                 allowing(creator).createNode(null, null, null);
                 inSequence(mySequence);
-                allowing(creator).createAce("username1", new String[] { 
"jcr:read", "jcr:write" }, new String[] {},
-                        null);
+                allowing(creator).getParent();
+                inSequence(mySequence);
+                allowing(creator).createAce("username1", 
toCollection(readAllow, writeAllow), null);
                 inSequence(mySequence);
-                allowing(creator).createAce("groupname1", new String[] { 
"jcr:read", "jcr:write" }, null, null);
+                allowing(creator).createAce("groupname1", 
toCollection(readAllow, writeAllow), null);
                 inSequence(mySequence);
-                allowing(creator).createAce("\"'groupname2'", new String[] { 
"jcr:read" }, new String[] { "jcr:write" },
-                        "first");
+                allowing(creator).createAce("\"'groupname2'", 
toCollection(readAllow, writeDeny), "first");
                 inSequence(mySequence);
                 allowing(creator).finishNode();
                 inSequence(mySequence);
diff --git 
a/src/test/java/org/apache/sling/jcr/contentloader/it/ContentloaderTestSupport.java
 
b/src/test/java/org/apache/sling/jcr/contentloader/it/ContentloaderTestSupport.java
index 6a3bee2..c635514 100644
--- 
a/src/test/java/org/apache/sling/jcr/contentloader/it/ContentloaderTestSupport.java
+++ 
b/src/test/java/org/apache/sling/jcr/contentloader/it/ContentloaderTestSupport.java
@@ -29,6 +29,7 @@ import static org.ops4j.pax.exam.CoreOptions.composite;
 import static org.ops4j.pax.exam.CoreOptions.junitBundles;
 import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
 import static org.ops4j.pax.exam.CoreOptions.streamBundle;
+import static org.ops4j.pax.exam.CoreOptions.when;
 import static 
org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
 import static org.ops4j.pax.tinybundles.core.TinyBundles.withBnd;
 
@@ -59,6 +60,7 @@ import org.junit.After;
 import org.junit.Before;
 import org.ops4j.pax.exam.Option;
 import org.ops4j.pax.exam.options.ModifiableCompositeOption;
+import org.ops4j.pax.exam.options.extra.VMOption;
 import org.ops4j.pax.exam.util.Filter;
 import org.ops4j.pax.tinybundles.core.TinyBundle;
 import org.ops4j.pax.tinybundles.core.TinyBundles;
@@ -106,9 +108,23 @@ public abstract class ContentloaderTestSupport extends 
TestSupport {
     private HealthCheckExecutor hcExecutor;
     
     public ModifiableCompositeOption baseConfiguration() {
+        final String vmOpt = System.getProperty("pax.vm.options");
+        VMOption vmOption = null;
+        if (vmOpt != null && !vmOpt.isEmpty()) {
+            vmOption = new VMOption(vmOpt);
+        }
+
+        final String jacocoOpt = System.getProperty("jacoco.command");
+        VMOption jacocoCommand = null;
+        if (jacocoOpt != null && !jacocoOpt.isEmpty()) {
+            jacocoCommand = new VMOption(jacocoOpt);
+        }
+
         final Option contentloader = 
mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.jcr.contentloader").version(SlingOptions.versionResolver.getVersion("org.apache.sling",
 "org.apache.sling.jcr.contentloader"));
         return composite(
             super.baseConfiguration(),
+            when(vmOption != null).useOptions(vmOption),
+            when(jacocoCommand != null).useOptions(jacocoCommand),
             
mavenBundle().groupId("org.glassfish").artifactId("jakarta.json").version("2.0.1"),
             quickstart(),
             // SLING-9735 - add server user for the o.a.s.jcr.contentloader 
bundle
diff --git 
a/src/test/java/org/apache/sling/jcr/contentloader/it/SLING11713InitialContentIT.java
 
b/src/test/java/org/apache/sling/jcr/contentloader/it/SLING11713InitialContentIT.java
new file mode 100644
index 0000000..119085c
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/jcr/contentloader/it/SLING11713InitialContentIT.java
@@ -0,0 +1,289 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.jcr.contentloader.it;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.composite;
+import static org.ops4j.pax.exam.CoreOptions.vmOption;
+import static 
org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.jcr.base.util.AccessControlUtil;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.options.ModifiableCompositeOption;
+import org.ops4j.pax.exam.options.extra.VMOption;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.osgi.framework.Bundle;
+
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * SLING-11713 test ACL json input structure to be less ambiguous for 
restrictions
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class SLING11713InitialContentIT extends ContentloaderTestSupport {
+
+    @Configuration
+    public Option[] configuration() throws IOException {
+        final String header = DEFAULT_PATH_IN_BUNDLE + ";path:=" + 
CONTENT_ROOT_PATH;
+        final Multimap<String, String> content = ImmutableListMultimap.of(
+            DEFAULT_PATH_IN_BUNDLE, "SLING-11713.json"
+        );
+        final Option bundle = buildInitialContentBundle(header, content);
+        // configure the health check component
+        Option hcConfig = 
factoryConfiguration("org.apache.sling.jcr.contentloader.hc.BundleContentLoadedCheck")
+            .put("hc.tags", new String[] {TAG_TESTING_CONTENT_LOADING})
+            .asOption();
+        return new Option[]{
+            baseConfiguration(),
+            hcConfig,
+            bundle,
+            optionalRemoteDebug()
+        };
+    }
+
+    /**
+     * Optionally configure remote debugging on the port supplied by the 
"debugPort"
+     * system property.
+     */
+    protected ModifiableCompositeOption optionalRemoteDebug() {
+        VMOption option = null;
+        String property = System.getProperty("debugPort");
+        if (property != null) {
+            option = 
vmOption(String.format("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=%s",
 property));
+        }
+        return composite(option);
+    }
+
+    /* (non-Javadoc)
+     * @see 
org.apache.sling.jcr.contentloader.it.ContentloaderTestSupport#setup()
+     */
+    @Before
+    @Override
+    public void setup() throws Exception {
+        super.setup();
+        
+        waitForContentLoaded();
+    }
+    
+    @Test
+    public void bundleStarted() {
+        final Bundle b = findBundle(BUNDLE_SYMBOLICNAME);
+        assertNotNull("Expecting bundle to be found:" + BUNDLE_SYMBOLICNAME, 
b);
+        assertEquals("Expecting bundle to be active:" + BUNDLE_SYMBOLICNAME, 
Bundle.ACTIVE, b.getState());
+    }
+
+    @Test
+    public void initialContentInstalled() throws RepositoryException {
+        final String folderPath = CONTENT_ROOT_PATH + "/SLING-11713";
+        assertTrue("Expecting initial content to be installed", 
session.itemExists(folderPath));
+        assertEquals("folder has node type 'sling:Folder'", "sling:Folder", 
session.getNode(folderPath).getPrimaryNodeType().getName());
+    }
+
+    @Test
+    public void userCreated() throws RepositoryException {
+        UserManager userManager = AccessControlUtil.getUserManager(session);
+        Authorizable authorizable = 
userManager.getAuthorizable("sling11713_user");
+        assertNotNull("Expecting test user to exist", authorizable);
+    }
+
+    @Test
+    public void groupCreated() throws RepositoryException {
+        UserManager userManager = AccessControlUtil.getUserManager(session);
+        Authorizable authorizable = 
userManager.getAuthorizable("sling11713_group");
+        assertNotNull("Expecting test group to exist", authorizable);
+        assertTrue(authorizable instanceof Group);
+        Iterator<Authorizable> members = ((Group) authorizable).getMembers();
+        assertTrue(members.hasNext());
+        Authorizable firstMember = members.next();
+        assertEquals("sling11713_user", firstMember.getID());
+    }
+
+    @Test
+    public void aceWithRestrictionsCreated() throws RepositoryException {
+        final String folderPath = CONTENT_ROOT_PATH + "/SLING-11713";
+        assertTrue("Expecting test folder to exist", 
session.itemExists(folderPath));
+
+        AccessControlManager accessControlManager = 
AccessControlUtil.getAccessControlManager(session);
+        AccessControlPolicy[] policies = 
accessControlManager.getPolicies(folderPath);
+        List<AccessControlEntry> allEntries = new 
ArrayList<AccessControlEntry>();
+        for (AccessControlPolicy accessControlPolicy : policies) {
+            if (accessControlPolicy instanceof AccessControlList) {
+                AccessControlEntry[] accessControlEntries = 
((AccessControlList) accessControlPolicy).getAccessControlEntries();
+                allEntries.addAll(Arrays.asList(accessControlEntries));
+            }
+        }
+        assertEquals(9, allEntries.size());
+        Map<String, List<AccessControlEntry>> aceMap = new HashMap<>();
+        for (AccessControlEntry accessControlEntry : allEntries) {
+            List<AccessControlEntry> aceList = 
aceMap.computeIfAbsent(accessControlEntry.getPrincipal().getName(), name -> new 
ArrayList<>());
+            aceList.add(accessControlEntry);
+        }
+
+        //check ACE for sling11713_user
+        List<AccessControlEntry> aceList = aceMap.get("sling11713_user");
+        assertNotNull(aceList);
+        assertEquals(3, aceList.size());
+        assertAce(aceList.get(0), "sling11713_user",
+                false, // isAllow
+                new String[] {"jcr:write"}, // PrivilegeNames
+                new String[] {"rep:glob"}, // RestrictionNames
+                new String[][] {new String[]{"glob1deny"}}); // 
RestrictionValues
+        assertAce(aceList.get(1), "sling11713_user",
+                true, // isAllow
+                new String[] {"jcr:read"}, // PrivilegeNames
+                new String[] {}, // RestrictionNames
+                new String[][] {}); // RestrictionValues
+        assertAce(aceList.get(2), "sling11713_user",
+                true, // isAllow
+                new String[] {"jcr:write"}, // PrivilegeNames
+                new String[] {"rep:glob"}, // RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+
+        //check ACE for sling11713_group
+        aceList = aceMap.get("sling11713_group");
+        assertNotNull(aceList);
+        assertEquals(1, aceList.size());
+        assertAce(aceList.get(0), "sling11713_group",
+                true, // isAllow
+                new String[] {"jcr:modifyAccessControl"}, // PrivilegeNames
+                new String[] {"rep:itemNames"}, // RestrictionNames
+                new String[][] {new String[]{"name1", "name2"}}); // 
RestrictionValues
+
+        //check ACE for everyone
+        aceList = aceMap.get("everyone");
+        assertNotNull(aceList);
+        assertEquals(3, aceList.size());
+        assertAce(aceList.get(0), "everyone",
+                false, // isAllow
+                new String[] {"jcr:write"}, // PrivilegeNames
+                new String[] {"rep:glob"}, // RestrictionNames
+                new String[][] {new String[]{"glob1deny"}}); // 
RestrictionValues
+        assertAce(aceList.get(1), "everyone",
+                true, // isAllow
+                new String[] {"jcr:read"}, // PrivilegeNames
+                new String[] {}, // RestrictionNames
+                new String[][] {}); // RestrictionValues
+        assertAce(aceList.get(2), "everyone",
+                true, // isAllow
+                new String[] {"jcr:write"}, // PrivilegeNames
+                new String[] {"rep:glob"}, // RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+
+        aceList = aceMap.get("sling11713_user2");
+        assertNotNull(aceList);
+        assertEquals(2, aceList.size());
+        assertAce(aceList.get(0), "sling11713_user2",
+                false, // isAllow
+                new String[] {"jcr:read"}, // PrivilegeNames
+                new String[] {"rep:itemNames"}, // RestrictionNames
+                new String[][] {new String[]{"name1"}}); // RestrictionValues
+        assertAce(aceList.get(1), "sling11713_user2",
+                true, // isAllow
+                new String[] {"jcr:read"}, // PrivilegeNames
+                new String[] {"rep:glob"}, // RestrictionNames
+                new String[][] {new String[]{"glob1allow"}}); // 
RestrictionValues
+    }
+
+    public static void assertAce(AccessControlEntry ace, String 
expectedPrincipal,
+            boolean isAllow, String[] expectedPrivilegeNames,
+            String[] expectedRestrictionNames, String[][] 
expectedRestrictionValues) throws RepositoryException {
+
+        assertNotNull("Expected ACE for test principal", expectedPrincipal);
+        assertEquals(expectedPrincipal, ace.getPrincipal().getName());
+
+        Privilege[] storedPrivileges = ace.getPrivileges();
+        assertNotNull(storedPrivileges);
+        assertEquals(expectedPrivilegeNames.length, storedPrivileges.length);
+        Set<String> privilegeNamesSet = Stream.of(storedPrivileges)
+            .map(item -> item.getName())
+            .collect(Collectors.toSet());
+        for (String pn : expectedPrivilegeNames) {
+            assertTrue("Expecting privilege: " + pn, 
privilegeNamesSet.contains(pn));
+        }
+
+        assertTrue(ace instanceof JackrabbitAccessControlEntry);
+        JackrabbitAccessControlEntry jace = (JackrabbitAccessControlEntry) ace;
+        assertEquals(isAllow, jace.isAllow());
+
+        //check restrictions
+        String[] storedRestrictionNames = jace.getRestrictionNames();
+        assertNotNull(storedRestrictionNames);
+        assertEquals(expectedRestrictionNames.length, 
storedRestrictionNames.length);
+        Set<String> restrictionNamesSet = Stream.of(storedRestrictionNames)
+            .collect(Collectors.toSet());
+        for (String rn : expectedRestrictionNames) {
+            assertTrue("Expecting restriction: " + rn, 
restrictionNamesSet.contains(rn));
+        }
+
+        if (expectedRestrictionValues.length > 0) {
+            for (int i = 0; i < expectedRestrictionValues.length; i++) {
+                String[] expected = expectedRestrictionValues[i];
+                Value[] storedRestrictionValues = 
jace.getRestrictions(storedRestrictionNames[i]);
+                assertNotNull(storedRestrictionValues);
+                assertEquals(expected.length, storedRestrictionValues.length);
+                Set<String> restrictionValuesSet = 
Stream.of(storedRestrictionValues)
+                        .map(item -> {
+                            try {
+                                return item.getString();
+                            } catch (IllegalStateException | 
RepositoryException e) {
+                                // should never get here
+                                return null;
+                            }
+                        })
+                        .collect(Collectors.toSet());
+                for (String rv : expected) {
+                    assertTrue("Expecting restriction value: " + rv, 
restrictionValuesSet.contains(rv));
+                }
+            }
+        }
+    }
+}
diff --git a/src/test/resources/initial-content/SLING-11713.json 
b/src/test/resources/initial-content/SLING-11713.json
new file mode 100644
index 0000000..367f2f7
--- /dev/null
+++ b/src/test/resources/initial-content/SLING-11713.json
@@ -0,0 +1,80 @@
+{
+    "jcr:primaryType" : "sling:Folder",
+    "security:principals": [
+        { 
+            "name": "sling11713_user", 
+            "password": "mypassword"
+        },
+        { 
+            "name": "sling11713_group", 
+            "isgroup": true,
+            "members":[
+               "sling11713_user"
+            ]
+        },
+        {
+            "name": "sling11713_user2",
+            "password": "mypassword"
+        }
+    ],
+    "security:acl": [
+        { 
+            "principal": "everyone", 
+            "granted": [
+                "jcr:read"
+            ],
+            "privileges":{
+                "jcr:write":{
+                    "allow":{
+                        "rep:glob":"glob1allow"
+                    },
+                    "deny":{
+                        "rep:glob":"glob1deny"
+                    }
+                }
+            }
+        },
+        { 
+            "principal": "sling11713_user", 
+            "privileges":{
+                "jcr:read": {
+                    "allow": true
+                },
+                "jcr:write":{
+                    "allow":{
+                        "rep:glob":"glob1allow"
+                    },
+                    "deny":{
+                        "rep:glob":"glob1deny"
+                    }
+                }
+            }
+        },
+        { 
+            "principal": "sling11713_group", 
+            "privileges":{
+                "jcr:modifyAccessControl":{
+                    "allow": {
+                        "rep:itemNames": [
+                            "name1",
+                            "name2"
+                        ]
+                    }
+                }
+            }
+        },
+        {
+            "principal": "sling11713_user2",
+            "privileges":{
+                "jcr:read":{
+                    "allow":{
+                        "rep:glob":["glob1allow"]
+                    },
+                    "deny":{
+                        "rep:itemNames": "name1"
+                    }
+                }
+            }
+        }
+    ]
+}


Reply via email to