Merge branch 'master' into feature/SLING-7768
diff --git a/bnd.bnd b/bnd.bnd
index 6e8ad2b..abe9d05 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -12,6 +12,10 @@
 Require-Capability:\
   osgi.service;filter:="(objectClass=org.osgi.service.event.EventHandler)";effective:=active;resolution:=optional
 
+-plugin:\
+  org.apache.sling.bnd.plugin.headers.parameters.remove.Plugin;\
+    'Require-Capability'='osgi.service;filter:="(objectClass=org.osgi.service.event.EventHandler)";effective:=active;cardinality:=multiple'
+
 -removeheaders:\
   Include-Resource,\
   Private-Package
diff --git a/pom.xml b/pom.xml
index cea7528..744001f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
     Licensed to the Apache Software Foundation (ASF) under one
     or more contributor license agreements.  See the NOTICE file
@@ -18,16 +18,18 @@
     under the License.
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
     <modelVersion>4.0.0</modelVersion>
+
     <parent>
         <groupId>org.apache.sling</groupId>
-        <artifactId>sling</artifactId>
-        <version>34</version>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>35</version>
         <relativePath />
     </parent>
 
     <artifactId>org.apache.sling.resourceresolver</artifactId>
-    <version>1.6.13-SNAPSHOT</version>
+    <version>1.6.17-SNAPSHOT</version>
 
     <name>Apache Sling Resource Resolver</name>
     <description>
@@ -50,8 +52,7 @@
         <plugins>
             <plugin>
                 <groupId>org.apache.sling</groupId>
-                <artifactId>maven-sling-plugin</artifactId>
-                <version>2.3.8</version>
+                <artifactId>sling-maven-plugin</artifactId>
                 <executions>
                     <execution>
                         <id>generate-adapter-metadata</id>
@@ -91,6 +92,13 @@
             <plugin>
                 <groupId>biz.aQute.bnd</groupId>
                 <artifactId>bnd-maven-plugin</artifactId>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.sling</groupId>
+                        <artifactId>org.apache.sling.bnd.plugin.headers.parameters.remove</artifactId>
+                        <version>1.0.0</version>
+                    </dependency>
+                </dependencies>
             </plugin>
             <plugin>
                 <groupId>biz.aQute.bnd</groupId>
@@ -124,13 +132,14 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.18.4</version>
+            <version>2.21.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.serviceusermapper</artifactId>
             <version>1.3.4</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
@@ -192,7 +201,7 @@
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.framework</artifactId>
-            <version>5.4.0</version>
+            <version>6.0.3</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -207,5 +216,11 @@
         	<version>2.3.10</version>
         	<scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.util.converter</artifactId>
+            <version>1.0.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/helper/ResourceResolverControl.java b/src/main/java/org/apache/sling/resourceresolver/impl/helper/ResourceResolverControl.java
index a5d591b..2eefd39 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/helper/ResourceResolverControl.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/helper/ResourceResolverControl.java
@@ -45,10 +45,12 @@
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.api.resource.path.PathBuilder;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
+import org.apache.sling.resourceresolver.impl.providers.ResourceProviderInfo;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
 import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorageProvider;
 import org.apache.sling.resourceresolver.impl.providers.stateful.AuthenticatedResourceProvider;
 import org.apache.sling.resourceresolver.impl.providers.tree.Node;
+import org.apache.sling.resourceresolver.impl.providers.tree.PathTree;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -499,71 +501,66 @@
         return null;
     }
 
-    private AuthenticatedResourceProvider checkSourceAndDest(final ResourceResolverContext context,
+    private AuthenticatedResourceProvider checkExistence(final ResourceResolverContext context, final String path,
+            final String type) throws PersistenceException {
+        final Node<ResourceProviderHandler> node = getResourceProviderStorage().getTree().getBestMatchingNode(path);
+        if (node == null) {
+            throw new PersistenceException(type.concat(" resource does not exist."), null, path, null);
+        }
+        AuthenticatedResourceProvider provider = null;
+        try {
+            provider = context.getProviderManager().getOrCreateProvider(node.getValue(), this);
+        } catch (LoginException e) {
+            // ignore
+        }
+        if (provider == null) {
+            throw new PersistenceException(type.concat(" resource does not exist."), null, path, null);
+        }
+        final Resource rsrc = provider.getResource(path, null, null);
+        if (rsrc == null) {
+            throw new PersistenceException(type.concat(" resource does not exist."), null, path, null);
+        }
+        return provider;
+    }
+
+    public AuthenticatedResourceProvider checkSourceAndDest(final ResourceResolverContext context,
             final String srcAbsPath, final String destAbsPath) throws PersistenceException {
         // check source
-        final Node<ResourceProviderHandler> srcNode = getResourceProviderStorage().getTree().getBestMatchingNode(srcAbsPath);
-        if ( srcNode == null ) {
-            throw new PersistenceException("Source resource does not exist.", null, srcAbsPath, null);
-        }
-        AuthenticatedResourceProvider srcProvider = null;
-        try {
-            srcProvider = context.getProviderManager().getOrCreateProvider(srcNode.getValue(), this);
-        } catch (LoginException e) {
-            // ignore
-        }
-        if ( srcProvider == null ) {
-            throw new PersistenceException("Source resource does not exist.", null, srcAbsPath, null);
-        }
-        final Resource srcResource = srcProvider.getResource(srcAbsPath, null, null);
-        if ( srcResource == null ) {
-            throw new PersistenceException("Source resource does not exist.", null, srcAbsPath, null);
-        }
-
+        final AuthenticatedResourceProvider srcProvider = checkExistence(context, srcAbsPath, "Source");
         // check destination
-        final Node<ResourceProviderHandler> destNode = getResourceProviderStorage().getTree().getBestMatchingNode(destAbsPath);
-        if ( destNode == null ) {
-            throw new PersistenceException("Destination resource does not exist.", null, destAbsPath, null);
-        }
-        AuthenticatedResourceProvider destProvider = null;
-        try {
-            destProvider = context.getProviderManager().getOrCreateProvider(destNode.getValue(), this);
-        } catch (LoginException e) {
-            // ignore
-        }
-        if ( destProvider == null ) {
-            throw new PersistenceException("Destination resource does not exist.", null, destAbsPath, null);
-        }
-        final Resource destResource = destProvider.getResource(destAbsPath, null, null);
-        if ( destResource == null ) {
-            throw new PersistenceException("Destination resource does not exist.", null, destAbsPath, null);
-        }
+        final AuthenticatedResourceProvider destProvider = checkExistence(context, destAbsPath, "Destination");
 
         // check for sub providers of src and dest
-        if ( srcProvider == destProvider && !collectProviders(context, srcNode) && !collectProviders(context, destNode) ) {
-            return srcProvider;
+        if (srcProvider == destProvider) {
+            final PathTree<ResourceProviderHandler> providerTree = getResourceProviderStorage().getTree();
+            final boolean sourceHasProvider = hasSubProvider(context, providerTree.getNode(srcAbsPath));
+            final boolean destHasProvider = hasSubProvider(context, providerTree.getNode(destAbsPath));
+            if (!(sourceHasProvider || destHasProvider)) {
+                return srcProvider;
+            }
         }
         return null;
     }
 
-    private boolean collectProviders(final ResourceResolverContext context,
-            final Node<ResourceProviderHandler> parent) {
-        boolean hasMoreProviders = false;
-        for (final Entry<String, Node<ResourceProviderHandler>> entry : parent.getChildren().entrySet()) {
-            if ( entry.getValue().getValue() != null ) {
-                try {
-                    context.getProviderManager().getOrCreateProvider(entry.getValue().getValue(), this);
-                    hasMoreProviders = true;
-                } catch ( final LoginException ignore) {
-                    // ignore
+    private boolean hasSubProvider(ResourceResolverContext context, Node<ResourceProviderHandler> node) {
+        if (node != null) {
+            for (final Node<ResourceProviderHandler> childNode : node.getChildren().values()) {
+                final ResourceProviderHandler handler = childNode.getValue();
+                if (handler != null) {
+                    try {
+                        context.getProviderManager().getOrCreateProvider(handler, this);
+                        return true;
+                    } catch (final LoginException ignore) {
+                        // ignore
+                    }
                 }
-            }
-            if ( collectProviders(context, entry.getValue())) {
-                hasMoreProviders = true;
+                if (hasSubProvider(context, childNode)) {
+                    return true;
+                }
             }
         }
 
-        return hasMoreProviders;
+        return false;
     }
 
     private void copy(final ResourceResolverContext context, final Resource src, final String dstPath, final List<Resource> newNodes) throws PersistenceException {
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTracker.java b/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTracker.java
index 98d60ff..f8f41b8 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTracker.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTracker.java
@@ -151,15 +151,7 @@
         this.providerReporter = generator.createProviderReporter();
         synchronized ( this.handlers ) {
             this.reporterGenerator = generator;
-            for (List<ResourceProviderHandler> list : handlers.values()) {
-                if ( !list.isEmpty() ) {
-                    final ResourceProviderHandler h = list.get(0);
-                    if (h != null) {
-                        updateProviderContext(h);
-                        h.update();
-                    }
-                }
-            }
+            this.updateHandlers();
         }
     }
 
@@ -355,7 +347,7 @@
      */
     private boolean activate(final ResourceProviderHandler handler) {
         synchronized (this.handlers) {
-            updateProviderContext(handler);
+            updateHandlers();
         }
         if ( !handler.activate() ) {
             logger.warn("Activating resource provider {} failed", handler.getInfo());
@@ -373,6 +365,9 @@
      */
     private void deactivate(final ResourceProviderHandler handler) {
         handler.deactivate();
+        synchronized (this.handlers) {
+            updateHandlers();
+        }
         logger.debug("Deactivated resource provider {}", handler.getInfo());
     }
 
@@ -485,6 +480,18 @@
         }
     }
 
+    private void updateHandlers() {
+        for (List<ResourceProviderHandler> list : handlers.values()) {
+            if ( !list.isEmpty() ) {
+                final ResourceProviderHandler h = list.get(0);
+                if (h != null) {
+                    updateProviderContext(h);
+                    h.update();
+                }
+            }
+        }
+    }
+
     private void updateProviderContext(final ResourceProviderHandler handler) {
         final Set<String> excludedPaths = new HashSet<>();
         final Path handlerPath = new Path(handler.getPath());
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java b/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java
index ef71aa7..9b4f1ac 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/SimpleValueMapImpl.java
@@ -23,6 +23,7 @@
 import java.util.Set;
 
 import org.apache.sling.api.resource.ValueMap;
+import org.jetbrains.annotations.NotNull;
 
 public class SimpleValueMapImpl implements ValueMap {
 
@@ -81,7 +82,7 @@
     }
 
     @SuppressWarnings("unchecked")
-    public <T> T get(String name, Class<T> type) {
+    public <T> T get(@NotNull String name, @NotNull Class<T> type) {
         Object o = delegate.get(name);
         if ( type.equals(String[].class) && ! ( o instanceof String[])) {
             // According to ValueMap if the value cannot be converted it should return null
@@ -93,8 +94,9 @@
         return (T) o;
     }
 
+    @NotNull
     @SuppressWarnings("unchecked")
-    public <T> T get(String name, T defaultValue) {
+    public <T> T get(@NotNull String name, @NotNull T defaultValue) {
         if ( delegate.containsKey(name)) {
             return (T) delegate.get(name);
         } 
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/helper/ResourceResolverControlTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/helper/ResourceResolverControlTest.java
index 45c3940..93957a2 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/helper/ResourceResolverControlTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/helper/ResourceResolverControlTest.java
@@ -340,6 +340,23 @@
     }
 
     /**
+     * Checks that its correctly calculated whether a copy/move can be done with the
+     * same provider
+     *
+     * @throws PersistenceException
+     */
+    @Test
+    public void checkProvidersForCopyMove() throws PersistenceException {
+        // first check same provider at root
+        configureResourceAt(rootProvider, "/a");
+        configureResourceAt(rootProvider, "/b");
+        assertNotNull(crp.checkSourceAndDest(context, "/a", "/b"));
+
+        // second check different providers
+        assertNull(crp.checkSourceAndDest(context, "/some/path/object", "/"));
+    }
+
+    /**
      * Verifies copying resources between the same ResourceProvider
      *
      * @throws PersistenceException persistence exception
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTrackerTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTrackerTest.java
index d0ed944..21ead48 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTrackerTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTrackerTest.java
@@ -19,15 +19,21 @@
 package org.apache.sling.resourceresolver.impl.providers;
 
 import static org.hamcrest.Matchers.arrayWithSize;
+import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasSize;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.sling.api.resource.observation.ResourceChange;
@@ -45,6 +51,8 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.service.event.EventAdmin;
 
 public class ResourceProviderTrackerTest {
@@ -55,7 +63,7 @@
     private EventAdmin eventAdmin;
     private ResourceProviderInfo rp2Info;
     private Fixture fixture;
-    
+
     @Before
     public void prepare() throws Exception {
         eventAdmin = context.getService(EventAdmin.class);
@@ -75,7 +83,7 @@
         fixture.registerResourceProvider(rp3, "invalid", AuthType.no);
 
         ResourceProviderTracker tracker = new ResourceProviderTracker();
-        tracker.setObservationReporterGenerator(new SimpleObservationReporterGenerator(new NoDothingObservationReporter()));
+        tracker.setObservationReporterGenerator(new SimpleObservationReporterGenerator(new DoNothingObservationReporter()));
         tracker.activate(context.bundleContext(), eventAdmin, new DoNothingChangeListener());
         return tracker;
     }
@@ -105,7 +113,7 @@
     @Test
     public void testActivationDeactivation() throws Exception {
         final ResourceProviderTracker tracker = new ResourceProviderTracker();
-        tracker.setObservationReporterGenerator(new SimpleObservationReporterGenerator(new NoDothingObservationReporter()));
+        tracker.setObservationReporterGenerator(new SimpleObservationReporterGenerator(new DoNothingObservationReporter()));
 
         // create boolean markers for the listener
         final AtomicBoolean addedCalled = new AtomicBoolean(false);
@@ -159,7 +167,7 @@
     @Test
     public void testReactivation() throws Exception {
         final ResourceProviderTracker tracker = new ResourceProviderTracker();
-        tracker.setObservationReporterGenerator(new SimpleObservationReporterGenerator(new NoDothingObservationReporter()));
+        tracker.setObservationReporterGenerator(new SimpleObservationReporterGenerator(new DoNothingObservationReporter()));
 
         // create boolean markers for the listener
         final AtomicBoolean addedCalled = new AtomicBoolean(false);
@@ -240,6 +248,62 @@
         assertThat(tracker.getResourceProviderStorage().getAllHandlers().size(), equalTo(0));
     }
 
+    /**
+     * This test verifies that shadowing of Resource observation is deterministic when ResourceProviders get registered and unregistered,
+     * meaning it is independent of the order in which those events happen.
+     * <p>
+     * It does so by
+     * 1) registering a ResourceProvider A on a deeper path then root (shadowing root)
+     * 2) registering a ResourceProvider B on root
+     * 3) unregistering the ResourceProvider A
+     * 4) and registering the ResoucreProvider A
+     * <p>
+     * This guarantees in both cases (A before B and B before A) the same excludes are applied in the ObservationReporter.
+     *
+     * @throws InvalidSyntaxException
+     */
+    @Test
+    public void testDeterministicObservationShadowing() throws InvalidSyntaxException {
+        final ResourceProviderTracker tracker = new ResourceProviderTracker();
+        final Map<String, List<String>> excludeSets = new HashMap<>();
+
+        tracker.activate(context.bundleContext(), eventAdmin, null);
+        tracker.setObservationReporterGenerator(new SimpleObservationReporterGenerator(new DoNothingObservationReporter()) {
+            @Override
+            public ObservationReporter create(Path path, PathSet excludes) {
+                List<String> excludeSetsPerPath = excludeSets.get(path.getPath());
+                if (excludeSetsPerPath == null) {
+                    excludeSetsPerPath = new ArrayList<>(1);
+                    excludeSets.put(path.getPath(), excludeSetsPerPath);
+                }
+
+                excludeSetsPerPath.clear();
+                for (Path exclude : excludes) {
+                    excludeSetsPerPath.add(exclude.getPath());
+                }
+
+                return super.create(path, excludes);
+            }
+        });
+
+        ResourceProvider<?> rp = mock(ResourceProvider.class);
+        ResourceProviderInfo info;
+        // register RP on /path, empty exclude set expected
+        info = fixture.registerResourceProvider(rp, "/path", AuthType.no);
+        assertNull(excludeSets.get("/"));
+        // register RP on /, expect /path excluded
+        fixture.registerResourceProvider(rp, "/", AuthType.no);
+        assertThat(excludeSets.get("/"), hasSize(1));
+        assertThat(excludeSets.get("/"), contains("/path"));
+        // unregister RP on /path,  empty exclude set expected
+        fixture.unregisterResourceProvider(info);
+        assertThat(excludeSets.get("/"), hasSize(0));
+        // register RP on /path again, expect /path excluded
+        fixture.registerResourceProvider(rp, "/path", AuthType.no);
+        assertThat(excludeSets.get("/"), hasSize(1));
+        assertThat(excludeSets.get("/"), contains("/path"));
+    }
+
     @Test
     public void fillDto() throws Exception {
         ResourceProviderTracker tracker = registerDefaultResourceProviderTracker();
@@ -252,7 +316,7 @@
         assertThat( dto.failedProviders, arrayWithSize(1));
     }
 
-    static final class NoDothingObservationReporter implements ObservationReporter {
+    static class DoNothingObservationReporter implements ObservationReporter {
         @Override
         public void reportChanges(Iterable<ResourceChange> changes, boolean distribute) {
         }
@@ -267,7 +331,7 @@
         }
     }
 
-    static final class SimpleObservationReporterGenerator implements ObservationReporterGenerator {
+    static class SimpleObservationReporterGenerator implements ObservationReporterGenerator {
         private final ObservationReporter reporter;
 
         SimpleObservationReporterGenerator(ObservationReporter reporter) {