This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.resourcemerger-1.1.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourcemerger.git
commit f043a2386cf2234cef716dc5b1163e369682b7b4 Author: Carsten Ziegeler <[email protected]> AuthorDate: Wed Feb 26 12:57:27 2014 +0000 SLING-3418 : Wrong path handling wrt search paths SLING-3419 : Component name specified instead of component label SLING-3420 : Implement ModifyingResourceProvider - first version git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/resourcemerger@1572039 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 5 + .../sling/resourcemerger/impl/MergedResource.java | 39 ++- .../impl/MergedResourceProvider.java | 314 +++++++++++++++------ .../impl/MergedResourceProviderFactory.java | 2 +- .../impl/MergedResourceProviderTest.java | 55 +++- 5 files changed, 321 insertions(+), 94 deletions(-) diff --git a/pom.xml b/pom.xml index 476da85..7778442 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,11 @@ <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/MergedResource.java b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResource.java index f799c6c..34c98af 100644 --- a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResource.java +++ b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResource.java @@ -21,9 +21,12 @@ package org.apache.sling.resourcemerger.impl; import java.util.List; import org.apache.sling.api.resource.AbstractResource; +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.ResourceMetadata; import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; import org.apache.sling.api.resource.ValueMap; /** @@ -44,7 +47,10 @@ public class MergedResource extends AbstractResource { private final ResourceMetadata metadata = new ResourceMetadata(); /** Cache value map. */ - private ValueMap properties; + private final ValueMap properties; + + /** Root path */ + private final String mergedRootPath; /** * Constructor @@ -58,7 +64,8 @@ public class MergedResource extends AbstractResource { final String mergeRootPath, final String relativePath, final List<Resource> mappedResources, - final List<ValueMap> valueMaps) { + final List<ValueMap> valueMaps, + final String mergedRootPath) { this.resolver = resolver; this.path = (relativePath.length() == 0 ? mergeRootPath : mergeRootPath + "/" + relativePath); this.properties = new MergedValueMap(valueMaps); @@ -71,6 +78,7 @@ public class MergedResource extends AbstractResource { i++; } metadata.put(MergedResourceConstants.METADATA_RESOURCES, resourcePaths); + this.mergedRootPath = mergedRootPath; } /** @@ -115,11 +123,34 @@ public class MergedResource extends AbstractResource { */ @Override @SuppressWarnings("unchecked") - public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) { + public <AdapterType> AdapterType adaptTo(final Class<AdapterType> type) { if (type == ValueMap.class) { return (AdapterType) this.properties; } - + if (type == ModifiableValueMap.class) { + final String paths[] = (String[])this.metadata.get(MergedResourceConstants.METADATA_RESOURCES); + final String[] searchPaths = resolver.getSearchPath(); + final String lastSearchPath = searchPaths[searchPaths.length-1]; + + if ( paths.length == 1 && paths[0].startsWith(lastSearchPath) ) { + final Resource copyResource = resolver.getResource(paths[0]); + if ( searchPaths.length == 1 ) { + return (AdapterType)copyResource.adaptTo(ModifiableValueMap.class); + } + final String prefix = searchPaths[searchPaths.length-2]; + final String createPath = prefix + path.substring(this.mergedRootPath.length() + 1); + try { + final Resource newResource = ResourceUtil.getOrCreateResource(resolver, ResourceUtil.getParent(createPath),copyResource.getResourceType(), null, false); + return (AdapterType)newResource.adaptTo(ModifiableValueMap.class); + } catch ( final PersistenceException pe) { + // we ignore this for now + return null; + } + } + final String resourcePath = paths[paths.length-1]; + final Resource rsrc = resolver.getResource(resourcePath); + return (AdapterType)rsrc.adaptTo(ModifiableValueMap.class); + } return super.adaptTo(type); } diff --git a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProvider.java b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProvider.java index 5f86a32..ece03c0 100644 --- a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProvider.java +++ b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProvider.java @@ -19,11 +19,15 @@ package org.apache.sling.resourcemerger.impl; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import javax.servlet.http.HttpServletRequest; +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.ResourceProvider; import org.apache.sling.api.resource.ResourceResolver; @@ -34,7 +38,8 @@ import org.apache.sling.api.resource.ValueMap; * The <code>MergedResourceProvider</code> is the resource provider providing * access to {@link MergedResource} objects. */ -public class MergedResourceProvider implements ResourceProvider { +public class MergedResourceProvider + implements ResourceProvider, ModifyingResourceProvider { private final String mergeRootPath; @@ -49,6 +54,52 @@ public class MergedResourceProvider implements ResourceProvider { return getResource(resolver, path); } + private static final class ExcludeEntry { + + 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; + } + } + } + + private static final class ParentHidingHandler { + + private final List<ExcludeEntry> entries = new ArrayList<MergedResourceProvider.ExcludeEntry>(); + + public ParentHidingHandler(final Resource parent) { + final ValueMap parentProps = ResourceUtil.getValueMap(parent); + final String[] childrenToHideArray = parentProps.get(MergedResourceConstants.PN_HIDE_CHILDREN, String[].class); + if ( childrenToHideArray != null ) { + for(final String value : childrenToHideArray) { + final ExcludeEntry entry = new ExcludeEntry(value); + this.entries.add(entry); + } + } + } + + public boolean isHidden(final String name) { + boolean hidden = false; + for(final ExcludeEntry entry : this.entries) { + if ( entry.name.equals("*") || entry.name.equals(name) ) { + hidden = !entry.exclude; + break; + } + } + return hidden; + } + } + /** * {@inheritDoc} */ @@ -64,22 +115,12 @@ public class MergedResourceProvider implements ResourceProvider { final String basePath = searchPaths[i]; // Try to get the corresponding physical resource for this base path - final String fullPath = basePath + "/" + relativePath; + final String fullPath = basePath + relativePath; // check parent for hiding final Resource parent = resolver.getResource(ResourceUtil.getParent(fullPath)); if ( parent != null ) { - boolean hidden = false; - final ValueMap parentProps = ResourceUtil.getValueMap(parent); - final String[] childrenToHideArray = parentProps.get(MergedResourceConstants.PN_HIDE_CHILDREN, String[].class); - if ( childrenToHideArray != null ) { - for(final String name : childrenToHideArray ) { - if ( name.equals(holder.name) || name.equals("*") ) { - hidden = true; - break; - } - } - } + final boolean hidden = new ParentHidingHandler(parent).isHidden(holder.name); if ( hidden ) { holder.resources.clear(); } else { @@ -116,39 +157,23 @@ public class MergedResourceProvider implements ResourceProvider { while ( index < holder.resources.size() ) { final Resource baseRes = holder.resources.get(index); // check if resource is hidden - boolean hidden = false; final ValueMap props = ResourceUtil.getValueMap(baseRes); holder.valueMaps.add(props); if ( props.get(MergedResourceConstants.PN_HIDE_RESOURCE, Boolean.FALSE) ) { - hidden = true; - } - if ( !hidden ) { - // check parent - final ValueMap parentProps = ResourceUtil.getValueMap(baseRes.getParent()); - final String[] childrenToHideArray = parentProps.get(MergedResourceConstants.PN_HIDE_CHILDREN, String[].class); - if ( childrenToHideArray != null ) { - for(final String name : childrenToHideArray ) { - if ( name.equals(baseRes.getName()) || name.equals("*") ) { - hidden = true; - break; - } - } - } - } - if ( hidden ) { // clear everything up to now for(int i=0;i<=index;i++) { holder.resources.remove(0); } holder.valueMaps.clear(); - index = -1; // start at zero + index = 0; // start at zero + } else { + index++; } - index++; } 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); + return new MergedResource(resolver, mergeRootPath, relativePath, holder.resources, holder.valueMaps, this.mergeRootPath); } return null; } @@ -168,69 +193,51 @@ public class MergedResourceProvider implements ResourceProvider { 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); + final Resource parentResource = resolver.getResource(basePath + relativePath); if ( parentResource != null ) { - final ValueMap parentProps = ResourceUtil.getValueMap(parentResource); - List<String> childrenToHide = new ArrayList<String>(); - boolean hideAll = false; - final String[] childrenToHideArray = parentProps.get(MergedResourceConstants.PN_HIDE_CHILDREN, String[].class); - if ( childrenToHideArray != null ) { - for(final String name : childrenToHideArray ) { - if ( name.equals("*") ) { - hideAll = true; - } else { - childrenToHide.add(name); + 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 ( hideAll ) { - candidates.clear(); - } else { - 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; + 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 = ResourceUtil.getValueMap(child); + 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 = ResourceUtil.getValueMap(child); - 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); } - if ( childrenToHide.size() > 0 ) { - final Iterator<ResourceHolder> iter = candidates.iterator(); - while ( iter.hasNext() ) { - final ResourceHolder holder = iter.next(); - if ( childrenToHide.contains(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(); } } } @@ -265,4 +272,135 @@ public class MergedResourceProvider implements ResourceProvider { } return null; } + + private ResourceHolder getAllResources(final ResourceResolver resolver, + final String path, + final String relativePath) { + 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 + final Resource parent = resolver.getResource(ResourceUtil.getParent(fullPath)); + if ( parent != null ) { + 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 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 { + // we only support modifications if there is more than one search path + final String[] searchPaths = resolver.getSearchPath(); + if ( searchPaths.length < 2 ) { + throw new PersistenceException("Modifying is only supported with at least two search paths", null, path, null); + } + // 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 String lastSearchPath = searchPaths[searchPaths.length-1]; + final ResourceHolder holder = this.getAllResources(resolver, path, relativePath); + if ( holder.resources.size() == 0 || holder.resources.size() == 1 && holder.resources.get(0).getPath().startsWith(lastSearchPath) ) { + final String useSearchPath = searchPaths[searchPaths.length-2]; + + final String createPath = useSearchPath + path.substring(this.mergeRootPath.length() + 1); + final Resource parentResource = ResourceUtil.getOrCreateResource(resolver, ResourceUtil.getParent(createPath), (String)null, null, false); + resolver.create(parentResource, ResourceUtil.getName(createPath), properties); + } + // TODO check hiding flag + 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 { + // we only support modifications if there is more than one search path + final String[] searchPaths = resolver.getSearchPath(); + if ( searchPaths.length < 2 ) { + throw new PersistenceException("Modifying is only supported with at least two search paths"); + } + // 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 ResourceHolder holder = this.getAllResources(resolver, path, relativePath); + final String lastSearchPath = searchPaths[searchPaths.length-1]; + + int deleted = 0; + for(final Resource rsrc : holder.resources) { + final String p = rsrc.getPath(); + if ( !p.startsWith(lastSearchPath) ) { + resolver.delete(rsrc); + deleted++; + } + } + if ( deleted < holder.resources.size() ) { + // create overlay resource which is hiding the other + final String prefix = searchPaths[searchPaths.length-2]; + final String createPath = prefix + path.substring(this.mergeRootPath.length() + 1); + 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 search paths 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 search paths 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 search paths will return in case of changes + return false; + } } 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 e093a45..5cbdfb2 100644 --- a/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderFactory.java +++ b/src/main/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderFactory.java @@ -33,7 +33,7 @@ import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.commons.osgi.PropertiesUtil; import org.apache.sling.resourcemerger.api.ResourceMergerService; -@Component(name = "Apache Sling Merged Resource Provider Factory", +@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}) 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 08bbab2..0034a79 100644 --- a/src/test/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderTest.java +++ b/src/test/java/org/apache/sling/resourcemerger/impl/MergedResourceProviderTest.java @@ -20,18 +20,23 @@ package org.apache.sling.resourcemerger.impl; import static org.junit.Assert.assertEquals; 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.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.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; +import org.apache.sling.testing.resourceresolver.MockResourceResolverFactoryOptions; import org.junit.Before; import org.junit.Test; @@ -42,7 +47,9 @@ public class MergedResourceProviderTest { private MergedResourceProvider provider; @Before public void setup() throws Exception { - final ResourceResolverFactory factory = new MockResourceResolverFactory(); + 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").p(MergedResourceConstants.PN_HIDE_CHILDREN, new String[] {"Z", "x", "y"}) @@ -70,6 +77,27 @@ public class MergedResourceProviderTest { this.provider = new MergedResourceProvider("/merged"); } + @Test public void testHideChildren() { + // check preconditions in libs and apps + assertNotNull(this.resolver.getResource("/libs/a/Z")); + assertNull(this.resolver.getResource("/libs/a/X")); + assertNotNull(this.resolver.getResource("/libs/a/Y")); + assertNull(this.resolver.getResource("/libs/a/x")); + assertNull(this.resolver.getResource("/libs/a/y")); + assertNull(this.resolver.getResource("/apps/a/Z")); + assertNotNull(this.resolver.getResource("/apps/a/X")); + assertNull(this.resolver.getResource("/apps/a/Y")); + assertNull(this.resolver.getResource("/apps/a/x")); + assertNull(this.resolver.getResource("/apps/a/y")); + + // now do the real checks + assertNull(this.provider.getResource(this.resolver, "/merged/a/Z")); + assertNotNull(this.provider.getResource(this.resolver, "/merged/a/Y")); + assertNotNull(this.provider.getResource(this.resolver, "/merged/a/X")); + assertNull(this.provider.getResource(this.resolver, "/merged/a/x")); + assertNull(this.provider.getResource(this.resolver, "/merged/a/y")); + } + @Test public void testListChildren() { final Resource rsrcA = this.provider.getResource(this.resolver, "/merged/a"); assertNotNull(rsrcA); @@ -125,4 +153,29 @@ 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(); + } + } } -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
