SLING-11340 Custom restriction fails validation during modifyAce call (#14)

diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessGetServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessGetServlet.java
index 6adc061..dc2bcda 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessGetServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessGetServlet.java
@@ -50,11 +50,9 @@
 import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinition;
-import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingHttpServletResponse;
 import org.apache.sling.api.resource.ResourceNotFoundException;
-import org.apache.sling.api.servlets.SlingAllMethodsServlet;
 import org.apache.sling.jcr.base.util.AccessControlUtil;
 import org.apache.sling.jcr.jackrabbit.accessmanager.LocalPrivilege;
 import org.apache.sling.jcr.jackrabbit.accessmanager.LocalRestriction;
@@ -64,21 +62,7 @@
 import org.jetbrains.annotations.Nullable;
 
 @SuppressWarnings("serial")
-public abstract class AbstractAccessGetServlet extends SlingAllMethodsServlet {
-
-    private transient RestrictionProvider restrictionProvider;
-
-    // @Reference
-    protected void bindRestrictionProvider(RestrictionProvider rp) {
-        this.restrictionProvider = rp;
-    }
-
-    /**
-     * Return the RestrictionProvider service
-     */
-    protected RestrictionProvider getRestrictionProvider() {
-        return restrictionProvider;
-    }
+public abstract class AbstractAccessGetServlet extends AbstractAccessServlet {
 
     /* (non-Javadoc)
      * @see org.apache.sling.api.servlets.SlingSafeMethodsServlet#doGet(org.apache.sling.api.SlingHttpServletRequest, org.apache.sling.api.SlingHttpServletResponse)
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessPostServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessPostServlet.java
index 50bead1..95d2bf3 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessPostServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessPostServlet.java
@@ -39,7 +39,6 @@
 import org.apache.sling.api.request.header.MediaRangeList;
 import org.apache.sling.api.resource.ResourceNotFoundException;
 import org.apache.sling.api.resource.ResourceUtil;
-import org.apache.sling.api.servlets.SlingAllMethodsServlet;
 import org.apache.sling.api.wrappers.SlingRequestPaths;
 import org.apache.sling.jcr.jackrabbit.accessmanager.impl.PrincipalAceHelper;
 import org.apache.sling.servlets.post.AbstractPostResponse;
@@ -57,7 +56,7 @@
 /**
  * Base class for all the POST servlets for the AccessManager operations
  */
-public abstract class AbstractAccessPostServlet extends SlingAllMethodsServlet {
+public abstract class AbstractAccessPostServlet extends AbstractAccessServlet {
     private static final long serialVersionUID = -5918670409789895333L;
 
     /**
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessServlet.java
new file mode 100644
index 0000000..a330d96
--- /dev/null
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessServlet.java
@@ -0,0 +1,63 @@
+/*
+ * 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.jackrabbit.accessmanager.post;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.CompositeRestrictionProvider;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+
+/**
+ * Base class for all the servlets for the AccessManager operations
+ */
+public abstract class AbstractAccessServlet extends SlingAllMethodsServlet {
+    private static final long serialVersionUID = 6615497265938616188L;
+
+    private transient RestrictionProvider compositeRestrictionProvider = null;
+    private transient Set<RestrictionProvider> restrictionProviders = new HashSet<>();
+
+    // @Reference
+    protected void bindRestrictionProvider(RestrictionProvider rp) {
+        synchronized (restrictionProviders) {
+            if (restrictionProviders.add(rp)) {
+                compositeRestrictionProvider = null;
+            }
+        }
+    }
+    protected void unbindRestrictionProvider(RestrictionProvider rp) {
+        synchronized (restrictionProviders) {
+            if (restrictionProviders.remove(rp)) {
+                compositeRestrictionProvider = null;
+            }
+        }
+    }
+
+    /**
+     * Return the RestrictionProvider service
+     */
+    protected RestrictionProvider getRestrictionProvider() {
+        synchronized (restrictionProviders) {
+            if (compositeRestrictionProvider == null) {
+                compositeRestrictionProvider = CompositeRestrictionProvider.newInstance(restrictionProviders);
+            }
+            return compositeRestrictionProvider;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractGetAceServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractGetAceServlet.java
index a200611..906c3aa 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractGetAceServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractGetAceServlet.java
@@ -39,7 +39,7 @@
 import org.apache.sling.jcr.jackrabbit.accessmanager.impl.JsonConvert;
 import org.apache.sling.jcr.jackrabbit.accessmanager.impl.PrivilegesHelper;
 
-@SuppressWarnings("serial")
+@SuppressWarnings({"serial", "java:S110"})
 public abstract class AbstractGetAceServlet extends AbstractAccessGetServlet {
 
     @Override
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractGetAclServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractGetAclServlet.java
index 910c364..684f734 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractGetAclServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractGetAclServlet.java
@@ -44,7 +44,7 @@
 import org.apache.sling.jcr.jackrabbit.accessmanager.impl.JsonConvert;
 import org.apache.sling.jcr.jackrabbit.accessmanager.impl.PrivilegesHelper;
 
-@SuppressWarnings("serial")
+@SuppressWarnings({"serial", "java:S110"})
 public abstract class AbstractGetAclServlet extends AbstractAccessGetServlet {
 
     /**
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/DeleteAcesServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/DeleteAcesServlet.java
index 62cf981..167dc45 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/DeleteAcesServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/DeleteAcesServlet.java
@@ -92,6 +92,7 @@
                     policyOption = ReferencePolicyOption.GREEDY,
                     service = PostResponseCreator.class)
     })
+@SuppressWarnings("java:S110")
 public class DeleteAcesServlet extends AbstractAccessPostServlet implements DeleteAces {
     private static final long serialVersionUID = 3784866802938282971L;
 
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetAceServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetAceServlet.java
index 17cc42c..af8ba7a 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetAceServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetAceServlet.java
@@ -35,6 +35,8 @@
 import org.apache.sling.jcr.jackrabbit.accessmanager.GetAce;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
 
 /**
  * <p>
@@ -79,6 +81,8 @@
 reference = {
         @Reference(name="RestrictionProvider",
                 bind = "bindRestrictionProvider",
+                cardinality = ReferenceCardinality.MULTIPLE,
+                policyOption = ReferencePolicyOption.GREEDY,
                 service = RestrictionProvider.class)
 }
 )
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetAclServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetAclServlet.java
index d325c42..8a84876 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetAclServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetAclServlet.java
@@ -33,6 +33,8 @@
 import org.apache.sling.jcr.jackrabbit.accessmanager.GetAcl;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
 
 /**
  * <p>
@@ -107,6 +109,8 @@
 reference = {
         @Reference(name="RestrictionProvider",
                 bind = "bindRestrictionProvider",
+                cardinality = ReferenceCardinality.MULTIPLE,
+                policyOption = ReferencePolicyOption.GREEDY,
                 service = RestrictionProvider.class)
 }
 )
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetEffectiveAceServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetEffectiveAceServlet.java
index 738bd8a..de04f85 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetEffectiveAceServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetEffectiveAceServlet.java
@@ -38,6 +38,8 @@
 import org.apache.sling.jcr.jackrabbit.accessmanager.impl.JsonConvert;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
 
 /**
  * <p>
@@ -82,6 +84,8 @@
 reference = {
         @Reference(name="RestrictionProvider",
                 bind = "bindRestrictionProvider",
+                cardinality = ReferenceCardinality.MULTIPLE,
+                policyOption = ReferencePolicyOption.GREEDY,
                 service = RestrictionProvider.class)
 }
 )
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetEffectiveAclServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetEffectiveAclServlet.java
index 224de8b..ed7677f 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetEffectiveAclServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetEffectiveAclServlet.java
@@ -35,6 +35,8 @@
 import org.apache.sling.jcr.jackrabbit.accessmanager.impl.JsonConvert;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
 
 /**
  * <p>
@@ -109,6 +111,8 @@
 reference = {
         @Reference(name="RestrictionProvider",
                 bind = "bindRestrictionProvider",
+                cardinality = ReferenceCardinality.MULTIPLE,
+                policyOption = ReferencePolicyOption.GREEDY,
                 service = RestrictionProvider.class)
 }
 )
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetPrincipalAceServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetPrincipalAceServlet.java
index 4d40d88..44610c4 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetPrincipalAceServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/GetPrincipalAceServlet.java
@@ -43,6 +43,8 @@
 import org.jetbrains.annotations.Nullable;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
 
 /**
  * <p>
@@ -87,6 +89,8 @@
 reference = {
         @Reference(name="RestrictionProvider",
                 bind = "bindRestrictionProvider",
+                cardinality = ReferenceCardinality.MULTIPLE,
+                policyOption = ReferencePolicyOption.GREEDY,
                 service = RestrictionProvider.class)
 }
 )
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyAceServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyAceServlet.java
index 13abd0a..9669aa4 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyAceServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyAceServlet.java
@@ -71,7 +71,6 @@
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.component.annotations.ReferenceCardinality;
-import org.osgi.service.component.annotations.ReferencePolicy;
 import org.osgi.service.component.annotations.ReferencePolicyOption;
 
 /**
@@ -135,7 +134,20 @@
         "sling.servlet.methods=POST",
         "sling.servlet.selectors=modifyAce",
         "sling.servlet.prefix:Integer=-1"
+},
+reference = {
+        @Reference(name="RestrictionProvider",
+                bind = "bindRestrictionProvider",
+                cardinality = ReferenceCardinality.MULTIPLE,
+                policyOption = ReferencePolicyOption.GREEDY,
+                service = RestrictionProvider.class),
+        @Reference(name = "PostResponseCreator",
+                bind = "bindPostResponseCreator",
+                cardinality = ReferenceCardinality.MULTIPLE,
+                policyOption = ReferencePolicyOption.GREEDY,
+                service = PostResponseCreator.class)
 })
+@SuppressWarnings("java:S110")
 public class ModifyAceServlet extends AbstractAccessPostServlet implements ModifyAce {
     private static final long serialVersionUID = -9182485466670280437L;
     private static final String INVALID_OR_NOT_SUPPORTED_RESTRICTION_NAME_WAS_SUPPLIED = "Invalid restriction name was supplied";
@@ -196,38 +208,6 @@
     private static final Pattern RESTRICTION_PATTERN_DELETE = Pattern.compile(String.format("^restriction@([^@]+)(@([^@]+))?%s$",
                 SlingPostConstants.SUFFIX_DELETE));
 
-    private transient RestrictionProvider restrictionProvider = null;
-
-    // NOTE: the @Reference annotation is not inherited, so subclasses will need to override the #bindRestrictionProvider 
-    // and #unbindRestrictionProvider methods to provide the @Reference annotation.     
-    //
-    @Reference(cardinality=ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY)
-    protected void bindRestrictionProvider(RestrictionProvider rp) {
-        this.restrictionProvider = rp;
-    }
-    protected void unbindRestrictionProvider(RestrictionProvider rp) { //NOSONAR
-        this.restrictionProvider = null;
-    }
-
-    /**
-     * Overridden since the @Reference annotation is not inherited from the super method
-     */
-    @Override
-    @Reference(service = PostResponseCreator.class,
-        cardinality = ReferenceCardinality.MULTIPLE,
-        policy = ReferencePolicy.DYNAMIC)
-    protected void bindPostResponseCreator(PostResponseCreator creator, Map<String, Object> properties) {
-        super.bindPostResponseCreator(creator, properties);
-    }
-
-    /* (non-Javadoc)
-     * @see org.apache.sling.jackrabbit.usermanager.impl.post.AbstractPostServlet#unbindPostResponseCreator(org.apache.sling.servlets.post.PostResponseCreator, java.util.Map)
-     */
-    @Override
-    protected void unbindPostResponseCreator(PostResponseCreator creator, Map<String, Object> properties) { //NOSONAR
-        super.unbindPostResponseCreator(creator, properties);
-    }
-
     /* (non-Javadoc)
      * @see org.apache.sling.jackrabbit.accessmanager.post.AbstractAccessPostServlet#handleOperation(org.apache.sling.api.SlingHttpServletRequest, org.apache.sling.servlets.post.PostResponse, java.util.List)
      */
@@ -277,7 +257,7 @@
             throw new RepositoryException("JCR Session not found");
         }
 
-        if (restrictionProvider == null) {
+        if (RestrictionProvider.EMPTY.equals(getRestrictionProvider())) {
             throw new IllegalStateException("No restriction provider is available so unable to process POSTed restriction values");
         }
 
@@ -310,7 +290,7 @@
      * @return map of restriction names to definition
      */
     protected @NotNull Map<String, RestrictionDefinition> buildRestrictionNameToDefinitionMap(@NotNull String resourcePath) {
-        Set<RestrictionDefinition> supportedRestrictions = restrictionProvider.getSupportedRestrictions(resourcePath);
+        Set<RestrictionDefinition> supportedRestrictions = getRestrictionProvider().getSupportedRestrictions(resourcePath);
         Map<String, RestrictionDefinition> srMap = new HashMap<>();
         for (RestrictionDefinition restrictionDefinition : supportedRestrictions) {
             srMap.put(restrictionDefinition.getName(), restrictionDefinition);
diff --git a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyPrincipalAceServlet.java b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyPrincipalAceServlet.java
index 8799ddb..4963693 100644
--- a/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyPrincipalAceServlet.java
+++ b/src/main/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/ModifyPrincipalAceServlet.java
@@ -112,6 +112,8 @@
 reference = {
         @Reference(name="RestrictionProvider",
                 bind = "bindRestrictionProvider",
+                cardinality = ReferenceCardinality.MULTIPLE,
+                policyOption = ReferencePolicyOption.GREEDY,
                 service = RestrictionProvider.class),
         @Reference(name = "PostResponseCreator",
                 bind = "bindPostResponseCreator",
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/JsonConvertTest.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/JsonConvertTest.java
index 1ad476f..f64bf77 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/JsonConvertTest.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/JsonConvertTest.java
@@ -46,6 +46,7 @@
 
 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.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.jackrabbit.oak.spi.security.principal.PrincipalImpl;
@@ -56,6 +57,7 @@
 import org.apache.sling.jcr.jackrabbit.accessmanager.LocalRestriction;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.jetbrains.annotations.NotNull;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -86,7 +88,9 @@
     private RestrictionDefinition rd(String restrictionName) {
         if (srMap == null) {
             //make a temp map for quick lookup below
-            RestrictionProvider restrictionProvider = context.getService(RestrictionProvider.class);
+            @NotNull
+            RestrictionProvider[] services = context.getServices(RestrictionProvider.class, null);
+            RestrictionProvider restrictionProvider = CompositeRestrictionProvider.newInstance(services);
             Set<RestrictionDefinition> supportedRestrictions = restrictionProvider.getSupportedRestrictions("/");
             srMap = new HashMap<>();
             for (RestrictionDefinition restrictionDefinition : supportedRestrictions) {
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelperTest.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelperTest.java
index 1d085b0..666082a 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelperTest.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/impl/PrivilegesHelperTest.java
@@ -42,6 +42,7 @@
 
 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.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.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
@@ -51,6 +52,7 @@
 import org.apache.sling.jcr.jackrabbit.accessmanager.LocalRestriction;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.junit.Before;
 import org.junit.Rule;
@@ -82,7 +84,9 @@
     private RestrictionDefinition rd(String restrictionName) {
         if (srMap == null) {
             //make a temp map for quick lookup below
-            RestrictionProvider restrictionProvider = context.getService(RestrictionProvider.class);
+            @NotNull
+            RestrictionProvider[] services = context.getServices(RestrictionProvider.class, null);
+            RestrictionProvider restrictionProvider = CompositeRestrictionProvider.newInstance(services);
             Set<RestrictionDefinition> supportedRestrictions = restrictionProvider.getSupportedRestrictions("/");
             srMap = new HashMap<>();
             for (RestrictionDefinition restrictionDefinition : supportedRestrictions) {
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/CustomRestrictionProviderImpl.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/CustomRestrictionProviderImpl.java
new file mode 100644
index 0000000..252d62c
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/CustomRestrictionProviderImpl.java
@@ -0,0 +1,106 @@
+/*
+ * 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.jackrabbit.accessmanager.it;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.AbstractRestrictionProvider;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restriction;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinition;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinitionImpl;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionPattern;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Sample implementation of the RestrictionProvider interface to test that
+ * custom restrictions can be used.
+ */
+public class CustomRestrictionProviderImpl extends AbstractRestrictionProvider {
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    public static final String SLING_CUSTOM_RESTRICTION = "sling:customRestriction";
+
+    public CustomRestrictionProviderImpl() {
+        super(supportedRestrictions());
+    }
+
+    private static Map<String, RestrictionDefinition> supportedRestrictions() {
+        RestrictionDefinition slingCustomRestriction = new RestrictionDefinitionImpl(SLING_CUSTOM_RESTRICTION, Type.STRINGS, false);
+        Map<String, RestrictionDefinition> supportedRestrictions = new HashMap<String, RestrictionDefinition>();
+        supportedRestrictions.put(slingCustomRestriction.getName(), slingCustomRestriction);
+        return Collections.unmodifiableMap(supportedRestrictions);
+    }
+
+    @Override
+    public RestrictionPattern getPattern(String oakPath, Tree tree) {
+        if (oakPath != null) {
+            PropertyState resourceTypes = tree.getProperty(SLING_CUSTOM_RESTRICTION);
+            if (resourceTypes != null) {
+                CustomRestrictionPattern resourceTypePattern = new CustomRestrictionPattern();
+                logger.trace("Returning resourceTypePattern={} for sling:customRestriction in getPattern(String,Tree)", resourceTypePattern);
+                return resourceTypePattern;
+            }
+        }
+        return RestrictionPattern.EMPTY;
+    }
+
+    @Override
+    public RestrictionPattern getPattern(String oakPath, Set<Restriction> restrictions) {
+        if (oakPath != null && !restrictions.isEmpty()) {
+            for (Restriction r : restrictions) {
+                String name = r.getDefinition().getName();
+                if (SLING_CUSTOM_RESTRICTION.equals(name)) {
+                    CustomRestrictionPattern resourceTypePattern = new CustomRestrictionPattern();
+                    logger.trace(
+                            "Returning resourceTypePattern={} for sling:customRestriction in getPattern(String,Set<Restriction>)",
+                            resourceTypePattern);
+                    return resourceTypePattern;
+                }
+            }
+        }
+
+        return RestrictionPattern.EMPTY;
+    }
+
+    /** Implementation of the {@link RestrictionPattern} interface that returns {@code false} */
+    public static class CustomRestrictionPattern implements RestrictionPattern {
+
+        @Override
+        public boolean matches(Tree tree, PropertyState property) {
+            return false;
+        }
+
+        @Override
+        public boolean matches(String path) {
+            return false;
+        }
+
+        @Override
+        public boolean matches() {
+            return false;
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/ModifyAceIT.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/ModifyAceIT.java
index 0f36503..63266ec 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/ModifyAceIT.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/it/ModifyAceIT.java
@@ -41,6 +41,7 @@
 
 import org.apache.http.NameValuePair;
 import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
 import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
 import org.apache.sling.servlets.post.JSONResponse;
 import org.apache.sling.servlets.post.PostResponseCreator;
@@ -63,6 +64,7 @@
 public class ModifyAceIT extends AccessManagerClientTestSupport {
 
     private ServiceRegistration<PostResponseCreator> serviceReg;
+    private ServiceRegistration<RestrictionProvider> serviceReg2;
     private VerifyAce verifyTrue = jsonValue -> assertEquals(ValueType.TRUE, jsonValue.getValueType());
 
     @Before
@@ -72,6 +74,8 @@
         Dictionary<String, Object> props = new Hashtable<>(); // NOSONAR
         serviceReg = bundle.getBundleContext().registerService(PostResponseCreator.class,
                 new CustomPostResponseCreatorImpl(), props);
+        serviceReg2 = bundle.getBundleContext().registerService(RestrictionProvider.class,
+                new CustomRestrictionProviderImpl(), props);
 
         super.before();
     }
@@ -82,6 +86,9 @@
         if (serviceReg != null) {
             serviceReg.unregister();
         }
+        if (serviceReg2 != null) {
+            serviceReg2.unregister();
+        }
 
         super.after();
     }
@@ -2312,4 +2319,50 @@
         assertEquals("javax.jcr.security.AccessControlException: No such privilege invalid_name", jsonObject.getString("status.message"));
     }
 
+    /**
+     * Test to verify adding an ACE with a custom restriction to
+     * the ACL
+     */
+    @Test
+    public void testAddAceWithCustomRestriction() throws IOException, JsonException {
+        testUserId = createTestUser();
+        testFolderUrl = createTestFolder();
+
+        // update the ACE
+        List<NameValuePair> postParams = new AcePostParamsBuilder(testUserId)
+                .withOrder("first")
+                .withPrivilegeRestriction(PrivilegeValues.ALLOW, PrivilegeConstants.JCR_READ,
+                        CustomRestrictionProviderImpl.SLING_CUSTOM_RESTRICTION, new String[] {"item1", "item2"})
+                .withPrivilegeRestriction(PrivilegeValues.DENY, PrivilegeConstants.JCR_WRITE,
+                        CustomRestrictionProviderImpl.SLING_CUSTOM_RESTRICTION, new String[] {"item1", "item2"})
+                .build();
+        addOrUpdateAce(testFolderUrl, postParams);
+
+        JsonObject user = getAce(testFolderUrl, testUserId);
+        assertNotNull(user);
+        assertEquals(testUserId, user.getString("principal"));
+        assertEquals(0, user.getInt("order"));
+
+        JsonObject privilegesObject = user.getJsonObject("privileges");
+        assertNotNull(privilegesObject);
+        assertEquals(2, privilegesObject.size());
+
+        VerifyAce verifyRestrictions = jsonValue -> {
+            assertNotNull(jsonValue);
+            assertTrue(jsonValue instanceof JsonObject);
+            JsonObject restrictionsObj = (JsonObject)jsonValue;
+
+            JsonValue customValue = restrictionsObj.get(CustomRestrictionProviderImpl.SLING_CUSTOM_RESTRICTION);
+            assertNotNull(customValue);
+            assertTrue(customValue instanceof JsonArray);
+            assertEquals(2, ((JsonArray)customValue).size());
+            assertEquals("item1", ((JsonArray)customValue).getString(0));
+            assertEquals("item2", ((JsonArray)customValue).getString(1));
+        };
+        //allow privilege
+        assertPrivilege(privilegesObject, true, PrivilegeValues.ALLOW, PrivilegeConstants.JCR_READ, verifyRestrictions);
+        //deny privilege
+        assertPrivilege(privilegesObject, true, PrivilegeValues.DENY, PrivilegeConstants.JCR_WRITE, verifyRestrictions);
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessServletTest.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessServletTest.java
new file mode 100644
index 0000000..a684e02
--- /dev/null
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/AbstractAccessServletTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.jackrabbit.accessmanager.post;
+
+import static org.junit.Assert.*;
+
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.CompositeRestrictionProvider;
+import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
+import org.apache.sling.jcr.jackrabbit.accessmanager.it.CustomRestrictionProviderImpl;
+import org.junit.Test;
+
+/**
+ * Simple test of the common AbstractAccessServlet
+ */
+public class AbstractAccessServletTest {
+
+    @Test
+    public void testBindRestrictionProvider() {
+        TestAccessServlet tas = new TestAccessServlet();
+
+        //starts out empty
+        assertEquals("Expected the empty RestrictionProvider", RestrictionProvider.EMPTY, tas.getRestrictionProvider());
+
+        //switch from empty to the single custom restriction provider
+        CustomRestrictionProviderImpl customRestrictionProvider = new CustomRestrictionProviderImpl();
+        tas.bindRestrictionProvider(customRestrictionProvider);
+        assertEquals("Expected the custom RestrictionProvider", customRestrictionProvider, tas.getRestrictionProvider());
+
+        //binding the same again doesn't change the state
+        tas.bindRestrictionProvider(customRestrictionProvider);
+        assertEquals("Expected the custom RestrictionProvider", customRestrictionProvider, tas.getRestrictionProvider());
+
+        // binding a second unique obj switces to a CompositeRestrictionProvider
+        CustomRestrictionProviderImpl customRestrictionProvider2 = new CustomRestrictionProviderImpl();
+        tas.bindRestrictionProvider(customRestrictionProvider2);
+        assertNotEquals("Expected the custom RestrictionProvider", customRestrictionProvider, tas.getRestrictionProvider());
+        assertTrue(tas.getRestrictionProvider() instanceof CompositeRestrictionProvider);
+    }
+
+    @Test
+    public void testUnbindRestrictionProvider() {
+        TestAccessServlet tas = new TestAccessServlet();
+        CustomRestrictionProviderImpl customRestrictionProvider = new CustomRestrictionProviderImpl();
+        tas.bindRestrictionProvider(customRestrictionProvider);
+        CustomRestrictionProviderImpl customRestrictionProvider2 = new CustomRestrictionProviderImpl();
+        tas.bindRestrictionProvider(customRestrictionProvider2);
+
+        //unbinding one of them switches back to last one left
+        tas.unbindRestrictionProvider(customRestrictionProvider2);
+        assertEquals("Expected the custom RestrictionProvider", customRestrictionProvider, tas.getRestrictionProvider());
+
+        //unbinding the same again doesn't change the state
+        tas.unbindRestrictionProvider(customRestrictionProvider2);
+        assertEquals("Expected the custom RestrictionProvider", customRestrictionProvider, tas.getRestrictionProvider());
+
+        //unbinding the second one switches back to empty
+        tas.unbindRestrictionProvider(customRestrictionProvider);
+        assertEquals("Expected the empty RestrictionProvider", RestrictionProvider.EMPTY, tas.getRestrictionProvider());
+    }
+
+    @Test
+    public void testGetRestrictionProvider() {
+        TestAccessServlet tas = new TestAccessServlet();
+        assertNotNull(tas.getRestrictionProvider());
+    }
+
+    private class TestAccessServlet extends AbstractAccessServlet {
+        private static final long serialVersionUID = -2948341218853558959L;
+    }
+}
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/LocalPrivilegeTest.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/LocalPrivilegeTest.java
index 857db6b..b2d6ef4 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/LocalPrivilegeTest.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/LocalPrivilegeTest.java
@@ -37,6 +37,7 @@
 
 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.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.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
@@ -46,6 +47,7 @@
 import org.apache.sling.jcr.jackrabbit.accessmanager.LocalRestriction;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.jetbrains.annotations.NotNull;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -75,7 +77,9 @@
     private RestrictionDefinition rd(String restrictionName) throws Exception {
         if (srMap == null) {
             //make a temp map for quick lookup below
-            RestrictionProvider restrictionProvider = context.getService(RestrictionProvider.class);
+            @NotNull
+            RestrictionProvider[] services = context.getServices(RestrictionProvider.class, null);
+            RestrictionProvider restrictionProvider = CompositeRestrictionProvider.newInstance(services);
             Set<RestrictionDefinition> supportedRestrictions = restrictionProvider.getSupportedRestrictions("/");
             srMap = new HashMap<>();
             for (RestrictionDefinition restrictionDefinition : supportedRestrictions) {
diff --git a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/LocalRestrictionTest.java b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/LocalRestrictionTest.java
index dc75978..b0d87ca 100644
--- a/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/LocalRestrictionTest.java
+++ b/src/test/java/org/apache/sling/jcr/jackrabbit/accessmanager/post/LocalRestrictionTest.java
@@ -34,12 +34,14 @@
 import javax.jcr.ValueFactory;
 
 import org.apache.jackrabbit.oak.security.authorization.restriction.RestrictionProviderImpl;
+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.jackrabbit.value.ValueFactoryImpl;
 import org.apache.sling.jcr.jackrabbit.accessmanager.LocalRestriction;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.jetbrains.annotations.NotNull;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -63,7 +65,9 @@
     private RestrictionDefinition rd(String restrictionName) throws Exception {
         if (srMap == null) {
             //make a temp map for quick lookup below
-            RestrictionProvider restrictionProvider = context.getService(RestrictionProvider.class);
+            @NotNull
+            RestrictionProvider[] services = context.getServices(RestrictionProvider.class, null);
+            RestrictionProvider restrictionProvider = CompositeRestrictionProvider.newInstance(services);
             Set<RestrictionDefinition> supportedRestrictions = restrictionProvider.getSupportedRestrictions("/");
             srMap = new HashMap<>();
             for (RestrictionDefinition restrictionDefinition : supportedRestrictions) {