You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ss...@apache.org on 2017/02/28 15:40:57 UTC

svn commit: r1784765 [1/5] - in /sling/branches/fsresource-1.1.x: ./ src/main/java/org/apache/sling/fsprovider/internal/ src/main/java/org/apache/sling/fsprovider/internal/mapper/ src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/ src/main/...

Author: sseifert
Date: Tue Feb 28 15:40:56 2017
New Revision: 1784765

URL: http://svn.apache.org/viewvc?rev=1784765&view=rev
Log:
backport of changes from SLING-6440 and SLING-6537 to 1.1.x version of fsresource based on old resource provider SPI

Added:
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeType.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyDefinition.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyIterator.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsValue.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParser.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverter.java   (with props)
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/
    sling/branches/fsresource-1.1.x/src/test/java/
    sling/branches/fsresource-1.1.x/src/test/java/org/
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtilTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParserTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverterTest.java   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder1/
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder1/file1a.txt   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder1/file1b.txt   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder1/folder11/
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder1/folder11/file11a.txt   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder2/
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder2/content/
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder2/content.json   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder2/content/content2.json   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder2/content/file2content.txt   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder2/folder21/
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder2/folder21/file21a.txt   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder3/
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder3/content/
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder3/content.jcr.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder3/content/content2.jcr.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder3/folder31/
    sling/branches/fsresource-1.1.x/src/test/resources/fs-test/folder3/folder31/file31a.txt   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/invalid-test/
    sling/branches/fsresource-1.1.x/src/test/resources/invalid-test/invalid.jcr.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/invalid-test/invalid.json   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/simplelogger.properties   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/META-INF/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/META-INF/vault/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/META-INF/vault/filter.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/META-INF/vault/settings.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/.content.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/.content.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/.content.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original.dir/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original.dir/.content.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/.content.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/conference/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/conference/.content.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/.content.xml   (with props)
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/navigation/
    sling/branches/fsresource-1.1.x/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/navigation/.content.xml   (with props)
Removed:
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
    sling/branches/fsresource-1.1.x/src/main/resources/
Modified:
    sling/branches/fsresource-1.1.x/pom.xml
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
    sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java

Modified: sling/branches/fsresource-1.1.x/pom.xml
URL: http://svn.apache.org/viewvc/sling/branches/fsresource-1.1.x/pom.xml?rev=1784765&r1=1784764&r2=1784765&view=diff
==============================================================================
--- sling/branches/fsresource-1.1.x/pom.xml (original)
+++ sling/branches/fsresource-1.1.x/pom.xml Tue Feb 28 15:40:56 2017
@@ -22,8 +22,8 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>26</version>
-        <relativePath/>
+        <version>29</version>
+        <relativePath />
     </parent>
 
     <artifactId>org.apache.sling.fsresource</artifactId>
@@ -41,17 +41,10 @@
         <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource</developerConnection>
         <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource</url>
     </scm>
-
+    
     <build>
         <plugins>
             <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-scr-plugin</artifactId>
-                <configuration>
-                    <specVersion>1.1</specVersion>
-                </configuration>
-            </plugin>
-            <plugin>
                 <groupId>org.apache.sling</groupId>
                 <artifactId>maven-sling-plugin</artifactId>
                 <executions>
@@ -68,25 +61,60 @@
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
                 <extensions>true</extensions>
+                <executions>
+                    <!-- Configure extra execution of 'manifest' in process-classes phase to make sure SCR metadata is generated before unit test runs -->
+                    <execution>
+                        <id>scr-metadata</id>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                        <configuration>
+                            <supportIncrementalBuild>true</supportIncrementalBuild>
+                        </configuration>
+                    </execution>
+                </executions>
                 <configuration>
+                    <!-- Export SCR metadata to classpath to have them available in unit tests -->
+                    <exportScr>true</exportScr>
                     <instructions>
-                        <Private-Package>
-                            org.apache.sling.fsprovider.internal
-                        </Private-Package>
+                        <!-- Embed Apache Johnzon -->
+                        <Embed-Dependency>
+                            johnzon-core;scope=compile;inline=false,
+                            geronimo-json_1.0_spec;scope=compile;inline=false
+                        </Embed-Dependency>
+                        <!-- Embed the nessecary parts of the jackrabbit-jcr-commons bundle as described in http://njbartlett.name/2014/05/26/static-linking.html -->
+                        <Conditional-Package>org.apache.jackrabbit.util</Conditional-Package>
+                        <Import-Package>
+                          !org.apache.jackrabbit.*,
+                          *
+                        </Import-Package>
                     </instructions>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                      <exclude>src/test/resources/fs-test/**</exclude>
+                      <exclude>src/test/resources/invalid-test/**</exclude>
+                      <exclude>src/test/resources/vaultfs-test/**/original</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
     <dependencies>
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>servlet-api</artifactId>
+            <version>2.4</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.3.0</version>
+            <version>2.4.0</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
@@ -95,19 +123,41 @@
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.core</artifactId>
+            <artifactId>osgi.core</artifactId>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.compendium</artifactId>
+            <artifactId>osgi.cmpn</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.3.2</version>
+            <scope>compile</scope>
         </dependency>
         <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+            <version>3.2.1</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.johnzon</groupId>
+            <artifactId>johnzon-core</artifactId>
+            <version>1.0.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-json_1.0_spec</artifactId>
+            <version>1.0-alpha-1</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-jcr-commons</artifactId>
+            <version>2.8.0</version>
+            <scope>compile</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
@@ -116,9 +166,33 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>org.apache.felix</groupId>
-            <artifactId>org.apache.felix.scr.annotations</artifactId>
-            <scope>compile</scope>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+            <version>1.9.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.osgi-mock</artifactId>
+            <version>2.2.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.logging-mock</artifactId>
+            <version>1.0.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.hamcrest</artifactId>
+            <version>1.0.2</version>
+            <scope>test</scope>
         </dependency>
     </dependencies>
 </project>

Added: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
URL: http://svn.apache.org/viewvc/sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java?rev=1784765&view=auto
==============================================================================
--- sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java (added)
+++ sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java Tue Feb 28 15:40:56 2017
@@ -0,0 +1,75 @@
+/*
+ * 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.fsprovider.internal;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Matches file names for content file extensions.
+ */
+public final class ContentFileExtensions {
+    
+    private final List<String> contentFileSuffixes;
+
+    public ContentFileExtensions(List<String> contentFileSuffixes) {
+        this.contentFileSuffixes = contentFileSuffixes;
+    }
+    
+    /**
+     * Get suffix from file name.
+     * @param file File
+     * @return Content file name suffix or null if not a context file.
+     */
+    public String getSuffix(File file) {
+        for (String suffix : contentFileSuffixes) {
+            if (StringUtils.endsWith(file.getName(), suffix)) {
+                return suffix;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Checks suffix from file name.
+     * @param file File
+     * @return true if content file
+     */
+    public boolean matchesSuffix(File file) {
+        return getSuffix(file) != null;
+    }
+    
+    /**
+     * @return Content file suffixes.
+     */
+    public Collection<String> getSuffixes() {
+        return contentFileSuffixes;
+    }
+    
+    /**
+     * @return true if not suffixes are defined.
+     */
+    public boolean isEmpty() {
+        return contentFileSuffixes.isEmpty();
+    }
+    
+}

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 28 15:40:56 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
URL: http://svn.apache.org/viewvc/sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java?rev=1784765&r1=1784764&r2=1784765&view=diff
==============================================================================
--- sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java (original)
+++ sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java Tue Feb 28 15:40:56 2017
@@ -19,12 +19,21 @@
 package org.apache.sling.fsprovider.internal;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Dictionary;
+import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.api.SlingConstants;
+import org.apache.sling.fsprovider.internal.mapper.ContentFile;
+import org.apache.sling.fsprovider.internal.mapper.FileResource;
+import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.osgi.service.event.EventAdmin;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,10 +42,9 @@ import org.slf4j.LoggerFactory;
  * This class is a monitor for the file system
  * that periodically checks for changes.
  */
-public class FileMonitor extends TimerTask {
+public final class FileMonitor extends TimerTask {
 
-    /** The logger. */
-    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+    private final Logger log = LoggerFactory.getLogger(this.getClass());
 
     private final Timer timer = new Timer();
     private boolean stop = false;
@@ -45,17 +53,23 @@ public class FileMonitor extends TimerTa
     private final Monitorable root;
 
     private final FsResourceProvider provider;
+    
+    private final ContentFileExtensions contentFileExtensions;
+    private final ContentFileCache contentFileCache;
 
     /**
      * Creates a new instance of this class.
      * @param provider The resource provider.
      * @param interval The interval between executions of the task, in milliseconds.
      */
-    public FileMonitor(final FsResourceProvider provider, final long interval) {
+    public FileMonitor(final FsResourceProvider provider, final long interval,
+            final ContentFileExtensions contentFileExtensions, final ContentFileCache contentFileCache) {
         this.provider = provider;
-        this.root = new Monitorable(this.provider.getProviderRoot(), this.provider.getRootFile());
-        createStatus(this.root);
-        logger.debug("Starting file monitor for {} with an interval of {}ms", this.root.file, interval);
+        this.contentFileExtensions = contentFileExtensions;
+        this.contentFileCache = contentFileCache;
+        this.root = new Monitorable(this.provider.getProviderRoot(), this.provider.getRootFile(), null);
+        createStatus(this.root, contentFileExtensions, contentFileCache);
+        log.debug("Starting file monitor for {} with an interval of {}ms", this.root.file, interval);
         timer.schedule(this, 0, interval);
     }
 
@@ -85,12 +99,13 @@ public class FileMonitor extends TimerTa
                 Thread.currentThread().interrupt();
             }
         }
-        logger.debug("Stopped file monitor for {}", this.root.file);
+        log.debug("Stopped file monitor for {}", this.root.file);
     }
 
     /**
      * @see java.util.TimerTask#run()
      */
+    @Override
     public void run() {
         synchronized (timer) {
             stopped = false;
@@ -123,24 +138,21 @@ public class FileMonitor extends TimerTa
      * @param localEA The event admin
      */
     private void check(final Monitorable monitorable, final EventAdmin localEA) {
-        logger.debug("Checking {}", monitorable.file);
+        log.trace("Checking {}", monitorable.file);
         // if the file is non existing, check if it has been readded
         if ( monitorable.status instanceof NonExistingStatus ) {
             if ( monitorable.file.exists() ) {
                 // new file and reset status
-                createStatus(monitorable);
-                sendEvents(monitorable,
-                           SlingConstants.TOPIC_RESOURCE_ADDED,
-                           localEA);
+                createStatus(monitorable, contentFileExtensions, contentFileCache);
+                sendEvents(monitorable, SlingConstants.TOPIC_RESOURCE_ADDED, localEA);
             }
         } else {
             // check if the file has been removed
             if ( !monitorable.file.exists() ) {
                 // removed file and update status
-                sendEvents(monitorable,
-                           SlingConstants.TOPIC_RESOURCE_REMOVED,
-                           localEA);
+                sendEvents(monitorable, SlingConstants.TOPIC_RESOURCE_REMOVED, localEA);
                 monitorable.status = NonExistingStatus.SINGLETON;
+                contentFileCache.remove(monitorable.path);
             } else {
                 // check for changes
                 final FileStatus fs = (FileStatus)monitorable.status;
@@ -148,10 +160,9 @@ public class FileMonitor extends TimerTa
                 if ( fs.lastModified < monitorable.file.lastModified() ) {
                     fs.lastModified = monitorable.file.lastModified();
                     // changed
-                    sendEvents(monitorable,
-                               SlingConstants.TOPIC_RESOURCE_CHANGED,
-                               localEA);
+                    sendEvents(monitorable, SlingConstants.TOPIC_RESOURCE_CHANGED, localEA);
                     changed = true;
+                    contentFileCache.remove(monitorable.path);
                 }
                 if ( fs instanceof DirStatus ) {
                     // directory
@@ -174,9 +185,8 @@ public class FileMonitor extends TimerTa
                                     }
                                 }
                                 if (children[i] == null) {
-                                    children[i] = new Monitorable(
-                                        monitorable.path + '/'
-                                            + files[i].getName(), files[i]);
+                                    children[i] = new Monitorable(monitorable.path + '/' + files[i].getName(), files[i],
+                                            contentFileExtensions.getSuffix(files[i]));
                                     children[i].status = NonExistingStatus.SINGLETON;
                                     check(children[i], localEA);
                                 }
@@ -195,40 +205,112 @@ public class FileMonitor extends TimerTa
      * Send the event async via the event admin.
      */
     private void sendEvents(final Monitorable monitorable, final String topic, final EventAdmin localEA) {
-        if ( logger.isDebugEnabled() ) {
-            logger.debug("Detected change for resource {} : {}", monitorable.path, topic);
+        if (log.isDebugEnabled()) {
+            log.debug("Detected change for resource {} : {}", monitorable.path, topic);
         }
 
-        final Dictionary<String, String> properties = new Hashtable<String, String>();
-        properties.put(SlingConstants.PROPERTY_PATH, monitorable.path);
-        final String type = monitorable.status instanceof FileStatus ?
-                FsResource.RESOURCE_TYPE_FILE : FsResource.RESOURCE_TYPE_FOLDER;
-        properties.put(SlingConstants.PROPERTY_RESOURCE_TYPE, type);
-        localEA.postEvent(new org.osgi.service.event.Event(topic, properties));
+        List<ResourceChange> changes = collectResourceChanges(monitorable, topic);
+        for (ResourceChange change : changes) {
+            if (log.isTraceEnabled()) {
+                log.debug("Send change for resource {}: {}", change.path, change.topic);
+            }
+            final Dictionary<String, String> properties = new Hashtable<String, String>();
+            properties.put(SlingConstants.PROPERTY_PATH, change.path);
+            if (change.resourceType != null) {
+                properties.put(SlingConstants.PROPERTY_RESOURCE_TYPE, change.resourceType);
+            }
+            localEA.postEvent(new org.osgi.service.event.Event(change.topic, properties));
+        }        
+    }
+    
+    @SuppressWarnings("unchecked")
+    private List<ResourceChange> collectResourceChanges(final Monitorable monitorable, final String topic) {
+        List<ResourceChange> changes = new ArrayList<>();
+        if (monitorable.status instanceof ContentFileStatus) {
+            ContentFile contentFile = ((ContentFileStatus)monitorable.status).contentFile;
+            if (StringUtils.equals(topic, SlingConstants.TOPIC_RESOURCE_CHANGED)) {
+                Map<String,Object> content = (Map<String,Object>)contentFile.getContent();
+                // we cannot easily report the diff of resource changes between two content files
+                // so we simulate a removal of the toplevel node and then add all nodes contained in the current content file again.
+                changes.add(buildContentResourceChange(SlingConstants.TOPIC_RESOURCE_REMOVED, content, monitorable.path));
+                addContentResourceChanges(changes, SlingConstants.TOPIC_RESOURCE_ADDED, content, monitorable.path);
+            }
+            else {
+                addContentResourceChanges(changes, topic, (Map<String,Object>)contentFile.getContent(), monitorable.path);
+            }
+        }
+        else {
+            ResourceChange change = new ResourceChange();
+            change.path = monitorable.path;
+            change.resourceType = monitorable.status instanceof FileStatus ?
+                    FileResource.RESOURCE_TYPE_FILE : FileResource.RESOURCE_TYPE_FOLDER;
+            change.topic = topic;
+            changes.add(change);
+        }
+        return changes;
+    }
+    @SuppressWarnings("unchecked")
+    private void addContentResourceChanges(final List<ResourceChange> changes, final String topic,
+            final Map<String,Object> content, final String path) {
+        changes.add(buildContentResourceChange(topic, content, path));
+        if (content != null) {
+            for (Map.Entry<String,Object> entry : content.entrySet()) {
+                if (entry.getValue() instanceof Map) {
+                    String childPath = path + "/" + entry.getKey();
+                    addContentResourceChanges(changes, topic, (Map<String,Object>)entry.getValue(), childPath);
+                }
+            }
+        }
+    }
+    private ResourceChange buildContentResourceChange(final String topic, final Map<String,Object> content, final String path) {
+        Set<String> addedPropertyNames = null;
+        if (content != null && topic == SlingConstants.TOPIC_RESOURCE_ADDED) {
+            addedPropertyNames = new HashSet<>();
+            for (Map.Entry<String,Object> entry : content.entrySet()) {
+                if (!(entry.getValue() instanceof Map)) {
+                    addedPropertyNames.add(entry.getKey());
+                }
+            }
+        }
+        ResourceChange change = new ResourceChange();
+        change.path = path;
+        change.resourceType = content != null ? (String)content.get("sling:resourceType") : null;
+        change.topic = topic;
+        return change;
     }
 
     /**
      * Create a status object for the monitorable
      */
-    private static void createStatus(final Monitorable monitorable) {
+    private static void createStatus(final Monitorable monitorable, ContentFileExtensions contentFileExtensions, ContentFileCache contentFileCache) {
         if ( !monitorable.file.exists() ) {
             monitorable.status = NonExistingStatus.SINGLETON;
         } else if ( monitorable.file.isFile() ) {
-            monitorable.status = new FileStatus(monitorable.file);
+            if (contentFileExtensions.matchesSuffix(monitorable.file)) {
+                monitorable.status = new ContentFileStatus(monitorable.file,
+                        new ContentFile(monitorable.file, monitorable.path, null, contentFileCache));
+            }
+            else {
+                monitorable.status = new FileStatus(monitorable.file);
+            }
         } else {
-            monitorable.status = new DirStatus(monitorable.file, monitorable.path);
+            monitorable.status = new DirStatus(monitorable.file, monitorable.path, contentFileExtensions, contentFileCache);
         }
     }
 
     /** The monitorable to hold the resource path, the file and the status. */
     private static final class Monitorable {
         public final String path;
-        public final File   file;
+        public final File file;
         public Object status;
-
-        public Monitorable(final String path, final File file) {
-            this.path = path;
+        public Monitorable(final String path, final File file, String contentFileSuffix) {
             this.file = file;
+            if (contentFileSuffix != null) {
+                this.path = StringUtils.substringBeforeLast(path, contentFileSuffix);
+            }
+            else {
+                this.path = path;
+            }
         }
     }
 
@@ -239,20 +321,30 @@ public class FileMonitor extends TimerTa
             this.lastModified = file.lastModified();
         }
     }
-
+    
+    /** Status for content files */
+    private static class ContentFileStatus extends FileStatus {
+        public final ContentFile contentFile;
+        public ContentFileStatus(final File file, final ContentFile contentFile) {
+            super(file);
+            this.contentFile = contentFile;
+        }
+    }
+    
     /** Status for directories. */
     private static final class DirStatus extends FileStatus {
         public Monitorable[] children;
 
-        public DirStatus(final File dir, final String path) {
+        public DirStatus(final File dir, final String path,
+                final ContentFileExtensions contentFileExtensions, final ContentFileCache contentFileCache) {
             super(dir);
             final File[] files = dir.listFiles();
             if (files != null) {
                 this.children = new Monitorable[files.length];
                 for (int i = 0; i < files.length; i++) {
-                    this.children[i] = new Monitorable(path + '/'
-                        + files[i].getName(), files[i]);
-                    FileMonitor.createStatus(this.children[i]);
+                    this.children[i] = new Monitorable(path + '/' + files[i].getName(), files[i],
+                            contentFileExtensions.getSuffix(files[i]));
+                    FileMonitor.createStatus(this.children[i], contentFileExtensions, contentFileCache);
                 }
             } else {
                 this.children = new Monitorable[0];
@@ -264,4 +356,11 @@ public class FileMonitor extends TimerTa
     private static final class NonExistingStatus {
         public static NonExistingStatus SINGLETON = new NonExistingStatus();
     }
-}
\ No newline at end of file
+
+    static class ResourceChange {
+        public String path;
+        public String resourceType;
+        public String topic;
+    }
+    
+}

Added: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java
URL: http://svn.apache.org/viewvc/sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java?rev=1784765&view=auto
==============================================================================
--- sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java (added)
+++ sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java Tue Feb 28 15:40:56 2017
@@ -0,0 +1,47 @@
+/*
+ * 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.fsprovider.internal;
+
+import java.util.Iterator;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+/**
+ * Maps files to resources.
+ */
+public interface FsResourceMapper {
+
+    /**
+     * Get single resource.
+     * @param resolver Resource resolver
+     * @param resourcePath Resource path
+     * @return Resource or null if not exists
+     */
+    Resource getResource(ResourceResolver resolver, String resourcePath);
+    
+    /**
+     * Get children of resource.
+     * @param resolver Resource resolver.
+     * @param parent Parent resource.
+     * @return Child resources or null if no children exist
+     */
+    Iterator<Resource> getChildren(ResourceResolver resolver, Resource parent);
+    
+}

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 28 15:40:56 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
URL: http://svn.apache.org/viewvc/sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java?rev=1784765&r1=1784764&r2=1784765&view=diff
==============================================================================
--- sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java (original)
+++ sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java Tue Feb 28 15:40:56 2017
@@ -19,101 +19,135 @@
 package org.apache.sling.fsprovider.internal;
 
 import java.io.File;
-import java.util.Collections;
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Iterator;
-import java.util.Map;
-import java.util.NoSuchElementException;
+import java.util.List;
+import java.util.Set;
 
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.ConfigurationPolicy;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.apache.felix.scr.annotations.ReferencePolicy;
-import org.apache.felix.scr.annotations.Service;
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.commons.collections.Predicate;
+import org.apache.commons.lang3.StringUtils;
 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.fsprovider.internal.mapper.ContentFileResourceMapper;
+import org.apache.sling.fsprovider.internal.mapper.FileResourceMapper;
+import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
+import org.apache.sling.fsprovider.internal.parser.ContentFileTypes;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
 import org.osgi.service.event.EventAdmin;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 
 /**
  * The <code>FsResourceProvider</code> is a resource provider which maps
- * filesystem files and folders into the virtual resource tree. The provider is
+ * file system files and folders into the virtual resource tree. The provider is
  * implemented in terms of a component factory, that is multiple instances of
  * this provider may be created by creating respective configuration.
  * <p>
  * Each provider instance is configured with two properties: The location in the
- * resource tree where resources are provided ({@link ResourceProvider#ROOTS})
+ * resource tree where resources are provided (provider.root)
  * and the file system path from where files and folders are mapped into the
- * resource ({@link #PROP_PROVIDER_FILE}).
+ * resource (provider.file).
  */
-@Component(
-        name="org.apache.sling.fsprovider.internal.FsResourceProvider",
-        label="%resource.resolver.name",
-        description="%resource.resolver.description",
-        configurationFactory=true,
-        policy=ConfigurationPolicy.REQUIRE,
-        metatype=true
-        )
-@Service(ResourceProvider.class)
-@Properties({
-    @Property(name="service.description", value="Sling Filesystem Resource Provider"),
-    @Property(name="service.vendor", value="The Apache Software Foundation"),
-    @Property(name=ResourceProvider.ROOTS),
-    @Property(name = "webconsole.configurationFactory.nameHint", 
-        value = "Root paths: {" + ResourceProvider.ROOTS + "}")
-})
-public class FsResourceProvider implements ResourceProvider {
-
-    /**
-     * The name of the configuration property providing file system path of
-     * files and folders mapped into the resource tree (value is
-     * "provider.file").
-     */
-    @Property
-    public static final String PROP_PROVIDER_FILE = "provider.file";
-
-    /**
-     * The name of the configuration property providing the check interval
-     * for file changes (value is "provider.checkinterval").
-     */
-    @Property(longValue=FsResourceProvider.DEFAULT_CHECKINTERVAL)
-    public static final String PROP_PROVIDER_CHECKINTERVAL = "provider.checkinterval";
-
-    public static final long DEFAULT_CHECKINTERVAL = 1000;
+@Component(name="org.apache.sling.fsprovider.internal.FsResourceProvider",
+           service=ResourceProvider.class,
+           configurationPolicy=ConfigurationPolicy.REQUIRE,
+           property={
+                   Constants.SERVICE_DESCRIPTION + "=Sling Filesystem Resource Provider",
+                   Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+           })
+@Designate(ocd=FsResourceProvider.Config.class, factory=true)
+public final class FsResourceProvider implements ResourceProvider {
+    
+    @ObjectClassDefinition(name = "Apache Sling Filesystem Resource Provider",
+            description = "Configure an instance of the filesystem " +
+                          "resource provider in terms of provider root and filesystem location")
+    public @interface Config {
+        /**
+         * The name of the configuration property providing file system path of
+         * files and folders mapped into the resource tree (value is
+         * "provider.file").
+         */
+        @AttributeDefinition(name = "Filesystem Root",
+                description = "Filesystem directory mapped to the virtual " +
+                        "resource tree. This property must not be an empty string. If the path is " +
+                        "relative it is resolved against sling.home or the current working directory. " +
+                        "The path may be a file or folder. If the path does not address an existing " +
+                        "file or folder, an empty folder is created.")
+        String provider_file();
+
+        /**
+         * The name of the configuration property providing the check interval
+         * for file changes (value is "provider.checkinterval").
+         */
+        @AttributeDefinition(name = "Check Interval",
+                             description = "If the interval has a value higher than 100, the provider will " +
+             "check the file system for changes periodically. This interval defines the period in milliseconds " +
+             "(the default is 1000). If a change is detected, resource events are sent through the event admin.")
+        long provider_checkinterval() default 1000;
+
+        @AttributeDefinition(name = "Provider Roots",
+                description = "Locations in the virtual resource tree where the " +
+                "filesystem resources are mapped in. This property must contain at least one non-empty string.")
+        String[] provider_roots();
+        
+        @AttributeDefinition(name = "Mount json",
+                description = "Mount .json files as content in the resource hierarchy.")
+        boolean provider_json_content();
+       
+        @AttributeDefinition(name = "Mount jcr.xml",
+                description = "Mount .jcr.xml files as content in the resource hierarchy.")
+        boolean provider_jcrxml_content();
+        
+        @AttributeDefinition(name = "Cache Size",
+                description = "Max. number of content files cached in memory.")
+        int provider_cache_size() default 1000;
+
+        /**
+         * Internal Name hint for web console.
+         */
+        String webconsole_configurationFactory_nameHint() default "Root paths: {" + ResourceProvider.ROOTS + "}";
+    }
 
     // The location in the resource tree where the resources are mapped
     private String providerRoot;
 
-    // providerRoot + "/" to be used for prefix matching of paths
-    private String providerRootPrefix;
-
     // The "root" file or folder in the file system
     private File providerFile;
 
-    /** The monitor to detect file changes. */
+    // The monitor to detect file changes.
     private FileMonitor monitor;
+    
+    // maps filesystem to resources
+    private FsResourceMapper fileMapper;
+    private FsResourceMapper contentFileMapper;
+    
+    // cache for parsed content files
+    private ContentFileCache contentFileCache;
 
-    @Reference(cardinality=ReferenceCardinality.OPTIONAL_UNARY, policy=ReferencePolicy.DYNAMIC)
+    @Reference(cardinality=ReferenceCardinality.OPTIONAL, policy=ReferencePolicy.DYNAMIC)
     private volatile EventAdmin eventAdmin;
-
-    /**
-     * Same as {@link #getResource(ResourceResolver, String)}, i.e. the
-     * <code>request</code> parameter is ignored.
-     *
-     * @see #getResource(ResourceResolver, String)
-     */
-    public Resource getResource(ResourceResolver resourceResolver,
-            HttpServletRequest request, String path) {
+    
+    @Override
+    public Resource getResource(ResourceResolver resourceResolver, HttpServletRequest request, String path) {
         return getResource(resourceResolver, path);
     }
 
     /**
-     * Returns a resource wrapping a filesystem file or folder for the given
+     * Returns a resource wrapping a file system file or folder for the given
      * path. If the <code>path</code> is equal to the configured resource tree
      * location of this provider, the configured file system file or folder is
      * used for the resource. Otherwise the configured resource tree location
@@ -121,130 +155,107 @@ public class FsResourceProvider implemen
      * to access the file or folder. If no such file or folder exists, this
      * method returns <code>null</code>.
      */
-    public Resource getResource(ResourceResolver resourceResolver, String path) {
-        return getResource(resourceResolver, path, getFile(path));
+    @Override
+    public Resource getResource(ResourceResolver resolver, String path) {
+        Resource rsrc = contentFileMapper.getResource(resolver, path);
+        if (rsrc == null) {
+            rsrc = fileMapper.getResource(resolver, path);
+        }
+        return rsrc;
     }
-
+    
     /**
      * Returns an iterator of resources.
      */
+    @SuppressWarnings("unchecked")
+    @Override
     public Iterator<Resource> listChildren(Resource parent) {
-        File parentFile = parent.adaptTo(File.class);
-
-        // not a FsResource, try to create one from the resource
-        if (parentFile == null) {
-            // if the parent path is at or below the provider root, get
-            // the respective file
-            parentFile = getFile(parent.getPath());
-
-            // if the parent path is actually the parent of the provider
-            // root, return a single element iterator just containing the
-            // provider file, unless the provider file is a directory and
-            // a repository item with the same path actually exists
-            if (parentFile == null) {
-
-                String parentPath = parent.getPath().concat("/");
-                if (providerRoot.startsWith(parentPath)) {
-                    String relPath = providerRoot.substring(parentPath.length());
-                    if (relPath.indexOf('/') < 0) {
-                        Resource res = getResource(
-                                parent.getResourceResolver(), providerRoot,
-                                providerFile);
-                        if (res != null) {
-                            return Collections.singletonList(res).iterator();
-                        }
-                    }
-                }
-
-                // no children here
-                return null;
-            }
-        }
-
-        final File[] children = parentFile.listFiles();
-
-        if (children != null && children.length > 0) {
-            final ResourceResolver resolver = parent.getResourceResolver();
-            final String parentPath = parent.getPath();
-            return new Iterator<Resource>() {
-                int index = 0;
-
-                Resource next = seek();
-
-                public boolean hasNext() {
-                    return next != null;
-                }
-
-                public Resource next() {
-                    if (!hasNext()) {
-                        throw new NoSuchElementException();
-                    }
-
-                    Resource result = next;
-                    next = seek();
-                    return result;
+        ResourceResolver resolver = parent.getResourceResolver();
+        
+        List<Iterator<Resource>> allChildren = new ArrayList<>();
+        Iterator<Resource> children;
+        
+        children = contentFileMapper.getChildren(resolver, parent);
+        if (children != null) {
+            allChildren.add(children);
+        }
+        
+        children = fileMapper.getChildren(resolver, parent);
+        if (children != null) {
+            allChildren.add(children);
+        }
+        
+    	if (allChildren.isEmpty()) {
+    	    return null;
+    	}
+    	else if (allChildren.size() == 1) {
+    	    return allChildren.get(0);
+    	}
+    	else {
+    	    // merge all children from the different iterators, but filter out potential duplicates with same resource name
+    	    return IteratorUtils.filteredIterator(IteratorUtils.chainedIterator(allChildren), new Predicate() {
+    	        private Set<String> names = new HashSet<>();
+                @Override
+                public boolean evaluate(Object object) {
+                    Resource resource = (Resource)object;
+                    return names.add(resource.getName());
                 }
-
-                public void remove() {
-                    throw new UnsupportedOperationException("remove");
-                }
-
-                private Resource seek() {
-                    while (index < children.length) {
-                        File file = children[index++];
-                        String path = parentPath + "/" + file.getName();
-                        Resource result = getResource(resolver, path, file);
-                        if (result != null) {
-                            return result;
-                        }
-                    }
-
-                    // nothing found any more
-                    return null;
-                }
-            };
-        }
-
-        // no children
-        return null;
+            });
+    	}
     }
 
     // ---------- SCR Integration
-
-    protected void activate(BundleContext bundleContext, Map<?, ?> props) {
-        String providerRoot = (String) props.get(ROOTS);
-        if (providerRoot == null || providerRoot.length() == 0) {
-            throw new IllegalArgumentException(ROOTS + " property must be set");
+    @Activate
+    protected void activate(BundleContext bundleContext, final Config config) {
+        String[] providerRoots = config.provider_roots();
+        if (providerRoots == null || providerRoots.length != 1 || StringUtils.isBlank(providerRoots[0])) {
+            throw new IllegalArgumentException("provider.roots property must be set to exactly one entry.");
         }
+        String providerRoot = config.provider_roots()[0];
 
-        String providerFileName = (String) props.get(PROP_PROVIDER_FILE);
+        String providerFileName = config.provider_file();
         if (providerFileName == null || providerFileName.length() == 0) {
-            throw new IllegalArgumentException(PROP_PROVIDER_FILE
-                    + " property must be set");
+            throw new IllegalArgumentException("provider.file property must be set");
         }
 
         this.providerRoot = providerRoot;
-        this.providerRootPrefix = providerRoot.concat("/");
         this.providerFile = getProviderFile(providerFileName, bundleContext);
+        
+        List<String> contentFileSuffixes = new ArrayList<>();
+        if (config.provider_json_content()) {
+            contentFileSuffixes.add(ContentFileTypes.JSON_SUFFIX);
+        }
+        if (config.provider_jcrxml_content()) {
+            contentFileSuffixes.add(ContentFileTypes.JCR_XML_SUFFIX);
+        }
+        ContentFileExtensions contentFileExtensions = new ContentFileExtensions(contentFileSuffixes);
+        
+        this.contentFileCache = new ContentFileCache(config.provider_cache_size());
+        this.fileMapper = new FileResourceMapper(this.providerRoot, this.providerFile, contentFileExtensions);
+        this.contentFileMapper = new ContentFileResourceMapper(this.providerRoot, this.providerFile,
+                contentFileExtensions, this.contentFileCache);
+        
         // start background monitor if check interval is higher than 100
-        long checkInterval = DEFAULT_CHECKINTERVAL;
-        final Object interval = props.get(PROP_PROVIDER_CHECKINTERVAL);
-        if ( interval != null && interval instanceof Long ) {
-            checkInterval = (Long)interval;
-        }
-        if ( checkInterval > 100 ) {
-            this.monitor = new FileMonitor(this, checkInterval);
+        if ( config.provider_checkinterval() > 100 ) {
+            this.monitor = new FileMonitor(this, config.provider_checkinterval(),
+                    contentFileExtensions, this.contentFileCache);
         }
     }
 
+    @Deactivate
     protected void deactivate() {
         if ( this.monitor != null ) {
             this.monitor.stop();
             this.monitor = null;
         }
         this.providerRoot = null;
-        this.providerRootPrefix = null;
         this.providerFile = null;
+        this.fileMapper = null;
+        this.contentFileMapper = null;
+        if (this.contentFileCache != null) {
+            this.contentFileCache.clear();
+            this.contentFileCache = null;
+        }
     }
 
     EventAdmin getEventAdmin() {
@@ -288,41 +299,4 @@ public class FsResourceProvider implemen
         return providerFile;
     }
 
-    /**
-     * Returns a file corresponding to the given absolute resource tree path. If
-     * the path equals the configured provider root, the provider root file is
-     * returned. If the path starts with the configured provider root, a file is
-     * returned relative to the provider root file whose relative path is the
-     * remains of the resource tree path without the provider root path.
-     * Otherwise <code>null</code> is returned.
-     */
-    private File getFile(String path) {
-        if (path.equals(providerRoot)) {
-            return providerFile;
-        }
-
-        if (path.startsWith(providerRootPrefix)) {
-            String relPath = path.substring(providerRootPrefix.length());
-            return new File(providerFile, relPath);
-        }
-
-        return null;
-    }
-
-    private Resource getResource(ResourceResolver resourceResolver,
-            String resourcePath, File file) {
-
-        if (file != null) {
-
-            // if the file exists, but is not a directory or no repository entry
-            // exists, return it as a resource
-            if (file.exists()) {
-                return new FsResource(resourceResolver, resourcePath, file);
-            }
-
-        }
-
-        // not applicable or not an existing file path
-        return null;
-    }
 }

Added: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
URL: http://svn.apache.org/viewvc/sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java?rev=1784765&view=auto
==============================================================================
--- sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java (added)
+++ sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java Tue Feb 28 15:40:56 2017
@@ -0,0 +1,166 @@
+/*
+ * 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.fsprovider.internal.mapper;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
+
+/**
+ * Reference to a file that contains a content fragment (e.g. JSON, JCR XML).
+ */
+public final class ContentFile {
+    
+    private final File file;
+    private final String path;
+    private final String subPath;
+    private final ContentFileCache contentFileCache;
+    private boolean contentInitialized;
+    private Object content;
+    private ValueMap valueMap;
+    
+    /**
+     * @param file File with content fragment
+     * @param path Root path of the content file
+     * @param subPath Relative path addressing content fragment inside file
+     * @param contentFileCache Content file cache
+     */
+    public ContentFile(File file, String path, String subPath, ContentFileCache contentFileCache) {
+        this.file = file;
+        this.path = path;
+        this.subPath = subPath;
+        this.contentFileCache = contentFileCache;
+    }
+
+    /**
+     * @param file File with content fragment
+     * @param path Root path of the content file
+     * @param subPath Relative path addressing content fragment inside file
+     * @param contentFileCache Content file cache
+     * @param content Content
+     */
+    public ContentFile(File file, String path, String subPath, ContentFileCache contentFileCache, Object content) {
+        this(file, path, subPath, contentFileCache);
+        this.contentInitialized = true;
+        this.content = content;
+    }
+
+    /**
+     * @return File with content fragment
+     */
+    public File getFile() {
+        return file;
+    }
+    
+    /**
+     * @return Root path of content file
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * @return Relative path addressing content fragment inside file
+     */
+    public String getSubPath() {
+        return subPath;
+    }
+    
+    /**
+     * Content object referenced by sub path.
+     * @return Map if resource, property value if property.
+     */
+    public Object getContent() {
+        if (!contentInitialized) {
+            Map<String,Object> rootContent = contentFileCache.get(path, file);
+            content = getDeepContent(rootContent, subPath);
+            contentInitialized = true;
+        }
+        return content;
+    }
+    
+    /**
+     * @return true if any content was found.
+     */
+    public boolean hasContent() {
+        return getContent() != null;
+    }
+    
+    /**
+     * @return true if content references resource map.
+     */
+    public boolean isResource() {
+        return (getContent() instanceof Map);
+    }
+    
+    /**
+     * @return ValueMap for resource. Never null.
+     */
+    @SuppressWarnings("unchecked")
+    public ValueMap getValueMap() {
+        if (valueMap == null) {
+            Object currentContent = getContent();
+            if (currentContent instanceof Map) {
+                valueMap = ValueMapUtil.toValueMap((Map<String,Object>)currentContent);
+            }
+            else {
+                valueMap = ValueMap.EMPTY;
+            }
+        }
+        return valueMap;
+    }
+    
+    /**
+     * Navigate to another sub path position in content file.
+     * @param newSubPath New sub path
+     * @return Content file
+     */
+    public ContentFile navigateTo(String newSubPath) {
+        return new ContentFile(file, path, newSubPath, contentFileCache);
+    }
+        
+    @SuppressWarnings("unchecked")
+    private static Object getDeepContent(Object object, String subPath) {
+        if (object == null) {
+            return null;
+        }
+        if (subPath == null) {
+            return object;
+        }
+        if (!(object instanceof Map)) {
+            return null;
+        }
+        String name;
+        String remainingSubPath;
+        int slashIndex = subPath.indexOf('/');
+        if (slashIndex >= 0) {
+            name = subPath.substring(0, slashIndex);
+            remainingSubPath = subPath.substring(slashIndex + 1);
+        }
+        else {
+            name = subPath;
+            remainingSubPath = null;
+        }
+        Object subObject = ((Map<String,Object>)object).get(name);
+        return getDeepContent(subObject, remainingSubPath);
+    }
+    
+}

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 28 15:40:56 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
URL: http://svn.apache.org/viewvc/sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java?rev=1784765&view=auto
==============================================================================
--- sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java (added)
+++ sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java Tue Feb 28 15:40:56 2017
@@ -0,0 +1,127 @@
+/*
+ * 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.fsprovider.internal.mapper;
+
+import javax.jcr.Node;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.sling.api.resource.AbstractResource;
+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;
+import org.apache.sling.fsprovider.internal.mapper.jcr.FsNode;
+
+/**
+ * Represents a JSON File with resource content.
+ */
+public final class ContentFileResource extends AbstractResource {
+
+    // the owning resource resolver
+    private final ResourceResolver resolver;
+
+    // the path of this resource in the resource tree
+    private final String resourcePath;
+
+    // the file wrapped by this instance
+    private final ContentFile contentFile;
+
+    // the resource type, assigned on demand
+    private String resourceType;
+    private String resourceSuperType;
+
+    // the resource metadata, assigned on demand
+    private ResourceMetadata metaData;
+
+    /**
+     * @param resolver The owning resource resolver
+     * @param resourcePath The resource path in the resource tree
+     * @param contentFile Content file with sub path
+     */
+    ContentFileResource(ResourceResolver resolver, ContentFile contentFile) {
+        this.resolver = resolver;
+        this.contentFile = contentFile;
+        this.resourcePath = contentFile.getPath()
+                + (contentFile.getSubPath() != null ? "/" + contentFile.getSubPath() : "");
+    }
+
+    public String getPath() {
+        return resourcePath;
+    }
+
+    public ResourceMetadata getResourceMetadata() {
+        if (metaData == null) {
+            metaData = new ResourceMetadata();
+            metaData.setModificationTime(contentFile.getFile().lastModified());
+            metaData.setResolutionPath(resourcePath);
+        }
+        return metaData;
+    }
+
+    public ResourceResolver getResourceResolver() {
+        return resolver;
+    }
+
+    public String getResourceSuperType() {
+        if (resourceSuperType == null) {
+            resourceSuperType = ResourceUtil.getValueMap(this).get("sling:resourceSuperType", String.class);
+        }
+        return resourceSuperType;
+    }
+
+    public String getResourceType() {
+        if (resourceType == null) {
+            ValueMap props = ResourceUtil.getValueMap(this);
+            resourceType = props.get("sling:resourceType", String.class);
+            if (resourceType == null) {
+                // fallback to jcr:primaryType when resource type not set
+                resourceType = props.get("jcr:primaryType", String.class);
+            }
+        }
+        return resourceType;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        if (type == ContentFile.class) {
+            return (AdapterType)this.contentFile;
+        }
+        else if (type == ValueMap.class) {
+            return (AdapterType)contentFile.getValueMap();
+        }
+        else if (type == Node.class && contentFile.isResource()) {
+            // support a subset of JCR API for content file resources
+            return (AdapterType)new FsNode(contentFile, getResourceResolver());
+        }
+        return super.adaptTo(type);
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+                .append("path", resourcePath)
+                .append("file", contentFile.getFile().getPath())
+                .append("subPath", contentFile.getSubPath())
+                .append("resourceType", getResourceType())
+                .build();
+    }
+
+}

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 28 15:40:56 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
URL: http://svn.apache.org/viewvc/sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java?rev=1784765&view=auto
==============================================================================
--- sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java (added)
+++ sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java Tue Feb 28 15:40:56 2017
@@ -0,0 +1,155 @@
+/*
+ * 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.fsprovider.internal.mapper;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.commons.collections.Transformer;
+import org.apache.commons.lang3.StringUtils;
+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.fsprovider.internal.ContentFileExtensions;
+import org.apache.sling.fsprovider.internal.FsResourceMapper;
+import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
+
+public final class ContentFileResourceMapper implements FsResourceMapper {
+    
+    // providerRoot + "/" to be used for prefix matching of paths
+    private final String providerRootPrefix;
+
+    // The "root" file or folder in the file system
+    private final File providerFile;
+    
+    private final ContentFileExtensions contentFileExtensions;
+    private final ContentFileCache contentFileCache;
+    
+    public ContentFileResourceMapper(String providerRoot, File providerFile,
+            ContentFileExtensions contentFileExtensions, ContentFileCache contentFileCache) {
+        this.providerRootPrefix = providerRoot.concat("/");
+        this.providerFile = providerFile;
+        this.contentFileExtensions = contentFileExtensions;
+        this.contentFileCache = contentFileCache;
+    }
+    
+    @Override
+    public Resource getResource(final ResourceResolver resolver, final String resourcePath) {
+        if (contentFileExtensions.isEmpty()) {
+            return null;
+        }
+        ContentFile contentFile = getFile(resourcePath, null);
+        if (contentFile != null && contentFile.hasContent()) {
+            return new ContentFileResource(resolver, contentFile);
+        }
+        else {
+            return null;
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public Iterator<Resource> getChildren(final ResourceResolver resolver, final Resource parent) {
+        if (contentFileExtensions.isEmpty()) {
+            return null;
+        }
+        final String parentPath = parent.getPath();
+        ContentFile parentContentFile = parent.adaptTo(ContentFile.class);
+
+        // not a FsResource, try to create one from the resource
+        if (parentContentFile == null) {
+            parentContentFile = getFile(parentPath, null);
+            if (parentContentFile == null) {
+                
+                // check if parent is a file resource that contains a file content resource
+                File parentFile = parent.adaptTo(File.class);
+                if (parentFile != null && parentFile.isDirectory()) {
+                    List<Resource> childResources = new ArrayList<>();
+                    for (File file : parentFile.listFiles()) {
+                        String filenameSuffix = contentFileExtensions.getSuffix(file);
+                        if (filenameSuffix != null) {
+                            String path = parentPath + "/" + StringUtils.substringBeforeLast(file.getName(), filenameSuffix);
+                            ContentFile contentFile = new ContentFile(file, path, null, contentFileCache);
+                            childResources.add(new ContentFileResource(resolver, contentFile));
+                        }
+                    }
+                    if (!childResources.isEmpty()) {
+                        return childResources.iterator();
+                    }
+                }
+                
+                // no children here
+                return null;
+            }
+        }
+
+        // get child resources from content fragments in content file
+        List<ContentFile> children = new ArrayList<>();
+        if (parentContentFile.hasContent() && parentContentFile.isResource()) {
+            Map<String,Object> content = (Map<String,Object>)parentContentFile.getContent();
+            for (Map.Entry<String, Object> entry: content.entrySet()) {
+                if (entry.getValue() instanceof Map) {
+                    String subPath;
+                    if (parentContentFile.getSubPath() == null) {
+                        subPath = entry.getKey();
+                    }
+                    else {
+                        subPath = parentContentFile.getSubPath() + "/" + entry.getKey();
+                    }
+                    children.add(new ContentFile(parentContentFile.getFile(), parentContentFile.getPath(), subPath, contentFileCache, entry.getValue()));
+                }
+            }
+        }
+        if (children.isEmpty()) {
+            return null;
+        }
+        else {
+            return IteratorUtils.transformedIterator(children.iterator(), new Transformer() {
+                @Override
+                public Object transform(Object input) {
+                    ContentFile contentFile = (ContentFile)input;
+                    return new ContentFileResource(resolver, contentFile);
+                }
+            });
+        }
+    }
+    
+    private ContentFile getFile(String path, String subPath) {
+        if (!StringUtils.startsWith(path, providerRootPrefix)) {
+            return null;
+        }
+        String relPath = path.substring(providerRootPrefix.length());
+        for (String filenameSuffix : contentFileExtensions.getSuffixes()) {
+            File file = new File(providerFile, relPath + filenameSuffix);
+            if (file.exists()) {
+                return new ContentFile(file, path, subPath, contentFileCache);
+            }
+        }
+        // try to find in parent path which contains content fragment
+        String parentPath = ResourceUtil.getParent(path);
+        String nextSubPath = path.substring(parentPath.length() + 1)
+                + (subPath != null ? "/" + subPath : "");
+        return getFile(parentPath, nextSubPath);
+    }
+
+}

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 28 15:40:56 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/branches/fsresource-1.1.x/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain