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/21 13:54:38 UTC

svn commit: r1783888 [1/3] - in /sling/trunk/bundles/extensions/fsresource: ./ 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/...

Author: sseifert
Date: Tue Feb 21 13:54:37 2017
New Revision: 1783888

URL: http://svn.apache.org/viewvc?rev=1783888&view=rev
Log:
SLING-6440 Filesystem Resource Provider: Support "mounting" content resources from JSON files

Added:
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyIterator.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsValue.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/parser/
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/mapper/
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtilTest.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/parser/
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java   (with props)
    sling/trunk/bundles/extensions/fsresource/src/test/resources/fs-test/folder2/content/
    sling/trunk/bundles/extensions/fsresource/src/test/resources/fs-test/folder2/content.json   (with props)
    sling/trunk/bundles/extensions/fsresource/src/test/resources/fs-test/folder2/content/content2.json   (with props)
    sling/trunk/bundles/extensions/fsresource/src/test/resources/fs-test/folder2/folder21/
    sling/trunk/bundles/extensions/fsresource/src/test/resources/fs-test/folder2/folder21/file21a.txt   (with props)
Removed:
    sling/trunk/bundles/extensions/fsresource/src/test/resources/fs-test/folder2/file2a.txt
Modified:
    sling/trunk/bundles/extensions/fsresource/pom.xml
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
    sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java
    sling/trunk/bundles/extensions/fsresource/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java

Modified: sling/trunk/bundles/extensions/fsresource/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/pom.xml?rev=1783888&r1=1783887&r2=1783888&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/pom.xml (original)
+++ sling/trunk/bundles/extensions/fsresource/pom.xml Tue Feb 21 13:54:37 2017
@@ -76,6 +76,12 @@
                 <configuration>
                     <!-- Export SCR metadata to classpath to have them available in unit tests -->
                     <exportScr>true</exportScr>
+                    <instructions>
+                        <Embed-Dependency>
+                            johnzon-core;scope=compile;inline=false,
+                            geronimo-json_1.0_spec;scope=compile;inline=false
+                        </Embed-Dependency>
+                    </instructions>
                 </configuration>
             </plugin>
             <plugin>
@@ -113,8 +119,34 @@
             <artifactId>slf4j-api</artifactId>
         </dependency>
         <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.3.2</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+            <version>3.2.1</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</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.sling</groupId>
@@ -123,6 +155,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <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>2.2.4</version>

Added: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java?rev=1783888&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java (added)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java Tue Feb 21 13:54:37 2017
@@ -0,0 +1,68 @@
+/*
+ * 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;
+    }
+    
+}

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 21 13:54:37 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java?rev=1783888&r1=1783887&r2=1783888&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java (original)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java Tue Feb 21 13:54:37 2017
@@ -23,6 +23,7 @@ import java.util.Collections;
 import java.util.Timer;
 import java.util.TimerTask;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.api.resource.observation.ResourceChange;
 import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
 import org.apache.sling.spi.resource.provider.ObservationReporter;
@@ -34,7 +35,7 @@ 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());
@@ -46,16 +47,19 @@ public class FileMonitor extends TimerTa
     private final Monitorable root;
 
     private final FsResourceProvider provider;
+    
+    private final ContentFileExtensions contentFileExtensions;
 
     /**
      * 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) {
         this.provider = provider;
-        this.root = new Monitorable(this.provider.getProviderRoot(), this.provider.getRootFile());
-        createStatus(this.root);
+        this.contentFileExtensions = contentFileExtensions;
+        this.root = new Monitorable(this.provider.getProviderRoot(), this.provider.getRootFile(), null);
+        createStatus(this.root, contentFileExtensions);
         logger.debug("Starting file monitor for {} with an interval of {}ms", this.root.file, interval);
         timer.schedule(this, 0, interval);
     }
@@ -130,7 +134,7 @@ public class FileMonitor extends TimerTa
         if ( monitorable.status instanceof NonExistingStatus ) {
             if ( monitorable.file.exists() ) {
                 // new file and reset status
-                createStatus(monitorable);
+                createStatus(monitorable, contentFileExtensions);
                 sendEvents(monitorable,
                            ChangeType.ADDED,
                            reporter);
@@ -176,9 +180,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], reporter);
                                 }
@@ -212,25 +215,29 @@ public class FileMonitor extends TimerTa
     /**
      * Create a status object for the monitorable
      */
-    private static void createStatus(final Monitorable monitorable) {
+    private static void createStatus(final Monitorable monitorable, ContentFileExtensions contentFileExtensions) {
         if ( !monitorable.file.exists() ) {
             monitorable.status = NonExistingStatus.SINGLETON;
         } else if ( monitorable.file.isFile() ) {
             monitorable.status = new FileStatus(monitorable.file);
         } else {
-            monitorable.status = new DirStatus(monitorable.file, monitorable.path);
+            monitorable.status = new DirStatus(monitorable.file, monitorable.path, contentFileExtensions);
         }
     }
 
     /** 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;
+            }
         }
     }
 
@@ -246,15 +253,15 @@ public class FileMonitor extends TimerTa
     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) {
             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);
                 }
             } else {
                 this.children = new Monitorable[0];

Modified: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java?rev=1783888&r1=1783887&r2=1783888&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java (original)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java Tue Feb 21 13:54:37 2017
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.fsprovider.internal;
+package org.apache.sling.fsprovider.internal.mapper;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -28,6 +28,8 @@ import java.util.Calendar;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
 import org.apache.sling.adapter.annotations.Adaptable;
 import org.apache.sling.adapter.annotations.Adapter;
 import org.apache.sling.api.resource.AbstractResource;
@@ -36,6 +38,7 @@ import org.apache.sling.api.resource.Res
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.fsprovider.internal.FsResourceProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,7 +50,7 @@ import org.slf4j.LoggerFactory;
     @Adapter({File.class, URL.class}),
     @Adapter(condition="If the resource is an FsResource and is a readable file.", value=InputStream.class)
 })
-public class FsResource extends AbstractResource {
+public final class FileResource extends AbstractResource {
 
     /**
      * The resource type for file system files mapped into the resource tree by
@@ -61,9 +64,6 @@ public class FsResource extends Abstract
      */
     static final String RESOURCE_TYPE_FOLDER = "nt:folder";
 
-    // default log, assigned on demand
-    private Logger log;
-
     // the owning resource resolver
     private final ResourceResolver resolver;
 
@@ -79,6 +79,8 @@ public class FsResource extends Abstract
     // the resource metadata, assigned on demand
     private ResourceMetadata metaData;
 
+    private static final Logger log = LoggerFactory.getLogger(FileResource.class);
+    
     /**
      * Creates an instance of this Filesystem resource.
      *
@@ -86,7 +88,7 @@ public class FsResource extends Abstract
      * @param resourcePath The resource path in the resource tree
      * @param file The wrapped file
      */
-    FsResource(ResourceResolver resolver, String resourcePath, File file) {
+    FileResource(ResourceResolver resolver, String resourcePath, File file) {
         this.resolver = resolver;
         this.resourcePath = resourcePath;
         this.file = file;
@@ -139,11 +141,8 @@ public class FsResource extends Abstract
      */
     public String getResourceType() {
         if (resourceType == null) {
-            resourceType = file.isFile()
-                    ? RESOURCE_TYPE_FILE
-                            : RESOURCE_TYPE_FOLDER;
+            resourceType = file.isFile() ? RESOURCE_TYPE_FILE : RESOURCE_TYPE_FOLDER;
         }
-
         return resourceType;
     }
 
@@ -156,39 +155,31 @@ public class FsResource extends Abstract
     @SuppressWarnings("unchecked")
     public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
         if (type == File.class) {
-
             return (AdapterType) file;
-
-        } else if (type == InputStream.class) {
-
+        }
+        else if (type == InputStream.class) {
             if (!file.isDirectory() && file.canRead()) {
-
                 try {
                     return (AdapterType) new FileInputStream(file);
-                } catch (IOException ioe) {
-                    getLog().info(
-                            "adaptTo: Cannot open a stream on the file " + file,
-                            ioe);
                 }
-
-            } else {
-
-                getLog().debug("adaptTo: File {} is not a readable file", file);
-
+                catch (IOException ioe) {
+                    log.info("adaptTo: Cannot open a stream on the file " + file, ioe);
+                }
             }
-
-        } else if (type == URL.class) {
-
+            else {
+                log.debug("adaptTo: File {} is not a readable file", file);
+            }
+        }
+        else if (type == URL.class) {
             try {
                 return (AdapterType) file.toURI().toURL();
-            } catch (MalformedURLException mue) {
-                getLog().info(
-                        "adaptTo: Cannot convert the file path " + file
-                        + " to an URL", mue);
+            }
+            catch (MalformedURLException mue) {
+                log.info("adaptTo: Cannot convert the file path " + file + " to an URL", mue);
             }
 
-        } else if (type == ValueMap.class) {
-
+        }
+        else if (type == ValueMap.class) {
             // this resource simulates nt:file/nt:folder behavior by returning it as resource type
             // we should simulate the corresponding JCR properties in a value map as well
             if (file.exists() && file.canRead()) {
@@ -200,18 +191,17 @@ public class FsResource extends Abstract
                 props.put("jcr:created", lastModifed);
                 return (AdapterType) new ValueMapDecorator(props);
             }
-
         }
-
         return super.adaptTo(type);
     }
 
-    // ---------- internal
-
-    private Logger getLog() {
-        if (log == null) {
-            log = LoggerFactory.getLogger(getClass());
-        }
-        return log;
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+                .append("path", resourcePath)
+                .append("file", file.getPath())
+                .append("resourceType", getResourceType())
+                .build();
     }
+
 }

Added: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java?rev=1783888&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java (added)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java Tue Feb 21 13:54:37 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/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 21 13:54:37 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java?rev=1783888&r1=1783887&r2=1783888&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java (original)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java Tue Feb 21 13:54:37 2017
@@ -19,14 +19,19 @@
 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.NoSuchElementException;
+import java.util.List;
 import java.util.Set;
 
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.commons.collections.Predicate;
 import org.apache.sling.api.resource.Resource;
 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.ContentFileParser;
 import org.apache.sling.spi.resource.provider.ObservationReporter;
 import org.apache.sling.spi.resource.provider.ProviderContext;
 import org.apache.sling.spi.resource.provider.ResolveContext;
@@ -61,12 +66,12 @@ import org.osgi.service.metatype.annotat
                    Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
            })
 @Designate(ocd=FsResourceProvider.Config.class, factory=true)
-public class FsResourceProvider extends ResourceProvider<Object> {
+public final class FsResourceProvider extends ResourceProvider<Object> {
     
     /**
      * Resource metadata property set by {@link FsResource} if the underlying file reference is a directory.
      */
-    static final String RESOURCE_METADATA_FILE_DIRECTORY = ":org.apache.sling.fsprovider.file.directory";
+    public static final String RESOURCE_METADATA_FILE_DIRECTORY = ":org.apache.sling.fsprovider.file.directory";
     
     @ObjectClassDefinition(name = "Apache Sling Filesystem Resource Provider",
             description = "Configure an instance of the filesystem " +
@@ -100,6 +105,10 @@ public class FsResourceProvider extends
                 "filesystem resources are mapped in. This property must not be an empty string.")
         String provider_root();
         
+        @AttributeDefinition(name = "Mount JSON",
+                description = "Mount .json files as content in the resource hierarchy.")
+        boolean provider_json_content();
+        
         /**
          * Internal Name hint for web console.
          */
@@ -109,14 +118,18 @@ public class FsResourceProvider extends
     // 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;
+    
+    // if true resources from filesystem are only "overlayed" to JCR resources, serving JCR as fallback within the same path
+    private boolean overlayParentResourceProvider;
 
     /**
      * Returns a resource wrapping a file system file or folder for the given
@@ -133,21 +146,30 @@ public class FsResourceProvider extends
             final String path,
             final ResourceContext resourceContext,
             final Resource parent) {
-        Resource rsrc = getResource(ctx.getResourceResolver(), path, getFile(path));
-        // make sure directory resources from parent resource provider have higher precedence than from this provider
-        // this allows properties like sling:resourceSuperType to take effect
-        if ( rsrc == null || rsrc.getResourceMetadata().containsKey(RESOURCE_METADATA_FILE_DIRECTORY) ) {
-        	// get resource from shadowed provider
-        	final ResourceProvider rp = ctx.getParentResourceProvider();
-        	if ( rp != null ) {
-        	    Resource resourceFromParentResourceProvider = rp.getResource((ResolveContext)ctx.getParentResolveContext(), 
-	            		path, 
-	            		resourceContext, parent);
-        	    if (resourceFromParentResourceProvider != null) {
-        	        rsrc = resourceFromParentResourceProvider;
-        	    }
-        	}        	
+        
+        ResourceResolver resolver = ctx.getResourceResolver();
+        Resource rsrc = contentFileMapper.getResource(resolver, path);
+        if (rsrc == null) {
+            rsrc = fileMapper.getResource(resolver, path);
+        }
+        
+        if (this.overlayParentResourceProvider) {
+            // make sure directory resources from parent resource provider have higher precedence than from this provider
+            // this allows properties like sling:resourceSuperType to take effect
+            if ( rsrc == null || rsrc.getResourceMetadata().containsKey(RESOURCE_METADATA_FILE_DIRECTORY) ) {
+            	// get resource from shadowed provider
+            	final ResourceProvider rp = ctx.getParentResourceProvider();
+            	if ( rp != null ) {
+            	    Resource resourceFromParentResourceProvider = rp.getResource((ResolveContext)ctx.getParentResolveContext(), 
+    	            		path, 
+    	            		resourceContext, parent);
+            	    if (resourceFromParentResourceProvider != null) {
+            	        rsrc = resourceFromParentResourceProvider;
+            	    }
+            	}        	
+            }
         }
+
         return rsrc;
     }
 
@@ -157,102 +179,49 @@ public class FsResourceProvider extends
     @SuppressWarnings("unchecked")
     @Override
     public Iterator<Resource> listChildren(final ResolveContext<Object> ctx, final 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;
-            }
+        ResourceResolver resolver = ctx.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);
+        }
+        
     	// get children from from shadowed provider
-    	final ResourceProvider rp = ctx.getParentResourceProvider();
-    	final Iterator<Resource> parentChildrenIterator;
-    	if ( rp != null ) {
-    		parentChildrenIterator = rp.listChildren(ctx.getParentResolveContext(), parent);
-    	} else {
-    		parentChildrenIterator = null;
-    	}
-        final File[] children = parentFile.listFiles();
-
-        final ResourceResolver resolver = ctx.getResourceResolver();
-        final String parentPath = parent.getPath();
-        return new Iterator<Resource>() {
-
-            final Set<String> names = new HashSet<>();
-
-            int index = 0;
-
-            Resource next = seek();
-
-            @Override
-            public boolean hasNext() {
-                return next != null;
-            }
-
-            @Override
-            public Resource next() {
-                if (!hasNext()) {
-                    throw new NoSuchElementException();
+        if (this.overlayParentResourceProvider) {
+        	final ResourceProvider parentResourceProvider = ctx.getParentResourceProvider();
+        	if (parentResourceProvider != null) {
+        		children = parentResourceProvider.listChildren(ctx.getParentResolveContext(), parent);
+                if (children != null) {
+                    allChildren.add(children);
                 }
+        	}
+        }
 
-                Resource result = next;
-                next = seek();
-                return result;
-            }
-
-            @Override
-            public void remove() {
-                throw new UnsupportedOperationException("remove");
-            }
-
-            private Resource seek() {
-                while (children != null && index < children.length) {
-                    File file = children[index++];
-                    String path = parentPath + "/" + file.getName();
-                    Resource result = getResource(resolver, path, file);
-                    if (result != null) {
-                    	names.add(file.getName());
-                        return result;
-                    }
-                }
-                if ( parentChildrenIterator != null ) {
-                	while ( parentChildrenIterator.hasNext() ) {
-                		final Resource result = parentChildrenIterator.next();
-                		if ( !names.contains(result.getName()) ) {
-                			names.add(result.getName());
-                			return result;
-                		}
-                	}
+    	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());
                 }
-                // nothing found any more
-                return null;
-            }
-        };
+            });
+    	}
     }
 
     // ---------- SCR Integration
@@ -269,11 +238,22 @@ public class FsResourceProvider extends
         }
 
         this.providerRoot = providerRoot;
-        this.providerRootPrefix = providerRoot.concat("/");
         this.providerFile = getProviderFile(providerFileName, bundleContext);
+        this.overlayParentResourceProvider = true;
+        
+        List<String> contentFileSuffixes = new ArrayList<>();
+        if (config.provider_json_content()) {
+            contentFileSuffixes.add(ContentFileParser.JSON_SUFFIX);
+            this.overlayParentResourceProvider = false;
+        }
+        ContentFileExtensions contentFileExtensions = new ContentFileExtensions(contentFileSuffixes);
+        
+        this.fileMapper = new FileResourceMapper(this.providerRoot, this.providerFile, contentFileExtensions);
+        this.contentFileMapper = new ContentFileResourceMapper(this.providerRoot, this.providerFile, contentFileExtensions);
+        
         // start background monitor if check interval is higher than 100
         if ( config.provider_checkinterval() > 100 ) {
-            this.monitor = new FileMonitor(this, config.provider_checkinterval());
+            this.monitor = new FileMonitor(this, config.provider_checkinterval(), contentFileExtensions);
         }
     }
 
@@ -284,8 +264,10 @@ public class FsResourceProvider extends
             this.monitor = null;
         }
         this.providerRoot = null;
-        this.providerRootPrefix = null;
         this.providerFile = null;
+        this.overlayParentResourceProvider = false;
+        this.fileMapper = null;
+        this.contentFileMapper = null;
     }
 
     File getRootFile() {
@@ -325,45 +307,6 @@ public class FsResourceProvider extends
         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(final ResourceResolver resolver,
-            final String resourcePath, 
-            final 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(resolver, resourcePath, file);
-            }
-
-        }
-
-        // not applicable or not an existing file path
-        return null;
-    }
-
     public ObservationReporter getObservationReporter() {
         final ProviderContext ctx = this.getProviderContext();
         if ( ctx != null ) {
@@ -371,4 +314,5 @@ public class FsResourceProvider extends
         }
         return null;
     }
+
 }

Added: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java?rev=1783888&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java (added)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java Tue Feb 21 13:54:37 2017
@@ -0,0 +1,143 @@
+/*
+ * 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.ContentFileParser;
+
+/**
+ * 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 subPath;
+    private boolean contentInitialized;
+    private Object content;
+    private ValueMap valueMap;
+    
+    /**
+     * @param file File with content fragment
+     * @param subPath Relative path addressing content fragment inside file
+     */
+    public ContentFile(File file, String subPath) {
+        this.file = file;
+        this.subPath = subPath;
+    }
+
+    /**
+     * @param file File with content fragment
+     * @param subPath Relative path addressing content fragment inside file
+     * @param content Content
+     */
+    public ContentFile(File file, String subPath, Object content) {
+        this.file = file;
+        this.subPath = subPath;
+        this.contentInitialized = true;
+        this.content = content;
+    }
+
+    /**
+     * @return File with content fragment
+     */
+    public File getFile() {
+        return file;
+    }
+
+    /**
+     * @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 = ContentFileParser.parse(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;
+    }
+    
+    @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/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 21 13:54:37 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java?rev=1783888&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java (added)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java Tue Feb 21 13:54:37 2017
@@ -0,0 +1,125 @@
+/*
+ * 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.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, String resourcePath, ContentFile contentFile) {
+        this.resolver = resolver;
+        this.resourcePath = resourcePath;
+        this.contentFile = contentFile;
+    }
+
+    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 = contentFile.getValueMap().get("sling:resourceSuperType", String.class);
+        }
+        return resourceSuperType;
+    }
+
+    public String getResourceType() {
+        if (resourceType == null) {
+            ValueMap props = getValueMap();
+            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(this);
+        }
+        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/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 21 13:54:37 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java?rev=1783888&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java (added)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java Tue Feb 21 13:54:37 2017
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+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;
+    
+    public ContentFileResourceMapper(String providerRoot, File providerFile, ContentFileExtensions contentFileExtensions) {
+        this.providerRootPrefix = providerRoot.concat("/");
+        this.providerFile = providerFile;
+        this.contentFileExtensions = contentFileExtensions;
+    }
+    
+    @Override
+    public Resource getResource(final ResourceResolver resolver, final String resourcePath) {
+        ContentFile contentFile = getFile(resourcePath, null);
+        if (contentFile != null && contentFile.hasContent()) {
+            return new ContentFileResource(resolver, resourcePath, contentFile);
+        }
+        else {
+            return null;
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public Iterator<Resource> getChildren(final ResourceResolver resolver, final Resource parent) {
+        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) {
+                            ContentFile contentFile = new ContentFile(file, null);
+                            String path = parentPath + "/" + StringUtils.substringBeforeLast(file.getName(), filenameSuffix);
+                            childResources.add(new ContentFileResource(resolver, path, 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(), subPath, 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;
+                    String path = parentPath + "/" + ResourceUtil.getName(contentFile.getSubPath());
+                    return new ContentFileResource(resolver, path, 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, subPath);
+            }
+        }
+        // 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/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 21 13:54:37 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java?rev=1783888&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java (added)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java Tue Feb 21 13:54:37 2017
@@ -0,0 +1,145 @@
+/*
+ * 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.Iterator;
+
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.commons.collections.Predicate;
+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.fsprovider.internal.ContentFileExtensions;
+import org.apache.sling.fsprovider.internal.FsResourceMapper;
+
+public final class FileResourceMapper implements FsResourceMapper {
+
+    // The location in the resource tree where the resources are mapped
+    private final String providerRoot;
+
+    // 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;
+    
+    public FileResourceMapper(String providerRoot, File providerFile, ContentFileExtensions contentFileExtensions) {
+        this.providerRoot = providerRoot;
+        this.providerRootPrefix = providerRoot.concat("/");
+        this.providerFile = providerFile;
+        this.contentFileExtensions = contentFileExtensions;
+    }
+    
+    @Override
+    public Resource getResource(final ResourceResolver resolver, final String resourcePath) {
+        File file = getFile(resourcePath);
+        if (file != null) {
+            return new FileResource(resolver, resourcePath, file);
+        }
+        else {
+            return null;
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public Iterator<Resource> getChildren(final ResourceResolver resolver, final Resource parent) {
+        final String parentPath = parent.getPath();
+        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(parentPath);
+
+            // 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) {
+
+                if (providerFile.exists() && !StringUtils.startsWith(parentPath, providerRoot)) {
+                    String parentPathPrefix = parentPath.concat("/");
+                    if (providerRoot.startsWith(parentPathPrefix)) {
+                        String relPath = providerRoot.substring(parentPathPrefix.length());
+                        if (relPath.indexOf('/') < 0) {
+                            Resource res = new FileResource(resolver, providerRoot, providerFile);
+                            return IteratorUtils.singletonIterator(res);
+                        }
+                    }
+                }
+
+                // no children here
+                return null;
+            }
+        }
+        
+        // ensure parent is a directory
+        if (!parentFile.isDirectory()) {
+            return null;
+        }
+
+        Iterator<File> children = IteratorUtils.filteredIterator(IteratorUtils.arrayIterator(parentFile.listFiles()), new Predicate() {
+            @Override
+            public boolean evaluate(Object object) {
+                File file = (File)object;
+                return !contentFileExtensions.matchesSuffix(file);
+            }
+        });
+        if (!children.hasNext()) {
+            return null;
+        }
+        return IteratorUtils.transformedIterator(children, new Transformer() {
+            @Override
+            public Object transform(Object input) {
+                File file = (File)input;
+                String path = parentPath + "/" + file.getName();
+                return new FileResource(resolver, path, file);
+            }
+        });
+    }
+
+    /**
+     * 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());
+            File file = new File(providerFile, relPath);
+            if (file.exists() && !contentFileExtensions.matchesSuffix(file)) {
+                return file;
+            }
+        }
+        return null;
+    }
+    
+}

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 21 13:54:37 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java?rev=1783888&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java (added)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java Tue Feb 21 13:54:37 2017
@@ -0,0 +1,57 @@
+/*
+ * 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.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+
+final class ValueMapUtil {
+    
+    private ValueMapUtil() {
+        // static methods only
+    }
+    
+    /**
+     * Convert map to value map.
+     * @param content Content map.
+     * @return Value map.
+     */
+    public static ValueMap toValueMap(Map<String,Object> content) {
+        Map<String,Object> props = new HashMap<>();
+        for (Map.Entry<String, Object> entry : ((Map<String,Object>)content).entrySet()) {
+            if (entry.getValue() instanceof Map) {
+                // skip child resources
+                continue;
+            }
+            else if (entry.getValue() instanceof Collection) {
+                // convert lists to arrays
+                props.put(entry.getKey(), ((Collection)entry.getValue()).toArray());
+            }
+            else {
+                props.put(entry.getKey(), entry.getValue());
+            }
+        }
+        return new ValueMapDecorator(props);
+    }
+
+}

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 21 13:54:37 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java?rev=1783888&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java (added)
+++ sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java Tue Feb 21 13:54:37 2017
@@ -0,0 +1,165 @@
+/*
+ * 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.jcr;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Item;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.ReferentialIntegrityException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.version.VersionException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+
+/**
+ * Simplified implementation of read-only content access via the JCR API.
+ */
+abstract class FsItem implements Item {
+    
+    protected final Resource resource;
+    protected final ValueMap props;
+    protected final ResourceResolver resolver;
+    
+    public FsItem(Resource resource) {
+        this.resource = resource;
+        this.props = resource.getValueMap();
+        this.resolver = resource.getResourceResolver();
+    }
+
+    @Override
+    public String getPath() throws RepositoryException {
+        return resource.getPath();
+    }
+
+    @Override
+    public String getName() throws RepositoryException {
+        return resource.getName();
+    }
+
+    @Override
+    public FsItem getAncestor(int depth) throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+        String path = ResourceUtil.getParent(resource.getPath(), getDepth() - depth - 1);
+        if (path != null) {
+            Resource ancestor = resolver.getResource(path);
+            if (ancestor != null) {
+                return new FsNode(ancestor);
+            }
+        }
+        throw new ItemNotFoundException(path);
+    }
+
+    @Override
+    public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+        Resource parent = resource.getParent();
+        if (parent != null) {
+            Node parentNode = parent.adaptTo(Node.class);
+            if (parentNode != null) {
+                return parentNode;
+            }
+        }
+        throw new ItemNotFoundException();
+    }
+
+    @Override
+    public int getDepth() throws RepositoryException {
+        if (StringUtils.equals("/", getPath())) {
+            return 0;
+        } else {
+            return StringUtils.countMatches(getPath(), "/");
+        }
+    }
+
+    @Override
+    public Session getSession() throws RepositoryException {
+        return resolver.adaptTo(Session.class);
+    }
+
+    @Override
+    public boolean isNode() {
+        return (this instanceof Node);
+    }
+
+    @Override
+    public boolean isNew() {
+        return false;
+    }
+
+    @Override
+    public boolean isModified() {
+        return false;
+    }
+
+    @Override
+    public boolean isSame(Item otherItem) throws RepositoryException {
+        return StringUtils.equals(getPath(), otherItem.getPath());
+    }
+
+    @Override
+    public void accept(ItemVisitor visitor) throws RepositoryException {
+        // do nothing
+    }
+    
+    @Override
+    public String toString() {
+        try {
+            return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+                    .append("path", getPath())
+                    .build();
+        }
+        catch (RepositoryException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+    
+    
+    // --- unsupported methods ---
+
+    @Override
+    public void save() throws AccessDeniedException, ItemExistsException, ConstraintViolationException,
+            InvalidItemStateException, ReferentialIntegrityException, VersionException, LockException,
+            NoSuchNodeTypeException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void refresh(boolean keepChanges) throws InvalidItemStateException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void remove() throws VersionException, LockException, ConstraintViolationException, AccessDeniedException,
+            RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+}

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java
------------------------------------------------------------------------------
--- svn:keywords (added)
+++ svn:keywords Tue Feb 21 13:54:37 2017
@@ -0,0 +1 @@
+LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author

Propchange: sling/trunk/bundles/extensions/fsresource/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain