You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2017/08/25 15:21:20 UTC

svn commit: r1806184 - in /sling/trunk/bundles/extensions/bundleresource: ./ src/main/java/org/apache/sling/bundleresource/impl/

Author: cziegeler
Date: Fri Aug 25 15:21:20 2017
New Revision: 1806184

URL: http://svn.apache.org/viewvc?rev=1806184&view=rev
Log:
SLING-6878 : Bundle resource provider: support mounting of JSON files

Modified:
    sling/trunk/bundles/extensions/bundleresource/README.txt
    sling/trunk/bundles/extensions/bundleresource/pom.xml
    sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/Activator.java
    sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResource.java
    sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceCache.java
    sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceIterator.java
    sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceProvider.java
    sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/MappedPath.java

Modified: sling/trunk/bundles/extensions/bundleresource/README.txt
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/bundleresource/README.txt?rev=1806184&r1=1806183&r2=1806184&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/bundleresource/README.txt (original)
+++ sling/trunk/bundles/extensions/bundleresource/README.txt Fri Aug 25 15:21:20 2017
@@ -6,16 +6,16 @@ based resources.
 Getting Started
 ===============
 
-This component uses a Maven 2 (http://maven.apache.org/) build
-environment. It requires a Java 5 JDK (or higher) and Maven (http://maven.apache.org/)
-2.0.7 or later. We recommend to use the latest Maven version.
+This component uses a Maven 3 (http://maven.apache.org/) build
+environment. It requires a Java 7 JDK (or higher) and Maven (http://maven.apache.org/)
+3.5.0 or later. We recommend to use the latest Maven version.
 
-If you have Maven 2 installed, you can compile and
+If you have Maven 3 installed, you can compile and
 package the jar using the following command:
 
     mvn package
 
-See the Maven 2 documentation for other build features.
+See the Maven 3 documentation for other build features.
 
 The latest source code for this component is available in the
 Subversion (http://subversion.tigris.org/) source repository of

Modified: sling/trunk/bundles/extensions/bundleresource/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/bundleresource/pom.xml?rev=1806184&r1=1806183&r2=1806184&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/bundleresource/pom.xml (original)
+++ sling/trunk/bundles/extensions/bundleresource/pom.xml Fri Aug 25 15:21:20 2017
@@ -78,6 +78,10 @@
     </build>
     <dependencies>
         <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
@@ -88,6 +92,12 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-json_1.0_spec</artifactId>
+            <version>1.0-alpha-1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.osgi</artifactId>
             <version>2.0.6</version>
@@ -97,6 +107,7 @@
             <groupId>org.osgi</groupId>
             <artifactId>osgi.core</artifactId>
         </dependency>
+        <!--  This is only used for some constants, no runtime dependency -->
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-jcr-commons</artifactId>
@@ -105,11 +116,23 @@
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
+            <artifactId>slf4j-simple</artifactId>
         </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.10.19</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.johnzon</groupId>
+            <artifactId>johnzon-core</artifactId>
+            <version>1.0.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>

Modified: sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/Activator.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/Activator.java?rev=1806184&r1=1806183&r2=1806184&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/Activator.java (original)
+++ sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/Activator.java Fri Aug 25 15:21:20 2017
@@ -120,7 +120,7 @@ public class Activator implements Bundle
                         new Object[] { prefixes, bundle.getSymbolicName(), bundle.getVersion(),
                             bundle.getBundleId() });
 
-                    final MappedPath[] roots = BundleResourceProvider.getRoots(bundle, prefixes);
+                    final MappedPath[] roots = MappedPath.getRoots(prefixes);
                     providers = new BundleResourceProvider[roots.length];
 
                     int index = 0;

Modified: sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResource.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResource.java?rev=1806184&r1=1806183&r2=1806184&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResource.java (original)
+++ sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResource.java Fri Aug 25 15:21:20 2017
@@ -21,14 +21,27 @@ import static org.apache.jackrabbit.JcrC
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.StringWriter;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonString;
+import javax.json.JsonValue;
 
 import org.apache.sling.api.resource.AbstractResource;
 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.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -40,103 +53,124 @@ public class BundleResource extends Abst
 
     private final ResourceResolver resourceResolver;
 
-    private final BundleResourceCache bundle;
+    private final BundleResourceCache cache;
 
     private final MappedPath mappedPath;
 
     private final String path;
 
-    private URL url;
-
-    private final String resourceType;
+    private URL resourceUrl;
 
     private final ResourceMetadata metadata;
 
-    public static BundleResource getResource(ResourceResolver resourceResolver,
-            BundleResourceCache bundle, MappedPath mappedPath,
-            String resourcePath) {
-
-        String entryPath = mappedPath.getEntryPath(resourcePath);
-
-        // first try, whether the bundle has an entry with a trailing slash
-        // which would be a folder. In this case we check whether the
-        // repository contains an item with the same path. If so, we
-        // don't create a BundleResource but instead return null to be
-        // able to return an item-based resource
-        URL entry = bundle.getEntry(entryPath.concat("/"));
-        if (entry != null) {
-
-            // append the slash to path for next steps
-            resourcePath = resourcePath.concat("/");
-        }
-
-        // if there is no entry with a trailing slash, try plain name
-        // which would then of course be a file
-        if (entry == null) {
-            entry = bundle.getEntry(entryPath);
-        }
-
-        // here we either have a folder for which no same-named item exists
-        // or a bundle file
-        if (entry != null) {
-            return new BundleResource(resourceResolver, bundle, mappedPath,
-                    resourcePath);
-        }
+    private final ValueMap valueMap;
 
-        // the bundle does not contain the path
-        return null;
-    }
-
-    public BundleResource(ResourceResolver resourceResolver,
-            BundleResourceCache bundle, MappedPath mappedPath,
-            String resourcePath) {
+    public BundleResource(final ResourceResolver resourceResolver,
+            final BundleResourceCache cache,
+            final MappedPath mappedPath,
+            final String resourcePath,
+            final String propsPath,
+            final boolean isFolder) {
 
         this.resourceResolver = resourceResolver;
-        this.bundle = bundle;
+        this.cache = cache;
         this.mappedPath = mappedPath;
 
         metadata = new ResourceMetadata();
         metadata.setResolutionPath(resourcePath);
-        metadata.setCreationTime(bundle.getBundle().getLastModified());
-        metadata.setModificationTime(bundle.getBundle().getLastModified());
+        metadata.setCreationTime(this.cache.getBundle().getLastModified());
+        metadata.setModificationTime(this.cache.getBundle().getLastModified());
+
+        this.path = resourcePath;
 
-        if (resourcePath.endsWith("/")) {
+        final Map<String, Object> properties = new HashMap<>();
+        this.valueMap = new ValueMapDecorator(Collections.unmodifiableMap(properties));
+        if (isFolder) {
 
-            this.path = resourcePath.substring(0, resourcePath.length() - 1);
-            this.resourceType = NT_FOLDER;
-            metadata.put(ResourceMetadata.INTERNAL_CONTINUE_RESOLVING, Boolean.TRUE);
+            properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, NT_FOLDER);
 
         } else {
 
-            this.path = resourcePath;
-            this.resourceType = NT_FILE;
+            properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, NT_FILE);
 
             try {
-                URL url = bundle.getEntry(mappedPath.getEntryPath(resourcePath));
-                metadata.setContentLength(url.openConnection().getContentLength());
-            } catch (Exception e) {
+                final URL url = this.cache.getEntry(mappedPath.getEntryPath(resourcePath));
+                if ( url != null ) {
+                    metadata.setContentLength(url.openConnection().getContentLength());
+                }
+            } catch (final Exception e) {
                 // don't care, we just have no content length
             }
         }
+
+        if ( propsPath != null ) {
+            try {
+                final URL url = this.cache.getEntry(mappedPath.getEntryPath(propsPath));
+                if (url != null) {
+                    final JsonObject obj = Json.createReader(url.openStream()).readObject();
+                    for(final Map.Entry<String, JsonValue> entry : obj.entrySet()) {
+                        final Object value = getValue(entry.getValue());
+                        if ( value != null ) {
+                            properties.put(entry.getKey(), value);
+                        }
+                    }
+                }
+            } catch (final IOException ioe) {
+                log.error(
+                        "getInputStream: Cannot get input stream for " + mappedPath.getEntryPath(propsPath), ioe);
+            }
+
+        }
+    }
+
+    private static Object getValue(final JsonValue value) {
+        switch ( value.getValueType() ) {
+            // type NULL -> return null
+            case NULL : return null;
+            // type TRUE or FALSE -> return boolean
+            case FALSE : return false;
+            case TRUE : return true;
+            // type String -> return String
+            case STRING : return ((JsonString)value).getString();
+            // type Number -> return long or double
+            case NUMBER : final JsonNumber num = (JsonNumber)value;
+                          if (num.isIntegral()) {
+                               return num.longValue();
+                          }
+                          return num.doubleValue();
+            // type ARRAY -> return JSON string
+            case ARRAY : final StringWriter writer = new StringWriter();
+                         Json.createWriter(writer).writeArray((JsonArray)value);
+                         return writer.toString();
+             // type OBJECT -> return JSON string
+             case OBJECT : final StringWriter mapWriter = new StringWriter();
+                           Json.createWriter(mapWriter).writeObject((JsonObject)value);
+                           return mapWriter.toString();
+        }
+        return null;
     }
 
+    @Override
     public String getPath() {
         return path;
     }
 
+    @Override
     public String getResourceType() {
-        return resourceType;
+        return this.valueMap.get(ResourceResolver.PROPERTY_RESOURCE_TYPE, String.class);
     }
 
-    /** Returns <code>null</code>, bundle resources have no super type */
+    @Override
     public String getResourceSuperType() {
-        return null;
+        return this.valueMap.get("sling:resourceSuperType", String.class);
     }
 
+    @Override
     public ResourceMetadata getResourceMetadata() {
         return metadata;
     }
 
+    @Override
     public ResourceResolver getResourceResolver() {
         return resourceResolver;
     }
@@ -148,9 +182,11 @@ public class BundleResource extends Abst
             return (Type) getInputStream(); // unchecked cast
         } else if (type == URL.class) {
             return (Type) getURL(); // unchecked cast
+        } else if (type == ValueMap.class) {
+            return (Type) valueMap; // unchecked cast
         }
 
-        // fall back to nothing
+        // fall back to adapter factories
         return super.adaptTo(type);
     }
 
@@ -185,17 +221,20 @@ public class BundleResource extends Abst
     }
 
     private URL getURL() {
-        if (url == null) {
-            try {
-                url = new URL(BundleResourceURLStreamHandler.PROTOCOL, null,
-                        -1, path, new BundleResourceURLStreamHandler(
-                                bundle.getBundle(), mappedPath.getEntryPath(path)));
-            } catch (MalformedURLException mue) {
-                log.error("getURL: Cannot get URL for " + this, mue);
+        if (resourceUrl == null) {
+            final URL url = this.cache.getEntry(mappedPath.getEntryPath(this.path));
+            if ( url != null ) {
+                try {
+                    resourceUrl = new URL(BundleResourceURLStreamHandler.PROTOCOL, null,
+                            -1, path, new BundleResourceURLStreamHandler(
+                                    cache.getBundle(), mappedPath.getEntryPath(path)));
+                } catch (MalformedURLException mue) {
+                    log.error("getURL: Cannot get URL for " + this, mue);
+                }
             }
         }
 
-        return url;
+        return resourceUrl;
     }
 
     @Override
@@ -204,7 +243,7 @@ public class BundleResource extends Abst
     }
 
     BundleResourceCache getBundle() {
-        return bundle;
+        return cache;
     }
 
     MappedPath getMappedPath() {

Modified: sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceCache.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceCache.java?rev=1806184&r1=1806183&r2=1806184&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceCache.java (original)
+++ sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceCache.java Fri Aug 25 15:21:20 2017
@@ -166,10 +166,9 @@ class BundleResourceCache {
         List<String> list = listCache.get(path);
         if (list == null) {
 
-            @SuppressWarnings("unchecked")
             Enumeration<String> entries = bundle.getEntryPaths(path);
             if (entries != null && entries.hasMoreElements()) {
-                list = new LinkedList<String>();
+                list = new LinkedList<>();
                 while (entries.hasMoreElements()) {
                     list.add(entries.nextElement());
                 }

Modified: sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceIterator.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceIterator.java?rev=1806184&r1=1806183&r2=1806184&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceIterator.java (original)
+++ sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceIterator.java Fri Aug 25 15:21:20 2017
@@ -56,7 +56,7 @@ class BundleResourceIterator implements
     /**
      * Creates an instance using the given parent bundle resource.
      */
-    BundleResourceIterator(BundleResource parent) {
+    BundleResourceIterator(final BundleResource parent) {
 
         if (parent.isFile()) {
 
@@ -75,9 +75,9 @@ class BundleResourceIterator implements
             this.resourceResolver = parent.getResourceResolver();
             this.bundle = parent.getBundle();
             this.mappedPath = parent.getMappedPath();
-            
+
             parentPath = mappedPath.getEntryPath(parentPath);
-            
+
             this.entries = parent.getBundle().getEntryPaths(parentPath);
             this.prefixLength = parentPath.length();
 
@@ -103,11 +103,13 @@ class BundleResourceIterator implements
     }
 
     /** Returns true if there is another Resource available */
+    @Override
     public boolean hasNext() {
         return nextResult != null;
     }
 
     /** Returns the next resource in the iterator */
+    @Override
     public Resource next() {
         if (!hasNext()) {
             throw new NoSuchElementException();
@@ -122,6 +124,7 @@ class BundleResourceIterator implements
      * Throws <code>UnsupportedOperationException</code> as this method is not
      * supported by this implementation.
      */
+    @Override
     public void remove() {
         throw new UnsupportedOperationException();
     }
@@ -136,14 +139,22 @@ class BundleResourceIterator implements
 
             // require leading slash
             if (!entry.startsWith("/")) {
-                entry = "/" + entry;
+                entry = "/".concat(entry);
             }
 
             int slash = entry.indexOf('/', prefixLength);
-            if (slash < 0 || slash == entry.length() - 1) {
+            if ((slash < 0 || slash == entry.length() - 1)
+                && (mappedPath.getJSONPropertiesExtension() == null || !entry.endsWith(mappedPath.getJSONPropertiesExtension()))) {
                 log.debug("seek: Using entry {}", entry);
+                final boolean isFolder = entry.endsWith("/");
+                String propsPath = null;
+                if ( mappedPath.getJSONPropertiesExtension() != null ) {
+                    propsPath = entry.concat(mappedPath.getJSONPropertiesExtension());
+                }
                 return new BundleResource(resourceResolver, bundle, mappedPath,
-                    entry);
+                        isFolder ? entry.substring(0, entry.length()-1): entry,
+                        propsPath,
+                        isFolder);
             }
 
             log.debug("seek: Ignoring entry {}", entry);

Modified: sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceProvider.java?rev=1806184&r1=1806183&r2=1806184&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceProvider.java (original)
+++ sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/BundleResourceProvider.java Fri Aug 25 15:21:20 2017
@@ -18,15 +18,12 @@
  */
 package org.apache.sling.bundleresource.impl;
 
-import java.util.ArrayList;
-import java.util.Collections;
+import java.net.URL;
 import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.Iterator;
-import java.util.List;
 
 import org.apache.sling.api.resource.Resource;
-import org.apache.sling.commons.osgi.ManifestHeader;
 import org.apache.sling.spi.resource.provider.ResolveContext;
 import org.apache.sling.spi.resource.provider.ResourceContext;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
@@ -38,8 +35,8 @@ public class BundleResourceProvider exte
 
     public static final String PROP_BUNDLE = BundleResourceProvider.class.getName();
 
-    /** The bundle providing the resources */
-    private final BundleResourceCache bundle;
+    /** The cache with the bundle providing the resources */
+    private final BundleResourceCache cache;
 
     /** The root path */
     private final MappedPath root;
@@ -47,49 +44,35 @@ public class BundleResourceProvider exte
     @SuppressWarnings("rawtypes")
     private volatile ServiceRegistration<ResourceProvider> serviceRegistration;
 
-    public static MappedPath[] getRoots(final Bundle bundle, final String rootList) {
-        List<MappedPath> prefixList = new ArrayList<>();
-
-        final ManifestHeader header = ManifestHeader.parse(rootList);
-        for (final ManifestHeader.Entry entry : header.getEntries()) {
-            final String resourceRoot = entry.getValue();
-            final String pathDirective = entry.getDirectiveValue("path");
-            if (pathDirective != null) {
-                prefixList.add(new MappedPath(resourceRoot, pathDirective));
-            } else {
-                prefixList.add(MappedPath.create(resourceRoot));
-            }
-        }
-       return prefixList.toArray(new MappedPath[prefixList.size()]);
-    }
-
     /**
      * Creates Bundle resource provider accessing entries in the given Bundle an
      * supporting resources below root paths given by the rootList which is a
      * comma (and whitespace) separated list of absolute paths.
      */
     public BundleResourceProvider(final Bundle bundle, final MappedPath root) {
-        this.bundle = new BundleResourceCache(bundle);
+        this.cache = new BundleResourceCache(bundle);
         this.root = root;
     }
 
     //---------- Service Registration
 
     long registerService() {
+        final Bundle bundle = this.cache.getBundle();
         final Dictionary<String, Object> props = new Hashtable<>();
         props.put(Constants.SERVICE_DESCRIPTION,
-            "Provider of bundle based resources");
+            "Provider of bundle based resources from bundle " + String.valueOf(bundle.getBundleId()));
         props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
-        props.put(ResourceProvider.PROPERTY_ROOT, getRoot());
-        props.put(PROP_BUNDLE, this.bundle.getBundle().getBundleId());
+        props.put(ResourceProvider.PROPERTY_ROOT, this.root.getResourceRoot());
+        props.put(PROP_BUNDLE,bundle.getBundleId());
 
-        serviceRegistration = this.bundle.getBundle().getBundleContext().registerService(ResourceProvider.class, this, props);
+        serviceRegistration = bundle.getBundleContext().registerService(ResourceProvider.class, this, props);
         return (Long) serviceRegistration.getReference().getProperty(Constants.SERVICE_ID);
     }
 
     void unregisterService() {
         if (serviceRegistration != null) {
             serviceRegistration.unregister();
+            serviceRegistration = null;
         }
     }
 
@@ -106,39 +89,78 @@ public class BundleResourceProvider exte
             final Resource parent) {
         final MappedPath mappedPath = getMappedPath(path);
         if (mappedPath != null) {
-            return BundleResource.getResource(ctx.getResourceResolver(), bundle,
-                mappedPath, path);
+            final String entryPath = mappedPath.getEntryPath(path);
+
+            // first try, whether the bundle has an entry with a trailing slash
+            // which would be a folder. In this case we check whether the
+            // repository contains an item with the same path. If so, we
+            // don't create a BundleResource but instead return null to be
+            // able to return an item-based resource
+            URL entry = cache.getEntry(entryPath.concat("/"));
+            final boolean isFolder = entry != null;
+
+            // if there is no entry with a trailing slash, try plain name
+            // which would then of course be a file
+            if (entry == null) {
+                entry = cache.getEntry(entryPath);
+                if ( entry == null && this.root.getJSONPropertiesExtension() != null ) {
+                    entry = cache.getEntry(entryPath + this.root.getJSONPropertiesExtension());
+                }
+            }
+
+            // here we either have a folder for which no same-named item exists
+            // or a bundle file
+            if (entry != null) {
+                // check if a JSON props file is directly requested
+                // if so, we deny the access
+                if ( this.root.getJSONPropertiesExtension() == null
+                     || !entryPath.endsWith(this.root.getJSONPropertiesExtension()) ) {
+
+                    String propsPath = null;
+                    if ( this.root.getJSONPropertiesExtension() != null ) {
+                        propsPath = entryPath.concat(this.root.getJSONPropertiesExtension());
+                    }
+                    return new BundleResource(ctx.getResourceResolver(),
+                            cache,
+                            mappedPath,
+                            path,
+                            propsPath,
+                            isFolder);
+                }
+            }
+
+            // the bundle does not contain the path
         }
 
         return null;
     }
 
     @Override
-    public Iterator<Resource> listChildren(ResolveContext<Object> ctx, Resource parent) {
-     	if (parent instanceof BundleResource && ((BundleResource)parent).getBundle() == this.bundle) {
+    public Iterator<Resource> listChildren(final ResolveContext<Object> ctx, final Resource parent) {
+     	if (parent instanceof BundleResource && ((BundleResource)parent).getBundle() == this.cache) {
             // bundle resources can handle this request directly when the parent
-    		//  resource is in the same bundle as this provider.
+    		    // resource is in the same bundle as this provider.
             return ((BundleResource) parent).listChildren();
-    	}
+      	}
 
         // ensure this provider may have children of the parent
         String parentPath = parent.getPath();
         MappedPath mappedPath = getMappedPath(parentPath);
         if (mappedPath != null) {
             return new BundleResourceIterator(parent.getResourceResolver(),
-                bundle, mappedPath, parentPath);
+                cache, mappedPath, parentPath);
         }
 
         // the parent resource cannot have children in this provider,
         // though this is basically not expected, we still have to
         // be prepared for such a situation
-        return Collections.<Resource> emptyList().iterator();
+        return null;
     }
 
     // ---------- Web Console plugin support
 
     BundleResourceCache getBundleResourceCache() {
-        return bundle;
+        return cache;
     }
 
     MappedPath getMappedPath() {
@@ -147,17 +169,11 @@ public class BundleResourceProvider exte
 
     // ---------- internal
 
-    /** Returns the root path */
-    private String getRoot() {
-        return this.root.getResourceRoot();
-    }
-
-    private MappedPath getMappedPath(String resourcePath) {
+    private MappedPath getMappedPath(final String resourcePath) {
         if (this.root.isChild(resourcePath)) {
             return root;
         }
 
         return null;
     }
-
 }

Modified: sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/MappedPath.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/MappedPath.java?rev=1806184&r1=1806183&r2=1806184&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/MappedPath.java (original)
+++ sling/trunk/bundles/extensions/bundleresource/src/main/java/org/apache/sling/bundleresource/impl/MappedPath.java Fri Aug 25 15:21:20 2017
@@ -18,15 +18,44 @@
  */
 package org.apache.sling.bundleresource.impl;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sling.commons.osgi.ManifestHeader;
+
 class MappedPath {
 
+    public static final String DIR_PATH = "path";
+    public static final String DIR_JSON = "propsJSON";
+
     private static final char prefixSeparatorChar = '!';
     private final String resourceRoot;
     private final String resourceRootPrefix;
     private final String entryRoot;
     private final String entryRootPrefix;
 
-    static MappedPath create(String configPath) {
+    private final String jsonExpandExtension;
+
+    public static MappedPath[] getRoots(final String rootList) {
+        List<MappedPath> prefixList = new ArrayList<>();
+
+        final ManifestHeader header = ManifestHeader.parse(rootList);
+        for (final ManifestHeader.Entry entry : header.getEntries()) {
+            final String resourceRoot = entry.getValue();
+            final String pathDirective = entry.getDirectiveValue(DIR_PATH);
+            final String expandDirective = entry.getDirectiveValue(DIR_JSON);
+            if (pathDirective != null) {
+                prefixList.add(new MappedPath(resourceRoot, pathDirective, expandDirective));
+            } else {
+                prefixList.add(MappedPath.create(resourceRoot, expandDirective));
+            }
+        }
+        return prefixList.toArray(new MappedPath[prefixList.size()]);
+    }
+
+
+    static MappedPath create(final String configPath,
+            final String expandDirective) {
         String resourceRoot;
         String entryRoot;
         int prefixSep = configPath.indexOf(prefixSeparatorChar);
@@ -37,63 +66,94 @@ class MappedPath {
             resourceRoot = configPath;
             entryRoot = null;
         }
-        return new MappedPath(resourceRoot, entryRoot);
+        return new MappedPath(resourceRoot, entryRoot, expandDirective);
     }
-    
-    MappedPath(String resourceRoot, String entryRoot) {
-        this.resourceRoot = resourceRoot;
+
+    MappedPath(final String resourceRoot,
+            final String entryRoot,
+            final String expandDirective) {
+        this.resourceRoot = ensureNoTrailingSlash(resourceRoot);
         this.resourceRootPrefix = ensureTrailingSlash(resourceRoot);
-        this.entryRoot = entryRoot;
+        this.entryRoot = ensureNoTrailingSlash(entryRoot);
         this.entryRootPrefix = ensureTrailingSlash(entryRoot);
+        this.jsonExpandExtension = ensureLeadingDot(expandDirective);
     }
-    
-    boolean isChild(String resourcePath) {
+
+    String getJSONPropertiesExtension() {
+        return this.jsonExpandExtension;
+    }
+
+    boolean isChild(final String resourcePath) {
         return resourcePath.startsWith(resourceRootPrefix)
             || resourcePath.equals(resourceRoot);
     }
-    
-    String getEntryPath(String resourcePath) {
+
+    String getEntryPath(final String resourcePath) {
         if (entryRootPrefix == null) {
             return resourcePath;
         }
-        
+
         if (resourcePath.startsWith(resourceRootPrefix)) {
             return entryRootPrefix.concat(resourcePath.substring(resourceRootPrefix.length()));
         } else if (resourcePath.equals(resourceRoot)) {
             return entryRoot;
         }
-        
+
         return null;
     }
-    
+
     String getResourceRoot() {
         return resourceRoot;
     }
-    
+
     String getResourceRootPrefix() {
         return resourceRootPrefix;
     }
-    
+
     String getEntryRoot() {
         return entryRoot;
     }
-    
+
     String getEntryRootPrefix() {
         return entryRootPrefix;
     }
-    
-    private static String ensureTrailingSlash(String path) {
+
+    private static String ensureLeadingDot(final String path) {
+        if (path == null || path.length() == 0) {
+            return null;
+        }
+
+        if (!path.startsWith(".")) {
+            return ".".concat(path);
+        }
+
+        return path;
+    }
+
+    private static String ensureNoTrailingSlash(final String path) {
         if (path == null || path.length() == 0) {
             return null;
         }
-        
+
+        if (path.endsWith("/")) {
+            return ensureNoTrailingSlash(path.substring(0, path.length() - 1));
+        }
+
+        return path;
+    }
+
+    private static String ensureTrailingSlash(final String path) {
+        if (path == null || path.length() == 0) {
+            return null;
+        }
+
         if (!path.endsWith("/")) {
             return path.concat("/");
         }
-        
+
         return path;
     }
-    
+
     @Override
     public String toString() {
         return "MappedPath: " + getResourceRoot() + " -> " + getEntryRoot();