You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:59:24 UTC

[sling-org-apache-sling-resourcemerger] 07/23: SLING-3423 / SLING-3657 - adding MergedResourcePicker for implementing additional merged resource selection algorithms. Implmeneting OverridingResourcePicker to provide /mnt/override which merges resources based on the resource type hierarchy

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

rombert pushed a commit to annotated tag org.apache.sling.resourcemerger-1.2.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourcemerger.git

commit 7e8a9e94e3cca8a464c8767dc0cd720a6d1eb113
Author: Justin Edelson <ju...@apache.org>
AuthorDate: Thu Sep 4 01:37:48 2014 +0000

    SLING-3423 / SLING-3657 - adding MergedResourcePicker for implementing additional merged resource selection algorithms. Implmeneting OverridingResourcePicker to provide /mnt/override which merges resources based on the resource type hierarchy
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/resourcemerger@1622390 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  15 +-
 .../impl/MergedResourcePickerWhiteboard.java       |  81 +++++++
 .../impl/MergedResourceProviderFactory.java        |  52 ++---
 ...eProvider.java => MergingResourceProvider.java} | 241 ++++++++++-----------
 .../impl/MergingResourceProviderFactory.java       |  48 ++++
 .../impl/OverridingResourcePicker.java             |  91 ++++++++
 .../resourcemerger/spi/MergedResourcePicker.java   |  54 +++++
 .../sling/resourcemerger/spi/package-info.java     |  27 +++
 .../impl/MergedResourceProviderTest.java           |   5 +-
 .../impl/OverridingResourceProviderTest.java       | 114 ++++++++++
 10 files changed, 570 insertions(+), 158 deletions(-)

diff --git a/pom.xml b/pom.xml
index 5fffca5..e35f39e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,6 +67,16 @@
         </dependency>
 
         <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>servlet-api</artifactId>
         </dependency>
@@ -89,11 +99,6 @@
             <artifactId>junit</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.compendium</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.testing.resourceresolver-mock</artifactId>
             <version>0.2.0</version>
diff --git a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourcePickerWhiteboard.java b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourcePickerWhiteboard.java
new file mode 100644
index 0000000..7449047
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourcePickerWhiteboard.java
@@ -0,0 +1,81 @@
+/*
+ * 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.resourcemerger.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.api.resource.ResourceProviderFactory;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.resourcemerger.spi.MergedResourcePicker;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+@Component
+public class MergedResourcePickerWhiteboard implements ServiceTrackerCustomizer {
+
+    private ServiceTracker tracker;
+
+    private BundleContext bundleContext;
+
+    @Activate
+    protected void activate(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+        tracker = new ServiceTracker(bundleContext, MergedResourcePicker.class.getName(), this);
+        tracker.open();
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        tracker.close();
+    }
+
+    public Object addingService(ServiceReference reference) {
+        MergedResourcePicker picker = (MergedResourcePicker) bundleContext.getService(reference);
+        String mergeRoot = PropertiesUtil.toString(reference.getProperty(MergedResourcePicker.MERGE_ROOT), null);
+        if (mergeRoot != null) {
+            ResourceProviderFactory providerFactory = new MergingResourceProviderFactory(mergeRoot, picker);
+            Dictionary<Object, Object> props = new Hashtable<Object, Object>();
+            props.put(ResourceProvider.ROOTS, mergeRoot);
+            props.put(ResourceProvider.OWNS_ROOTS, true);
+            return bundleContext.registerService(ResourceProviderFactory.class.getName(), providerFactory, props);
+        } else {
+            return null;
+        }
+    }
+
+    public void modifiedService(ServiceReference reference, Object service) {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void removedService(ServiceReference reference, Object service) {
+        if (service instanceof ServiceRegistration) {
+            ((ServiceRegistration) service).unregister();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderFactory.java b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderFactory.java
index 3357b8a..e5ba8bf 100644
--- a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderFactory.java
+++ b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderFactory.java
@@ -18,55 +18,54 @@
  */
 package org.apache.sling.resourcemerger.impl;
 
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Properties;
 import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.NonExistingResource;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceProvider;
-import org.apache.sling.api.resource.ResourceProviderFactory;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.apache.sling.resourcemerger.api.ResourceMergerService;
+import org.apache.sling.resourcemerger.spi.MergedResourcePicker;
 
 @Component(label = "Apache Sling Merged Resource Provider Factory",
            description = "This resource provider delivers merged resources based on the search paths.",
            metatype=true)
-@Service(value = {ResourceProviderFactory.class, ResourceMergerService.class})
-@Properties({
-    @Property(name = ResourceProvider.ROOTS, value=MergedResourceProviderFactory.DEFAULT_ROOT,
-            label="Root",
-            description="The mount point of merged resources"),
-    @Property(name = ResourceProvider.OWNS_ROOTS, boolValue=true, propertyPrivate=true)
-})
+@Service
+@Property(name=MergedResourcePicker.MERGE_ROOT, value=MergedResourceProviderFactory.DEFAULT_ROOT,
+    label="Root",
+    description="The mount point of merged resources")
 /**
  * The <code>MergedResourceProviderFactory</code> creates merged resource
  * providers and implements the <code>ResourceMergerService</code>.
  */
-public class MergedResourceProviderFactory implements ResourceProviderFactory, ResourceMergerService {
+public class MergedResourceProviderFactory implements MergedResourcePicker, ResourceMergerService {
 
     public static final String DEFAULT_ROOT = "/mnt/overlay";
 
     private String mergeRootPath;
 
-    /**
-     * {@inheritDoc}
-     */
-    public ResourceProvider getResourceProvider(final Map<String, Object> stringObjectMap)
-    throws LoginException {
-        return new MergedResourceProvider(mergeRootPath);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public ResourceProvider getAdministrativeResourceProvider(final Map<String, Object> stringObjectMap)
-    throws LoginException {
-        return new MergedResourceProvider(mergeRootPath);
+    public Iterator<Resource> pickResources(ResourceResolver resolver, String relativePath) {
+        List<Resource> resources = new ArrayList<Resource>();
+        final String[] searchPaths = resolver.getSearchPath();
+        for (int i = searchPaths.length - 1; i >= 0; i--) {
+            final String basePath = searchPaths[i];
+            final String fullPath = basePath + relativePath;
+            Resource resource = resolver.getResource(fullPath);
+            if (resource != null) {
+                resources.add(resource);
+            } else {
+                resources.add(new NonExistingResource(resolver, fullPath));
+            }
+        }
+        return resources.iterator();
     }
 
     /**
@@ -128,8 +127,5 @@ public class MergedResourceProviderFactory implements ResourceProviderFactory, R
     @Activate
     protected void configure(final Map<String, Object> properties) {
         mergeRootPath = PropertiesUtil.toString(properties.get(ResourceProvider.ROOTS), DEFAULT_ROOT);
-        if (mergeRootPath.endsWith("/")) {
-            mergeRootPath = mergeRootPath.substring(0, mergeRootPath.length() - 1);
-        }
     }
 }
diff --git a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProvider.java b/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProvider.java
similarity index 53%
rename from src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProvider.java
rename to src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProvider.java
index eca50ac..05fa13c 100644
--- a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProvider.java
+++ b/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProvider.java
@@ -29,25 +29,17 @@ import org.apache.sling.api.resource.ResourceProvider;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.resourcemerger.spi.MergedResourcePicker;
 
-/**
- * The <code>MergedResourceProvider</code> is the resource provider providing
- * access to {@link MergedResource} objects.
- */
-public class MergedResourceProvider
-    implements ResourceProvider {
+class MergingResourceProvider implements ResourceProvider {
 
     private final String mergeRootPath;
 
-    public MergedResourceProvider(final String mergeRootPath) {
-        this.mergeRootPath = mergeRootPath;
-    }
+    private final MergedResourcePicker picker;
 
-    /**
-     * {@inheritDoc}
-     */
-    public Resource getResource(final ResourceResolver resolver, final HttpServletRequest request, final String path) {
-        return getResource(resolver, path);
+    MergingResourceProvider(String mergeRootPath, MergedResourcePicker picker) {
+        this.mergeRootPath = mergeRootPath;
+        this.picker = picker;
     }
 
     private static final class ParentHidingHandler {
@@ -55,7 +47,7 @@ public class MergedResourceProvider
         private final String[] childrenToHideArray;
 
         public ParentHidingHandler(final Resource parent) {
-            if ( parent == null ) {
+            if (parent == null) {
                 this.childrenToHideArray = null;
             } else {
                 final ValueMap parentProps = parent.getValueMap();
@@ -65,9 +57,9 @@ public class MergedResourceProvider
 
         public boolean isHidden(final String name) {
             boolean hidden = false;
-            if ( this.childrenToHideArray != null ) {
-                for(final String entry : childrenToHideArray) {
-                    if ( entry.equals("*") || entry.equals(name) ) {
+            if (this.childrenToHideArray != null) {
+                for (final String entry : childrenToHideArray) {
+                    if (entry.equals("*") || entry.equals(name)) {
                         hidden = true;
                         break;
                     }
@@ -77,42 +69,6 @@ public class MergedResourceProvider
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public Resource getResource(final ResourceResolver resolver, final String path) {
-        final String relativePath = getRelativePath(path);
-
-        if ( relativePath != null ) {
-            final ResourceHolder holder = new ResourceHolder(ResourceUtil.getName(path));
-
-            // Loop over provided base paths, start with least import
-            final String[] searchPaths = resolver.getSearchPath();
-            for(int i=searchPaths.length-1; i >= 0; i--) {
-                final String basePath = searchPaths[i];
-
-                // Try to get the corresponding physical resource for this base path
-                final String fullPath = basePath + relativePath;
-
-                // check parent for hiding
-                // SLING 3521 : if parent is not readable, nothing is hidden
-                final Resource parent = resolver.getResource(ResourceUtil.getParent(fullPath));
-                final boolean hidden = new ParentHidingHandler(parent).isHidden(holder.name);
-                if ( hidden ) {
-                    holder.resources.clear();
-                } else {
-                    final Resource baseRes = resolver.getResource(fullPath);
-                    if (baseRes != null) {
-                        holder.resources.add(baseRes);
-                    }
-                }
-            }
-            return createMergedResource(resolver, relativePath, holder);
-        }
-
-        return null;
-    }
-
     private static final class ResourceHolder {
         public final String name;
         public final List<Resource> resources = new ArrayList<Resource>();
@@ -126,18 +82,17 @@ public class MergedResourceProvider
     /**
      * Create the merged resource based on the provided resources
      */
-    private Resource createMergedResource(final ResourceResolver resolver,
-            final String relativePath,
+    private Resource createMergedResource(final ResourceResolver resolver, final String relativePath,
             final ResourceHolder holder) {
         int index = 0;
-        while ( index < holder.resources.size() ) {
+        while (index < holder.resources.size()) {
             final Resource baseRes = holder.resources.get(index);
             // check if resource is hidden
             final ValueMap props = baseRes.getValueMap();
             holder.valueMaps.add(props);
-            if ( props.get(MergedResourceConstants.PN_HIDE_RESOURCE, Boolean.FALSE) ) {
+            if (props.get(MergedResourceConstants.PN_HIDE_RESOURCE, Boolean.FALSE)) {
                 // clear everything up to now
-                for(int i=0;i<=index;i++) {
+                for (int i = 0; i <= index; i++) {
                     holder.resources.remove(0);
                 }
                 holder.valueMaps.clear();
@@ -155,73 +110,122 @@ public class MergedResourceProvider
     }
 
     /**
+     * Gets the relative path out of merge root path
+     *
+     * @param path Absolute path
+     * @return Relative path
+     */
+    private String getRelativePath(String path) {
+        if (path.startsWith(mergeRootPath)) {
+            path = path.substring(mergeRootPath.length());
+            if (path.length() == 0) {
+                return path;
+            } else if (path.charAt(0) == '/') {
+                return path.substring(1);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Resource getResource(ResourceResolver resolver, String path) {
+        final String relativePath = getRelativePath(path);
+
+        if (relativePath != null) {
+            final ResourceHolder holder = new ResourceHolder(ResourceUtil.getName(path));
+
+            final Iterator<Resource> resources = picker.pickResources(resolver, relativePath);
+
+            if (!resources.hasNext()) {
+                return null;
+            }
+
+            while (resources.hasNext()) {
+                Resource resource = resources.next();
+                // check parent for hiding
+                // SLING 3521 : if parent is not readable, nothing is hidden
+                final Resource parent = resource.getParent();
+                final boolean hidden = new ParentHidingHandler(parent).isHidden(holder.name);
+                if (hidden) {
+                    holder.resources.clear();
+                } else if (!ResourceUtil.isNonExistingResource(resource)) {
+                    holder.resources.add(resource);
+                }
+            }
+            return createMergedResource(resolver, relativePath, holder);
+        }
+
+        return null;
+    }
+
+    /**
      * {@inheritDoc}
      */
-    public Iterator<Resource> listChildren(final Resource resource) {
+    public Iterator<Resource> listChildren(Resource resource) {
         final ResourceResolver resolver = resource.getResourceResolver();
 
         final String relativePath = getRelativePath(resource.getPath());
 
-        if ( relativePath != null ) {
+        if (relativePath != null) {
             final List<ResourceHolder> candidates = new ArrayList<ResourceHolder>();
 
-            // Loop over provided base paths, start with least import
-            final String[] searchPaths = resolver.getSearchPath();
-            for(int i=searchPaths.length-1; i >= 0; i--) {
-                final String basePath = searchPaths[i];
-                final Resource parentResource = resolver.getResource(basePath + relativePath);
-                if ( parentResource != null ) {
-                    final ParentHidingHandler handler = new ParentHidingHandler(parentResource);
-                    for(final Resource child : parentResource.getChildren()) {
-                        final String rsrcName = child.getName();
-                        ResourceHolder holder = null;
-                        for(final ResourceHolder current : candidates) {
-                            if ( current.name.equals(rsrcName) ) {
-                                holder = current;
-                                break;
-                            }
-                        }
-                        if ( holder == null ) {
-                            holder = new ResourceHolder(rsrcName);
-                            candidates.add(holder);
+            final Iterator<Resource> resources = picker.pickResources(resolver, relativePath);
+
+            while (resources.hasNext()) {
+                Resource parentResource = resources.next();
+                final ParentHidingHandler handler = new ParentHidingHandler(parentResource);
+                for (final Resource child : parentResource.getChildren()) {
+                    final String rsrcName = child.getName();
+                    ResourceHolder holder = null;
+                    for (final ResourceHolder current : candidates) {
+                        if (current.name.equals(rsrcName)) {
+                            holder = current;
+                            break;
                         }
-                        holder.resources.add(child);
-
-                        // Check if children need reordering
-                        int orderBeforeIndex = -1;
-                        final ValueMap vm = child.getValueMap();
-                        final String orderBefore = vm.get(MergedResourceConstants.PN_ORDER_BEFORE, String.class);
-                        if (orderBefore != null && !orderBefore.equals(rsrcName)) {
-                            // search entry
-                            int index = 0;
-                            while (index < candidates.size()) {
-                                final ResourceHolder current = candidates.get(index);
-                                if ( current.name.equals(orderBefore) ) {
-                                    orderBeforeIndex = index;
-                                    break;
-                                }
-                                index++;
+                    }
+                    if (holder == null) {
+                        holder = new ResourceHolder(rsrcName);
+                        candidates.add(holder);
+                    }
+                    holder.resources.add(child);
+
+                    // Check if children need reordering
+                    int orderBeforeIndex = -1;
+                    final ValueMap vm = child.getValueMap();
+                    final String orderBefore = vm.get(MergedResourceConstants.PN_ORDER_BEFORE, String.class);
+                    if (orderBefore != null && !orderBefore.equals(rsrcName)) {
+                        // search entry
+                        int index = 0;
+                        while (index < candidates.size()) {
+                            final ResourceHolder current = candidates.get(index);
+                            if (current.name.equals(orderBefore)) {
+                                orderBeforeIndex = index;
+                                break;
                             }
+                            index++;
                         }
+                    }
 
-                        if (orderBeforeIndex > -1) {
-                            candidates.add(orderBeforeIndex, holder);
-                            candidates.remove(candidates.size() - 1);
-                        }
+                    if (orderBeforeIndex > -1) {
+                        candidates.add(orderBeforeIndex, holder);
+                        candidates.remove(candidates.size() - 1);
                     }
-                    final Iterator<ResourceHolder> iter = candidates.iterator();
-                    while ( iter.hasNext() ) {
-                        final ResourceHolder holder = iter.next();
-                        if ( handler.isHidden(holder.name) ) {
-                            iter.remove();
-                        }
+                }
+                final Iterator<ResourceHolder> iter = candidates.iterator();
+                while (iter.hasNext()) {
+                    final ResourceHolder holder = iter.next();
+                    if (handler.isHidden(holder.name)) {
+                        iter.remove();
                     }
                 }
             }
             final List<Resource> children = new ArrayList<Resource>();
-            for(final ResourceHolder holder : candidates) {
-                final Resource mergedResource = this.createMergedResource(resolver, (relativePath.length() == 0 ? holder.name : relativePath + '/' + holder.name), holder);
-                if ( mergedResource != null ) {
+            for (final ResourceHolder holder : candidates) {
+                final Resource mergedResource = this.createMergedResource(resolver,
+                        (relativePath.length() == 0 ? holder.name : relativePath + '/' + holder.name), holder);
+                if (mergedResource != null) {
                     children.add(mergedResource);
                 }
             }
@@ -231,21 +235,12 @@ public class MergedResourceProvider
         return null;
     }
 
+
     /**
-     * Gets the relative path out of merge root path
-     *
-     * @param path Absolute path
-     * @return Relative path
+     * {@inheritDoc}
      */
-    private String getRelativePath(String path) {
-        if ( path.startsWith(mergeRootPath) ) {
-            path = path.substring(mergeRootPath.length());
-            if ( path.length() == 0 ) {
-                return path;
-            } else if ( path.charAt(0) == '/' ) {
-                return path.substring(1);
-            }
-        }
-        return null;
+    public Resource getResource(final ResourceResolver resolver, final HttpServletRequest request, final String path) {
+        return getResource(resolver, path);
     }
+
 }
diff --git a/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProviderFactory.java b/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProviderFactory.java
new file mode 100644
index 0000000..f2a5be1
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProviderFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.resourcemerger.impl;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceProvider;
+import org.apache.sling.api.resource.ResourceProviderFactory;
+import org.apache.sling.resourcemerger.spi.MergedResourcePicker;
+
+class MergingResourceProviderFactory implements ResourceProviderFactory {
+
+    private final String mergeRootPath;
+
+    private final MergedResourcePicker picker;
+
+    MergingResourceProviderFactory(String mergeRootPath, MergedResourcePicker picker) {
+        this.mergeRootPath = mergeRootPath;
+        this.picker = picker;
+    }
+
+    public ResourceProvider getResourceProvider(Map<String, Object> authenticationInfo) throws LoginException {
+        return new MergingResourceProvider(mergeRootPath, picker);
+    }
+
+    public ResourceProvider getAdministrativeResourceProvider(Map<String, Object> authenticationInfo)
+            throws LoginException {
+        return new MergingResourceProvider(mergeRootPath, picker);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resourcemerger/impl/OverridingResourcePicker.java b/src/main/java/org/apache/sling/resourcemerger/impl/OverridingResourcePicker.java
new file mode 100644
index 0000000..738ba38
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourcemerger/impl/OverridingResourcePicker.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.resourcemerger.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.NonExistingResource;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.resourcemerger.spi.MergedResourcePicker;
+
+@Component
+@Service
+@Property(name = MergedResourcePicker.MERGE_ROOT, value = "/mnt/override")
+public class OverridingResourcePicker implements MergedResourcePicker {
+
+    public Iterator<Resource> pickResources(ResourceResolver resolver, String relativePath) {
+        String absPath = "/" + relativePath;
+        final List<Resource> resources = new ArrayList<Resource>();
+
+        Resource currentTarget = resolver.getResource(absPath);
+
+        if (currentTarget != null) {
+            resources.add(currentTarget);
+
+            while (currentTarget != null) {
+                final Resource inheritanceRootResource = findInheritanceRoot(currentTarget);
+                if (inheritanceRootResource == null) {
+                    currentTarget = null;
+                } else {
+                    final String relPath = currentTarget.getPath()
+                            .substring(inheritanceRootResource.getPath().length());
+                    final String superType = inheritanceRootResource.getResourceSuperType();
+                    if (superType == null) {
+                        currentTarget = null;
+                    } else {
+                        final String superTypeChildPath = superType + relPath;
+                        final Resource superTypeResource = resolver.getResource(superTypeChildPath);
+                        if (superTypeResource != null) {
+                            resources.add(superTypeResource);
+                            currentTarget = superTypeResource;
+                        } else {
+                            resources.add(new NonExistingResource(resolver, superTypeChildPath));
+                            currentTarget = null;
+                        }
+                    }
+                }
+            }
+
+            Collections.reverse(resources);
+        }
+        return resources.iterator();
+    }
+
+    private Resource findInheritanceRoot(Resource target) {
+        String superType = target.getResourceSuperType();
+        if (superType != null) {
+            return target;
+        } else {
+            Resource parent = target.getParent();
+            if (parent == null) {
+                return null;
+            } else {
+                return findInheritanceRoot(parent);
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/resourcemerger/spi/MergedResourcePicker.java b/src/main/java/org/apache/sling/resourcemerger/spi/MergedResourcePicker.java
new file mode 100644
index 0000000..01179ee
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourcemerger/spi/MergedResourcePicker.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.resourcemerger.spi;
+
+import java.util.Iterator;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * Service interface which can be implemented to define an algorithm used to pick
+ * resources to be merged. For each picker service, a separate ResourceProviderFactory
+ * will be exposed at the MERGE_ROOT of the picker.
+ */
+@ConsumerType
+public interface MergedResourcePicker {
+
+    /**
+     * Service property name identifying the root path for the merged resources.
+     * By convention, starts, with /mnt, although this is by no means required.
+     * The value of this service property must not end in a slash.
+     */
+    String MERGE_ROOT = "merge.root";
+
+    /**
+     * Method invoked by the MergingResourceProvider to identify the resources to be merged for a given
+     * relative path. The resources returned may be either resources returned from the ResourceResolver
+     * directory or an instance of NonExistingResource.
+     * 
+     * @param resolver the ResourceResolver
+     * @param relativePath the path relative to the merge root
+     * @return an iterator of Resource objects
+     */
+    Iterator<Resource> pickResources(ResourceResolver resolver, String relativePath);
+
+}
diff --git a/src/main/java/org/apache/sling/resourcemerger/spi/package-info.java b/src/main/java/org/apache/sling/resourcemerger/spi/package-info.java
new file mode 100644
index 0000000..68a22a6
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourcemerger/spi/package-info.java
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides a service to merge multiple physical resources into a single one
+ */
+@Version("1.0.0")
+package org.apache.sling.resourcemerger.spi;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/src/test/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderTest.java b/src/test/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderTest.java
index 315e4e4..b888795 100644
--- a/src/test/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderTest.java
+++ b/src/test/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderTest.java
@@ -28,6 +28,7 @@ import java.util.Iterator;
 import java.util.List;
 
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceProvider;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.api.resource.ValueMap;
@@ -41,7 +42,7 @@ public class MergedResourceProviderTest {
 
     private ResourceResolver resolver;
 
-    private MergedResourceProvider provider;
+    private ResourceProvider provider;
 
     @Before public void setup() throws Exception {
         final MockResourceResolverFactoryOptions options = new MockResourceResolverFactoryOptions();
@@ -71,7 +72,7 @@ public class MergedResourceProviderTest {
                                             .resource(".Z")
                                         .commit();
 
-        this.provider = new MergedResourceProvider("/merged");
+        this.provider = new MergingResourceProvider("/merged", new MergedResourceProviderFactory());
     }
 
     @Test public void testHideChildren() {
diff --git a/src/test/java/org/apache/sling/resourcemerger/impl/OverridingResourceProviderTest.java b/src/test/java/org/apache/sling/resourcemerger/impl/OverridingResourceProviderTest.java
new file mode 100644
index 0000000..ca7c520
--- /dev/null
+++ b/src/test/java/org/apache/sling/resourcemerger/impl/OverridingResourceProviderTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.resourcemerger.impl;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.testing.resourceresolver.MockHelper;
+import org.apache.sling.testing.resourceresolver.MockResourceResolverFactory;
+import org.apache.sling.testing.resourceresolver.MockResourceResolverFactoryOptions;
+import org.junit.Before;
+import org.junit.Test;
+
+public class OverridingResourceProviderTest {
+    
+    private static final String SUPER_TYPE = "sling:resourceSuperType";
+
+    private ResourceResolver resolver;
+
+    private MergingResourceProvider provider;
+
+    /*
+     * Tree is:
+     * /apps/a/1/a
+     * /apps/a/1/b
+     * /apps/a/1/b/1
+     * /apps/a/1/c
+     * /apps/a/2/c
+     */
+    @Before
+    public void setup() throws Exception {
+        final MockResourceResolverFactoryOptions options = new MockResourceResolverFactoryOptions();
+        options.setSearchPaths(new String[] {"/apps", "/libs"});
+        final ResourceResolverFactory factory = new MockResourceResolverFactory(options);
+        this.resolver = factory.getAdministrativeResourceResolver(null);
+        MockHelper.create(this.resolver)
+                    .resource("/apps")
+                    .resource("a")
+                    .resource("1").p("a", "1").p("b", "1")
+                    .resource("a").p("1", "a").p("2", "b")
+                    .resource(".b").p("1", "a").p("2", "b")
+                    .resource("1")
+                    .resource("/apps/a/1/c").p("1", "a").p("2", "b")
+                    .resource("/apps/a/2").p(SUPER_TYPE, "a/1").p("b", "2").p(MergedResourceConstants.PN_HIDE_CHILDREN, new String[] {"b"})
+                    .resource("c").p("1", "c")
+                    .commit();
+
+        this.provider = new MergingResourceProvider("/override", new OverridingResourcePicker());
+    }
+
+    @Test
+    public void testOverridingOnTarget() {
+        final Resource rsrcA2 = this.provider.getResource(this.resolver, "/override/apps/a/2");
+        final ValueMap vm = rsrcA2.adaptTo(ValueMap.class);
+        assertNotNull(vm);
+        assertEquals(3, vm.size()); //3rd is resource:superType
+        assertEquals("1", vm.get("a"));
+        assertEquals("2", vm.get("b"));
+    }
+
+    @Test
+    public void testOverridingViaParent() {
+        final Resource rsrcA2 = this.provider.getResource(this.resolver, "/override/apps/a/2/c");
+        final ValueMap vm = rsrcA2.adaptTo(ValueMap.class);
+        assertNotNull(vm);
+        assertEquals(2, vm.size());
+        assertEquals("c", vm.get("1"));
+        assertEquals("b", vm.get("2"));
+    }
+
+    @Test
+    public void testHideChildrenFromList() {
+        final Resource rsrcA2 = this.provider.getResource(this.resolver, "/override/apps/a/2");
+        final Iterator<Resource> children = this.provider.listChildren(rsrcA2);
+        final List<String> names = new ArrayList<String>();
+        while (children.hasNext()) {
+            names.add(children.next().getName());
+        }
+        assertTrue(names.contains("a"));
+        assertFalse(names.contains("b"));
+        assertTrue(names.contains("c"));
+    }
+
+    @Test
+    public void testHideChildrenFromGet() {
+        assertNotNull(this.provider.getResource(this.resolver, "/override/apps/a/1/b/1"));
+        assertNull(this.provider.getResource(this.resolver, "/override/apps/a/2/b"));
+        assertNull(this.provider.getResource(this.resolver, "/override/apps/a/2/b/1"));
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.