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 02e184dabca598e4c7476c9c38849d315c2675df
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Sat Sep 13 13:08:08 2014 +0000

    SLING-3909 : Merged ResourceProviders should be optionally modifiable
    
    git-svn-id: 
https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/resourcemerger@1624734
 13f79535-47bb-0310-9956-ffa450edef68
---
 .../resourcemerger/impl/CRUDMergedResource.java    |  88 ++++++++++
 .../impl/CRUDMergedResourceProvider.java           | 186 +++++++++++++++++++++
 .../impl/MergedResourcePickerWhiteboard.java       |   3 +-
 .../impl/MergedResourceProviderFactory.java        |  21 ++-
 .../impl/MergingResourceProvider.java              |  68 +++++---
 .../impl/MergingResourceProviderFactory.java       |  24 ++-
 .../resourcemerger/spi/MergedResourcePicker.java   |  10 +-
 .../impl/MergedResourceProviderTest.java           |  85 +++++++++-
 .../impl/OverridingResourceProviderTest.java       |  10 +-
 9 files changed, 454 insertions(+), 41 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/resourcemerger/impl/CRUDMergedResource.java 
b/src/main/java/org/apache/sling/resourcemerger/impl/CRUDMergedResource.java
new file mode 100644
index 0000000..f662c02
--- /dev/null
+++ b/src/main/java/org/apache/sling/resourcemerger/impl/CRUDMergedResource.java
@@ -0,0 +1,88 @@
+/*
+ * 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.Iterator;
+import java.util.List;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+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;
+
+/**
+ * {@inheritDoc}
+ */
+public class CRUDMergedResource extends MergedResource {
+
+    private final MergedResourcePicker picker;
+
+    private final String relativePath;
+
+    /**
+     * Constructor
+     *
+     * @param resolver      Resource resolver
+     * @param mergeRootPath   Merge root path
+     * @param relativePath    Relative path
+     * @param mappedResources List of physical mapped resources' paths
+     */
+    CRUDMergedResource(final ResourceResolver resolver,
+                   final String mergeRootPath,
+                   final String relativePath,
+                   final List<Resource> mappedResources,
+                   final List<ValueMap> valueMaps,
+                   final MergedResourcePicker picker) {
+        super(resolver, mergeRootPath, relativePath, mappedResources, 
valueMaps);
+        this.picker = picker;
+        this.relativePath = relativePath;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public <AdapterType> AdapterType adaptTo(final Class<AdapterType> type) {
+        if (type == ModifiableValueMap.class) {
+            final Iterator<Resource> iter = 
this.picker.pickResources(this.getResourceResolver(), this.relativePath);
+            Resource highestRsrc = null;
+            while ( iter.hasNext() ) {
+                highestRsrc = iter.next();
+            }
+            if ( ResourceUtil.isNonExistingResource(highestRsrc) ) {
+                final String paths[] = 
(String[])this.getResourceMetadata().get(MergedResourceConstants.METADATA_RESOURCES);
+
+                final Resource copyResource = 
this.getResourceResolver().getResource(paths[paths.length - 1]);
+                try {
+                    final Resource newResource = 
ResourceUtil.getOrCreateResource(this.getResourceResolver(), 
highestRsrc.getPath(), copyResource.getResourceType(), null, false);
+                    return 
(AdapterType)newResource.adaptTo(ModifiableValueMap.class);
+                } catch ( final PersistenceException pe) {
+                    // we ignore this for now
+                    return null;
+                }
+            }
+            return (AdapterType)highestRsrc.adaptTo(ModifiableValueMap.class);
+        }
+        return super.adaptTo(type);
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/resourcemerger/impl/CRUDMergedResourceProvider.java
 
b/src/main/java/org/apache/sling/resourcemerger/impl/CRUDMergedResourceProvider.java
new file mode 100644
index 0000000..96a667b
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/resourcemerger/impl/CRUDMergedResourceProvider.java
@@ -0,0 +1,186 @@
+/*
+ * 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.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.ModifyingResourceProvider;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.resourcemerger.spi.MergedResourcePicker;
+
+/**
+ * This is a modifiable resource provider.
+ */
+public class CRUDMergedResourceProvider
+    extends MergingResourceProvider
+    implements ModifyingResourceProvider {
+
+    public CRUDMergedResourceProvider(final String mergeRootPath,
+            final MergedResourcePicker picker) {
+        super(mergeRootPath, picker, false);
+    }
+
+    private static final class ExtendedResourceHolder {
+        public ResourceHolder holder;
+        public int count;
+        public String lowestResourcePath;
+        public String highestResourcePath;
+    }
+    private ExtendedResourceHolder getAllResources(final ResourceResolver 
resolver,
+            final String path,
+            final String relativePath) {
+        final ExtendedResourceHolder holder = new ExtendedResourceHolder();
+        holder.holder = new ResourceHolder(ResourceUtil.getName(path));
+
+        holder.count = 0;
+
+        // Loop over resources
+        final Iterator<Resource> iter = this.picker.pickResources(resolver, 
relativePath);
+        while ( iter.hasNext() ) {
+            final Resource rsrc = iter.next();
+            holder.count++;
+            if ( holder.count == 1 ) {
+                holder.lowestResourcePath = rsrc.getPath();
+            }
+            holder.highestResourcePath = rsrc.getPath();
+            if ( !ResourceUtil.isNonExistingResource(rsrc) ) {
+                // check parent for hiding
+                final Resource parent = rsrc.getParent();
+                if ( parent != null ) {
+                    final boolean hidden = new 
ParentHidingHandler(parent).isHidden(holder.holder.name);
+                    if ( hidden ) {
+                        holder.holder.resources.clear();
+                    } else {
+                        holder.holder.resources.add(rsrc);
+                    }
+                }
+            }
+        }
+
+        return holder;
+    }
+
+    /**
+     * @see 
org.apache.sling.api.resource.ModifyingResourceProvider#create(org.apache.sling.api.resource.ResourceResolver,
 java.lang.String, java.util.Map)
+     */
+    public Resource create(final ResourceResolver resolver,
+            final String path,
+            final Map<String, Object> properties)
+    throws PersistenceException {
+        // check if the resource exists
+        final Resource mountResource = this.getResource(resolver, path);
+        if ( mountResource != null ) {
+            throw new PersistenceException("Resource at " + path + " already 
exists.", null, path, null);
+        }
+        // creating of the root mount resource is not supported
+        final String relativePath = getRelativePath(path);
+        if ( relativePath == null || relativePath.length() == 0 ) {
+            throw new PersistenceException("Resource at " + path + " can't be 
created.", null, path, null);
+        }
+
+        final ExtendedResourceHolder holder = this.getAllResources(resolver, 
path, relativePath);
+        // we only support modifications if there is more than one location 
merged
+        if ( holder.count < 2 ) {
+            throw new PersistenceException("Modifying is only supported with 
at least two potentially merged resources.", null, path, null);
+        }
+        if ( holder.holder.resources.size() == 0
+             || (holder.holder.resources.size() < holder.count && 
!holder.holder.resources.get(holder.holder.resources.size() - 
1).getPath().equals(holder.highestResourcePath) )) {
+            final String createPath = holder.highestResourcePath;
+            final Resource parentResource = 
ResourceUtil.getOrCreateResource(resolver, ResourceUtil.getParent(createPath), 
(String)null, null, false);
+            resolver.create(parentResource, ResourceUtil.getName(createPath), 
properties);
+        } else {
+            final Resource hidingResource = 
resolver.getResource(holder.highestResourcePath);
+            if ( hidingResource != null ) {
+                final ModifiableValueMap mvm = 
hidingResource.adaptTo(ModifiableValueMap.class);
+                mvm.remove(MergedResourceConstants.PN_HIDE_RESOURCE);
+                mvm.putAll(properties);
+            }
+            // TODO check parent hiding
+        }
+        return this.getResource(resolver, path);
+    }
+
+    /**
+     * @see 
org.apache.sling.api.resource.ModifyingResourceProvider#delete(org.apache.sling.api.resource.ResourceResolver,
 java.lang.String)
+     */
+    public void delete(final ResourceResolver resolver, final String path)
+    throws PersistenceException {
+        // deleting of the root mount resource is not supported
+        final String relativePath = getRelativePath(path);
+        if ( relativePath == null || relativePath.length() == 0 ) {
+            throw new PersistenceException("Resource at " + path + " can't be 
created.", null, path, null);
+        }
+
+        // check if the resource exists
+        final Resource mntResource = this.getResource(resolver, path);
+        if ( mntResource == null ) {
+            throw new PersistenceException("Resource at " + path + " does not 
exist", null, path, null);
+        }
+        final ExtendedResourceHolder holder = this.getAllResources(resolver, 
path, relativePath);
+        // we only support modifications if there is more than one location 
merged
+        if ( holder.count < 2 ) {
+            throw new PersistenceException("Modifying is only supported with 
at least two potentially merged resources.", null, path, null);
+        }
+
+        int deleted = 0;
+        for(final Resource rsrc : holder.holder.resources) {
+            final String p = rsrc.getPath();
+            if ( !p.equals(holder.lowestResourcePath) ) {
+                resolver.delete(rsrc);
+                deleted++;
+            }
+        }
+        if ( deleted < holder.holder.resources.size() ) {
+            // create overlay resource which is hiding the other
+            final String createPath = holder.highestResourcePath;
+            final Resource parentResource = 
ResourceUtil.getOrCreateResource(resolver, ResourceUtil.getParent(createPath), 
(String)null, null, false);
+            final Map<String, Object> properties = new HashMap<String, 
Object>();
+            properties.put(MergedResourceConstants.PN_HIDE_RESOURCE, 
Boolean.TRUE);
+            resolver.create(parentResource, ResourceUtil.getName(createPath), 
properties);
+        }
+    }
+
+    /**
+     * @see 
org.apache.sling.api.resource.ModifyingResourceProvider#revert(org.apache.sling.api.resource.ResourceResolver)
+     */
+    public void revert(final ResourceResolver resolver) {
+        // the provider for the merged resources will revert
+    }
+
+    /**
+     * @see 
org.apache.sling.api.resource.ModifyingResourceProvider#commit(org.apache.sling.api.resource.ResourceResolver)
+     */
+    public void commit(final ResourceResolver resolver) throws 
PersistenceException {
+        // the provider for the merged resources will commit
+    }
+
+    /**
+     * @see 
org.apache.sling.api.resource.ModifyingResourceProvider#hasChanges(org.apache.sling.api.resource.ResourceResolver)
+     */
+    public boolean hasChanges(final ResourceResolver resolver) {
+        // the provider for the merged resources will return changes
+        return false;
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourcePickerWhiteboard.java
 
b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourcePickerWhiteboard.java
index b6bd79e..8954a75 100644
--- 
a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourcePickerWhiteboard.java
+++ 
b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourcePickerWhiteboard.java
@@ -63,7 +63,8 @@ public class MergedResourcePickerWhiteboard implements 
ServiceTrackerCustomizer
         if ( picker != null ) {
             final String mergeRoot = 
PropertiesUtil.toString(reference.getProperty(MergedResourcePicker.MERGE_ROOT), 
null);
             if (mergeRoot != null) {
-                final ResourceProviderFactory providerFactory = new 
MergingResourceProviderFactory(mergeRoot, picker);
+                final ResourceProviderFactory providerFactory = new 
MergingResourceProviderFactory(mergeRoot, picker,
+                        
PropertiesUtil.toBoolean(reference.getProperty(MergedResourcePicker.READ_ONLY), 
true));
                 final Dictionary<Object, Object> props = new Hashtable<Object, 
Object>();
                 props.put(ResourceProvider.ROOTS, mergeRoot);
                 props.put(ResourceProvider.OWNS_ROOTS, true);
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 e5ba8bf..a8d33e6 100644
--- 
a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderFactory.java
+++ 
b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderFactory.java
@@ -25,6 +25,7 @@ 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.NonExistingResource;
@@ -38,10 +39,16 @@ 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
-@Property(name=MergedResourcePicker.MERGE_ROOT, 
value=MergedResourceProviderFactory.DEFAULT_ROOT,
-    label="Root",
-    description="The mount point of merged resources")
+@Service(value={MergedResourcePicker.class, ResourceMergerService.class})
+@Properties({
+    @Property(name=MergedResourcePicker.MERGE_ROOT, 
value=MergedResourceProviderFactory.DEFAULT_ROOT,
+            label="Root",
+            description="The mount point of merged resources"),
+    @Property(name=MergedResourcePicker.READ_ONLY, boolValue=true,
+    label="Read Only",
+    description="Specifies if the resources are read-only or can be modified.")
+
+})
 /**
  * The <code>MergedResourceProviderFactory</code> creates merged resource
  * providers and implements the <code>ResourceMergerService</code>.
@@ -52,13 +59,13 @@ public class MergedResourceProviderFactory implements 
MergedResourcePicker, Reso
 
     private String mergeRootPath;
 
-    public Iterator<Resource> pickResources(ResourceResolver resolver, String 
relativePath) {
-        List<Resource> resources = new ArrayList<Resource>();
+    public Iterator<Resource> pickResources(final ResourceResolver resolver, 
final String relativePath) {
+        final 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);
+            final Resource resource = resolver.getResource(fullPath);
             if (resource != null) {
                 resources.add(resource);
             } else {
diff --git 
a/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProvider.java
 
b/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProvider.java
index 05fa13c..66df03e 100644
--- 
a/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProvider.java
+++ 
b/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProvider.java
@@ -33,34 +33,61 @@ import 
org.apache.sling.resourcemerger.spi.MergedResourcePicker;
 
 class MergingResourceProvider implements ResourceProvider {
 
-    private final String mergeRootPath;
+    protected final String mergeRootPath;
 
-    private final MergedResourcePicker picker;
+    protected final MergedResourcePicker picker;
 
-    MergingResourceProvider(String mergeRootPath, MergedResourcePicker picker) 
{
+    private final boolean readOnly;
+
+    MergingResourceProvider(final String mergeRootPath,
+            final MergedResourcePicker picker,
+            final boolean readOnly) {
         this.mergeRootPath = mergeRootPath;
         this.picker = picker;
+        this.readOnly = readOnly;
     }
 
-    private static final class ParentHidingHandler {
+    protected static final class ExcludeEntry {
 
-        private final String[] childrenToHideArray;
+        public final String name;
+        public final boolean exclude;
+
+        public ExcludeEntry(final String value) {
+            if ( value.startsWith("!!") ) {
+                this.name = value.substring(1);
+                this.exclude = false;
+            } else if ( value.startsWith("!") ) {
+                this.name = value.substring(1);
+                this.exclude = true;
+            } else {
+                this.name = value;
+                this.exclude = false;
+            }
+        }
+    }
+
+    protected static final class ParentHidingHandler {
+
+        private List<ExcludeEntry> entries;
 
         public ParentHidingHandler(final Resource parent) {
-            if (parent == null) {
-                this.childrenToHideArray = null;
-            } else {
-                final ValueMap parentProps = parent.getValueMap();
-                this.childrenToHideArray = 
parentProps.get(MergedResourceConstants.PN_HIDE_CHILDREN, String[].class);
+            final ValueMap parentProps = ResourceUtil.getValueMap(parent);
+            final String[] childrenToHideArray = 
parentProps.get(MergedResourceConstants.PN_HIDE_CHILDREN, String[].class);
+            if ( childrenToHideArray != null ) {
+                this.entries = new ArrayList<ExcludeEntry>();
+                for(final String value : childrenToHideArray) {
+                    final ExcludeEntry entry = new ExcludeEntry(value);
+                    this.entries.add(entry);
+                }
             }
         }
 
         public boolean isHidden(final String name) {
             boolean hidden = false;
-            if (this.childrenToHideArray != null) {
-                for (final String entry : childrenToHideArray) {
-                    if (entry.equals("*") || entry.equals(name)) {
-                        hidden = true;
+            if ( this.entries != null ) {
+                for(final ExcludeEntry entry : this.entries) {
+                    if ( entry.name.equals("*") || entry.name.equals(name) ) {
+                        hidden = !entry.exclude;
                         break;
                     }
                 }
@@ -69,7 +96,7 @@ class MergingResourceProvider implements ResourceProvider {
         }
     }
 
-    private static final class ResourceHolder {
+    protected static final class ResourceHolder {
         public final String name;
         public final List<Resource> resources = new ArrayList<Resource>();
         public final List<ValueMap> valueMaps = new ArrayList<ValueMap>();
@@ -104,7 +131,10 @@ class MergingResourceProvider implements ResourceProvider {
 
         if (!holder.resources.isEmpty()) {
             // create a new merged resource based on the list of mapped 
physical resources
-            return new MergedResource(resolver, mergeRootPath, relativePath, 
holder.resources, holder.valueMaps);
+            if ( this.readOnly ) {
+                return new MergedResource(resolver, mergeRootPath, 
relativePath, holder.resources, holder.valueMaps);
+            }
+            return new CRUDMergedResource(resolver, mergeRootPath, 
relativePath, holder.resources, holder.valueMaps, this.picker);
         }
         return null;
     }
@@ -115,7 +145,7 @@ class MergingResourceProvider implements ResourceProvider {
      * @param path Absolute path
      * @return Relative path
      */
-    private String getRelativePath(String path) {
+    protected String getRelativePath(String path) {
         if (path.startsWith(mergeRootPath)) {
             path = path.substring(mergeRootPath.length());
             if (path.length() == 0) {
@@ -130,7 +160,7 @@ class MergingResourceProvider implements ResourceProvider {
     /**
      * {@inheritDoc}
      */
-    public Resource getResource(ResourceResolver resolver, String path) {
+    public Resource getResource(final ResourceResolver resolver, final String 
path) {
         final String relativePath = getRelativePath(path);
 
         if (relativePath != null) {
@@ -143,7 +173,7 @@ class MergingResourceProvider implements ResourceProvider {
             }
 
             while (resources.hasNext()) {
-                Resource resource = resources.next();
+                final Resource resource = resources.next();
                 // check parent for hiding
                 // SLING 3521 : if parent is not readable, nothing is hidden
                 final Resource parent = resource.getParent();
diff --git 
a/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProviderFactory.java
 
b/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProviderFactory.java
index f2a5be1..3382fc7 100644
--- 
a/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProviderFactory.java
+++ 
b/src/main/java/org/apache/sling/resourcemerger/impl/MergingResourceProviderFactory.java
@@ -31,18 +31,30 @@ class MergingResourceProviderFactory implements 
ResourceProviderFactory {
 
     private final MergedResourcePicker picker;
 
-    MergingResourceProviderFactory(String mergeRootPath, MergedResourcePicker 
picker) {
+    private final boolean readOnly;
+
+    MergingResourceProviderFactory(final String mergeRootPath,
+            final MergedResourcePicker picker,
+            final boolean readOnly) {
         this.mergeRootPath = mergeRootPath;
         this.picker = picker;
+        this.readOnly = readOnly;
     }
 
-    public ResourceProvider getResourceProvider(Map<String, Object> 
authenticationInfo) throws LoginException {
-        return new MergingResourceProvider(mergeRootPath, picker);
+    public ResourceProvider getResourceProvider(final Map<String, Object> 
authenticationInfo)
+    throws LoginException {
+        if ( this.readOnly ) {
+            return new MergingResourceProvider(mergeRootPath, picker, 
this.readOnly);
+        }
+        return new CRUDMergedResourceProvider(mergeRootPath, picker);
     }
 
-    public ResourceProvider getAdministrativeResourceProvider(Map<String, 
Object> authenticationInfo)
-            throws LoginException {
-        return new MergingResourceProvider(mergeRootPath, picker);
+    public ResourceProvider getAdministrativeResourceProvider(final 
Map<String, Object> authenticationInfo)
+    throws LoginException {
+        if ( this.readOnly ) {
+            return new MergingResourceProvider(mergeRootPath, picker, 
this.readOnly);
+        }
+        return new CRUDMergedResourceProvider(mergeRootPath, picker);
     }
 
 }
diff --git 
a/src/main/java/org/apache/sling/resourcemerger/spi/MergedResourcePicker.java 
b/src/main/java/org/apache/sling/resourcemerger/spi/MergedResourcePicker.java
index 01179ee..f158470 100644
--- 
a/src/main/java/org/apache/sling/resourcemerger/spi/MergedResourcePicker.java
+++ 
b/src/main/java/org/apache/sling/resourcemerger/spi/MergedResourcePicker.java
@@ -41,10 +41,18 @@ public interface MergedResourcePicker {
     String MERGE_ROOT = "merge.root";
 
     /**
+     * Service property name specifying whether the resources are read-only
+     * or support CRUD operations. If not specified this property defaults
+     * to <code>true</code>. The value of this property must be of type
+     * Boolean.
+     */
+    String READ_ONLY = "merge.readOnly";
+
+    /**
      * 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
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 b888795..cded41b 100644
--- 
a/src/test/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderTest.java
+++ 
b/src/test/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderTest.java
@@ -24,13 +24,15 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
+import org.apache.sling.api.resource.PersistenceException;
 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.ResourceUtil;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.testing.resourceresolver.MockHelper;
 import org.apache.sling.testing.resourceresolver.MockResourceResolverFactory;
@@ -42,7 +44,7 @@ public class MergedResourceProviderTest {
 
     private ResourceResolver resolver;
 
-    private ResourceProvider provider;
+    private CRUDMergedResourceProvider provider;
 
     @Before public void setup() throws Exception {
         final MockResourceResolverFactoryOptions options = new 
MockResourceResolverFactoryOptions();
@@ -63,7 +65,8 @@ public class MergedResourceProviderTest {
                                                            .p("d", "1")
                                             .resource(".X")
                                         .resource("/libs")
-                                          .resource("a")
+                                          .resource("deleteTest")
+                                          .resource(".a")
                                             .resource("1").p("a", "5").p("c", 
"2")
                                             
.resource(".2").p(ResourceResolver.PROPERTY_RESOURCE_TYPE, "libs")
                                             .resource(".3").p("a", "1").p("b", 
"2").p("c", "3")
@@ -72,7 +75,7 @@ public class MergedResourceProviderTest {
                                             .resource(".Z")
                                         .commit();
 
-        this.provider = new MergingResourceProvider("/merged", new 
MergedResourceProviderFactory());
+        this.provider = new CRUDMergedResourceProvider("/merged", new 
MergedResourceProviderFactory());
     }
 
     @Test public void testHideChildren() {
@@ -151,4 +154,78 @@ public class MergedResourceProviderTest {
         assertEquals("2", vm.get("e"));
         assertEquals("x", vm.get("b"));
     }
+
+    @Test public void testSimpleCreateAndDelete() throws PersistenceException {
+        final String path = "/merged/a/new";
+        try {
+            final Resource rsrc = this.provider.create(this.resolver, path, 
Collections.singletonMap("foo", (Object)"bla"));
+            assertNotNull(rsrc);
+            assertEquals(path, rsrc.getPath());
+            final ValueMap vm = ResourceUtil.getValueMap(rsrc);
+            assertEquals("bla", vm.get("foo"));
+
+            final Resource realResource = 
this.resolver.getResource("/apps/a/new");
+            assertNotNull(realResource);
+            final ValueMap vmReal = ResourceUtil.getValueMap(realResource);
+            assertEquals("bla", vmReal.get("foo"));
+            assertNull(this.resolver.getResource("/libs/a/new"));
+
+            this.provider.delete(this.resolver, path);
+            assertNull(this.provider.getResource(this.resolver, path));
+            assertNull(this.resolver.getResource("/libs/a/new"));
+            assertNull(this.resolver.getResource("/apps/a/new"));
+
+        } finally {
+            this.resolver.revert();
+        }
+    }
+
+    @Test public void testDeleteByHiding() throws PersistenceException {
+        final String path = "/merged/deleteTest";
+        try {
+            assertNotNull(this.resolver.getResource("/libs/deleteTest"));
+            assertNull(this.resolver.getResource("/apps/deleteTest"));
+
+            final Resource rsrc = this.provider.getResource(this.resolver, 
path);
+            assertNotNull(rsrc);
+            assertEquals(path, rsrc.getPath());
+
+            this.provider.delete(this.resolver, path);
+
+            assertNull(this.provider.getResource(this.resolver, path));
+            assertNotNull(this.resolver.getResource("/libs/deleteTest"));
+            final Resource hidingRsrc = 
this.resolver.getResource("/apps/deleteTest");
+            assertNotNull(hidingRsrc);
+            final ValueMap vm = hidingRsrc.getValueMap();
+            assertEquals(Boolean.TRUE, 
vm.get(MergedResourceConstants.PN_HIDE_RESOURCE));
+
+        } finally {
+            this.resolver.revert();
+        }
+    }
+
+    @Test public void testDeleteByHidingAndCreate() throws 
PersistenceException {
+        final String path = "/merged/deleteTest";
+        try {
+            assertNotNull(this.resolver.getResource("/libs/deleteTest"));
+            assertNull(this.resolver.getResource("/apps/deleteTest"));
+
+            final Resource rsrc = this.provider.getResource(this.resolver, 
path);
+            assertNotNull(rsrc);
+            assertEquals(path, rsrc.getPath());
+
+            this.provider.delete(this.resolver, path);
+            this.provider.create(this.resolver, path, 
Collections.singletonMap("foo", (Object)"bla"));
+
+            assertNotNull(this.provider.getResource(this.resolver, path));
+            assertNotNull(this.resolver.getResource("/libs/deleteTest"));
+            final Resource hidingRsrc = 
this.resolver.getResource("/apps/deleteTest");
+            assertNotNull(hidingRsrc);
+            final ValueMap vm = hidingRsrc.getValueMap();
+            assertEquals("bla", vm.get("foo"));
+
+        } finally {
+            this.resolver.revert();
+        }
+    }
 }
diff --git 
a/src/test/java/org/apache/sling/resourcemerger/impl/OverridingResourceProviderTest.java
 
b/src/test/java/org/apache/sling/resourcemerger/impl/OverridingResourceProviderTest.java
index ca7c520..814338e 100644
--- 
a/src/test/java/org/apache/sling/resourcemerger/impl/OverridingResourceProviderTest.java
+++ 
b/src/test/java/org/apache/sling/resourcemerger/impl/OverridingResourceProviderTest.java
@@ -18,7 +18,11 @@
  */
 package org.apache.sling.resourcemerger.impl;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -35,7 +39,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 public class OverridingResourceProviderTest {
-    
+
     private static final String SUPER_TYPE = "sling:resourceSuperType";
 
     private ResourceResolver resolver;
@@ -68,7 +72,7 @@ public class OverridingResourceProviderTest {
                     .resource("c").p("1", "c")
                     .commit();
 
-        this.provider = new MergingResourceProvider("/override", new 
OverridingResourcePicker());
+        this.provider = new MergingResourceProvider("/override", new 
OverridingResourcePicker(), true);
     }
 
     @Test

-- 
To stop receiving notification emails like this one, please contact
"[email protected]" <[email protected]>.

Reply via email to