You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:38:33 UTC

[sling-org-apache-sling-fsresource] annotated tag org.apache.sling.fsresource-2.0.0 created (now be51418)

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a change to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git.


      at be51418  (tag)
 tagging 6bc27b31788e3a02203269dc84ec44147b076354 (commit)
      by Stefan Seifert
      on Mon Mar 27 16:15:23 2017 +0000

- Log -----------------------------------------------------------------
org.apache.sling.fsresource-2.0.0
-----------------------------------------------------------------------

This annotated tag includes the following new commits:

     new 0c33118  SLING-583 Initial implementation of a filesystem resource provider
     new fd8bbdc  Code formatting
     new 6f617dc  SLING-583 Fix FsResourceProvider.listChildren if the provider root is to be return as the single entry in the iterator
     new fc83e32  SLING-583 Latest change introduces dependency on JCR API (because a directory is not returned any more as a resource if a repository item with the same name exists)
     new 7ab3c81  SLING-583 Cleanup to consistently not return FsResource instances for directories "hiding" existing repository items.
     new a46c284  Remove redundant null check
     new c361d2a  SLING-808 : Increase version to 5-incubator-SNAPSHOT
     new a284078  SLING-829 Cosmetics to the bundle/project names
     new 6ec1315  SLING-829 Ensure Apache Sling prefix on all configuration names
     new 4d89314  SLING-865 - Correct location
     new 48e6a1d  SLING-865 - Correct pom and svn reference.
     new 5150158  SLING-916 move non-exported packages to internal packages and remove javadoc configuration which will be inherited from parent pom
     new 19d32c4  Fix fsresource provider and sling plugin after recent refactorings.
     new 6799d74  Use release parent pom
     new bee824c  [maven-release-plugin] prepare release org.apache.sling.fsresource-0.9.0-incubator
     new fe60fe1  [maven-release-plugin] prepare for next development iteration
     new 8958da0  Use next dev version of parent pom
     new 36ccd05  Add missing licence, notice etc.
     new 6bcf253  Prepare new release.
     new 16644cd  [maven-release-plugin] prepare release org.apache.sling.fsresource-0.9.2-incubator
     new 746e1d1  [maven-release-plugin] prepare for next development iteration
     new 16ecaa5  Use next parent pom for development.
     new f943311  Set parent pom to released version.
     new ab2cf0f  Move Sling to new TLP location
     new 501bb88  SLING-1011 : Remove disclaimer file.
     new b47fdc9  SLING-1011 : Adjust svn location
     new 40f9091  SLING-1011 : Remove disclaimer from readme's, adjust links to webite, fix versions in poms.
     new 164dbcf  SLING-1033 Upgrade to Sling parent POM 6 (and ensure web app has the leglize stuff, too)
     new 2f0cded  Use official release instead of incubator release.
     new 10c9088  Update to latest Sling releases.
     new a8b5bdf  Update to latest release.
     new 2d2ef3d  SLING-1186 Upgrade to Sling Parent POM 8-SNAPSHOT (to use SCR plugin 1.4.0), set DS specification version of components to 1.1, convert component into a regular (non-ComponentFactory) component and ensure meta type descriptor is generated with factoryPid set to the same value as the PID to have factory configuration support.
     new 3111d96  SLING-1186 Parent POM 7 is good enough (no need for 8-SNAPSHOT)
     new 2051b5f  SLING-1187 Remove FsProviderConstants (moved constants to FsResource) and FsFolderServlet (duplicate code from default GET Servlet)
     new 3a95821  SLING-1192 use nt:file and nt:folder for files and folders and drop the special resource super type
     new a78ef39  SLING-1205 Upgrade all projects to parent POM 8 and use OSGi provided OSGi libraries
     new 5137b7e  Just code cleanup - no functional changes.
     new 71d0b66  Check whether the abstract file is a directory before trying to open the FileInputStream on it (to prevent a nasty log message)
     new 93cda71  SLING-1387 : File system provider should send resource events
     new 38aa5a1  SLING-1387 : File system provider should send resource events
     new 5d6630a  [maven-release-plugin] prepare release org.apache.sling.fsresource-1.0.0
     new c0d679e  [maven-release-plugin] prepare for next development iteration
     new feb2473  SLING-1516 Fixed by looking for a factory pid on activation.
     new b109367  SLING-1516  A much better fix provided by Felix.
     new 8df175a  Use released parent pom.
     new a30c588  SLING-1193 Fsresoruce and bundle resource implement the 2.0.8 API which means they cant be used with ResourceUtil, updated.
     new c385893  SLING-1193 Have the bundle plugin set the correct import version range for the resource API implemented by the bundles
     new 61509be  SLING-1625 Check File.listFiles() result to prevent NullPointerException
     new b5c493c  Use latest Sling API release
     new 8cd0450  Fix javadocs
     new 26c5948  updating all modules to parent 10-SNAPSHOT in anticipation of emma additions to parent
     new 9281408  fixing relativePaths
     new a8d1b87  Update to recent snapshots
     new 2979d42  Use latest releases.
     new abcabe3  Remove obsolete licence and notice
     new 4d518af  [maven-release-plugin] prepare release org.apache.sling.fsresource-1.0.2
     new 820f5c2  [maven-release-plugin] prepare for next development iteration
     new 04c2091  SLING-2150 : Update plugins to use the latest available versions
     new 15cc17a  Update to recent snapshot
     new 366633f  Using latest released parent pom
     new 630b18e  SLING-2187 - adding new module to contain our custom notice file; adding remote-resources plugin configuration to parent pom and removing all existing appended-resources NOTICE files
     new f4a2616  temporarily using snapshots during release vote
     new 15c85c9  using latest releases
     new 0aba830  SLING-2312 - adding adapter annotations for fsresource bundle
     new 8309283  SLING-2314 - applying patch from Stefan Seifert to switch annotation style on FSResourceProvider. Thanks for the patch!
     new 8318373  SLING-1081 : fsresource: allow to adapt to ValueMap
     new cc90f52  using next snapshots during release process
     new d3e5959  Use released adapter annotations.
     new 7c14443  Use released maven sling plugin
     new a30d734  SLING-2480 : Add config for maven-sling-plugin to m2e configuration
     new 1c8b6ab  SLING-2483 Use provide:=true instead of version macros to define the proper import version range. Also fix a small glitch in the parent pom relativePath of the framework-extension-ws bundle
     new d61a155  Correctly increase resource package version and include latest resource providers.
     new 9363ec0  Update to latest parent pom
     new 28ecd71  Use released versions
     new 195c76d  SLING-2541 : General mechanism to chain resource providers
     new e71601f  Preparing release
     new 5468aa2  [maven-release-plugin] prepare release org.apache.sling.fsresource-1.1.0
     new 7b1b0a6  [maven-release-plugin] prepare for next development iteration
     new f867347  Set dependencies to latest snapshots after release
     new 747e4bb  Update to released bundles
     new d93f502  Use latest releases and update to new parent pom
     new 94c55fa  Update to latest parent pom and use latest releases in launchpad
     new 2653bcb  SLING-2739 :  Add methods for handling the resource type hierarchy to the resource resolver
     new 0e9dd0f  Prepare for release
     new a76cf10  [maven-release-plugin] prepare release org.apache.sling.fsresource-1.1.2
     new 418479c  [maven-release-plugin] prepare for next development iteration
     new fb5221e  Use latest snapshot
     new 8cf1d4e  Use released versions after release.
     new b273649  Correct reactor pom and update to parent pom 16
     new c9aca5b  Update to latest parent pom
     new 592bc1e  SLING-2944 Revert Sling API dependency to 2.3.0 again
     new d5588c1  Update to parent pom 18
     new 0bb72cc  SLING-3286 - Remove plugin version overrides from poms
     new 2254ba3  Update to parent pom v19
     new 9584f16  3804 : Fields for dynamic references must be volatile
     new 07281a3  Updated to parent version 20
     new 4392745  [maven-release-plugin] prepare release org.apache.sling.fsresource-1.1.4
     new a4d1ae4  [maven-release-plugin] prepare for next development iteration
     new 2b642b2  Update to Sling Parent POM 22 with baselining enabled
     new 43259e6  SLING-4698 - Set parent.relativePath to empty for all modules
     new be69e70  Update to Sling Parent 23
     new b030d0c  set parent version to 24 and add empty relativePath where missing
     new 0320672  Update the main reactor to parent 25
     new 9f877f5  SLING-5192 - FsResourceProvider should set webconsole.configurationFactory.nameHint
     new a5f05c9  Switch to parent pom 26
     new dde630e  SLING-6279 : Switch from event admin to Sling Resource Observation API
     new a0bc09c  SLING-6279 : Switch from event admin to Sling Resource Observation API
     new 741a449  SLING-6364 fix provider.roots config property name, and property labels/descriptions (patch provided by Sandro Boehme)
     new c2b7f0c  SLING-6364 rename config property back to provider.root because this is expected for an resource provider
     new 566413e  SLING-6364 make sure webconsole nameHint is included in metatype
     new 57279b1  [maven-release-plugin] prepare release org.apache.sling.fsresource-1.2.0
     new 1958519  [maven-release-plugin] prepare for next development iteration
     new bcf3200  SLING-6439 : Filesystem Resource Provider does not support overlaying nodes from repository
     new 9998dd0  SLING-6439 make sure directory resources from parent resource provider have higher precedence than from this provider this allows properties like sling:resourceSuperType to take effect
     new 4ef6f63  SLING-6439 define own property for handling directory references instead of the deprecated ResourceMetadata.INTERNAL_CONTINUE_RESOLVING
     new a08a244  fix javadoc errors
     new 6aa918b  [maven-release-plugin] prepare release org.apache.sling.fsresource-1.2.2
     new 2a1916a  [maven-release-plugin] prepare for next development iteration
     new 17c7392  SLING-6526 Switch back to Sling API 2.11.0 add unit test coverage
     new 1309a70  SLING-6440 Filesystem Resource Provider: Support "mounting" content resources from JSON files
     new fe118cc  SLING-6440 Filesystem Resource Provider: Support "mounting" content resources from JSON files
     new 110f28b  SLING-6440 fix invalid path in unit test
     new eadcbb2  SLING-6440 further improvements for content file/json support: - enhance jcr api layer - make sure jcr primary type is always defined - send events for all nodes in content files - introduce simple LRU memory cache for parsed content files
     new 94e04e0  SLING-6537 add support for .jcr.xml files
     new e3b8e0f  SLING-6537 add support for .jcr.xml files
     new 0115c1a  SLING-6440 refactor fs jcr implementation to avoid cyclic dependencies to resource API and some further improvements
     new 299edbd  SLING-6440 simplify FileMonitor
     new c1a74d3  SLING-6440 switch to fscontentparser rename "Filesystem" to "File System" update to latest parent
     new a66ffe6  SLING-6440 update to latest API
     new 42d217a  set version in trunk to 2.0.0-SNAPSHOT (based on new Resource Provider SPI, 1.x in branch for old SPI)
     new 8290b1b  use Sling Parent 30
     new 1816787  SLING-6537 FileVault XML support
     new 8d9d081  SLING-6537 FileVault XML support
     new 9449933  SLING-6440 use ManifestHeader to parse directives
     new 37acacb  SLING-6537 FileVault XML support
     new 32e4272  SLING-6537 BigDecimal support, use copy of enhanced ValueMap with proper type converstion from Sling API 2.17.0
     new 3fb8dec  SLING-6440 rename INITIAL_CONTENT_FILES_FOLDERS to FILES_FOLDERS
     new a48dbbd  SLING-6630 make FILES_FOLDERS default
     new ee0c02d  cosmetic: use consistent wording, reorder config attributes
     new 9f5cdf6  SLING-6440 switch to latest jcr/contentparser API
     new 259fc58  SLING-6440 switch to latest jcr/contentparser API
     new 69333f2  SLING-6440 switch to latest contentparser API
     new 5f9e9c0  update dependencies
     new 80a7f03  fix javadoc errors
     new da43d7d  [maven-release-plugin] prepare release org.apache.sling.fsresource-2.0.0
     new 6bc27b3  [maven-release-plugin] copy for tag org.apache.sling.fsresource-2.0.0

The 146 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


-- 
To stop receiving notification emails like this one, please contact
['"commits@sling.apache.org" <co...@sling.apache.org>'].

[sling-org-apache-sling-fsresource] 03/29: SLING-6440 Filesystem Resource Provider: Support "mounting" content resources from JSON files

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 1309a7011211495891482746f57a4de78237e79e
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Tue Feb 21 13:54:37 2017 +0000

    SLING-6440 Filesystem Resource Provider: Support "mounting" content resources from JSON files
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1783888 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  41 +-
 .../fsprovider/internal/ContentFileExtensions.java |  68 +++
 .../sling/fsprovider/internal/FileMonitor.java     |  43 +-
 .../sling/fsprovider/internal/FsResource.java      |  74 ++--
 .../fsprovider/internal/FsResourceMapper.java      |  47 ++
 .../fsprovider/internal/FsResourceProvider.java    | 254 +++++------
 .../fsprovider/internal/mapper/ContentFile.java    | 143 ++++++
 .../internal/mapper/ContentFileResource.java       | 125 ++++++
 .../internal/mapper/ContentFileResourceMapper.java | 146 +++++++
 .../internal/mapper/FileResourceMapper.java        | 145 +++++++
 .../fsprovider/internal/mapper/ValueMapUtil.java   |  57 +++
 .../fsprovider/internal/mapper/jcr/FsItem.java     | 165 +++++++
 .../fsprovider/internal/mapper/jcr/FsNode.java     | 481 +++++++++++++++++++++
 .../internal/mapper/jcr/FsNodeIterator.java        |  74 ++++
 .../fsprovider/internal/mapper/jcr/FsProperty.java | 241 +++++++++++
 .../internal/mapper/jcr/FsPropertyIterator.java    |  79 ++++
 .../fsprovider/internal/mapper/jcr/FsValue.java    | 162 +++++++
 .../internal/parser/ContentFileParser.java         |  52 +++
 .../fsprovider/internal/parser/JsonFileParser.java | 126 ++++++
 .../sling/fsprovider/internal/FileMonitorTest.java |  49 ++-
 .../sling/fsprovider/internal/FilesFolderTest.java |  11 +-
 ...sFolderTest.java => InvalidRootFolderTest.java} |  29 +-
 .../sling/fsprovider/internal/JcrMixedTest.java    |   6 +-
 .../sling/fsprovider/internal/JsonContentTest.java | 234 ++++++++++
 .../sling/fsprovider/internal/TestUtils.java       |  42 +-
 .../internal/mapper/ContentFileTest.java           | 112 +++++
 .../internal/mapper/ValueMapUtilTest.java          |  55 +++
 .../internal/parser/ContentFileParserTest.java     |  49 +++
 src/test/resources/fs-test/folder2/content.json    | 262 +++++++++++
 .../fs-test/folder2/content/content2.json          |   4 +
 src/test/resources/fs-test/folder2/file2a.txt      |   1 -
 .../resources/fs-test/folder2/folder21/file21a.txt |   1 +
 32 files changed, 3115 insertions(+), 263 deletions(-)

diff --git a/pom.xml b/pom.xml
index 1353c6c..fa7233a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -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>
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java b/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
new file mode 100644
index 0000000..3711ac9
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
@@ -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;
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
index c964359..7249912 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -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 TimerTask {
     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 TimerTask {
         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 TimerTask {
                                     }
                                 }
                                 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 TimerTask {
     /**
      * 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 TimerTask {
     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];
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
index 502d0ca..7472dc6 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
@@ -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.ResourceMetadata;
 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 AbstractResource {
      */
     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 AbstractResource {
     // 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 AbstractResource {
      * @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 AbstractResource {
      */
     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 AbstractResource {
     @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 AbstractResource {
                 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();
     }
+
 }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java
new file mode 100644
index 0000000..4cb2c17
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceMapper.java
@@ -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);
+    
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index f2ed39a..f379bad 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -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.annotations.ObjectClassDefinition;
                    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 ResourceProvider<Object> {
                 "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 ResourceProvider<Object> {
     // 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 ResourceProvider<Object> {
             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 ResourceProvider<Object> {
     @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 ResourceProvider<Object> {
         }
 
         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 ResourceProvider<Object> {
             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 ResourceProvider<Object> {
         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 ResourceProvider<Object> {
         }
         return null;
     }
+
 }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
new file mode 100644
index 0000000..319a3de
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
@@ -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);
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
new file mode 100644
index 0000000..11bfcbb
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
@@ -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();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
new file mode 100644
index 0000000..1fe257c
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
@@ -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);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java
new file mode 100644
index 0000000..12b0f7a
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResourceMapper.java
@@ -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;
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
new file mode 100644
index 0000000..67122b1
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
@@ -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);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java
new file mode 100644
index 0000000..11e1836
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java
@@ -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();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
new file mode 100644
index 0000000..308701c
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
@@ -0,0 +1,481 @@
+/*
+ * 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 java.io.InputStream;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Binary;
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.InvalidLifecycleTransitionException;
+import javax.jcr.Item;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.MergeException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.ActivityViolationException;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionException;
+import javax.jcr.version.VersionHistory;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Simplified implementation of read-only content access via the JCR API.
+ */
+public final class FsNode extends FsItem implements Node {
+    
+    public FsNode(Resource resource) {
+        super(resource);
+    }
+    
+    @Override
+    public Node getNode(String relPath) throws PathNotFoundException, RepositoryException {
+        Resource child = resource.getChild(relPath);
+        if (child != null) {
+            return new FsNode(child);
+        }
+        throw new PathNotFoundException(relPath);
+    }
+
+    @Override
+    public NodeIterator getNodes() throws RepositoryException {
+        return new FsNodeIterator(resource.listChildren());
+    }
+
+    @Override
+    public Property getProperty(String relPath) throws PathNotFoundException, RepositoryException {
+        if (props.containsKey(relPath)) {
+            return new FsProperty(resource, relPath, this);
+        }
+        throw new PathNotFoundException(relPath);
+    }
+
+    @Override
+    public PropertyIterator getProperties() throws RepositoryException {
+        return new FsPropertyIterator(props.keySet().iterator(), resource, this);
+    }
+
+    @Override
+    public String getUUID() throws UnsupportedRepositoryOperationException, RepositoryException {
+        String uuid = props.get("jcr:uuid", String.class);
+        if (uuid != null) {
+            return uuid;
+        }
+        else {
+            throw new UnsupportedRepositoryOperationException();
+        }
+    }
+
+    @Override
+    public boolean hasNode(String relPath) throws RepositoryException {
+        return resource.getChild(relPath) != null;
+    }
+
+    @Override
+    public boolean hasProperty(String relPath) throws RepositoryException {
+        return props.containsKey(relPath);
+    }
+
+    @Override
+    public boolean hasNodes() throws RepositoryException {
+        return resource.listChildren().hasNext();
+    }
+
+    @Override
+    public boolean hasProperties() throws RepositoryException {
+        return !props.isEmpty();
+    }
+
+    @Override
+    public boolean isNodeType(String nodeTypeName) throws RepositoryException {
+        return StringUtils.equals(nodeTypeName, props.get("jcr:primaryType", String.class));
+    }
+
+    @Override
+    public boolean canAddMixin(String mixinName) throws NoSuchNodeTypeException, RepositoryException {
+        return false;
+    }
+
+    @Override
+    public boolean isCheckedOut() throws RepositoryException {
+        return false;
+    }
+
+    @Override
+    public boolean holdsLock() throws RepositoryException {
+        return false;
+    }
+
+    @Override
+    public boolean isLocked() throws RepositoryException {
+        return false;
+    }
+
+
+    // --- unsupported methods ---
+    
+    @Override
+    public Node addNode(String relPath) throws ItemExistsException, PathNotFoundException, VersionException,
+            ConstraintViolationException, LockException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Node addNode(String relPath, String primaryNodeTypeName)
+            throws ItemExistsException, PathNotFoundException, NoSuchNodeTypeException, LockException, VersionException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void orderBefore(String srcChildRelPath, String destChildRelPath)
+            throws UnsupportedRepositoryOperationException, VersionException, ConstraintViolationException,
+            ItemNotFoundException, LockException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, Value value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, Value value, int type) throws ValueFormatException, VersionException,
+            LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, Value[] values) throws ValueFormatException, VersionException,
+            LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, Value[] values, int type) throws ValueFormatException, VersionException,
+            LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, String[] values) throws ValueFormatException, VersionException,
+            LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, String[] values, int type) throws ValueFormatException, VersionException,
+            LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, String value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, String value, int type) throws ValueFormatException, VersionException,
+            LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, InputStream value) throws ValueFormatException, VersionException,
+            LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, Binary value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, boolean value) throws ValueFormatException, VersionException,
+            LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, double value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, BigDecimal value) throws ValueFormatException, VersionException,
+            LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, long value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, Calendar value) throws ValueFormatException, VersionException,
+            LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property setProperty(String name, Node value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getReferences() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getReferences(String name) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getWeakReferences() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getWeakReferences(String name) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setPrimaryType(String nodeTypeName) throws NoSuchNodeTypeException, VersionException,
+            ConstraintViolationException, LockException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void addMixin(String mixinName) throws NoSuchNodeTypeException, VersionException,
+            ConstraintViolationException, LockException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeMixin(String mixinName) throws NoSuchNodeTypeException, VersionException,
+            ConstraintViolationException, LockException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeDefinition getDefinition() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Version checkin() throws VersionException, UnsupportedRepositoryOperationException,
+            InvalidItemStateException, LockException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void checkout() throws UnsupportedRepositoryOperationException, LockException, ActivityViolationException,
+            RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void doneMerge(Version version) throws VersionException, InvalidItemStateException,
+            UnsupportedRepositoryOperationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void cancelMerge(Version version) throws VersionException, InvalidItemStateException,
+            UnsupportedRepositoryOperationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void update(String srcWorkspace) throws NoSuchWorkspaceException, AccessDeniedException, LockException,
+            InvalidItemStateException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeIterator merge(String srcWorkspace, boolean bestEffort) throws NoSuchWorkspaceException,
+            AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getCorrespondingNodePath(String workspaceName)
+            throws ItemNotFoundException, NoSuchWorkspaceException, AccessDeniedException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeIterator getSharedSet() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeSharedSet()
+            throws VersionException, LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void removeShare()
+            throws VersionException, LockException, ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void restore(String versionName, boolean removeExisting) throws VersionException, ItemExistsException,
+            UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void restore(Version version, boolean removeExisting) throws VersionException, ItemExistsException,
+            InvalidItemStateException, UnsupportedRepositoryOperationException, LockException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void restore(Version version, String relPath, boolean removeExisting)
+            throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException,
+            UnsupportedRepositoryOperationException, LockException, InvalidItemStateException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void restoreByLabel(String versionLabel, boolean removeExisting)
+            throws VersionException, ItemExistsException, UnsupportedRepositoryOperationException, LockException,
+            InvalidItemStateException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public VersionHistory getVersionHistory() throws UnsupportedRepositoryOperationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Version getBaseVersion() throws UnsupportedRepositoryOperationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Lock lock(boolean isDeep, boolean isSessionScoped) throws UnsupportedRepositoryOperationException,
+            LockException, AccessDeniedException, InvalidItemStateException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Lock getLock()
+            throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void unlock() throws UnsupportedRepositoryOperationException, LockException, AccessDeniedException,
+            InvalidItemStateException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void followLifecycleTransition(String transition)
+            throws UnsupportedRepositoryOperationException, InvalidLifecycleTransitionException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String[] getAllowedLifecycleTransistions()
+            throws UnsupportedRepositoryOperationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeIterator getNodes(String namePattern) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeType getPrimaryNodeType() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getIdentifier() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int getIndex() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeType[] getMixinNodeTypes() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getProperties(String namePattern) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
new file mode 100644
index 0000000..a2ef2fe
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
@@ -0,0 +1,74 @@
+/*
+ * 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 java.util.Iterator;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Simplified implementation of read-only content access via the JCR API.
+ */
+class FsNodeIterator implements NodeIterator {
+    
+    private final Iterator<Resource> resources;
+
+    public FsNodeIterator(Iterator<Resource> resources) {
+        this.resources = resources;
+    }
+
+    public boolean hasNext() {
+        return resources.hasNext();
+    }
+
+    public Object next() {
+        return nextNode();
+    }
+
+    @Override
+    public Node nextNode() {
+        return resources.next().adaptTo(Node.class);
+    }
+
+    
+    // --- unsupported methods ---
+        
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void skip(long skipNum) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long getSize() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long getPosition() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java
new file mode 100644
index 0000000..4d1d138
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java
@@ -0,0 +1,241 @@
+/*
+ * 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 java.io.InputStream;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Binary;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.PropertyDefinition;
+import javax.jcr.version.VersionException;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Simplified implementation of read-only content access via the JCR API.
+ */
+class FsProperty extends FsItem implements Property {
+    
+    private final String propertyName;
+    private final Node node;
+    
+    public FsProperty(Resource resource, String propertyName, Node node) {
+        super(resource);
+        this.propertyName = propertyName;
+        this.node = node;
+    }
+    
+    @Override
+    public String getName() throws RepositoryException {
+        return propertyName;
+    }
+
+    @Override
+    public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+        return getNode();
+    }
+
+    @Override
+    public Node getNode() throws ItemNotFoundException, ValueFormatException, RepositoryException {
+        return node;
+    }
+    
+    @Override
+    public String getPath() throws RepositoryException {
+        return resource.getPath() + "/" + propertyName;
+    }
+
+    @Override
+    public Value getValue() throws ValueFormatException, RepositoryException {
+        return new FsValue(props, propertyName);
+    }
+
+    @Override
+    public String getString() throws ValueFormatException, RepositoryException {
+        return getValue().getString();
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public InputStream getStream() throws ValueFormatException, RepositoryException {
+        return getValue().getStream();
+    }
+
+    @Override
+    public Binary getBinary() throws ValueFormatException, RepositoryException {
+        return getValue().getBinary();
+    }
+
+    @Override
+    public long getLong() throws ValueFormatException, RepositoryException {
+        return getValue().getLong();
+    }
+
+    @Override
+    public double getDouble() throws ValueFormatException, RepositoryException {
+        return getValue().getDouble();
+    }
+
+    @Override
+    public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
+        return getValue().getDecimal();
+    }
+
+    @Override
+    public Calendar getDate() throws ValueFormatException, RepositoryException {
+        return getValue().getDate();
+    }
+
+    @Override
+    public boolean getBoolean() throws ValueFormatException, RepositoryException {
+        return getValue().getBoolean();
+    }
+
+    @Override
+    public Value[] getValues() throws ValueFormatException, RepositoryException {
+        if (!isMultiple()) {
+            throw new ValueFormatException();
+        }
+        Object value = props.get(propertyName);
+        int size = Array.getLength(value);
+        Value[] result = new Value[size];
+        for (int i=0; i<size; i++) {
+            result[i] = new FsValue(props, propertyName, i);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean isMultiple() throws RepositoryException {
+        Object value = props.get(propertyName);
+        return value != null && value.getClass().isArray();
+    }
+
+    @Override
+    public int getType() throws RepositoryException {
+        return getValue().getType();
+    }
+    
+
+    // --- unsupported methods ---
+    
+    @Override
+    public void setValue(Value value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(Value[] values) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(String value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(String[] values) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(InputStream value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(Binary value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(long value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(double value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(BigDecimal value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(Calendar value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(boolean value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void setValue(Node value) throws ValueFormatException, VersionException, LockException,
+            ConstraintViolationException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyDefinition getDefinition() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Property getProperty() throws ItemNotFoundException, ValueFormatException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long getLength() throws ValueFormatException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long[] getLengths() throws ValueFormatException, RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyIterator.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyIterator.java
new file mode 100644
index 0000000..de2d572
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyIterator.java
@@ -0,0 +1,79 @@
+/*
+ * 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 java.util.Iterator;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Simplified implementation of read-only content access via the JCR API.
+ */
+class FsPropertyIterator implements PropertyIterator {
+    
+    private final Iterator<String> propertyNames;
+    private final Resource resource;
+    private final Node node;
+    
+    public FsPropertyIterator(Iterator<String> propertyNames, Resource resource, Node node) {
+        this.propertyNames = propertyNames;
+        this.resource = resource;
+        this.node = node;
+    }
+
+    public boolean hasNext() {
+        return propertyNames.hasNext();
+    }
+
+    public Object next() {
+        return nextProperty();
+    }
+
+    @Override
+    public Property nextProperty() {
+        return new FsProperty(resource, propertyNames.next(), node);
+    }
+
+    
+    // --- unsupported methods ---
+        
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void skip(long skipNum) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long getSize() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long getPosition() {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsValue.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsValue.java
new file mode 100644
index 0000000..4ca2873
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsValue.java
@@ -0,0 +1,162 @@
+/*
+ * 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 java.io.InputStream;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.util.Calendar;
+
+import javax.jcr.Binary;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+
+import org.apache.sling.api.resource.ValueMap;
+
+/**
+ * Simplified implementation of read-only content access via the JCR API.
+ */
+class FsValue implements Value {
+    
+    private final ValueMap props;
+    private final String propertyName;
+    private final int arrayIndex;
+    
+    public FsValue(ValueMap props, String propertyName) {
+        this.props = props;
+        this.propertyName = propertyName;
+        this.arrayIndex = -1;
+    }
+
+    public FsValue(ValueMap props, String propertyName, int arrayIndex) {
+        this.props = props;
+        this.propertyName = propertyName;
+        this.arrayIndex = arrayIndex;
+    }
+
+    @Override
+    public String getString() throws ValueFormatException, IllegalStateException, RepositoryException {
+        if (arrayIndex >= 0) {
+            return props.get(propertyName, String[].class)[arrayIndex];
+        }
+        else {
+            return props.get(propertyName, String.class);
+        }
+    }
+
+    @Override
+    public long getLong() throws ValueFormatException, RepositoryException {
+        if (arrayIndex >= 0) {
+            return props.get(propertyName, Long[].class)[arrayIndex];
+        }
+        else {
+            return props.get(propertyName, 0L);
+        }
+    }
+
+    @Override
+    public double getDouble() throws ValueFormatException, RepositoryException {
+        if (arrayIndex >= 0) {
+            return props.get(propertyName, Double[].class)[arrayIndex];
+        }
+        else {
+            return props.get(propertyName, 0d);
+        }
+    }
+
+    @Override
+    public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
+        if (arrayIndex >= 0) {
+            return props.get(propertyName, BigDecimal[].class)[arrayIndex];
+        }
+        else {
+            return props.get(propertyName, BigDecimal.ZERO);
+        }
+    }
+
+    @Override
+    public Calendar getDate() throws ValueFormatException, RepositoryException {
+        if (arrayIndex >= 0) {
+            return props.get(propertyName, Calendar[].class)[arrayIndex];
+        }
+        else {
+            return props.get(propertyName, Calendar.class);
+        }
+    }
+
+    @Override
+    public boolean getBoolean() throws ValueFormatException, RepositoryException {
+        if (arrayIndex >= 0) {
+            return props.get(propertyName, Boolean[].class)[arrayIndex];
+        }
+        else {
+            return props.get(propertyName, false);
+        }
+    }
+
+    @Override
+    public int getType() {
+        Object value = props.get(propertyName);
+        if (value == null) {
+            return PropertyType.UNDEFINED;
+        }
+        Class type = value.getClass();
+        if (type.isArray() && Array.getLength(value) > 0) {
+            Object firstItem = Array.get(value, 0);
+            if (firstItem != null) {
+                type = firstItem.getClass();
+            }
+        }
+        if (type == String.class) {
+            return PropertyType.STRING;
+        }
+        if (type == Boolean.class || type == boolean.class) {
+            return PropertyType.BOOLEAN;
+        }
+        if (type == BigDecimal.class) {
+            return PropertyType.DECIMAL;
+        }
+        if (type == Double.class || type == double.class || type == Float.class || type == float.class) {
+            return PropertyType.DOUBLE;
+        }
+        if (Number.class.isAssignableFrom(type)) {
+            return PropertyType.LONG;
+        }
+        if (type == Calendar.class) {
+            return PropertyType.DATE;
+        }
+        return PropertyType.UNDEFINED;
+    }
+
+
+    // --- unsupported methods ---
+    
+    @Override
+    public InputStream getStream() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Binary getBinary() throws RepositoryException {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
new file mode 100644
index 0000000..46d5b85
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
@@ -0,0 +1,52 @@
+/*
+ * 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.parser;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Parses file that contains content fragments (e.g. JSON, JCR XML).
+ */
+public final class ContentFileParser {
+    
+    /**
+     * JSON content files.
+     */
+    public static final String JSON_SUFFIX = ".json";
+    
+    private ContentFileParser() {
+        // static methods only
+    }
+    
+    /**
+     * Parse content from file.
+     * @param file File. Type is detected automatically.
+     * @return Content or null if content could not be parsed.
+     */
+    public static Map<String,Object> parse(File file) {
+        if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) {
+            return JsonFileParser.parse(file);
+        }
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java
new file mode 100644
index 0000000..c9eddc5
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java
@@ -0,0 +1,126 @@
+/*
+ * 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.parser;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.json.JsonReaderFactory;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Parses JSON file that contains content fragments.
+ */
+class JsonFileParser {
+    
+    private static final Logger log = LoggerFactory.getLogger(JsonFileParser.class);
+    
+    private static final JsonReaderFactory JSON_READER_FACTORY;
+    static {
+        // allow comments in JSON files
+        Map<String,Object> jsonReaderFactoryConfig = new HashMap<>();
+        jsonReaderFactoryConfig.put("org.apache.johnzon.supports-comments", true);
+        // workaround for JsonProvider classloader issue until https://issues.apache.org/jira/browse/GERONIMO-6560 is fixed
+        ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(JsonFileParser.class.getClassLoader());
+            JSON_READER_FACTORY = Json.createReaderFactory(jsonReaderFactoryConfig);
+        }
+        finally {
+            Thread.currentThread().setContextClassLoader(oldClassLoader);
+        }
+    }
+    
+    private JsonFileParser() {
+        // static methods only
+    }
+    
+    /**
+     * Parse JSON file.
+     * @param file File
+     * @return Content
+     */
+    public static Map<String,Object> parse(File file) {
+        log.debug("Parse JSON content from {}", file.getPath());
+        try {
+            try (FileInputStream fis = new FileInputStream(file);
+                    JsonReader reader = JSON_READER_FACTORY.createReader(fis)) {
+                return toMap(reader.readObject());
+            }
+        }
+        catch (IOException ex) {
+            log.warn("Error parsing JSON content from " + file.getPath(), ex);
+            return null;
+        }
+    }
+    
+    private static Map<String,Object> toMap(JsonObject object) {
+        Map<String,Object> map = new LinkedHashMap<>();
+        for (Map.Entry<String, JsonValue> entry : object.entrySet()) {
+            map.put(entry.getKey(), convertValue(entry.getValue()));
+        }
+        return map;
+    }
+    
+    private static Object convertValue(JsonValue value) {
+        switch (value.getValueType()) {
+            case STRING:
+                return ((JsonString)value).getString();
+            case NUMBER:
+                JsonNumber numberValue = (JsonNumber)value;
+                if (numberValue.isIntegral()) {
+                    return numberValue.longValue();
+                }
+                else {
+                    return numberValue.doubleValue();
+                }
+            case TRUE:
+                return true;
+            case FALSE:
+                return false;
+            case NULL:
+                return null;
+            case ARRAY:
+                JsonArray arrayValue = (JsonArray)value;
+                Object[] values = new Object[arrayValue.size()];
+                for (int i=0; i<values.length; i++) {
+                    values[i] = convertValue(arrayValue.get(i));
+                }
+                return values;
+            case OBJECT:
+                return toMap((JsonObject)value);
+            default:
+                throw new IllegalArgumentException("Unexpected JSON value type: " + value.getValueType());
+        }
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
index 3201c3f..a6f7568 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
@@ -63,7 +63,8 @@ public class FileMonitorTest {
                 context.registerInjectActivateService(new FsResourceProvider(),
                         "provider.file", tempDir.getPath(),
                         "provider.root", "/fs-test",
-                        "provider.checkinterval", 120);
+                        "provider.checkinterval", 120,
+                        "provider.json.content", true);
                 
                 // register resource change listener
                 context.registerService(ResourceChangeListener.class, resourceListener,
@@ -85,7 +86,7 @@ public class FileMonitorTest {
         assertTrue(changes.isEmpty());
         
         File file1a = new File(tempDir, "folder1/file1a.txt");
-        FileUtils.write(file1a, "newcontent");
+        FileUtils.touch(file1a);
         
         Thread.sleep(250);
 
@@ -153,6 +154,50 @@ public class FileMonitorTest {
         assertChange(changes, 1, "/fs-test/folder1", ChangeType.REMOVED);
     }
 
+    @Test
+    public void testUpdateJsonContent() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file1a = new File(tempDir, "folder2/content.json");
+        FileUtils.touch(file1a);
+        
+        Thread.sleep(250);
+
+        assertEquals(1, changes.size());
+        assertChange(changes, 0, "/fs-test/folder2/content", ChangeType.CHANGED);
+    }
+    
+    @Test
+    public void testAddJsonContent() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file1c = new File(tempDir, "folder1/file1c.json");
+        FileUtils.write(file1c, "{'prop1':'value1'}");
+        
+        Thread.sleep(250);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, 0, "/fs-test/folder1", ChangeType.CHANGED);
+        assertChange(changes, 1, "/fs-test/folder1/file1c", ChangeType.ADDED);
+    }
+    
+    @Test
+    public void testRemoveJsonContent() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file1a = new File(tempDir, "folder2/content.json");
+        file1a.delete();
+        
+        Thread.sleep(250);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, 0, "/fs-test/folder2", ChangeType.CHANGED);
+        assertChange(changes, 1, "/fs-test/folder2/content", ChangeType.REMOVED);
+    }
+    
     
     private void assertChange(List<ResourceChange> changes, int index, String path, ChangeType changeType) {
         ResourceChange change = changes.get(index);
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
index d837d6e..578befd 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
@@ -18,12 +18,13 @@
  */
 package org.apache.sling.fsprovider.internal;
 
-import static org.apache.sling.fsprovider.internal.TestUtils.REGISTER_FSRESOURCE_PLUGIN;
 import static org.apache.sling.fsprovider.internal.TestUtils.assertFile;
 import static org.apache.sling.fsprovider.internal.TestUtils.assertFolder;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin;
 import org.apache.sling.hamcrest.ResourceMatchers;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
@@ -42,7 +43,7 @@ public class FilesFolderTest {
 
     @Rule
     public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
-        .plugin(REGISTER_FSRESOURCE_PLUGIN)
+        .plugin(new RegisterFsResourcePlugin())
         .build();
 
     @Before
@@ -63,14 +64,16 @@ public class FilesFolderTest {
         assertFile(fsroot, "folder1/file1a.txt", "file1a");
         assertFile(fsroot, "folder1/file1b.txt", "file1b");
         assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
-        assertFile(fsroot, "folder2/file2a.txt", "file2a");
+        assertFile(fsroot, "folder2/content.json", null);
     }
 
     @Test
     public void testListChildren() {
         assertThat(root, ResourceMatchers.containsChildren("fs-test"));
         assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2"));
-        assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("file1a.txt", "file1b.txt"));
+        assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("folder11", "file1a.txt", "file1b.txt"));
+        assertThat(fsroot.getChild("folder2"), ResourceMatchers.hasChildren("folder21", "content.json"));
+        assertFalse(fsroot.getChild("folder1/file1a.txt").listChildren().hasNext());
     }
 
 }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java b/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java
similarity index 57%
copy from src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
copy to src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java
index d837d6e..9f38c1a 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java
@@ -18,13 +18,11 @@
  */
 package org.apache.sling.fsprovider.internal;
 
-import static org.apache.sling.fsprovider.internal.TestUtils.REGISTER_FSRESOURCE_PLUGIN;
-import static org.apache.sling.fsprovider.internal.TestUtils.assertFile;
-import static org.apache.sling.fsprovider.internal.TestUtils.assertFolder;
-import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 
 import org.apache.sling.api.resource.Resource;
-import org.apache.sling.hamcrest.ResourceMatchers;
+import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
@@ -33,44 +31,35 @@ import org.junit.Rule;
 import org.junit.Test;
 
 /**
- * Test access to files and folders from filesystem.
+ * Test with invalid fs folder.
  */
-public class FilesFolderTest {
+public class InvalidRootFolderTest {
 
-    private Resource root;
     private Resource fsroot;
 
     @Rule
     public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
-        .plugin(REGISTER_FSRESOURCE_PLUGIN)
+        .plugin(new RegisterFsResourcePlugin("provider.file", "/invalid-folder"))
         .build();
 
     @Before
     public void setUp() {
-        root = context.resourceResolver().getResource("/");
         fsroot = context.resourceResolver().getResource("/fs-test");
     }
 
     @Test
     public void testFolders() {
-        assertFolder(fsroot, "folder1");
-        assertFolder(fsroot, "folder1/folder11");
-        assertFolder(fsroot, "folder2");
+        assertNull(fsroot.getChild("folder1"));
     }
 
     @Test
     public void testFiles() {
-        assertFile(fsroot, "folder1/file1a.txt", "file1a");
-        assertFile(fsroot, "folder1/file1b.txt", "file1b");
-        assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
-        assertFile(fsroot, "folder2/file2a.txt", "file2a");
+        assertNull(fsroot.getChild("folder1/file1a.txt"));
     }
 
     @Test
     public void testListChildren() {
-        assertThat(root, ResourceMatchers.containsChildren("fs-test"));
-        assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2"));
-        assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("file1a.txt", "file1b.txt"));
+        assertFalse(fsroot.listChildren().hasNext());
     }
 
 }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java
index 41bdbae..1231090 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java
@@ -18,7 +18,6 @@
  */
 package org.apache.sling.fsprovider.internal;
 
-import static org.apache.sling.fsprovider.internal.TestUtils.REGISTER_FSRESOURCE_PLUGIN;
 import static org.apache.sling.fsprovider.internal.TestUtils.assertFile;
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertThat;
@@ -27,6 +26,7 @@ import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin;
 import org.apache.sling.hamcrest.ResourceMatchers;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
@@ -45,7 +45,7 @@ public class JcrMixedTest {
 
     @Rule
     public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
-        .plugin(REGISTER_FSRESOURCE_PLUGIN)
+        .plugin(new RegisterFsResourcePlugin())
         .build();
 
     @Before
@@ -84,7 +84,7 @@ public class JcrMixedTest {
         assertFile(fsroot, "folder1/file1a.txt", "file1a");
         assertFile(fsroot, "folder1/file1b.txt", "file1b");
         assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
-        assertFile(fsroot, "folder2/file2a.txt", "file2a");
+        assertFile(fsroot, "folder2/content.json", null);
 
         // do not expected properties from JCR for files
         Resource file1a = fsroot.getChild("folder1/file1a.txt");
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
new file mode 100644
index 0000000..1dcf7a4
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
@@ -0,0 +1,234 @@
+/*
+ * 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 static org.apache.sling.fsprovider.internal.TestUtils.assertFile;
+import static org.apache.sling.fsprovider.internal.TestUtils.assertFolder;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin;
+import org.apache.sling.hamcrest.ResourceMatchers;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Test access to files and folders from filesystem.
+ */
+public class JsonContentTest {
+
+    private Resource root;
+    private Resource fsroot;
+
+    @Rule
+    public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
+        .plugin(new RegisterFsResourcePlugin("provider.json.content", true))
+        .build();
+
+    @Before
+    public void setUp() {
+        root = context.resourceResolver().getResource("/");
+        fsroot = context.resourceResolver().getResource("/fs-test");
+    }
+
+    @Test
+    public void testFolders() {
+        assertFolder(fsroot, "folder1");
+        assertFolder(fsroot, "folder1/folder11");
+        assertFolder(fsroot, "folder2");
+    }
+
+    @Test
+    public void testFiles() {
+        assertFile(fsroot, "folder1/file1a.txt", "file1a");
+        assertFile(fsroot, "folder1/file1b.txt", "file1b");
+        assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
+        assertNull(fsroot.getChild("folder2/content.json"));
+    }
+
+    @Test
+    public void testListChildren() {
+        assertThat(root, ResourceMatchers.containsChildren("fs-test"));
+        assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2"));
+        assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("folder11", "file1a.txt", "file1b.txt"));
+        assertThat(fsroot.getChild("folder2"), ResourceMatchers.hasChildren("folder21", "content"));
+    }
+
+    @Test
+    public void testJsonContent_Root() {
+        Resource underTest = fsroot.getChild("folder2/content");
+        assertNotNull(underTest);
+        assertEquals("app:Page", underTest.getValueMap().get("jcr:primaryType", String.class));
+        assertEquals("app:Page", underTest.getResourceType());
+        assertThat(underTest, ResourceMatchers.hasChildren("jcr:content"));
+    }
+
+    @Test
+    public void testJsonContent_Level1() {
+        Resource underTest = fsroot.getChild("folder2/content/jcr:content");
+        assertNotNull(underTest);
+        assertEquals("app:PageContent", underTest.getValueMap().get("jcr:primaryType", String.class));
+        assertEquals("sample/components/homepage", underTest.getResourceType());
+        assertEquals("sample/components/supertype", underTest.getResourceSuperType());
+        assertThat(underTest, ResourceMatchers.hasChildren("par", "header", "newslist", "lead", "image", "carousel", "rightpar"));
+    }
+
+    @Test
+    public void testJsonContent_Level5() {
+        Resource underTest = fsroot.getChild("folder2/content/jcr:content/par/image/file/jcr:content");
+        assertNotNull(underTest);
+        assertEquals("nt:resource", underTest.getValueMap().get("jcr:primaryType", String.class));
+        assertFalse(underTest.listChildren().hasNext());
+    }
+
+    @Test
+    public void testJsonContent_Datatypes() {
+        Resource underTest = fsroot.getChild("folder2/content/toolbar/profiles/jcr:content");
+        ValueMap props = underTest.getValueMap();
+        
+        assertEquals("Profiles", props.get("jcr:title", String.class));
+        assertEquals(true, props.get("booleanProp", false));
+        assertEquals((Long)1234567890123L, props.get("longProp", Long.class));
+        assertEquals((Double)1.2345d, props.get("decimalProp", Double.class), 0.00001d);
+        
+        assertArrayEquals(new String[] { "aa", "bb", "cc" }, props.get("stringPropMulti", String[].class));
+        assertArrayEquals(new Long[] { 1234567890123L, 55L }, props.get("longPropMulti", Long[].class));
+    }
+
+    @Test
+    public void testJsonContent_Datatypes_JCR() throws RepositoryException {
+        Resource underTest = fsroot.getChild("folder2/content/toolbar/profiles/jcr:content");
+        ValueMap props = underTest.getValueMap();
+        Node node = underTest.adaptTo(Node.class);
+        
+        assertEquals("/fs-test/folder2/content/toolbar/profiles/jcr:content", node.getPath());
+        assertEquals(6, node.getDepth());
+        assertTrue(node.isNodeType("app:PageContent"));
+        
+        assertTrue(node.hasProperty("jcr:title"));
+        assertEquals(PropertyType.STRING, node.getProperty("jcr:title").getType());
+        assertFalse(node.getProperty("jcr:title").isMultiple());
+        assertEquals("/fs-test/folder2/content/toolbar/profiles/jcr:content/jcr:title", node.getProperty("jcr:title").getPath());
+        assertEquals("Profiles", node.getProperty("jcr:title").getString());
+        assertEquals(PropertyType.BOOLEAN, node.getProperty("booleanProp").getType());
+        assertEquals(true, node.getProperty("booleanProp").getBoolean());
+        assertEquals(PropertyType.LONG, node.getProperty("longProp").getType());
+        assertEquals(1234567890123L, node.getProperty("longProp").getLong());
+        assertEquals(PropertyType.DOUBLE, node.getProperty("decimalProp").getType());
+        assertEquals(1.2345d, node.getProperty("decimalProp").getDouble(), 0.00001d);
+        
+        assertEquals(PropertyType.STRING, node.getProperty("stringPropMulti").getType());
+        assertTrue(node.getProperty("stringPropMulti").isMultiple());
+        Value[] stringPropMultiValues = node.getProperty("stringPropMulti").getValues();
+        assertEquals(3, stringPropMultiValues.length);
+        assertEquals("aa", stringPropMultiValues[0].getString());
+        assertEquals("bb", stringPropMultiValues[1].getString());
+        assertEquals("cc", stringPropMultiValues[2].getString());
+
+        assertEquals(PropertyType.LONG, node.getProperty("longPropMulti").getType());
+        assertTrue(node.getProperty("longPropMulti").isMultiple());
+        Value[] longPropMultiValues = node.getProperty("longPropMulti").getValues();
+        assertEquals(2, longPropMultiValues.length);
+        assertEquals(1234567890123L, longPropMultiValues[0].getLong());
+        assertEquals(55L, longPropMultiValues[1].getLong());
+        
+        // assert property iterator
+        Set<String> propertyNames = new HashSet<>();
+        PropertyIterator propertyIterator = node.getProperties();
+        while (propertyIterator.hasNext()) {
+            propertyNames.add(propertyIterator.nextProperty().getName());
+        }
+        assertTrue(props.keySet().containsAll(propertyNames));
+
+        // assert node iterator
+        Set<String> nodeNames = new HashSet<>();
+        NodeIterator nodeIterator = node.getNodes();
+        while (nodeIterator.hasNext()) {
+            nodeNames.add(nodeIterator.nextNode().getName());
+        }
+        assertEquals(ImmutableSet.of("par", "rightpar"), nodeNames);
+        
+        // node hierarchy
+        assertTrue(node.hasNode("rightpar"));
+        Node rightpar = node.getNode("rightpar");
+        assertEquals(7, rightpar.getDepth());
+        Node parent = rightpar.getParent();
+        assertTrue(node.isSame(parent));
+        Node ancestor = (Node)rightpar.getAncestor(4);
+        assertEquals(underTest.getParent().getPath(), ancestor.getPath());        
+    }
+
+    @Test
+    public void testJsonContent_InvalidPath() {
+        Resource underTest = fsroot.getChild("folder2/content/jcr:content/xyz");
+        assertNull(underTest);
+    }
+
+    @Test
+    public void testJcrMixedContent() throws RepositoryException {
+        // prepare mixed JCR content
+        Node node = root.adaptTo(Node.class);
+        Node fstest = node.addNode("fs-test", "nt:folder");
+        fstest.addNode("folder3", "nt:folder");
+
+        assertNull(fsroot.getChild("folder3"));
+    }
+
+    @Test
+    public void testFolder2ChildNodes() throws RepositoryException {
+        Resource folder2 = fsroot.getChild("folder2");
+        List<Resource> children = ImmutableList.copyOf(folder2.listChildren());
+        
+        assertEquals(2, children.size());
+        Resource child1 = children.get(0);
+        assertEquals("content", child1.getName());
+        assertEquals("app:Page", child1.getResourceType());
+        assertEquals("app:Page", child1.getValueMap().get("jcr:primaryType", String.class));
+
+        Resource child2 = children.get(1);
+        assertEquals("folder21", child2.getName());
+        assertEquals("nt:folder", child2.getValueMap().get("jcr:primaryType", String.class));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
index 8d9785e..9ee6162 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
@@ -28,25 +28,33 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.CharEncoding;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.hamcrest.ResourceMatchers;
+import org.apache.sling.testing.mock.osgi.MapUtil;
 import org.apache.sling.testing.mock.osgi.context.AbstractContextPlugin;
-import org.apache.sling.testing.mock.osgi.context.ContextPlugin;
 import org.apache.sling.testing.mock.sling.context.SlingContextImpl;
 
 class TestUtils {
 
-    public static ContextPlugin<SlingContextImpl> REGISTER_FSRESOURCE_PLUGIN = new AbstractContextPlugin<SlingContextImpl>() {
+    public static class RegisterFsResourcePlugin extends AbstractContextPlugin<SlingContextImpl> {
+        private final Map<String,Object> props;
+        public RegisterFsResourcePlugin(Object... props) {
+            this.props = MapUtil.toMap(props); 
+        }
         @Override
         public void beforeSetUp(SlingContextImpl context) throws Exception {
-            context.registerInjectActivateService(new FsResourceProvider(),
-                    "provider.file", "src/test/resources/fs-test",
-                    "provider.root", "/fs-test",
-                    "provider.checkinterval", 0);
+            Map<String,Object> config = new HashMap<>();
+            config.put("provider.file", "src/test/resources/fs-test");
+            config.put("provider.root", "/fs-test");
+            config.put("provider.checkinterval", 0);
+            config.putAll(props);
+            context.registerInjectActivateService(new FsResourceProvider(), config);
         }
     };
 
@@ -69,19 +77,21 @@ class TestUtils {
         assertThat(file, ResourceMatchers.props("jcr:primaryType", "nt:file"));
         assertEquals("nt:file", file.getResourceType());
         
-        try {
-            try (InputStream is = file.adaptTo(InputStream.class)) {
-                String data = IOUtils.toString(is, CharEncoding.UTF_8);
-                assertEquals(content, data);
-            }
-        }
-        catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
-
         assertNull(file.getResourceSuperType());
         assertEquals(file.getName(), file.adaptTo(File.class).getName());
         assertTrue(StringUtils.contains(file.adaptTo(URL.class).toString(), file.getName()));
+        
+        if (content != null) {
+            try {
+                try (InputStream is = file.adaptTo(InputStream.class)) {
+                    String data = IOUtils.toString(is, CharEncoding.UTF_8);
+                    assertEquals(content, data);
+                }
+            }
+            catch (IOException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
     }    
 
 }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
new file mode 100644
index 0000000..b3c18ee
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.junit.Test;
+
+public class ContentFileTest {
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testRootContent() {
+        File file = new File("src/test/resources/fs-test/folder2/content.json");
+        
+        ContentFile underTest = new ContentFile(file, null);
+        assertEquals(file, underTest.getFile());
+        assertNull(underTest.getSubPath());
+        
+        assertTrue(underTest.hasContent());
+
+        Map<String,Object> content = (Map<String,Object>)underTest.getContent();
+        assertEquals("app:Page", content.get("jcr:primaryType"));
+        assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
+
+        ValueMap props = underTest.getValueMap();
+        assertEquals("app:Page", props.get("jcr:primaryType"));
+        assertNull(props.get("jcr:content"));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testContentLevel1() {
+        File file = new File("src/test/resources/fs-test/folder2/content.json");
+        
+        ContentFile underTest = new ContentFile(file, "jcr:content");
+        assertEquals(file, underTest.getFile());
+        assertEquals("jcr:content", underTest.getSubPath());
+        
+        assertTrue(underTest.hasContent());
+
+        Map<String,Object> content = (Map<String,Object>)underTest.getContent();
+        assertEquals("app:PageContent", content.get("jcr:primaryType"));
+
+        ValueMap props = underTest.getValueMap();
+        assertEquals("app:PageContent", props.get("jcr:primaryType"));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testContentLevel5() {
+        File file = new File("src/test/resources/fs-test/folder2/content.json");
+        
+        ContentFile underTest = new ContentFile(file, "jcr:content/par/image/file/jcr:content");
+        assertEquals(file, underTest.getFile());
+        assertEquals("jcr:content/par/image/file/jcr:content", underTest.getSubPath());
+        
+        assertTrue(underTest.hasContent());
+
+        Map<String,Object> content = (Map<String,Object>)underTest.getContent();
+        assertEquals("nt:resource", content.get("jcr:primaryType"));
+
+        ValueMap props = underTest.getValueMap();
+        assertEquals("nt:resource", props.get("jcr:primaryType"));
+    }
+
+    @Test
+    public void testContentProperty() {
+        File file = new File("src/test/resources/fs-test/folder2/content.json");
+        
+        ContentFile underTest = new ContentFile(file, "jcr:content/jcr:title");
+        assertEquals(file, underTest.getFile());
+        assertEquals("jcr:content/jcr:title", underTest.getSubPath());
+        
+        assertTrue(underTest.hasContent());
+
+        assertEquals("English", underTest.getContent());
+
+        assertTrue(underTest.getValueMap().isEmpty());
+    }
+
+    @Test
+    public void testInvalidFile() {
+        File file = new File("src/test/resources/fs-test/folder1/file1a.txt");
+        ContentFile underTest = new ContentFile(file, null);
+        assertFalse(underTest.hasContent());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtilTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtilTest.java
new file mode 100644
index 0000000..71d6a38
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtilTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class ValueMapUtilTest {
+
+    @Test
+    public void testToValueMap() {
+        Map<String,Object> content = new HashMap<>();
+        content.put("stringProp", "abc");
+        content.put("intProp", 123);
+        content.put("childNode", ImmutableMap.<String,Object>of());
+        content.put("stringArray", new String[] { "a", "b", "c" });
+        content.put("stringList", ImmutableList.of("ab", "cd"));
+        content.put("intList", ImmutableList.of(12, 34));
+        
+        ValueMap props = ValueMapUtil.toValueMap(content);
+        assertEquals("abc", props.get("stringProp", String.class));
+        assertEquals((Integer)123, props.get("intProp", 0));
+        assertNull(props.get("childNode"));
+        assertArrayEquals(new String[] { "a", "b", "c" }, props.get("stringArray", String[].class));
+        assertArrayEquals(new String[] { "ab", "cd" }, props.get("stringList", String[].class));
+        assertArrayEquals(new Integer[] { 12, 34 }, props.get("intList", Integer[].class));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java
new file mode 100644
index 0000000..0bc14a4
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.parser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.File;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class ContentFileParserTest {
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testParseJson() {
+        File file = new File("src/test/resources/fs-test/folder2/content.json");
+        Map<String,Object> content = ContentFileParser.parse(file);
+        assertNotNull(content);
+        assertEquals("app:Page", content.get("jcr:primaryType"));
+        assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
+    }
+
+    @Test
+    public void testParseInvalidJson() {
+        File file = new File("src/test/resources/fs-test/folder1/file1a.txt");
+        Map<String,Object> content = ContentFileParser.parse(file);
+        assertNull(content);
+    }
+
+}
diff --git a/src/test/resources/fs-test/folder2/content.json b/src/test/resources/fs-test/folder2/content.json
new file mode 100644
index 0000000..e808ef8
--- /dev/null
+++ b/src/test/resources/fs-test/folder2/content.json
@@ -0,0 +1,262 @@
+/* Comment example */
+{
+  "jcr:primaryType": "app:Page",
+  "jcr:createdBy": "admin",
+  "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+  /* Comment example */
+  "jcr:content": {
+    "jcr:primaryType": "app:PageContent",  /* Comment example */
+    "jcr:createdBy": "admin",
+    "jcr:title": "English",
+    "app:template": "/apps/sample/templates/homepage",
+    "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+    "app:lastModified": "Tue Apr 22 2014 15:11:24 GMT+0200",
+    "dateISO8601String": "2014-04-22T15:11:24.000+02:00",
+    "pageTitle": "Sample Homepage",
+    "sling:resourceType": "sample/components/homepage",
+    "sling:resourceSuperType": "sample/components/supertype",
+    "app:designPath": "/etc/designs/sample",
+    "app:lastModifiedBy": "admin",
+    "utf8Property": "äöü߀",
+    "par": {
+      "jcr:primaryType": "nt:unstructured",
+      "sling:resourceType": "foundation/components/parsys",
+      "colctrl": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "layout": "2;colctrl-lt0",
+        "jcr:created": "Mon Aug 23 2010 22:02:24 GMT+0200",
+        "jcr:lastModified": "Mon Aug 23 2010 22:02:35 GMT+0200",
+        "sling:resourceType": "foundation/components/parsys/colctrl"
+      },
+      "image": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "fileReference": "/content/dam/sample/portraits/jane_doe.jpg",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:03:39 GMT+0200",
+        "width": "340",
+        "jcr:lastModified": "Sun Oct 31 2010 21:39:50 GMT+0100",
+        "sling:resourceType": "foundation/components/image",
+        "file": {
+          "jcr:primaryType": "nt:file",
+          "jcr:createdBy": "admin",
+          "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+          "jcr:content": {
+            "jcr:primaryType": "nt:resource",
+            "jcr:lastModifiedBy": "anonymous",
+            "jcr:mimeType": "image/jpeg",
+            "jcr:lastModified": "Thu Aug 07 2014 16:32:59 GMT+0200",
+            ":jcr:data": 24377,
+            "jcr:uuid": "eda76d00-b2cd-4b59-878f-c33f71ceaddc"
+          }
+        }
+      },
+      "title_1": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:title": "Strategic Consulting",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:12:08 GMT+0200",
+        "jcr:lastModified": "Wed Oct 27 2010 21:33:24 GMT+0200",
+        "sling:resourceType": "sample/components/title"
+      },
+      "text_1": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Sun Oct 31 2010 21:48:04 GMT+0100",
+        "text": "<p><span class=\"Apple-style-span\" style=\"font-size: 12px;\">In&nbsp;today's competitive market, organizations can face several key geometric challenges:<\/span><\/p>\n<ul>\n<li><span class=\"Apple-style-span\" style=\"font-size: 12px;\">Polyhedral Sectioning<\/span><\/li>\n<li><span class=\"Apple-style-span\" style=\"font-size: 12px;\">Triangulation&nbsp;<\/span><\/li>\n<li><span class=\"Apple-style-span\" style=\"font-size: 12px;\">Trigonometric Calculation<\/span><\ [...]
+        "jcr:lastModified": "Sun Oct 31 2010 21:49:06 GMT+0100",
+        "sling:resourceType": "foundation/components/text",
+        "textIsRich": "true"
+      },
+      "col_break12825937554040": {
+        "jcr:primaryType": "nt:unstructured",
+        "controlType": "break",
+        "sling:resourceType": "foundation/components/parsys/colctrl"
+      },
+      "image_0": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "fileReference": "/content/dam/sample/offices/clean_room.jpg",
+        "height": "226",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:04:46 GMT+0200",
+        "jcr:lastModified": "Fri Nov 05 2010 10:38:15 GMT+0100",
+        "sling:resourceType": "foundation/components/image",
+        "imageRotate": "0",
+        "file": {
+          "jcr:primaryType": "nt:file",
+          "jcr:createdBy": "admin",
+          "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
+          "jcr:content": {
+            "jcr:primaryType": "nt:resource",
+            "jcr:lastModifiedBy": "anonymous",
+            "jcr:mimeType": "image/jpeg",
+            "jcr:lastModified": "Thu Aug 07 2014 16:32:59 GMT+0200",
+            ":jcr:data": 21142,
+            "jcr:uuid": "6139077f-191f-4337-aaef-55456ebe6784"
+          }
+        }
+      },
+      "title_2": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:title": "Shape Technology",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:12:13 GMT+0200",
+        "jcr:lastModified": "Tue Oct 26 2010 21:16:29 GMT+0200",
+        "sling:resourceType": "sample/components/title"
+      },
+      "text_0": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Mon Aug 23 2010 22:16:30 GMT+0200",
+        "text": "<p>The Sample investment in R&amp;D has done more than solidify our industry leadership role, we have now outpaced our competitors to such an extent that we are in an altogether new space.<\/p>\n<p>This is why our high quality polygons and polyhedra provide the only turnkey solutions across the whole range of euclidean geometry. And our mathematicians are working on the next generation of fractal curves to bring you shapes that are unthinkable today.<\/p>\n<p><\/p>\n<p>< [...]
+        "jcr:lastModified": "Mon Nov 08 2010 20:39:00 GMT+0100",
+        "sling:resourceType": "foundation/components/text",
+        "textIsRich": "true"
+      },
+      "col_end12825937444810": {
+        "jcr:primaryType": "nt:unstructured",
+        "controlType": "end",
+        "sling:resourceType": "foundation/components/parsys/colctrl"
+      }
+    },
+    "header": {
+      "jcr:primaryType": "nt:unstructured",
+      "jcr:title": "trust our experience\r\nto manage your business",
+      "imageReference": "/content/dam/sample/header.png",
+      "text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc eget neque. Nunc condimentum ipsum et orci. Aenean est. Cras eget diam. read more",
+      "sling:resourceType": "sample/components/header"
+    },
+    "newslist": {
+      "jcr:primaryType": "nt:unstructured",
+      "headline": "trust our experience\nto manage your business",
+      "text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc eget neque. Nunc condimentum ipsum et orci. Aenean est. Cras eget diam. read more",
+      "sling:resourceType": "sample/components/listchildren",
+      "listroot": "/content/sample/en/about/news"
+    },
+    "lead": {
+      "jcr:primaryType": "nt:unstructured",
+      "jcr:title": "World Leader in Applied Geometry ",
+      "jcr:lastModifiedBy": "admin",
+      "text": "Lead Text",
+      "title": "Lead Title",
+      "jcr:description": "Sample has been selling and servicing shapes for over 2000 years. From our beginnings as a small vendor of squares and rectangles we have grown our business into a leading global provider of platonic solids and fractals. Join us as we lead geometry into the future.",
+      "jcr:lastModified": "Wed Jan 19 2011 14:35:29 GMT+0100",
+      "sling:resourceType": "sample/components/lead",
+      "app:annotations": {"jcr:primaryType": "nt:unstructured"}
+    },
+    "image": {
+      "jcr:primaryType": "nt:unstructured",
+      "jcr:lastModifiedBy": "admin",
+      "jcr:lastModified": "Wed Oct 27 2010 21:30:59 GMT+0200",
+      "imageRotate": "0"
+    },
+    "carousel": {
+      "jcr:primaryType": "nt:unstructured",
+      "playSpeed": "6000",
+      "jcr:lastModifiedBy": "admin",
+      "pages": [
+        "/content/sample/en/events/techsummit",
+        "/content/sample/en/events/userconf",
+        "/content/sample/en/events/shapecon",
+        "/content/sample/en/events/dsc"
+      ],
+      "jcr:lastModified": "Tue Oct 05 2010 14:14:27 GMT+0200",
+      "transTime": "1000",
+      "sling:resourceType": "foundation/components/carousel",
+      "listFrom": "static"
+    },
+    "rightpar": {
+      "jcr:primaryType": "nt:unstructured",
+      "sling:resourceType": "foundation/components/parsys",
+      "teaser": {
+        "jcr:primaryType": "nt:unstructured",
+        "jcr:createdBy": "admin",
+        "jcr:lastModifiedBy": "admin",
+        "jcr:created": "Tue Jan 25 2011 11:30:09 GMT+0100",
+        "campaignpath": "/content/campaigns/sample",
+        "jcr:lastModified": "Wed Feb 02 2011 08:40:30 GMT+0100",
+        "sling:resourceType": "personalization/components/teaser"
+      }
+    }
+  },
+  "toolbar": {
+    "jcr:primaryType": "app:Page",
+    "jcr:createdBy": "admin",
+    "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+    "jcr:content": {
+      "jcr:primaryType": "app:PageContent",
+      "subtitle": "Contains the toolbar",
+      "jcr:createdBy": "admin",
+      "jcr:title": "Toolbar",
+      "app:template": "/apps/sample/templates/contentpage",
+      "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+      "app:lastModified": "Wed Aug 25 2010 22:51:02 GMT+0200",
+      "hideInNav": "true",
+      "sling:resourceType": "sample/components/contentpage",
+      "app:lastModifiedBy": "admin",
+      "par": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "foundation/components/parsys"
+      },
+      "rightpar": {
+        "jcr:primaryType": "nt:unstructured",
+        "sling:resourceType": "foundation/components/iparsys",
+        "iparsys_fake_par": {
+          "jcr:primaryType": "nt:unstructured",
+          "sling:resourceType": "foundation/components/iparsys/par"
+        }
+      }
+    },
+    "profiles": {
+      "jcr:primaryType": "app:Page",
+      "jcr:createdBy": "admin",
+      "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+      "jcr:content": {
+        "jcr:primaryType": "app:PageContent",
+        "jcr:createdBy": "admin",
+        "jcr:title": "Profiles",
+        "app:template": "/apps/sample/templates/contentpage",
+        "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
+        "app:lastModified": "Thu Nov 05 2009 20:27:13 GMT+0100",
+        "hideInNav": true,
+        "sling:resourceType": "sample/components/contentpage",
+        "app:lastModifiedBy": "admin",
+        "longProp": 1234567890123,
+        "decimalProp": 1.2345,
+        "booleanProp": true,
+        "longPropMulti": [1234567890123,55],
+        "decimalPropMulti": [1.2345,1.1],
+        "booleanPropMulti": [true,false],
+        "stringPropMulti": ["aa","bb","cc"],
+        "par": {
+          "jcr:primaryType": "nt:unstructured",
+          "sling:resourceType": "foundation/components/parsys",
+          "textimage": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "foundation/components/textimage"
+          },
+          "mygadgets": {
+            "jcr:primaryType": "nt:unstructured",
+            "gadgets": "http://customer.meteogroup.de/meteogroup/gadgets/wetter24.xml\nhttp://germanweatherradar.googlecode.com/svn/trunk/german-weather-radar.xml\nhttp://www.digitalpowered.info/gadget/ski.pictures.xml\nhttp://www.canbuffi.de/gadgets/clock/clock.xml",
+            "sling:resourceType": "personalization/components/mygadgets"
+          }
+        },
+        "rightpar": {
+          "jcr:primaryType": "nt:unstructured",
+          "sling:resourceType": "foundation/components/iparsys",
+          "iparsys_fake_par": {
+            "jcr:primaryType": "nt:unstructured",
+            "sling:resourceType": "foundation/components/iparsys/par"
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/resources/fs-test/folder2/content/content2.json b/src/test/resources/fs-test/folder2/content/content2.json
new file mode 100644
index 0000000..261509a
--- /dev/null
+++ b/src/test/resources/fs-test/folder2/content/content2.json
@@ -0,0 +1,4 @@
+{
+  "jcr:primaryType": "app:Page",
+  "jcr:createdBy": "admin"
+}
diff --git a/src/test/resources/fs-test/folder2/file2a.txt b/src/test/resources/fs-test/folder2/file2a.txt
deleted file mode 100644
index e2baf26..0000000
--- a/src/test/resources/fs-test/folder2/file2a.txt
+++ /dev/null
@@ -1 +0,0 @@
-file2a
\ No newline at end of file
diff --git a/src/test/resources/fs-test/folder2/folder21/file21a.txt b/src/test/resources/fs-test/folder2/folder21/file21a.txt
new file mode 100644
index 0000000..3d5becc
--- /dev/null
+++ b/src/test/resources/fs-test/folder2/folder21/file21a.txt
@@ -0,0 +1 @@
+file21a
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 12/29: SLING-6440 update to latest API

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit a66ffe63fd18e6898a936d563ac6a15b797a0fa6
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Thu Mar 2 21:56:09 2017 +0000

    SLING-6440 update to latest API
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1785199 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                                             | 4 ++--
 .../sling/fsprovider/internal/parser/ContentFileParserUtil.java     | 6 +++---
 .../apache/sling/fsprovider/internal/parser/ContentFileTypes.java   | 6 +++---
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/pom.xml b/pom.xml
index 1ab9b94..16113c8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,7 +66,7 @@
                         <Embed-Dependency>
                             johnzon-core;scope=compile;inline=false,
                             geronimo-json_1.0_spec;scope=compile;inline=false,
-                            org.apache.sling.fscontentparser;scope=compile;inline=false
+                            org.apache.sling.commons.fscontentparser;scope=compile;inline=false
                         </Embed-Dependency>
                     </instructions>
                 </configuration>
@@ -139,7 +139,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.fscontentparser</artifactId>
+            <artifactId>org.apache.sling.commons.fscontentparser</artifactId>
             <version>1.0.0-SNAPSHOT</version>
             <scope>compile</scope>
         </dependency>
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
index ec9af59..4b46157 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
@@ -25,9 +25,9 @@ import java.io.File;
 import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.fscontentparser.ContentFileExtension;
 import org.apache.sling.fscontentparser.ContentFileParser;
 import org.apache.sling.fscontentparser.ContentFileParserFactory;
+import org.apache.sling.fscontentparser.ContentFileType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -44,13 +44,13 @@ class ContentFileParserUtil {
         ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
         try {
             Thread.currentThread().setContextClassLoader(ContentFileParserUtil.class.getClassLoader());
-            JSON_PARSER = ContentFileParserFactory.create(ContentFileExtension.JSON);
+            JSON_PARSER = ContentFileParserFactory.create(ContentFileType.JSON);
         }
         finally {
             Thread.currentThread().setContextClassLoader(oldClassLoader);
         }
     }
-    private static final ContentFileParser JCR_XML_PARSER = ContentFileParserFactory.create(ContentFileExtension.JCR_XML);
+    private static final ContentFileParser JCR_XML_PARSER = ContentFileParserFactory.create(ContentFileType.JCR_XML);
     
     private ContentFileParserUtil() {
         // static methods only
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
index 689654d..f3e2c22 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
@@ -18,7 +18,7 @@
  */
 package org.apache.sling.fsprovider.internal.parser;
 
-import org.apache.sling.fscontentparser.ContentFileExtension;
+import org.apache.sling.fscontentparser.ContentFileType;
 
 /**
  * Content file types.
@@ -28,12 +28,12 @@ public final class ContentFileTypes {
     /**
      * JSON content files.
      */
-    public static final String JSON_SUFFIX = "." + ContentFileExtension.JSON;
+    public static final String JSON_SUFFIX = "." + ContentFileType.JSON.getExtension();
 
     /**
      * JCR XML content files.
      */
-    public static final String JCR_XML_SUFFIX = "." + ContentFileExtension.JCR_XML;
+    public static final String JCR_XML_SUFFIX = "." + ContentFileType.JCR_XML.getExtension();
         
     private ContentFileTypes() {
         // static methods only

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 26/29: update dependencies

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 5f9e9c0d22fae184e96afbdf5978c971f10636a2
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Thu Mar 23 15:13:09 2017 +0000

    update dependencies
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1788266 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index efb1f26..9714e9a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -146,7 +146,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.jcr.contentparser</artifactId>
-            <version>1.0.0-SNAPSHOT</version>
+            <version>1.0.0</version>
             <scope>compile</scope>
         </dependency>
         <dependency>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 13/29: set version in trunk to 2.0.0-SNAPSHOT (based on new Resource Provider SPI, 1.x in branch for old SPI)

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 42d217af0f6b3323ffb298165bb88a265669b7ba
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Mar 3 13:12:55 2017 +0000

    set version in trunk to 2.0.0-SNAPSHOT (based on new Resource Provider SPI, 1.x in branch for old SPI)
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1785291 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 16113c8..3a525ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
 
     <artifactId>org.apache.sling.fsresource</artifactId>
     <packaging>bundle</packaging>
-    <version>1.2.3-SNAPSHOT</version>
+    <version>2.0.0-SNAPSHOT</version>
 
     <name>Apache Sling File System Resource Provider</name>
     <description>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 02/29: SLING-6526 Switch back to Sling API 2.11.0 add unit test coverage

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 17c73925e657b3b05ed8628188874c084e753bb1
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Feb 17 17:45:13 2017 +0000

    SLING-6526 Switch back to Sling API 2.11.0
    add unit test coverage
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1783439 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  45 +++++-
 .../sling/fsprovider/internal/FileMonitor.java     |   4 +-
 .../fsprovider/internal/FsResourceProvider.java    |   1 +
 .../sling/fsprovider/internal/FileMonitorTest.java | 174 +++++++++++++++++++++
 .../sling/fsprovider/internal/FilesFolderTest.java |  76 +++++++++
 .../sling/fsprovider/internal/JcrMixedTest.java    | 103 ++++++++++++
 .../sling/fsprovider/internal/TestUtils.java       |  87 +++++++++++
 src/test/resources/fs-test/folder1/file1a.txt      |   1 +
 src/test/resources/fs-test/folder1/file1b.txt      |   1 +
 .../resources/fs-test/folder1/folder11/file11a.txt |   1 +
 src/test/resources/fs-test/folder2/file2a.txt      |   1 +
 src/test/resources/simplelogger.properties         |  19 +++
 12 files changed, 510 insertions(+), 3 deletions(-)

diff --git a/pom.xml b/pom.xml
index c7e99ac..1353c6c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,6 +61,31 @@
                 <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>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                      <exclude>src/test/resources/fs-test/**</exclude>
+                    </excludes>
+                </configuration>
             </plugin>
         </plugins>
     </build>
@@ -72,7 +97,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.15.0</version>
+            <version>2.11.0</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
@@ -97,5 +122,23 @@
             <version>1.0.0</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+            <version>2.2.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.logging-mock</artifactId>
+            <version>2.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>
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
index 5729b7c..c964359 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -203,8 +203,8 @@ public class FileMonitor extends TimerTask {
 
         for(final ObserverConfiguration config : reporter.getObserverConfigurations()) {
             if ( config.matches(monitorable.path) ) {
-                final ResourceChange change = new ResourceChange(changeType, monitorable.path, false);
-                reporter.reportChanges(config, Collections.singleton(change), false);
+                final ResourceChange change = new ResourceChange(changeType, monitorable.path, false, null, null, null);
+                reporter.reportChanges(Collections.singleton(change), false);
             }
         }
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index 4a32737..f2ed39a 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -154,6 +154,7 @@ public class FsResourceProvider extends ResourceProvider<Object> {
     /**
      * Returns an iterator of resources.
      */
+    @SuppressWarnings("unchecked")
     @Override
     public Iterator<Resource> listChildren(final ResolveContext<Object> ctx, final Resource parent) {
         File parentFile = parent.adaptTo(File.class);
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
new file mode 100644
index 0000000..3201c3f
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
@@ -0,0 +1,174 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
+import org.apache.sling.testing.mock.sling.junit.SlingContextCallback;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test events when changing filesystem content.
+ */
+public class FileMonitorTest {
+
+    private final File tempDir;
+    private final ResourceListener resourceListener = new ResourceListener();
+    
+    public FileMonitorTest() throws Exception {
+        tempDir = Files.createTempDirectory(getClass().getName()).toFile();
+    }
+
+    @Rule
+    public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
+        .beforeSetUp(new SlingContextCallback() {
+            @Override
+            public void execute(SlingContext context) throws Exception {
+                // copy test content to temp. directory
+                tempDir.mkdirs();
+                File sourceDir = new File("src/test/resources/fs-test");
+                FileUtils.copyDirectory(sourceDir, tempDir);
+                
+                // mount temp. directory
+                context.registerInjectActivateService(new FsResourceProvider(),
+                        "provider.file", tempDir.getPath(),
+                        "provider.root", "/fs-test",
+                        "provider.checkinterval", 120);
+                
+                // register resource change listener
+                context.registerService(ResourceChangeListener.class, resourceListener,
+                        ResourceChangeListener.PATHS, new String[] { "/fs-test" });
+            }
+        })
+        .afterTearDown(new SlingContextCallback() {
+            @Override
+            public void execute(SlingContext context) throws Exception {
+                // remove temp directory
+                tempDir.delete();
+            }
+        })
+        .build();
+
+    @Test
+    public void testUpdateFile() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file1a = new File(tempDir, "folder1/file1a.txt");
+        FileUtils.write(file1a, "newcontent");
+        
+        Thread.sleep(250);
+
+        assertEquals(1, changes.size());
+        assertChange(changes, 0, "/fs-test/folder1/file1a.txt", ChangeType.CHANGED);
+    }
+    
+    @Test
+    public void testAddFile() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file1c = new File(tempDir, "folder1/file1c.txt");
+        FileUtils.write(file1c, "newcontent");
+        
+        Thread.sleep(250);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, 0, "/fs-test/folder1", ChangeType.CHANGED);
+        assertChange(changes, 1, "/fs-test/folder1/file1c.txt", ChangeType.ADDED);
+    }
+    
+    @Test
+    public void testRemoveFile() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file1a = new File(tempDir, "folder1/file1a.txt");
+        file1a.delete();
+        
+        Thread.sleep(250);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, 0, "/fs-test/folder1", ChangeType.CHANGED);
+        assertChange(changes, 1, "/fs-test/folder1/file1a.txt", ChangeType.REMOVED);
+    }
+    
+    @Test
+    public void testAddFolder() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File folder3 = new File(tempDir, "folder3");
+        folder3.mkdir();
+        
+        Thread.sleep(250);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, 0, "/fs-test", ChangeType.CHANGED);
+        assertChange(changes, 1, "/fs-test/folder3", ChangeType.ADDED);
+    }
+    
+    @Test
+    public void testRemoveFolder() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File folder1 = new File(tempDir, "folder1");
+        FileUtils.deleteDirectory(folder1);
+        
+        Thread.sleep(250);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, 0, "/fs-test", ChangeType.CHANGED);
+        assertChange(changes, 1, "/fs-test/folder1", ChangeType.REMOVED);
+    }
+
+    
+    private void assertChange(List<ResourceChange> changes, int index, String path, ChangeType changeType) {
+        ResourceChange change = changes.get(index);
+        assertEquals(path, change.getPath());
+        assertEquals(changeType, change.getType());
+    }
+    
+    static class ResourceListener implements ResourceChangeListener {
+        private final List<ResourceChange> allChanges = new ArrayList<>();
+        @Override
+        public void onChange(List<ResourceChange> changes) {
+            allChanges.addAll(changes);
+        }
+        public List<ResourceChange> getChanges() {
+            return allChanges;
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
new file mode 100644
index 0000000..d837d6e
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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 static org.apache.sling.fsprovider.internal.TestUtils.REGISTER_FSRESOURCE_PLUGIN;
+import static org.apache.sling.fsprovider.internal.TestUtils.assertFile;
+import static org.apache.sling.fsprovider.internal.TestUtils.assertFolder;
+import static org.junit.Assert.assertThat;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.hamcrest.ResourceMatchers;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test access to files and folders from filesystem.
+ */
+public class FilesFolderTest {
+
+    private Resource root;
+    private Resource fsroot;
+
+    @Rule
+    public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
+        .plugin(REGISTER_FSRESOURCE_PLUGIN)
+        .build();
+
+    @Before
+    public void setUp() {
+        root = context.resourceResolver().getResource("/");
+        fsroot = context.resourceResolver().getResource("/fs-test");
+    }
+
+    @Test
+    public void testFolders() {
+        assertFolder(fsroot, "folder1");
+        assertFolder(fsroot, "folder1/folder11");
+        assertFolder(fsroot, "folder2");
+    }
+
+    @Test
+    public void testFiles() {
+        assertFile(fsroot, "folder1/file1a.txt", "file1a");
+        assertFile(fsroot, "folder1/file1b.txt", "file1b");
+        assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
+        assertFile(fsroot, "folder2/file2a.txt", "file2a");
+    }
+
+    @Test
+    public void testListChildren() {
+        assertThat(root, ResourceMatchers.containsChildren("fs-test"));
+        assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2"));
+        assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("file1a.txt", "file1b.txt"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java
new file mode 100644
index 0000000..41bdbae
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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 static org.apache.sling.fsprovider.internal.TestUtils.REGISTER_FSRESOURCE_PLUGIN;
+import static org.apache.sling.fsprovider.internal.TestUtils.assertFile;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.hamcrest.ResourceMatchers;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test access mixed with JCR content on same path.
+ */
+public class JcrMixedTest {
+
+    private Resource root;
+    private Resource fsroot;
+
+    @Rule
+    public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
+        .plugin(REGISTER_FSRESOURCE_PLUGIN)
+        .build();
+
+    @Before
+    public void setUp() throws RepositoryException {
+        root = context.resourceResolver().getResource("/");
+        fsroot = context.resourceResolver().getResource("/fs-test");
+        
+        // prepare mixed JCR content
+        Node node = root.adaptTo(Node.class);
+        Node fstest = node.addNode("fs-test", "nt:folder");
+        // folder1
+        Node folder1 = fstest.addNode("folder1", "nt:folder");
+        folder1.setProperty("prop1", "value1");
+        folder1.setProperty("prop2", 123L);
+        // folder1/file1a.txt
+        Node file1a = folder1.addNode("file1a.txt", "nt:file");
+        file1a.setProperty("prop1", "value2");
+        file1a.setProperty("prop2", 234L);
+        // folder1/file1c.txt
+        folder1.addNode("file1c.txt", "nt:file");
+        // folder3
+        fstest.addNode("folder3", "nt:folder");
+    }
+
+    @Test
+    public void testFolders() {
+        // expected properties from JCR for folders
+        Resource folder1 = fsroot.getChild("folder1");
+        assertThat(folder1, ResourceMatchers.props("jcr:primaryType", "nt:folder",
+                "prop1", "value1",
+                "prop2", 123L));
+    }
+
+    @Test
+    public void testFiles() {
+        assertFile(fsroot, "folder1/file1a.txt", "file1a");
+        assertFile(fsroot, "folder1/file1b.txt", "file1b");
+        assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
+        assertFile(fsroot, "folder2/file2a.txt", "file2a");
+
+        // do not expected properties from JCR for files
+        Resource file1a = fsroot.getChild("folder1/file1a.txt");
+        assertThat(file1a, not(ResourceMatchers.props(
+                "prop1", "value2",
+                "prop2", 234L)));
+    }
+
+    @Test
+    public void testListChildren() {
+        assertThat(root, ResourceMatchers.containsChildren("fs-test"));
+        assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2", "folder3"));
+        assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("file1a.txt", "file1b.txt", "file1c.txt"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
new file mode 100644
index 0000000..8d9785e
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
@@ -0,0 +1,87 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.CharEncoding;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.hamcrest.ResourceMatchers;
+import org.apache.sling.testing.mock.osgi.context.AbstractContextPlugin;
+import org.apache.sling.testing.mock.osgi.context.ContextPlugin;
+import org.apache.sling.testing.mock.sling.context.SlingContextImpl;
+
+class TestUtils {
+
+    public static ContextPlugin<SlingContextImpl> REGISTER_FSRESOURCE_PLUGIN = new AbstractContextPlugin<SlingContextImpl>() {
+        @Override
+        public void beforeSetUp(SlingContextImpl context) throws Exception {
+            context.registerInjectActivateService(new FsResourceProvider(),
+                    "provider.file", "src/test/resources/fs-test",
+                    "provider.root", "/fs-test",
+                    "provider.checkinterval", 0);
+        }
+    };
+
+    public static void assertFolder(Resource resource, String path) {
+        Resource folder = resource.getChild(path);
+        assertNotNull(path, folder);
+        
+        assertThat(folder, ResourceMatchers.props("jcr:primaryType", "nt:folder"));
+        assertEquals("nt:folder", folder.getResourceType());
+        
+        assertNull(folder.getResourceSuperType());
+        assertEquals(folder.getName(), folder.adaptTo(File.class).getName());
+        assertTrue(StringUtils.contains(folder.adaptTo(URL.class).toString(), folder.getName()));
+    }
+
+    public static void assertFile(Resource resource, String path, String content) {
+        Resource file = resource.getChild(path);
+        assertNotNull(path, file);
+        
+        assertThat(file, ResourceMatchers.props("jcr:primaryType", "nt:file"));
+        assertEquals("nt:file", file.getResourceType());
+        
+        try {
+            try (InputStream is = file.adaptTo(InputStream.class)) {
+                String data = IOUtils.toString(is, CharEncoding.UTF_8);
+                assertEquals(content, data);
+            }
+        }
+        catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+
+        assertNull(file.getResourceSuperType());
+        assertEquals(file.getName(), file.adaptTo(File.class).getName());
+        assertTrue(StringUtils.contains(file.adaptTo(URL.class).toString(), file.getName()));
+    }    
+
+}
diff --git a/src/test/resources/fs-test/folder1/file1a.txt b/src/test/resources/fs-test/folder1/file1a.txt
new file mode 100644
index 0000000..9f0f26f
--- /dev/null
+++ b/src/test/resources/fs-test/folder1/file1a.txt
@@ -0,0 +1 @@
+file1a
\ No newline at end of file
diff --git a/src/test/resources/fs-test/folder1/file1b.txt b/src/test/resources/fs-test/folder1/file1b.txt
new file mode 100644
index 0000000..518f63d
--- /dev/null
+++ b/src/test/resources/fs-test/folder1/file1b.txt
@@ -0,0 +1 @@
+file1b
\ No newline at end of file
diff --git a/src/test/resources/fs-test/folder1/folder11/file11a.txt b/src/test/resources/fs-test/folder1/folder11/file11a.txt
new file mode 100644
index 0000000..4940b5d
--- /dev/null
+++ b/src/test/resources/fs-test/folder1/folder11/file11a.txt
@@ -0,0 +1 @@
+file11a
\ No newline at end of file
diff --git a/src/test/resources/fs-test/folder2/file2a.txt b/src/test/resources/fs-test/folder2/file2a.txt
new file mode 100644
index 0000000..e2baf26
--- /dev/null
+++ b/src/test/resources/fs-test/folder2/file2a.txt
@@ -0,0 +1 @@
+file2a
\ No newline at end of file
diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties
new file mode 100644
index 0000000..e62c1ea
--- /dev/null
+++ b/src/test/resources/simplelogger.properties
@@ -0,0 +1,19 @@
+# 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.
+
+org.slf4j.simpleLogger.defaultLogLevel=warn
+org.slf4j.simpleLogger.log.org.apache.sling.fsprovider.internal=warn

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 29/29: [maven-release-plugin] copy for tag org.apache.sling.fsresource-2.0.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 6bc27b31788e3a02203269dc84ec44147b076354
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Mon Mar 27 16:15:23 2017 +0000

    [maven-release-plugin] copy for tag org.apache.sling.fsresource-2.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.fsresource-2.0.0@1788972 13f79535-47bb-0310-9956-ffa450edef68

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 15/29: SLING-6537 FileVault XML support

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 1816787009b9791647fb67e72711a63fa31c9851
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Mon Mar 6 21:48:18 2017 +0000

    SLING-6537 FileVault XML support
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1785771 13f79535-47bb-0310-9956-ffa450edef68
---
 .../fsprovider/internal/ContentFileExtensions.java |   3 +-
 .../sling/fsprovider/internal/FileMonitor.java     |  98 ++++++---
 .../apache/sling/fsprovider/internal/FsMode.java   |  41 ++++
 .../fsprovider/internal/FsResourceProvider.java    | 147 +++++++++----
 .../internal/InitialContentImportOptions.java      |  76 +++++++
 .../fsprovider/internal/mapper/ContentFile.java    |  53 +++--
 .../internal/mapper/ContentFileResourceMapper.java |  16 +-
 .../internal/mapper/FileVaultResourceMapper.java   | 201 ++++++++++++++++++
 .../fsprovider/internal/mapper/jcr/FsNode.java     |  20 +-
 .../internal/mapper/jcr/FsNodeIterator.java        |   9 +-
 .../internal/parser/ContentFileParserUtil.java     |   6 +-
 .../sling/fsprovider/internal/FileMonitorTest.java |  60 ++----
 .../fsprovider/internal/FileVaultContentTest.java  | 147 +++++++++++++
 .../internal/FileVaultFileMonitorTest.java         | 232 +++++++++++++++++++++
 .../internal/InitialContentImportOptionsTest.java  |  59 ++++++
 .../fsprovider/internal/JcrXmlContentTest.java     |   5 +-
 .../sling/fsprovider/internal/JsonContentTest.java |   7 +-
 .../sling/fsprovider/internal/TestUtils.java       |  28 +++
 .../vaultfs-test/META-INF/vault/filter.xml         |   4 +-
 .../jcr_root/content/dam/talk.png/.content.xml     |   2 -
 20 files changed, 1053 insertions(+), 161 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java b/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
index 5097750..f9cacec 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
@@ -41,8 +41,9 @@ public final class ContentFileExtensions {
      * @return Content file name suffix or null if not a context file.
      */
     public String getSuffix(File file) {
+        String fileName = "/" + file.getName();
         for (String suffix : contentFileSuffixes) {
-            if (StringUtils.endsWith(file.getName(), suffix)) {
+            if (StringUtils.endsWith(fileName, suffix)) {
                 return suffix;
             }
         }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
index 7d77482..319e712 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sling.fsprovider.internal;
 
+import static org.apache.jackrabbit.vault.util.Constants.ROOT_DIR;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
@@ -26,6 +28,7 @@ import java.util.Timer;
 import java.util.TimerTask;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
 import org.apache.sling.api.resource.observation.ResourceChange;
 import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
 import org.apache.sling.fsprovider.internal.mapper.ContentFile;
@@ -50,7 +53,7 @@ public final class FileMonitor extends TimerTask {
     private final Monitorable root;
 
     private final FsResourceProvider provider;
-    
+    private final FsMode fsMode;
     private final ContentFileExtensions contentFileExtensions;
     private final ContentFileCache contentFileCache;
 
@@ -59,12 +62,19 @@ public final class FileMonitor extends TimerTask {
      * @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, FsMode fsMode,
             final ContentFileExtensions contentFileExtensions, final ContentFileCache contentFileCache) {
         this.provider = provider;
+        this.fsMode = fsMode;
         this.contentFileExtensions = contentFileExtensions;
         this.contentFileCache = contentFileCache;
-        this.root = new Monitorable(this.provider.getProviderRoot(), this.provider.getRootFile(), null);
+        
+        File rootFile = this.provider.getRootFile();
+        if (fsMode == FsMode.FILEVAULT_XML) {
+            rootFile = new File(this.provider.getRootFile(), ROOT_DIR + PlatformNameFormat.getPlatformPath(this.provider.getProviderRoot()));
+        }
+        this.root = new Monitorable(this.provider.getProviderRoot(), rootFile, 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);
@@ -142,6 +152,13 @@ public final class FileMonitor extends TimerTask {
                 // new file and reset status
                 createStatus(monitorable, contentFileExtensions, contentFileCache);
                 sendEvents(monitorable, ChangeType.ADDED, reporter);
+                final FileStatus fs = (FileStatus)monitorable.status;
+                if ( fs instanceof DirStatus ) {
+                    final DirStatus ds = (DirStatus)fs;
+                    // remove monitorables for new folder and update folder children to send events for directory contents
+                    ds.children = new Monitorable[0];
+                    checkDirStatusChildren(monitorable, reporter);
+                }
             }
         } else {
             // check if the file has been removed
@@ -170,45 +187,50 @@ public final class FileMonitor extends TimerTask {
                     // if the dir changed we have to update
                     if ( changed ) {
                         // and now update
-                        final File[] files = monitorable.file.listFiles();
-                        if (files != null) {
-                            final Monitorable[] children = new Monitorable[files.length];
-                            for (int i = 0; i < files.length; i++) {
-                                // search in old list
-                                for (int m = 0; m < ds.children.length; m++) {
-                                    if (ds.children[m].file.equals(files[i])) {
-                                        children[i] = ds.children[m];
-                                        break;
-                                    }
-                                }
-                                if (children[i] == null) {
-                                    children[i] = new Monitorable(monitorable.path + '/' + files[i].getName(), files[i],
-                                            contentFileExtensions.getSuffix(files[i]));
-                                    children[i].status = NonExistingStatus.SINGLETON;
-                                    check(children[i], reporter);
-                                }
-                            }
-                            ds.children = children;
-                        } else {
-                            ds.children = new Monitorable[0];
-                        }
+                        checkDirStatusChildren(monitorable, reporter);
                     }
                 }
             }
         }
     }
+    
+    private void checkDirStatusChildren(final Monitorable dirMonitorable, final ObservationReporter reporter) {
+        final DirStatus ds = (DirStatus)dirMonitorable.status;
+        final File[] files = dirMonitorable.file.listFiles();
+        if (files != null) {
+            final Monitorable[] children = new Monitorable[files.length];
+            for (int i = 0; i < files.length; i++) {
+                // search in old list
+                for (int m = 0; m < ds.children.length; m++) {
+                    if (ds.children[m].file.equals(files[i])) {
+                        children[i] = ds.children[m];
+                        break;
+                    }
+                }
+                if (children[i] == null) {
+                    children[i] = new Monitorable(dirMonitorable.path + '/' + files[i].getName(), files[i],
+                            contentFileExtensions.getSuffix(files[i]));
+                    children[i].status = NonExistingStatus.SINGLETON;
+                    check(children[i], reporter);
+                }
+            }
+            ds.children = children;
+        } else {
+            ds.children = new Monitorable[0];
+        }
+    }
 
     /**
      * Send the event async via the event admin.
      */
     private void sendEvents(final Monitorable monitorable, final ChangeType changeType, final ObservationReporter reporter) {
         if (log.isDebugEnabled()) {
-            log.debug("Detected change for resource {} : {}", monitorable.path, changeType);
+            log.debug("Detected change for resource {} : {}", transformPath(monitorable.path), changeType);
         }
 
         List<ResourceChange> changes = null;
         for (final ObserverConfiguration config : reporter.getObserverConfigurations()) {
-            if (config.matches(monitorable.path)) {
+            if (config.matches(transformPath(monitorable.path))) {
                 if (changes == null) {
                     changes = collectResourceChanges(monitorable, changeType);
                 }
@@ -224,6 +246,20 @@ public final class FileMonitor extends TimerTask {
         }
     }
     
+    /**
+     * Transform path for resource event.
+     * @param path Path
+     * @return Transformed path
+     */
+    private String transformPath(String path) {
+        if (fsMode == FsMode.FILEVAULT_XML) {
+            return PlatformNameFormat.getRepositoryPath(path);
+        }
+        else {
+            return path;
+        }
+    }
+    
     @SuppressWarnings("unchecked")
     private List<ResourceChange> collectResourceChanges(final Monitorable monitorable, final ChangeType changeType) {
         List<ResourceChange> changes = new ArrayList<>();
@@ -233,15 +269,15 @@ public final class FileMonitor extends TimerTask {
                 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(ChangeType.REMOVED,  monitorable.path));
-                addContentResourceChanges(changes, ChangeType.ADDED, content, monitorable.path);
+                changes.add(buildContentResourceChange(ChangeType.REMOVED,  transformPath(monitorable.path)));
+                addContentResourceChanges(changes, ChangeType.ADDED, content, transformPath(monitorable.path));
             }
             else {
-                addContentResourceChanges(changes, changeType, (Map<String,Object>)contentFile.getContent(), monitorable.path);
+                addContentResourceChanges(changes, changeType, (Map<String,Object>)contentFile.getContent(), transformPath(monitorable.path));
             }
         }
         else {
-            changes.add(buildContentResourceChange(changeType, monitorable.path));
+            changes.add(buildContentResourceChange(changeType, transformPath(monitorable.path)));
         }
         return changes;
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java b/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
new file mode 100644
index 0000000..15af91f
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Different modes for Filesystem provider support and filesystem layouts.
+ */
+public enum FsMode {
+    
+    /**
+     * Sling-Initial-Content filesystem layout, with full support for JSON and jcr.xml content files.
+     */
+    INITIAL_CONTENT,
+    
+    /**
+     * Sling-Initial-Content filesystem layout, support only files and folders (classic mode).
+     */
+    INITIAL_CONTENT_FILES_FOLDERS,
+
+    /**
+     * FileVault XML format (expanded content package).
+     */
+    FILEVAULT_XML
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index d214987..2b55028 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -24,13 +24,16 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
 
 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.ResourceResolver;
 import org.apache.sling.fsprovider.internal.mapper.ContentFileResourceMapper;
 import org.apache.sling.fsprovider.internal.mapper.FileResourceMapper;
+import org.apache.sling.fsprovider.internal.mapper.FileVaultResourceMapper;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.apache.sling.fsprovider.internal.parser.ContentFileTypes;
 import org.apache.sling.spi.resource.provider.ObservationReporter;
@@ -47,6 +50,7 @@ import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
 import org.osgi.service.metatype.annotations.Designate;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.osgi.service.metatype.annotations.Option;
 
 /**
  * The <code>FsResourceProvider</code> is a resource provider which maps
@@ -106,17 +110,25 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
                 "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();
-       
-        @AttributeDefinition(name = "Mount jcr.xml",
-                description = "Mount .jcr.xml files as content in the resource hierarchy.")
-        boolean provider_jcrxml_content();
+        @AttributeDefinition(name = "Filesystem layout",
+                description = "Filesystem layout mode for files, folders and content.",
+                options={
+                        @Option(value="INITIAL_CONTENT", label="INITIAL_CONTENT - "
+                                + "Sling-Initial-Content filesystem layout, with full support for JSON and jcr.xml content files."),
+                        @Option(value="INITIAL_CONTENT_FILES_FOLDERS", label="INITIAL_CONTENT_FILES_FOLDERS - "
+                                + "Sling-Initial-Content filesystem layout, support only files and folders (classic mode)."),
+                        @Option(value="FILEVAULT_XML", label="FILEVAULT_XML - "
+                                + "FileVault XML format (expanded content package)."),
+                })
+        FsMode provider_fs_mode() default FsMode.INITIAL_CONTENT;
+        
+        @AttributeDefinition(name = "Init. Content Options",
+                description = "Import options for Sling-Initial-Content filesystem layout. Supported options: overwrite, ignoreImportProviders.")
+        String provider_initial_content_import_options();
         
         @AttributeDefinition(name = "Cache Size",
                 description = "Max. number of content files cached in memory.")
-        int provider_cache_size() default 1000;
+        int provider_cache_size() default 10000;
 
         /**
          * Internal Name hint for web console.
@@ -134,8 +146,10 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
     private FileMonitor monitor;
     
     // maps filesystem to resources
+    private FsMode fsMode;
     private FsResourceMapper fileMapper;
     private FsResourceMapper contentFileMapper;
+    private FileVaultResourceMapper fileVaultMapper;
     
     // if true resources from filesystem are only "overlayed" to JCR resources, serving JCR as fallback within the same path
     private boolean overlayParentResourceProvider;
@@ -160,12 +174,30 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
             final Resource parent) {
         
         ResourceResolver resolver = ctx.getResourceResolver();
-        Resource rsrc = contentFileMapper.getResource(resolver, path);
-        if (rsrc == null) {
-            rsrc = fileMapper.getResource(resolver, path);
+        
+        boolean askParentResourceProvider;
+        Resource rsrc = null;
+
+        if (fsMode == FsMode.FILEVAULT_XML) {
+            // filevault: check if path matches, if not fallback to parent resource provider
+            if (fileVaultMapper.pathMatches(path)) {
+                askParentResourceProvider = false;
+                rsrc = fileVaultMapper.getResource(resolver, path);
+            }
+            else {
+                askParentResourceProvider = true;
+            }
+        }
+        else {
+            // Sling-Initial-Content: mount folder/files an content files
+            askParentResourceProvider = this.overlayParentResourceProvider;
+            rsrc = contentFileMapper.getResource(resolver, path);
+            if (rsrc == null) {
+                rsrc = fileMapper.getResource(resolver, path);
+            }
         }
         
-        if (this.overlayParentResourceProvider) {
+        if (askParentResourceProvider) {
             // 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) ) {
@@ -181,7 +213,7 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
             	}        	
             }
         }
-
+        
         return rsrc;
     }
 
@@ -195,24 +227,48 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
         
         List<Iterator<Resource>> allChildren = new ArrayList<>();
         Iterator<Resource> children;
+        boolean askParentResourceProvider;
         
-        children = contentFileMapper.getChildren(resolver, parent);
-        if (children != null) {
-            allChildren.add(children);
+        if (fsMode == FsMode.FILEVAULT_XML) {
+            // filevault: always ask provider, it checks itself if children matches the filters
+            askParentResourceProvider = true;
+            children = fileVaultMapper.getChildren(resolver, parent);
+            if (children != null) {
+                allChildren.add(children);
+            }
         }
-        
-        children = fileMapper.getChildren(resolver, parent);
-        if (children != null) {
-            allChildren.add(children);
+        else {
+            // Sling-Initial-Content: get all matchind folders/files and content files
+            askParentResourceProvider = this.overlayParentResourceProvider;
+            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
-        if (this.overlayParentResourceProvider) {
+        if (askParentResourceProvider) {
         	final ResourceProvider parentResourceProvider = ctx.getParentResourceProvider();
         	if (parentResourceProvider != null) {
         		children = parentResourceProvider.listChildren(ctx.getParentResolveContext(), parent);
                 if (children != null) {
-                    allChildren.add(children);
+                    if (fsMode == FsMode.FILEVAULT_XML) {
+                        // filevault: include all children from parent resource provider that do not match the filters
+                        allChildren.add(IteratorUtils.filteredIterator(children, new Predicate() {
+                            @Override
+                            public boolean evaluate(Object object) {
+                                Resource child = (Resource)object;
+                                return !fileVaultMapper.pathMatches(child.getPath());
+                            }
+                        }));
+                    }
+                    else {
+                        allChildren.add(children);
+                    }
                 }
         	}
         }
@@ -239,39 +295,54 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
     // ---------- SCR Integration
     @Activate
     protected void activate(BundleContext bundleContext, final Config config) {
+        fsMode = config.provider_fs_mode();
         String providerRoot = config.provider_root();
-        if (providerRoot == null || providerRoot.length() == 0) {
+        if (StringUtils.isBlank(providerRoot)) {
             throw new IllegalArgumentException("provider.root property must be set");
         }
 
         String providerFileName = config.provider_file();
-        if (providerFileName == null || providerFileName.length() == 0) {
+        if (StringUtils.isBlank(providerFileName)) {
             throw new IllegalArgumentException("provider.file property must be set");
         }
 
         this.providerRoot = providerRoot;
         this.providerFile = getProviderFile(providerFileName, bundleContext);
-        this.overlayParentResourceProvider = true;
+        this.overlayParentResourceProvider = false;
         
+        InitialContentImportOptions options = new InitialContentImportOptions(config.provider_initial_content_import_options());
+                
         List<String> contentFileSuffixes = new ArrayList<>();
-        if (config.provider_json_content()) {
-            contentFileSuffixes.add(ContentFileTypes.JSON_SUFFIX);
-            this.overlayParentResourceProvider = false;
+        if (fsMode == FsMode.FILEVAULT_XML) {
+            contentFileSuffixes.add("/" + DOT_CONTENT_XML);
+        }
+        else if (fsMode == FsMode.INITIAL_CONTENT_FILES_FOLDERS) {
+            overlayParentResourceProvider = true;
         }
-        if (config.provider_jcrxml_content()) {
-            contentFileSuffixes.add(ContentFileTypes.JCR_XML_SUFFIX);
-            this.overlayParentResourceProvider = false;
+        else if (fsMode == FsMode.INITIAL_CONTENT) {
+            overlayParentResourceProvider = !options.isOverwrite();
+            if (!options.getIgnoreImportProviders().contains("json")) {
+                contentFileSuffixes.add(ContentFileTypes.JSON_SUFFIX);
+            }
+            if (!options.getIgnoreImportProviders().contains("jcr.xml")) {
+                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);
+        if (fsMode == FsMode.FILEVAULT_XML) {
+            this.fileVaultMapper = new FileVaultResourceMapper(this.providerFile, this.contentFileCache);
+        }
+        else {
+            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
-        if ( config.provider_checkinterval() > 100 ) {
-            this.monitor = new FileMonitor(this, config.provider_checkinterval(),
+        if (config.provider_checkinterval() > 100) {
+            this.monitor = new FileMonitor(this, config.provider_checkinterval(), fsMode,
                     contentFileExtensions, this.contentFileCache);
         }
     }
@@ -287,10 +358,12 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
         this.overlayParentResourceProvider = false;
         this.fileMapper = null;
         this.contentFileMapper = null;
+        this.fileVaultMapper = null;
         if (this.contentFileCache != null) {
             this.contentFileCache.clear();
             this.contentFileCache = null;
         }
+        this.fsMode = null;
     }
 
     File getRootFile() {
@@ -332,7 +405,7 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
 
     public ObservationReporter getObservationReporter() {
         final ProviderContext ctx = this.getProviderContext();
-        if ( ctx != null ) {
+        if (ctx != null) {
             return ctx.getObservationReporter();
         }
         return null;
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java b/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java
new file mode 100644
index 0000000..5353d0f
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java
@@ -0,0 +1,76 @@
+/*
+ * 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.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+
+class InitialContentImportOptions {
+
+    /**
+     * The overwrite directive specifying if content should be overwritten or
+     * just initially added.
+     */
+    private static final String OVERWRITE_DIRECTIVE = "overwrite";
+
+    /**
+     * The ignore content readers directive specifying whether the available ContentReaders
+     * should be used during content loading.
+     */
+    private static final String IGNORE_CONTENT_READERS_DIRECTIVE = "ignoreImportProviders";
+
+    
+    private final boolean overwrite;
+    private final Set<String> ignoreImportProviders;
+    
+    public InitialContentImportOptions(String optionsString) {
+        Map<String,String> options = parseOptions(optionsString);
+        overwrite = BooleanUtils.toBoolean(options.get(OVERWRITE_DIRECTIVE));
+        ignoreImportProviders = new HashSet<>(Arrays.asList(StringUtils.split(StringUtils.defaultString(options.get(IGNORE_CONTENT_READERS_DIRECTIVE)))));
+    }
+    
+    private static Map<String,String> parseOptions(String optionsString) {
+        Map<String,String> options = new HashMap<>();
+        String[] optionsList = StringUtils.split(optionsString, ";");
+        if (optionsList != null) {
+            for (String keyValueString : optionsList) {
+                String[] keyValue = StringUtils.splitByWholeSeparator(keyValueString, ":=");
+                if (keyValue.length == 2) {
+                    options.put(StringUtils.trim(keyValue[0]), StringUtils.trim(keyValue[1]));
+                }
+            }
+        }
+        return options;
+    }
+
+    public boolean isOverwrite() {
+        return overwrite;
+    }
+
+    public Set<String> getIgnoreImportProviders() {
+        return ignoreImportProviders;
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
index d31a851..27e09d5 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
@@ -19,8 +19,11 @@
 package org.apache.sling.fsprovider.internal.mapper;
 
 import java.io.File;
+import java.util.Iterator;
 import java.util.Map;
 
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.commons.collections.Predicate;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 
@@ -51,19 +54,6 @@ public final class ContentFile {
     }
 
     /**
-     * @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() {
@@ -129,14 +119,47 @@ public final class ContentFile {
     }
     
     /**
+     * @return Child maps.
+     */
+    @SuppressWarnings("unchecked")
+    public Iterator<Map.Entry<String,Map<String,Object>>> getChildren() {
+        if (!isResource()) {
+            return IteratorUtils.emptyIterator();
+        }
+        return IteratorUtils.filteredIterator(((Map)getContent()).entrySet().iterator(), new Predicate() {
+            @Override
+            public boolean evaluate(Object object) {
+                Map.Entry<String,Object> entry = (Map.Entry<String,Object>)object;
+                return entry.getValue() instanceof Map;
+            }
+        });
+    }
+    
+    /**
      * Navigate to another sub path position in content file.
-     * @param newSubPath New sub path
+     * @param newSubPath New sub path related to root path of content file
      * @return Content file
      */
-    public ContentFile navigateTo(String newSubPath) {
+    public ContentFile navigateToAbsolute(String newSubPath) {
         return new ContentFile(file, path, newSubPath, contentFileCache);
     }
         
+    /**
+     * Navigate to another sub path position in content file.
+     * @param newSubPath New sub path relative to current sub path in content file
+     * @return Content file
+     */
+    public ContentFile navigateToRelative(String newSubPath) {
+        String absoluteSubPath;
+        if (newSubPath == null) {
+            absoluteSubPath = this.subPath;
+        }
+        else {
+            absoluteSubPath = (this.subPath != null ? this.subPath + "/" : "") + newSubPath;
+        }
+        return new ContentFile(file, path, absoluteSubPath, contentFileCache);
+    }
+        
     @SuppressWarnings("unchecked")
     private static Object getDeepContent(Object object, String subPath) {
         if (object == null) {
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
index e6edd04..09b6ffe 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
@@ -106,18 +106,10 @@ public final class ContentFileResourceMapper implements FsResourceMapper {
         // 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()));
-                }
+            Iterator<Map.Entry<String,Map<String,Object>>> childMaps = parentContentFile.getChildren();
+            while (childMaps.hasNext()) {
+                Map.Entry<String,Map<String,Object>> entry = childMaps.next();
+                children.add(parentContentFile.navigateToRelative(entry.getKey()));
             }
         }
         if (children.isEmpty()) {
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
new file mode 100644
index 0000000..88e001e
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
@@ -0,0 +1,201 @@
+/*
+ * 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 static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
+import static org.apache.jackrabbit.vault.util.Constants.FILTER_XML;
+import static org.apache.jackrabbit.vault.util.Constants.META_DIR;
+import static org.apache.jackrabbit.vault.util.Constants.ROOT_DIR;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.commons.collections.Transformer;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
+import org.apache.jackrabbit.vault.fs.config.ConfigurationException;
+import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
+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.FsResourceMapper;
+import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class FileVaultResourceMapper implements FsResourceMapper {
+    
+    private static final String DOT_CONTENT_XML_SUFFIX = "/" + DOT_CONTENT_XML;
+    private static final String DOT_DIR_SUFFIX = "/.dir";
+
+    private final File providerFile;
+    private final ContentFileCache contentFileCache;
+    private final WorkspaceFilter workspaceFilter;
+    
+    private static final Logger log = LoggerFactory.getLogger(FileVaultResourceMapper.class);
+    
+    public FileVaultResourceMapper(File providerFile, ContentFileCache contentFileCache) {
+        this.providerFile = providerFile;
+        this.contentFileCache = contentFileCache;
+        this.workspaceFilter = getWorkspaceFilter();
+    }
+    
+    @Override
+    public Resource getResource(final ResourceResolver resolver, final String resourcePath) {
+        
+        // direct file
+        File file = getFile(resourcePath);
+        if (file != null && file.isFile()) {
+            return new FileResource(resolver, resourcePath, file);
+        }
+        
+        // content file
+        ContentFile contentFile = getContentFile(resourcePath, null);
+        if (contentFile != null) {
+            return new ContentFileResource(resolver, contentFile);
+        }
+        
+        // fallback to directory resource if folder was found but nothing else
+        if (file != null && file.isDirectory()) {
+            return new FileResource(resolver, resourcePath, file);
+        }
+        
+        return null;
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public Iterator<Resource> getChildren(final ResourceResolver resolver, final Resource parent) {
+        String parentPath = parent.getPath();
+        
+        Set<String> childPaths = new LinkedHashSet<>();
+        
+        // get children from content resource of parent
+        ContentFile parentContentFile = getContentFile(parentPath, null);
+        if (parentContentFile != null) {
+            Iterator<Map.Entry<String,Map<String,Object>>> childMaps = parentContentFile.getChildren();
+            while (childMaps.hasNext()) {
+                Map.Entry<String,Map<String,Object>> entry = childMaps.next();
+                String childPath = parentPath + "/" + entry.getKey();
+                if (pathMatches(childPath)) {
+                    childPaths.add(childPath);
+                }
+            }
+        }
+        
+        // additional check for children in filesystem
+        File parentFile = getFile(parentPath);
+        if (parentFile != null && parentFile.isDirectory()) {
+            for (File childFile : parentFile.listFiles()) {
+                String childPath = parentPath + "/" + PlatformNameFormat.getRepositoryName(childFile.getName());
+                if (pathMatches(childPath) && !childPaths.contains(childPath)) {
+                    childPaths.add(childPath);
+                }
+            }
+        }
+        
+        if (childPaths.isEmpty()) {
+            return null;
+        }
+        else {
+            return IteratorUtils.transformedIterator(childPaths.iterator(), new Transformer() {
+                @Override
+                public Object transform(Object input) {
+                    String path = (String)input;
+                    return getResource(resolver, path);
+                }
+            });
+        }
+    }
+
+    /**
+     * @return Workspace filter or null if none found.
+     */
+    private WorkspaceFilter getWorkspaceFilter() {
+        File filter = new File(providerFile, META_DIR + "/" + FILTER_XML);
+        if (filter.exists()) {
+            try {
+                DefaultWorkspaceFilter workspaceFilter = new DefaultWorkspaceFilter();
+                workspaceFilter.load(filter);
+                return workspaceFilter;
+            } catch (IOException | ConfigurationException ex) {
+                log.error("Unable to parse workspace filter: " + filter.getPath(), ex);
+            }
+        }
+        else {
+            log.warn("Workspace filter not found: " + filter.getPath());
+        }
+        return null;
+    }
+    
+    /**
+     * Checks if the given path matches the workspace filter.
+     * @param path Path
+     * @return true if path matches
+     */
+    public boolean pathMatches(String path) {
+        // ignore .dir folder
+        if (StringUtils.endsWith(path, DOT_DIR_SUFFIX) || StringUtils.endsWith(path, DOT_CONTENT_XML_SUFFIX)) {
+            return false;
+        }
+        if (workspaceFilter == null) {
+            return false;
+        }
+        else {
+            return workspaceFilter.contains(path);
+        }
+    }
+    
+    private File getFile(String path) {
+        if (StringUtils.endsWith(path, DOT_CONTENT_XML_SUFFIX)) {
+            return null;
+        }
+        File file = new File(providerFile, ROOT_DIR + PlatformNameFormat.getPlatformPath(path));
+        if (file.exists()) {
+            return file;
+        }
+        return null;
+    }
+    
+    private ContentFile getContentFile(String path, String subPath) {
+        File file = new File(providerFile, ROOT_DIR + PlatformNameFormat.getPlatformPath(path) + DOT_CONTENT_XML_SUFFIX);
+        if (file.exists()) {
+            ContentFile contentFile = new ContentFile(file, path, subPath, contentFileCache);
+            if (contentFile.hasContent()) {
+                return contentFile;
+            }
+        }
+        
+        // try to find in parent path which contains content fragment
+        String parentPath = ResourceUtil.getParent(path);
+        if (parentPath == null) {
+            return null;
+        }
+        String nextSubPath = path.substring(parentPath.length() + 1)
+                + (subPath != null ? "/" + subPath : "");
+        return getContentFile(parentPath, nextSubPath);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
index 6f52691..23abe66 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
@@ -110,22 +110,22 @@ public final class FsNode extends FsItem implements Node {
             else {
                 subPath = path.substring(contentFile.getPath().length() + 1);
             }
-            ContentFile referencedFile = contentFile.navigateTo(subPath);
+            ContentFile referencedFile = contentFile.navigateToAbsolute(subPath);
             if (referencedFile.hasContent()) {
                 return new FsNode(referencedFile, resolver);
             }
         }
-        else {
-            // node is outside content file
-            Node refNode = null;
-            Resource resource = resolver.getResource(path);
-            if (resource != null) {
-                refNode = resource.adaptTo(Node.class);
-                if (refNode != null) {
-                    return refNode;
-                }
+        
+        // check if node is outside content file
+        Node refNode = null;
+        Resource resource = resolver.getResource(path);
+        if (resource != null) {
+            refNode = resource.adaptTo(Node.class);
+            if (refNode != null) {
+                return refNode;
             }
         }
+
         throw new PathNotFoundException(relPath);
     }
 
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
index f03b0a7..09db7d4 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
@@ -63,14 +63,7 @@ class FsNodeIterator implements NodeIterator {
     @Override
     public Node nextNode() {
         Map.Entry<String,Map<String,Object>> nextEntry = children.next();
-        String subPath;
-        if (contentFile.getSubPath() == null) {
-            subPath = nextEntry.getKey();
-        }
-        else {
-            subPath = contentFile.getSubPath() + "/" + nextEntry.getKey();
-        }
-        return new FsNode(contentFile.navigateTo(subPath), resolver);
+        return new FsNode(contentFile.navigateToRelative(nextEntry.getKey()), resolver);
     }
 
     
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
index 4b46157..6ac72c5 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
@@ -18,6 +18,7 @@
  */
 package org.apache.sling.fsprovider.internal.parser;
 
+import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
 import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JCR_XML_SUFFIX;
 import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JSON_SUFFIX;
 
@@ -62,11 +63,14 @@ class ContentFileParserUtil {
      * @return Content or null if content could not be parsed.
      */
     public static Map<String,Object> parse(File file) {
+        if (!file.exists()) {
+            return null;
+        }
         try {
             if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) {
                 return JSON_PARSER.parse(file);
             }
-            else if (StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) {
+            else if (StringUtils.equals(file.getName(), DOT_CONTENT_XML) || StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) {
                 return JCR_XML_PARSER.parse(file);
             }
         }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
index 020405d..bc4892f 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
@@ -18,19 +18,19 @@
  */
 package org.apache.sling.fsprovider.internal;
 
+import static org.apache.sling.fsprovider.internal.TestUtils.assertChange;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.nio.file.Files;
-import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.commons.io.FileUtils;
-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.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.fsprovider.internal.TestUtils.ResourceListener;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
@@ -39,10 +39,13 @@ import org.junit.Rule;
 import org.junit.Test;
 
 /**
- * Test events when changing filesystem content.
+ * Test events when changing filesystem content (Sling-Initial-Content).
  */
 public class FileMonitorTest {
 
+    private static final int CHECK_INTERVAL = 120;
+    private static final int WAIT_INTERVAL = 250;
+    
     private final File tempDir;
     private final ResourceListener resourceListener = new ResourceListener();
     
@@ -64,8 +67,9 @@ public class FileMonitorTest {
                 context.registerInjectActivateService(new FsResourceProvider(),
                         "provider.file", tempDir.getPath(),
                         "provider.root", "/fs-test",
-                        "provider.checkinterval", 120,
-                        "provider.json.content", true);
+                        "provider.checkinterval", CHECK_INTERVAL,
+                        "provider.fs.mode", FsMode.INITIAL_CONTENT.name(),
+                        "provider.initial.content.import.options", "overwrite:=true;ignoreImportProviders:=jcr.xml");
                 
                 // register resource change listener
                 context.registerService(ResourceChangeListener.class, resourceListener,
@@ -89,7 +93,7 @@ public class FileMonitorTest {
         File file1a = new File(tempDir, "folder1/file1a.txt");
         FileUtils.touch(file1a);
         
-        Thread.sleep(250);
+        Thread.sleep(WAIT_INTERVAL);
 
         assertEquals(1, changes.size());
         assertChange(changes, "/fs-test/folder1/file1a.txt", ChangeType.CHANGED);
@@ -103,7 +107,7 @@ public class FileMonitorTest {
         File file1c = new File(tempDir, "folder1/file1c.txt");
         FileUtils.write(file1c, "newcontent");
         
-        Thread.sleep(250);
+        Thread.sleep(WAIT_INTERVAL);
 
         assertEquals(2, changes.size());
         assertChange(changes, "/fs-test/folder1", ChangeType.CHANGED);
@@ -118,7 +122,7 @@ public class FileMonitorTest {
         File file1a = new File(tempDir, "folder1/file1a.txt");
         file1a.delete();
         
-        Thread.sleep(250);
+        Thread.sleep(WAIT_INTERVAL);
 
         assertEquals(2, changes.size());
         assertChange(changes, "/fs-test/folder1", ChangeType.CHANGED);
@@ -133,7 +137,7 @@ public class FileMonitorTest {
         File folder99 = new File(tempDir, "folder99");
         folder99.mkdir();
         
-        Thread.sleep(250);
+        Thread.sleep(WAIT_INTERVAL);
 
         assertEquals(2, changes.size());
         assertChange(changes, "/fs-test", ChangeType.CHANGED);
@@ -148,7 +152,7 @@ public class FileMonitorTest {
         File folder1 = new File(tempDir, "folder1");
         FileUtils.deleteDirectory(folder1);
         
-        Thread.sleep(250);
+        Thread.sleep(WAIT_INTERVAL);
 
         assertEquals(2, changes.size());
         assertChange(changes, "/fs-test", ChangeType.CHANGED);
@@ -156,30 +160,29 @@ public class FileMonitorTest {
     }
 
     @Test
-    public void testUpdateJsonContent() throws Exception {
+    public void testUpdateContent() throws Exception {
         List<ResourceChange> changes = resourceListener.getChanges();
         assertTrue(changes.isEmpty());
         
         File file1a = new File(tempDir, "folder2/content.json");
         FileUtils.touch(file1a);
         
-        Thread.sleep(250);
+        Thread.sleep(WAIT_INTERVAL);
 
-        assertTrue(changes.size() > 1);
         assertChange(changes, "/fs-test/folder2/content", ChangeType.REMOVED);
         assertChange(changes, "/fs-test/folder2/content", ChangeType.ADDED);
         assertChange(changes, "/fs-test/folder2/content/jcr:content", ChangeType.ADDED);
     }
     
     @Test
-    public void testAddJsonContent() throws Exception {
+    public void testAddContent() throws Exception {
         List<ResourceChange> changes = resourceListener.getChanges();
         assertTrue(changes.isEmpty());
         
         File file1c = new File(tempDir, "folder1/file1c.json");
         FileUtils.write(file1c, "{\"prop1\":\"value1\",\"child1\":{\"prop2\":\"value1\"}}");
         
-        Thread.sleep(250);
+        Thread.sleep(WAIT_INTERVAL);
 
         assertEquals(3, changes.size());
         assertChange(changes, "/fs-test/folder1", ChangeType.CHANGED);
@@ -188,41 +191,18 @@ public class FileMonitorTest {
     }
     
     @Test
-    public void testRemoveJsonContent() throws Exception {
+    public void testRemoveContent() throws Exception {
         List<ResourceChange> changes = resourceListener.getChanges();
         assertTrue(changes.isEmpty());
         
         File file1a = new File(tempDir, "folder2/content.json");
         file1a.delete();
         
-        Thread.sleep(250);
+        Thread.sleep(WAIT_INTERVAL);
 
         assertEquals(2, changes.size());
         assertChange(changes, "/fs-test/folder2", ChangeType.CHANGED);
         assertChange(changes, "/fs-test/folder2/content", ChangeType.REMOVED);
     }
     
-    
-    private void assertChange(List<ResourceChange> changes, String path, ChangeType changeType) {
-        boolean found = false;
-        for (ResourceChange change : changes) {
-            if (StringUtils.equals(change.getPath(), path) && change.getType() == changeType) {
-                found = true;
-                break;
-            }
-        }
-        assertTrue("Change with path=" + path + ", changeType=" + changeType, found);
-    }
-    
-    static class ResourceListener implements ResourceChangeListener {
-        private final List<ResourceChange> allChanges = new ArrayList<>();
-        @Override
-        public void onChange(List<ResourceChange> changes) {
-            allChanges.addAll(changes);
-        }
-        public List<ResourceChange> getChanges() {
-            return allChanges;
-        }
-    }
-
 }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java
new file mode 100644
index 0000000..a6f6744
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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 static org.apache.sling.fsprovider.internal.TestUtils.assertFile;
+import static org.apache.sling.fsprovider.internal.TestUtils.assertFolder;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin;
+import org.apache.sling.hamcrest.ResourceMatchers;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Test access FileFault XML files, folders and content.
+ */
+public class FileVaultContentTest {
+
+    private Resource damAsset;
+    private Resource sampleContent;
+
+    @Rule
+    public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
+        .plugin(new RegisterFsResourcePlugin(
+                "provider.fs.mode", FsMode.FILEVAULT_XML.name(),
+                "provider.file", "src/test/resources/vaultfs-test",
+                "provider.root", "/content/dam/talk.png"
+                ))
+        .plugin(new RegisterFsResourcePlugin(
+                "provider.fs.mode", FsMode.FILEVAULT_XML.name(),
+                "provider.file", "src/test/resources/vaultfs-test",
+                "provider.root", "/content/samples"
+                ))
+        .build();
+
+    @Before
+    public void setUp() {
+        damAsset = context.resourceResolver().getResource("/content/dam/talk.png");
+        sampleContent = context.resourceResolver().getResource("/content/samples");
+    }
+
+    @Test
+    public void testDamAsset() {
+        assertNotNull(damAsset);
+        assertEquals("app:Asset", damAsset.getResourceType());
+        
+        Resource content = damAsset.getChild("jcr:content");
+        assertNotNull(content);
+        assertEquals("app:AssetContent", content.getResourceType());
+        
+        Resource metadata = content.getChild("metadata");
+        assertNotNull(metadata);
+        ValueMap props = metadata.getValueMap();
+        assertEquals((Integer)4, props.get("app:Bitsperpixel", Integer.class));
+        
+        assertFolder(content, "renditions");
+        assertFile(content, "renditions/original", null);
+        assertFile(content, "renditions/web.1280.1280.png", null);
+    }
+
+    @Test
+    public void testSampleContent() {
+        assertNotNull(sampleContent);
+        assertEquals("sling:OrderedFolder", sampleContent.getResourceType());
+
+        Resource enContent = sampleContent.getChild("en/jcr:content");
+        assertArrayEquals(new String[] { "/etc/mobile/groups/responsive" }, enContent.getValueMap().get("app:deviceGroups", String[].class));
+    }
+
+    @Test
+    public void testListChildren() {
+        Resource en = sampleContent.getChild("en");
+        List<Resource> children = ImmutableList.copyOf(en.listChildren());
+        assertEquals(2, children.size());
+        
+        Resource child1 = children.get(0);
+        assertEquals("jcr:content", child1.getName());
+        assertEquals("samples/sample-app/components/content/page/homepage", child1.getResourceType());
+ 
+        Resource child2 = children.get(1);
+        assertEquals("tools", child2.getName());
+        assertEquals("app:Page", child2.getResourceType());
+        
+        // child3 (conference) is hidden because of filter
+    }
+
+    @Test
+    public void testJcrMixedContent() throws RepositoryException {
+        // prepare mixed JCR content
+        Node root = context.resourceResolver().adaptTo(Session.class).getNode("/");
+        Node content = root.addNode("content", "nt:folder");
+        Node samples = content.addNode("samples", "nt:folder");
+        Node en = samples.addNode("en", "nt:folder");
+        Node conference = en.addNode("conference", "nt:folder");
+        conference.addNode("page2", "nt:folder");
+        samples.addNode("it", "nt:folder");
+        
+        // pass-through because of filter
+        assertNotNull(context.resourceResolver().getResource("/content/samples/en/conference"));
+        assertNotNull(sampleContent.getChild("en/conference"));
+        assertNotNull(context.resourceResolver().getResource("/content/samples/en/conference/page2"));
+        assertNotNull(sampleContent.getChild("en/conference/page2"));
+        
+        // hidden because overlayed by resource provider
+        assertNull(context.resourceResolver().getResource("/content/samples/it"));
+        assertNull(sampleContent.getChild("it"));
+
+        // list children with mixed content
+        Resource enResource = sampleContent.getChild("en");
+        assertThat(enResource, ResourceMatchers.containsChildren("jcr:content", "tools", "conference"));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java
new file mode 100644
index 0000000..8fe5f3b
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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 static org.apache.sling.fsprovider.internal.TestUtils.assertChange;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.fsprovider.internal.TestUtils.ResourceListener;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
+import org.apache.sling.testing.mock.sling.junit.SlingContextCallback;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test events when changing filesystem content (FileVault XML).
+ */
+public class FileVaultFileMonitorTest {
+    
+    private static final int CHECK_INTERVAL = 120;
+    private static final int WAIT_INTERVAL = 250;
+
+    private final File tempDir;
+    private final ResourceListener resourceListener = new ResourceListener();
+    
+    public FileVaultFileMonitorTest() throws Exception {
+        tempDir = Files.createTempDirectory(getClass().getName()).toFile();
+    }
+
+    @Rule
+    public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
+        .beforeSetUp(new SlingContextCallback() {
+            @Override
+            public void execute(SlingContext context) throws Exception {
+                // copy test content to temp. directory
+                tempDir.mkdirs();
+                File sourceDir = new File("src/test/resources/vaultfs-test");
+                FileUtils.copyDirectory(sourceDir, tempDir);
+                
+                // mount temp. directory
+                context.registerInjectActivateService(new FsResourceProvider(),
+                        "provider.file", tempDir.getPath(),
+                        "provider.root", "/content/dam/talk.png",
+                        "provider.checkinterval", CHECK_INTERVAL,
+                        "provider.fs.mode", FsMode.FILEVAULT_XML.name());
+                context.registerInjectActivateService(new FsResourceProvider(),
+                        "provider.file", tempDir.getPath(),
+                        "provider.root", "/content/samples",
+                        "provider.checkinterval", CHECK_INTERVAL,
+                        "provider.fs.mode", FsMode.FILEVAULT_XML.name());
+                
+                // register resource change listener
+                context.registerService(ResourceChangeListener.class, resourceListener,
+                        ResourceChangeListener.PATHS, new String[] { "/content/dam/talk.png", "/content/samples" });
+            }
+        })
+        .afterTearDown(new SlingContextCallback() {
+            @Override
+            public void execute(SlingContext context) throws Exception {
+                // remove temp directory
+                tempDir.delete();
+            }
+        })
+        .build();
+
+    @Test
+    public void testUpdateFile() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file = new File(tempDir, "jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png");
+        FileUtils.touch(file);
+        
+        Thread.sleep(WAIT_INTERVAL);
+
+        assertEquals(1, changes.size());
+        assertChange(changes, "/content/dam/talk.png/jcr:content/renditions/web.1280.1280.png", ChangeType.CHANGED);
+    }
+    
+    @Test
+    public void testAddFile() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file = new File(tempDir, "jcr_root/content/dam/talk.png/_jcr_content/renditions/text.txt");
+        FileUtils.write(file, "newcontent");
+        
+        Thread.sleep(WAIT_INTERVAL);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, "/content/dam/talk.png/jcr:content/renditions", ChangeType.CHANGED);
+        assertChange(changes, "/content/dam/talk.png/jcr:content/renditions/text.txt", ChangeType.ADDED);
+    }
+    
+    @Test
+    public void testRemoveFile() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file = new File(tempDir, "jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png");
+        file.delete();
+        
+        Thread.sleep(WAIT_INTERVAL);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, "/content/dam/talk.png/jcr:content/renditions", ChangeType.CHANGED);
+        assertChange(changes, "/content/dam/talk.png/jcr:content/renditions/web.1280.1280.png", ChangeType.REMOVED);
+    }
+    
+    @Test
+    public void testAddFolder() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File folder = new File(tempDir, "jcr_root/content/dam/talk.png/_jcr_content/newfolder");
+        folder.mkdir();
+        
+        Thread.sleep(WAIT_INTERVAL);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, "/content/dam/talk.png/jcr:content", ChangeType.CHANGED);
+        assertChange(changes, "/content/dam/talk.png/jcr:content/newfolder", ChangeType.ADDED);
+    }
+    
+    @Test
+    public void testRemoveFolder() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File folder = new File(tempDir, "jcr_root/content/dam/talk.png/_jcr_content/renditions");
+        FileUtils.deleteDirectory(folder);
+        
+        Thread.sleep(WAIT_INTERVAL);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, "/content/dam/talk.png/jcr:content", ChangeType.CHANGED);
+        assertChange(changes, "/content/dam/talk.png/jcr:content/renditions", ChangeType.REMOVED);
+    }
+
+    @Test
+    public void testUpdateContent() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file = new File(tempDir, "jcr_root/content/samples/en/.content.xml");
+        FileUtils.touch(file);
+        
+        Thread.sleep(WAIT_INTERVAL);
+
+        assertChange(changes, "/content/samples/en", ChangeType.REMOVED);
+        assertChange(changes, "/content/samples/en", ChangeType.ADDED);
+        assertChange(changes, "/content/samples/en/jcr:content", ChangeType.ADDED);
+    }
+    
+    @Test
+    public void testAddContent() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file = new File(tempDir, "jcr_root/content/samples/fr/.content.xml");
+        file.getParentFile().mkdir();
+        FileUtils.write(file, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+                + "<jcr:root xmlns:jcr=\"http://www.jcp.org/jcr/1.0\" xmlns:app=\"http://sample.com/jcr/app/1.0\" "
+                + "xmlns:sling=\"http://sling.apache.org/jcr/sling/1.0\" jcr:primaryType=\"app:Page\">\n"
+                + "<jcr:content jcr:primaryType=\"app:PageContent\"/>\n"
+                + "</jcr:root>");
+        
+        Thread.sleep(WAIT_INTERVAL);
+
+        assertChange(changes, "/content/samples", ChangeType.CHANGED);
+        assertChange(changes, "/content/samples/fr", ChangeType.ADDED);
+        assertChange(changes, "/content/samples/fr/jcr:content", ChangeType.ADDED);
+    }
+    
+    @Test
+    public void testRemoveContent() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file = new File(tempDir, "jcr_root/content/samples/en");
+        FileUtils.deleteDirectory(file);
+        
+        Thread.sleep(WAIT_INTERVAL);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, "/content/samples", ChangeType.CHANGED);
+        assertChange(changes, "/content/samples/en", ChangeType.REMOVED);
+    }
+    
+    @Test
+    public void testRemoveContentDotXmlOnly() throws Exception {
+        List<ResourceChange> changes = resourceListener.getChanges();
+        assertTrue(changes.isEmpty());
+        
+        File file = new File(tempDir, "jcr_root/content/samples/en/.content.xml");
+        file.delete();
+        
+        Thread.sleep(WAIT_INTERVAL);
+
+        assertEquals(2, changes.size());
+        assertChange(changes, "/content/samples/en", ChangeType.CHANGED);
+        // this second event is not fully correct, but this is a quite special case, accept it for now 
+        assertChange(changes, "/content/samples/en", ChangeType.REMOVED);
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java b/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java
new file mode 100644
index 0000000..513c51f
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+public class InitialContentImportOptionsTest {
+
+    @Test
+    public void testNull() {
+        InitialContentImportOptions underTest = new InitialContentImportOptions(null);
+        assertFalse(underTest.isOverwrite());
+        assertTrue(underTest.getIgnoreImportProviders().isEmpty());
+    }
+
+    @Test
+    public void testBlank() {
+        InitialContentImportOptions underTest = new InitialContentImportOptions(" ");
+        assertFalse(underTest.isOverwrite());
+        assertTrue(underTest.getIgnoreImportProviders().isEmpty());
+    }
+
+    @Test
+    public void testOptions1() {
+        InitialContentImportOptions underTest = new InitialContentImportOptions("overwrite:=true;ignoreImportProviders:=xml,json");
+        assertTrue(underTest.isOverwrite());
+        assertEquals(ImmutableSet.of("xml,json"), underTest.getIgnoreImportProviders());
+    }
+
+    @Test
+    public void testOptions2() {
+        InitialContentImportOptions underTest = new InitialContentImportOptions(" overwrite := false ; ignoreImportProviders := xml ");
+        assertFalse(underTest.isOverwrite());
+        assertEquals(ImmutableSet.of("xml"), underTest.getIgnoreImportProviders());
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
index ded7f29..c1dc1c2 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
@@ -55,7 +55,10 @@ public class JcrXmlContentTest {
 
     @Rule
     public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
-        .plugin(new RegisterFsResourcePlugin("provider.jcrxml.content", true))
+            .plugin(new RegisterFsResourcePlugin(
+                    "provider.fs.mode", FsMode.INITIAL_CONTENT.name(),
+                    "provider.initial.content.import.options", "overwrite:=true;ignoreImportProviders:=json"
+                    ))
         .build();
 
     @Before
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
index 031812f..74c7d81 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
@@ -56,7 +56,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 /**
- * Test access to files and folders from filesystem.
+ * Test access to files and folders and JSON content from filesystem.
  */
 public class JsonContentTest {
 
@@ -65,7 +65,10 @@ public class JsonContentTest {
 
     @Rule
     public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
-        .plugin(new RegisterFsResourcePlugin("provider.json.content", true))
+        .plugin(new RegisterFsResourcePlugin(
+                "provider.fs.mode", FsMode.INITIAL_CONTENT.name(),
+                "provider.initial.content.import.options", "overwrite:=true;ignoreImportProviders:=jcr.xml"
+                ))
         .build();
 
     @Before
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
index 9ee6162..bbaf34b 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
@@ -28,13 +28,18 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.CharEncoding;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
 import org.apache.sling.hamcrest.ResourceMatchers;
 import org.apache.sling.testing.mock.osgi.MapUtil;
 import org.apache.sling.testing.mock.osgi.context.AbstractContextPlugin;
@@ -53,6 +58,7 @@ class TestUtils {
             config.put("provider.file", "src/test/resources/fs-test");
             config.put("provider.root", "/fs-test");
             config.put("provider.checkinterval", 0);
+            config.put("provider.fs.mode", FsMode.INITIAL_CONTENT_FILES_FOLDERS.name());
             config.putAll(props);
             context.registerInjectActivateService(new FsResourceProvider(), config);
         }
@@ -94,4 +100,26 @@ class TestUtils {
         }
     }    
 
+    public static void assertChange(List<ResourceChange> changes, String path, ChangeType changeType) {
+        boolean found = false;
+        for (ResourceChange change : changes) {
+            if (StringUtils.equals(change.getPath(), path) && change.getType() == changeType) {
+                found = true;
+                break;
+            }
+        }
+        assertTrue("Change with path=" + path + ", changeType=" + changeType + " expected", found);
+    }
+    
+    public static class ResourceListener implements ResourceChangeListener {
+        private final List<ResourceChange> allChanges = new ArrayList<>();
+        @Override
+        public void onChange(List<ResourceChange> changes) {
+            allChanges.addAll(changes);
+        }
+        public List<ResourceChange> getChanges() {
+            return allChanges;
+        }
+    }
+
 }
diff --git a/src/test/resources/vaultfs-test/META-INF/vault/filter.xml b/src/test/resources/vaultfs-test/META-INF/vault/filter.xml
index 20be2d8..a56ee24 100644
--- a/src/test/resources/vaultfs-test/META-INF/vault/filter.xml
+++ b/src/test/resources/vaultfs-test/META-INF/vault/filter.xml
@@ -19,5 +19,7 @@
 -->
 <workspaceFilter version="1.0">
     <filter root="/content/dam/talk.png" />
-    <filter root="/content/samples" />
+    <filter root="/content/samples">
+        <exclude pattern="/content/samples/en/conference(/.*)?"/>
+    </filter>
 </workspaceFilter>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml
index 4f8312a..e207a1e 100644
--- a/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml
+++ b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml
@@ -39,8 +39,6 @@
         dc:format="image/png"
         dc:modified="{Date}2014-09-19T21:20:26.812+02:00"
         jcr:primaryType="nt:unstructured"
-        tiff:ImageLength="{Long}270"
-        tiff:ImageWidth="{Long}480"
         writebackEnable="{Boolean}true" />
   </jcr:content>
 </jcr:root>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 16/29: SLING-6537 FileVault XML support

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 8d9d081634417674b7b75e7c0871e4110ec7276b
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Tue Mar 7 21:56:10 2017 +0000

    SLING-6537 FileVault XML support
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1785892 13f79535-47bb-0310-9956-ffa450edef68
---
 .../java/org/apache/sling/fsprovider/internal/FileMonitor.java    | 4 ++--
 .../org/apache/sling/fsprovider/internal/FsResourceProvider.java  | 8 ++++----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
index 319e712..d8f7795 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -166,7 +166,7 @@ public final class FileMonitor extends TimerTask {
                 // removed file and update status
                 sendEvents(monitorable, ChangeType.REMOVED, reporter);
                 monitorable.status = NonExistingStatus.SINGLETON;
-                contentFileCache.remove(monitorable.path);
+                contentFileCache.remove(transformPath(monitorable.path));
             } else {
                 // check for changes
                 final FileStatus fs = (FileStatus)monitorable.status;
@@ -176,7 +176,7 @@ public final class FileMonitor extends TimerTask {
                     // changed
                     sendEvents(monitorable, ChangeType.CHANGED, reporter);
                     changed = true;
-                    contentFileCache.remove(monitorable.path);
+                    contentFileCache.remove(transformPath(monitorable.path));
                 }
                 if ( fs instanceof DirStatus ) {
                     // directory
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index 2b55028..2c8384e 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -114,11 +114,11 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
                 description = "Filesystem layout mode for files, folders and content.",
                 options={
                         @Option(value="INITIAL_CONTENT", label="INITIAL_CONTENT - "
-                                + "Sling-Initial-Content filesystem layout, with full support for JSON and jcr.xml content files."),
+                                + "Sling-Initial-Content filesystem layout, with full support for JSON and jcr.xml content files"),
                         @Option(value="INITIAL_CONTENT_FILES_FOLDERS", label="INITIAL_CONTENT_FILES_FOLDERS - "
-                                + "Sling-Initial-Content filesystem layout, support only files and folders (classic mode)."),
+                                + "Sling-Initial-Content filesystem layout, support only files and folders (classic mode)"),
                         @Option(value="FILEVAULT_XML", label="FILEVAULT_XML - "
-                                + "FileVault XML format (expanded content package)."),
+                                + "FileVault XML format (expanded content package)"),
                 })
         FsMode provider_fs_mode() default FsMode.INITIAL_CONTENT;
         
@@ -133,7 +133,7 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
         /**
          * Internal Name hint for web console.
          */
-        String webconsole_configurationFactory_nameHint() default "Root path: {" + ResourceProvider.PROPERTY_ROOT + "}";
+        String webconsole_configurationFactory_nameHint() default "{provider.fs.mode}: {" + ResourceProvider.PROPERTY_ROOT + "}";
     }
 
     // The location in the resource tree where the resources are mapped

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 20/29: SLING-6440 rename INITIAL_CONTENT_FILES_FOLDERS to FILES_FOLDERS

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 3fb8decc77907a4dea71e463e5f62e5617e9b555
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Mar 10 15:36:30 2017 +0000

    SLING-6440 rename INITIAL_CONTENT_FILES_FOLDERS to FILES_FOLDERS
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1786366 13f79535-47bb-0310-9956-ffa450edef68
---
 src/main/java/org/apache/sling/fsprovider/internal/FsMode.java    | 8 ++++----
 .../org/apache/sling/fsprovider/internal/FsResourceProvider.java  | 8 ++++----
 src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java | 2 +-
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java b/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
index 15af91f..66f1af7 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
@@ -19,19 +19,19 @@
 package org.apache.sling.fsprovider.internal;
 
 /**
- * Different modes for Filesystem provider support and filesystem layouts.
+ * Different modes for File system provider support and file system layouts.
  */
 public enum FsMode {
     
     /**
-     * Sling-Initial-Content filesystem layout, with full support for JSON and jcr.xml content files.
+     * Sling-Initial-Content filesystem layout, supports file and folders ant content files in JSON and jcr.xml format.
      */
     INITIAL_CONTENT,
     
     /**
-     * Sling-Initial-Content filesystem layout, support only files and folders (classic mode).
+     * Support only files and folders (classic mode).
      */
-    INITIAL_CONTENT_FILES_FOLDERS,
+    FILES_FOLDERS,
 
     /**
      * FileVault XML format (expanded content package).
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index 2fa1e3e..47af999 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -115,9 +115,9 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
                 description = "Filesystem layout mode for files, folders and content.",
                 options={
                         @Option(value="INITIAL_CONTENT", label="INITIAL_CONTENT - "
-                                + "Sling-Initial-Content filesystem layout, with full support for JSON and jcr.xml content files"),
-                        @Option(value="INITIAL_CONTENT_FILES_FOLDERS", label="INITIAL_CONTENT_FILES_FOLDERS - "
-                                + "Sling-Initial-Content filesystem layout, support only files and folders (classic mode)"),
+                                + "Sling-Initial-Content filesystem layout, supports file and folders ant content files in JSON and jcr.xml format"),
+                        @Option(value="FILES_FOLDERS", label="FILES_FOLDERS - "
+                                + "Support only files and folders (classic mode)"),
                         @Option(value="FILEVAULT_XML", label="FILEVAULT_XML - "
                                 + "FileVault XML format (expanded content package)"),
                 })
@@ -325,7 +325,7 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
                 filterXmlFile = new File(config.provider_filevault_filterxml_path());
             }
         }
-        else if (fsMode == FsMode.INITIAL_CONTENT_FILES_FOLDERS) {
+        else if (fsMode == FsMode.FILES_FOLDERS) {
             overlayParentResourceProvider = true;
         }
         else if (fsMode == FsMode.INITIAL_CONTENT) {
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
index 6fed2b2..edfbc68 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
@@ -58,7 +58,7 @@ class TestUtils {
             config.put("provider.file", "src/test/resources/fs-test");
             config.put("provider.root", "/fs-test");
             config.put("provider.checkinterval", 0);
-            config.put("provider.fs.mode", FsMode.INITIAL_CONTENT_FILES_FOLDERS.name());
+            config.put("provider.fs.mode", FsMode.FILES_FOLDERS.name());
             config.putAll(props);
             context.registerInjectActivateService(new FsResourceProvider(), config);
         }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 21/29: SLING-6630 make FILES_FOLDERS default

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit a48dbbd35bbd8bbe64c0e403ee2a900ea6cd9d50
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Mar 10 15:47:54 2017 +0000

    SLING-6630 make FILES_FOLDERS default
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1786369 13f79535-47bb-0310-9956-ffa450edef68
---
 src/main/java/org/apache/sling/fsprovider/internal/FsMode.java | 10 +++++-----
 .../apache/sling/fsprovider/internal/FsResourceProvider.java   |  6 +++---
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java b/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
index 66f1af7..50eeebd 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
@@ -24,16 +24,16 @@ package org.apache.sling.fsprovider.internal;
 public enum FsMode {
     
     /**
-     * Sling-Initial-Content filesystem layout, supports file and folders ant content files in JSON and jcr.xml format.
-     */
-    INITIAL_CONTENT,
-    
-    /**
      * Support only files and folders (classic mode).
      */
     FILES_FOLDERS,
 
     /**
+     * Sling-Initial-Content filesystem layout, supports file and folders ant content files in JSON and jcr.xml format.
+     */
+    INITIAL_CONTENT,
+    
+    /**
      * FileVault XML format (expanded content package).
      */
     FILEVAULT_XML
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index 47af999..8863bdf 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -114,14 +114,14 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
         @AttributeDefinition(name = "Filesystem layout",
                 description = "Filesystem layout mode for files, folders and content.",
                 options={
-                        @Option(value="INITIAL_CONTENT", label="INITIAL_CONTENT - "
-                                + "Sling-Initial-Content filesystem layout, supports file and folders ant content files in JSON and jcr.xml format"),
                         @Option(value="FILES_FOLDERS", label="FILES_FOLDERS - "
                                 + "Support only files and folders (classic mode)"),
+                        @Option(value="INITIAL_CONTENT", label="INITIAL_CONTENT - "
+                                + "Sling-Initial-Content filesystem layout, supports file and folders ant content files in JSON and jcr.xml format"),
                         @Option(value="FILEVAULT_XML", label="FILEVAULT_XML - "
                                 + "FileVault XML format (expanded content package)"),
                 })
-        FsMode provider_fs_mode() default FsMode.INITIAL_CONTENT;
+        FsMode provider_fs_mode() default FsMode.FILES_FOLDERS;
         
         @AttributeDefinition(name = "Init. Content Options",
                 description = "Import options for Sling-Initial-Content filesystem layout. Supported options: overwrite, ignoreImportProviders.")

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 08/29: SLING-6537 add support for .jcr.xml files

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit e3b8e0f2873fc9a9409fde8cd1768f39c9e0c2af
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Feb 24 21:07:20 2017 +0000

    SLING-6537 add support for .jcr.xml files
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1784332 13f79535-47bb-0310-9956-ffa450edef68
---
 .../dam/talk.png/_jcr_content/renditions/original        | Bin 0 -> 8668 bytes
 .../talk.png/_jcr_content/renditions/web.1280.1280.png   | Bin 0 -> 5252 bytes
 2 files changed, 0 insertions(+), 0 deletions(-)

diff --git a/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original
new file mode 100644
index 0000000..0d42760
Binary files /dev/null and b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original differ
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png
new file mode 100644
index 0000000..27b0374
Binary files /dev/null and b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/web.1280.1280.png differ

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 28/29: [maven-release-plugin] prepare release org.apache.sling.fsresource-2.0.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit da43d7ddf892fc79f1f956034d71683afec57f5a
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Mon Mar 27 16:15:10 2017 +0000

    [maven-release-plugin] prepare release org.apache.sling.fsresource-2.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1788971 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 9714e9a..3039556 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
 
     <artifactId>org.apache.sling.fsresource</artifactId>
     <packaging>bundle</packaging>
-    <version>2.0.0-SNAPSHOT</version>
+    <version>2.0.0</version>
 
     <name>Apache Sling File System Resource Provider</name>
     <description>
@@ -36,9 +36,9 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource</connection>
-        <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>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.fsresource-2.0.0</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.fsresource-2.0.0</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.fsresource-2.0.0</url>
     </scm>
 
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 11/29: SLING-6440 switch to fscontentparser rename "Filesystem" to "File System" update to latest parent

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit c1a74d392a17a5801d2f9848449b9d22d42b0b6f
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Thu Mar 2 17:34:31 2017 +0000

    SLING-6440 switch to fscontentparser
    rename "Filesystem" to "File System"
    update to latest parent
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1785169 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  44 ++----
 .../fsprovider/internal/FsResourceProvider.java    |   8 +-
 .../fsprovider/internal/mapper/FileResource.java   |   2 +-
 .../fsprovider/internal/mapper/ValueMapUtil.java   |   7 -
 .../internal/parser/ContentFileCache.java          |   2 +-
 ...tFileParser.java => ContentFileParserUtil.java} |  27 +++-
 .../internal/parser/ContentFileTypes.java          |   6 +-
 .../internal/parser/JcrXmlFileParser.java          | 147 ------------------
 .../internal/parser/JcrXmlValueConverter.java      | 168 ---------------------
 .../fsprovider/internal/parser/JsonFileParser.java | 125 ---------------
 .../fsprovider/internal/JcrXmlContentTest.java     |  10 +-
 .../sling/fsprovider/internal/JsonContentTest.java |  15 +-
 ...serTest.java => ContentFileParserUtilTest.java} |  10 +-
 .../internal/parser/JcrXmlFileParserTest.java      |  34 -----
 .../internal/parser/JcrXmlValueConverterTest.java  | 105 -------------
 15 files changed, 62 insertions(+), 648 deletions(-)

diff --git a/pom.xml b/pom.xml
index ea0200f..1ab9b94 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>29</version>
+        <version>30-SNAPSHOT</version>
         <relativePath />
     </parent>
 
@@ -30,10 +30,9 @@
     <packaging>bundle</packaging>
     <version>1.2.3-SNAPSHOT</version>
 
-    <name>Apache Sling Filesystem Resource Provider</name>
+    <name>Apache Sling File System Resource Provider</name>
     <description>
-        Provides a ResourceProvider implementation supporting filesystem
-        based resources.
+        Provides a ResourceProvider implementation supporting file system based resources.
     </description>
 
     <scm>
@@ -61,33 +60,14 @@
                 <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>
-                        <!-- Embed Apache Johnzon -->
+                        <!-- Embed Apache Johnzon and fscontentparser -->
                         <Embed-Dependency>
                             johnzon-core;scope=compile;inline=false,
-                            geronimo-json_1.0_spec;scope=compile;inline=false
+                            geronimo-json_1.0_spec;scope=compile;inline=false,
+                            org.apache.sling.fscontentparser;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>
@@ -152,18 +132,18 @@
             <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>
             <artifactId>adapter-annotations</artifactId>
             <version>1.0.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.fscontentparser</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <scope>test</scope>
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index 65246c4..d214987 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -63,7 +63,7 @@ import org.osgi.service.metatype.annotations.ObjectClassDefinition;
            service=ResourceProvider.class,
            configurationPolicy=ConfigurationPolicy.REQUIRE,
            property={
-                   Constants.SERVICE_DESCRIPTION + "=Sling Filesystem Resource Provider",
+                   Constants.SERVICE_DESCRIPTION + "=Sling File System Resource Provider",
                    Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
            })
 @Designate(ocd=FsResourceProvider.Config.class, factory=true)
@@ -74,7 +74,7 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
      */
     public static final String RESOURCE_METADATA_FILE_DIRECTORY = ":org.apache.sling.fsprovider.file.directory";
     
-    @ObjectClassDefinition(name = "Apache Sling Filesystem Resource Provider",
+    @ObjectClassDefinition(name = "Apache Sling File System Resource Provider",
             description = "Configure an instance of the filesystem " +
                           "resource provider in terms of provider root and filesystem location")
     public @interface Config {
@@ -83,8 +83,8 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
          * files and folders mapped into the resource tree (value is
          * "provider.file").
          */
-        @AttributeDefinition(name = "Filesystem Root",
-                description = "Filesystem directory mapped to the virtual " +
+        @AttributeDefinition(name = "File System Root",
+                description = "File system 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 " +
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java
index 7472dc6..83f345e 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java
@@ -82,7 +82,7 @@ public final class FileResource extends AbstractResource {
     private static final Logger log = LoggerFactory.getLogger(FileResource.class);
     
     /**
-     * Creates an instance of this Filesystem resource.
+     * Creates an instance of this File system resource.
      *
      * @param resolver The owning resource resolver
      * @param resourcePath The resource path in the resource tree
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
index 8ddbda7..0d12ee1 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
@@ -22,8 +22,6 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
-import javax.jcr.nodetype.NodeType;
-
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.api.wrappers.ValueMapDecorator;
 
@@ -55,11 +53,6 @@ final class ValueMapUtil {
             }
         }
         
-        // fallback to default jcr:primaryType is none is set
-        if (!props.containsKey("jcr:primaryType")) {
-            props.put("jcr:primaryType", NodeType.NT_UNSTRUCTURED);
-        }
-        
         return new ValueMapDecorator(props);
     }
 
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
index 289fb67..745ae60 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
@@ -57,7 +57,7 @@ public final class ContentFileCache {
             content = contentCache.get(path);
         }
         if (content == null) {
-            content = ContentFileParser.parse(file);
+            content = ContentFileParserUtil.parse(file);
             if (content == null) {
                 content = NULL_MAP;
             }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
similarity index 63%
rename from src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
rename to src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
index 81a69a3..ec9af59 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
@@ -25,17 +25,34 @@ import java.io.File;
 import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.fscontentparser.ContentFileExtension;
+import org.apache.sling.fscontentparser.ContentFileParser;
+import org.apache.sling.fscontentparser.ContentFileParserFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * Parses files that contains content fragments (e.g. JSON, JCR XML).
  */
-class ContentFileParser {
+class ContentFileParserUtil {
     
-    private static final Logger log = LoggerFactory.getLogger(ContentFileParser.class);
+    private static final Logger log = LoggerFactory.getLogger(ContentFileParserUtil.class);
     
-    private ContentFileParser() {
+    private static final ContentFileParser JSON_PARSER;
+    static {
+        // workaround for JsonProvider classloader issue until https://issues.apache.org/jira/browse/GERONIMO-6560 is fixed
+        ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(ContentFileParserUtil.class.getClassLoader());
+            JSON_PARSER = ContentFileParserFactory.create(ContentFileExtension.JSON);
+        }
+        finally {
+            Thread.currentThread().setContextClassLoader(oldClassLoader);
+        }
+    }
+    private static final ContentFileParser JCR_XML_PARSER = ContentFileParserFactory.create(ContentFileExtension.JCR_XML);
+    
+    private ContentFileParserUtil() {
         // static methods only
     }
     
@@ -47,10 +64,10 @@ class ContentFileParser {
     public static Map<String,Object> parse(File file) {
         try {
             if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) {
-                return JsonFileParser.parse(file);
+                return JSON_PARSER.parse(file);
             }
             else if (StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) {
-                return JcrXmlFileParser.parse(file);
+                return JCR_XML_PARSER.parse(file);
             }
         }
         catch (Throwable ex) {
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
index 85e6445..689654d 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sling.fsprovider.internal.parser;
 
+import org.apache.sling.fscontentparser.ContentFileExtension;
+
 /**
  * Content file types.
  */
@@ -26,12 +28,12 @@ public final class ContentFileTypes {
     /**
      * JSON content files.
      */
-    public static final String JSON_SUFFIX = ".json";
+    public static final String JSON_SUFFIX = "." + ContentFileExtension.JSON;
 
     /**
      * JCR XML content files.
      */
-    public static final String JCR_XML_SUFFIX = ".jcr.xml";
+    public static final String JCR_XML_SUFFIX = "." + ContentFileExtension.JCR_XML;
         
     private ContentFileTypes() {
         // static methods only
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParser.java
deleted file mode 100644
index 6174bf9..0000000
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParser.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * 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.parser;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Stack;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.apache.jackrabbit.util.ISO9075;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-import org.xml.sax.helpers.DefaultHandler;
-
-/**
- * Parses JCR XML files that contains content fragments.
- */
-class JcrXmlFileParser {
-    
-    private static final Logger log = LoggerFactory.getLogger(JcrXmlFileParser.class);
-    
-    private static final SAXParserFactory SAX_PARSER_FACTORY;
-    static {
-        SAX_PARSER_FACTORY = SAXParserFactory.newInstance();
-        SAX_PARSER_FACTORY.setNamespaceAware(true);
-    }
-    
-    private JcrXmlFileParser() {
-        // static methods only
-    }
-    
-    /**
-     * Parse JSON file.
-     * @param file File
-     * @return Content
-     */
-    public static Map<String,Object> parse(File file) {
-        log.debug("Parse JCR XML content from {}", file.getPath());
-        try (FileInputStream fis = new FileInputStream(file)) {
-            XmlHandler xmlHandler = new XmlHandler();
-            SAXParser parser = SAX_PARSER_FACTORY.newSAXParser();
-            parser.parse(fis, xmlHandler);
-            if (xmlHandler.hasError()) {
-                throw xmlHandler.getError();
-            }
-            return xmlHandler.getContent();
-        }
-        catch (IOException | ParserConfigurationException | SAXException ex) {
-            log.warn("Error parsing JCR XML content from " + file.getPath(), ex);
-            return null;
-        }
-    }
-    
-    /**
-     * Decodes element or attribute names.
-     * @param qname qname
-     * @return Decoded name
-     */
-    static String decodeName(String qname) {
-        return ISO9075.decode(qname);
-    }
-    
-    /**
-     * Parses XML stream to Map.
-     */
-    static class XmlHandler extends DefaultHandler {
-        private final Map<String,Object> content = new LinkedHashMap<>();
-        private final Stack<Map<String,Object>> elements = new Stack<>();
-        private SAXParseException error;
-        
-        public Map<String,Object> getContent() {
-            return content;
-        }
-        
-        public boolean hasError() {
-            return error != null;
-        }
-        
-        public SAXParseException getError() {
-            return error;
-        }
-
-        @Override
-        public void startElement(String uri, String localName, String qName, Attributes attributes)
-                throws SAXException {
-            
-            // prepare map for element
-            Map<String,Object> element;
-            if (elements.isEmpty()) {
-                element = content;
-            }
-            else {
-                element = new HashMap<>();
-                elements.peek().put(decodeName(qName), element);
-            }
-            elements.push(element);
-            
-            // get attributes
-            for (int i=0; i<attributes.getLength(); i++) {
-                element.put(decodeName(attributes.getQName(i)), JcrXmlValueConverter.parseValue(attributes.getValue(i)));
-            }
-        }
-
-        @Override
-        public void endElement(String uri, String localName, String qName) throws SAXException {
-            elements.pop();
-        }
-
-        @Override
-        public void error(SAXParseException ex) throws SAXException {
-            this.error = ex;
-        }
-
-        @Override
-        public void fatalError(SAXParseException ex) throws SAXException {
-            this.error = ex;
-        }
-        
-    }
-    
-}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverter.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverter.java
deleted file mode 100644
index 8a0d0db..0000000
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverter.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * 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.parser;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.jackrabbit.util.ISO8601;
-
-/**
- * Parses JCR XML files that contains content fragments.
- */
-class JcrXmlValueConverter {
-    
-    private static final Pattern TYPE_PREFIX = Pattern.compile("^\\{([^\\{\\}]+)\\}(.+)$");
-    private static final Pattern VALUE_ARRAY = Pattern.compile("^\\[(.*)\\]$");
-    
-    private JcrXmlValueConverter() {
-        // static methods only
-    }
-    
-    /**
-     * Parse JSON value from XML Attribute.
-     * @param value XML attribute value
-     * @return Value object
-     */
-    public static Object parseValue(final String rawValue) {
-        String value = rawValue;
-        String[] valueArray = null;
-        
-        if (rawValue == null) {
-            return null;
-        }
-        
-        // detect type prefix
-        String typePrefix = null;
-        Matcher typePrefixMatcher = TYPE_PREFIX.matcher(value);
-        if (typePrefixMatcher.matches()) {
-            typePrefix = typePrefixMatcher.group(1);
-            value = typePrefixMatcher.group(2);
-        }
-        
-        // check for array
-        Matcher arrayMatcher = VALUE_ARRAY.matcher(value);
-        if (arrayMatcher.matches()) {
-            value = null;
-            valueArray = splitPreserveAllTokens(arrayMatcher.group(1), ',');
-        }
-
-        // convert values
-        if (valueArray != null) {
-            Object[] result = new Object[valueArray.length];
-            for (int i=0; i<valueArray.length; i++) {
-                result[i] = convertValue(valueArray[i], typePrefix, true);
-            }
-            return result;
-        }
-        else {
-            return convertValue(value, typePrefix, false);
-        }
-    }
-    
-    /**
-     * Split string preserving all tokens - but ignore separators that are escaped with \.
-     * @param str Combined string
-     * @param sep Separator
-     * @return Tokens
-     */
-    private static String[] splitPreserveAllTokens(String str, char sep) {
-        final int len = str.length();
-        if (len == 0) {
-            return ArrayUtils.EMPTY_STRING_ARRAY;
-        }
-        final List<String> list = new ArrayList<String>();
-        int i = 0, start = 0;
-        boolean match = false;
-        boolean lastMatch = false;
-        boolean escaped = false;
-        while (i < len) {
-            if (str.charAt(i) == '\\' && !escaped) {
-                escaped = true;
-            }
-            else {
-                if (str.charAt(i) == sep && !escaped) {
-                    lastMatch = true;
-                    list.add(str.substring(start, i));
-                    match = false;
-                    start = ++i;
-                    continue;
-                }
-                lastMatch = false;
-                match = true;
-                escaped = false;
-            }
-            i++;
-        }
-        if (match || lastMatch) {
-            list.add(str.substring(start, i));
-        }
-        return list.toArray(new String[list.size()]);        
-    }
-    
-    /**
-     * Parse value depending on type prefix.
-     * @param value Value
-     * @param typePrefix Type prefix
-     * @param inArray Value is in array
-     * @return Value object
-     */
-    private static Object convertValue(final String value, final String typePrefix, final boolean inArray) {
-        if (typePrefix == null || StringUtils.equals(typePrefix, "Name")) {
-            return deescapeStringValue(value, inArray);
-        }
-        else if (StringUtils.equals(typePrefix, "Boolean")) {
-            return Boolean.valueOf(value);
-        }
-        else if (StringUtils.equals(typePrefix, "Long")) {
-            return Long.valueOf(value);
-        }
-        else if (StringUtils.equals(typePrefix, "Decimal")) {
-            return Double.valueOf(value);
-        }
-        else if (StringUtils.equals(typePrefix, "Date")) {
-            return ISO8601.parse(value);
-        }
-        else {
-            throw new IllegalArgumentException("Unexpected type prefix: " + typePrefix);
-        }
-    }
-    
-    /**
-     * De-escape string value.
-     * @param value Escaped string value
-     * @param inArray In array
-     * @return De-escaped string value
-     */
-    private static String deescapeStringValue(final String value, final boolean inArray) {
-        String descapedValue = value;
-        if (inArray) {
-          descapedValue = StringUtils.replace(descapedValue, "\\,", ",");
-        }
-        else if (StringUtils.startsWith(descapedValue, "\\{") || StringUtils.startsWith(descapedValue, "\\[")) {
-            descapedValue = descapedValue.substring(1);
-        }
-        return StringUtils.replace(descapedValue, "\\\\", "\\");
-    }
-        
-}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java
deleted file mode 100644
index 8070e0d..0000000
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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.parser;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonNumber;
-import javax.json.JsonObject;
-import javax.json.JsonReader;
-import javax.json.JsonReaderFactory;
-import javax.json.JsonString;
-import javax.json.JsonValue;
-import javax.json.stream.JsonParsingException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Parses JSON files that contains content fragments.
- */
-class JsonFileParser {
-    
-    private static final Logger log = LoggerFactory.getLogger(JsonFileParser.class);
-    
-    private static final JsonReaderFactory JSON_READER_FACTORY;
-    static {
-        // allow comments in JSON files
-        Map<String,Object> jsonReaderFactoryConfig = new HashMap<>();
-        jsonReaderFactoryConfig.put("org.apache.johnzon.supports-comments", true);
-        // workaround for JsonProvider classloader issue until https://issues.apache.org/jira/browse/GERONIMO-6560 is fixed
-        ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
-        try {
-            Thread.currentThread().setContextClassLoader(JsonFileParser.class.getClassLoader());
-            JSON_READER_FACTORY = Json.createReaderFactory(jsonReaderFactoryConfig);
-        }
-        finally {
-            Thread.currentThread().setContextClassLoader(oldClassLoader);
-        }
-    }
-    
-    private JsonFileParser() {
-        // static methods only
-    }
-    
-    /**
-     * Parse JSON file.
-     * @param file File
-     * @return Content
-     */
-    public static Map<String,Object> parse(File file) {
-        log.debug("Parse JSON content from {}", file.getPath());
-        try (FileInputStream fis = new FileInputStream(file);
-                JsonReader reader = JSON_READER_FACTORY.createReader(fis)) {
-            return toMap(reader.readObject());
-        }
-        catch (IOException | JsonParsingException ex) {
-            log.warn("Error parsing JSON content from " + file.getPath(), ex);
-            return null;
-        }
-    }
-    
-    private static Map<String,Object> toMap(JsonObject object) {
-        Map<String,Object> map = new LinkedHashMap<>();
-        for (Map.Entry<String, JsonValue> entry : object.entrySet()) {
-            map.put(entry.getKey(), convertValue(entry.getValue()));
-        }
-        return map;
-    }
-    
-    private static Object convertValue(JsonValue value) {
-        switch (value.getValueType()) {
-            case STRING:
-                return ((JsonString)value).getString();
-            case NUMBER:
-                JsonNumber numberValue = (JsonNumber)value;
-                if (numberValue.isIntegral()) {
-                    return numberValue.longValue();
-                }
-                else {
-                    return numberValue.doubleValue();
-                }
-            case TRUE:
-                return true;
-            case FALSE:
-                return false;
-            case NULL:
-                return null;
-            case ARRAY:
-                JsonArray arrayValue = (JsonArray)value;
-                Object[] values = new Object[arrayValue.size()];
-                for (int i=0; i<values.length; i++) {
-                    values[i] = convertValue(arrayValue.get(i));
-                }
-                return values;
-            case OBJECT:
-                return toMap((JsonObject)value);
-            default:
-                throw new IllegalArgumentException("Unexpected JSON value type: " + value.getValueType());
-        }
-    }
-    
-}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
index 39f18d4..ded7f29 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
@@ -90,7 +90,7 @@ public class JcrXmlContentTest {
     }
 
     @Test
-    public void testJsonContent_Root() {
+    public void testContent_Root() {
         Resource underTest = fsroot.getChild("folder3/content");
         assertNotNull(underTest);
         assertEquals("app:Page", underTest.getValueMap().get("jcr:primaryType", String.class));
@@ -99,7 +99,7 @@ public class JcrXmlContentTest {
     }
 
     @Test
-    public void testJsonContent_Level1() {
+    public void testContent_Level1() {
         Resource underTest = fsroot.getChild("folder3/content/jcr:content");
         assertNotNull(underTest);
         assertEquals("app:PageContent", underTest.getValueMap().get("jcr:primaryType", String.class));
@@ -108,7 +108,7 @@ public class JcrXmlContentTest {
     }
 
     @Test
-    public void testJsonContent_Level3() {
+    public void testContent_Level3() {
         Resource underTest = fsroot.getChild("folder3/content/jcr:content/content/contentheadline");
         assertNotNull(underTest);
         assertEquals("nt:unstructured", underTest.getValueMap().get("jcr:primaryType", String.class));
@@ -117,7 +117,7 @@ public class JcrXmlContentTest {
     }
 
     @Test
-    public void testJsonContent_Datatypes() {
+    public void testContent_Datatypes() {
         Resource underTest = fsroot.getChild("folder3/content/jcr:content");
         ValueMap props = underTest.getValueMap();
         
@@ -131,7 +131,7 @@ public class JcrXmlContentTest {
     }
 
     @Test
-    public void testJsonContent_InvalidPath() {
+    public void testContent_InvalidPath() {
         Resource underTest = fsroot.getChild("folder2/content/jcr:content/xyz");
         assertNull(underTest);
     }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
index 797575b..031812f 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
@@ -42,6 +42,7 @@ import javax.jcr.nodetype.NodeType;
 
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.fscontentparser.ParserOptions;
 import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin;
 import org.apache.sling.hamcrest.ResourceMatchers;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
@@ -99,7 +100,7 @@ public class JsonContentTest {
     }
 
     @Test
-    public void testJsonContent_Root() {
+    public void testContent_Root() {
         Resource underTest = fsroot.getChild("folder2/content");
         assertNotNull(underTest);
         assertEquals("app:Page", underTest.getValueMap().get("jcr:primaryType", String.class));
@@ -108,7 +109,7 @@ public class JsonContentTest {
     }
 
     @Test
-    public void testJsonContent_Level1() {
+    public void testContent_Level1() {
         Resource underTest = fsroot.getChild("folder2/content/jcr:content");
         assertNotNull(underTest);
         assertEquals("app:PageContent", underTest.getValueMap().get("jcr:primaryType", String.class));
@@ -118,7 +119,7 @@ public class JsonContentTest {
     }
 
     @Test
-    public void testJsonContent_Level5() {
+    public void testContent_Level5() {
         Resource underTest = fsroot.getChild("folder2/content/jcr:content/par/image/file/jcr:content");
         assertNotNull(underTest);
         assertEquals("nt:resource", underTest.getValueMap().get("jcr:primaryType", String.class));
@@ -126,7 +127,7 @@ public class JsonContentTest {
     }
 
     @Test
-    public void testJsonContent_Datatypes() {
+    public void testContent_Datatypes() {
         Resource underTest = fsroot.getChild("folder2/content/toolbar/profiles/jcr:content");
         ValueMap props = underTest.getValueMap();
         
@@ -140,7 +141,7 @@ public class JsonContentTest {
     }
 
     @Test
-    public void testJsonContent_Datatypes_JCR() throws RepositoryException {
+    public void testContent_Datatypes_JCR() throws RepositoryException {
         Resource underTest = fsroot.getChild("folder2/content/toolbar/profiles/jcr:content");
         ValueMap props = underTest.getValueMap();
         Node node = underTest.adaptTo(Node.class);
@@ -218,11 +219,11 @@ public class JsonContentTest {
     @Test
     public void testFallbackNodeType() throws RepositoryException {
         Resource underTest = fsroot.getChild("folder2/content/jcr:content/par/title_2");
-        assertEquals(NodeType.NT_UNSTRUCTURED, underTest.adaptTo(Node.class).getPrimaryNodeType().getName());
+        assertEquals(ParserOptions.DEFAULT_PRIMARY_TYPE, underTest.adaptTo(Node.class).getPrimaryNodeType().getName());
     }
     
     @Test
-    public void testJsonContent_InvalidPath() {
+    public void testContent_InvalidPath() {
         Resource underTest = fsroot.getChild("folder2/content/jcr:content/xyz");
         assertNull(underTest);
     }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java
similarity index 86%
rename from src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java
rename to src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java
index 290ea21..9bdaa51 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java
@@ -27,13 +27,13 @@ import java.util.Map;
 
 import org.junit.Test;
 
-public class ContentFileParserTest {
+public class ContentFileParserUtilTest {
 
     @SuppressWarnings("unchecked")
     @Test
     public void testParseJson() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
-        Map<String,Object> content = ContentFileParser.parse(file);
+        Map<String,Object> content = ContentFileParserUtil.parse(file);
         assertNotNull(content);
         assertEquals("app:Page", content.get("jcr:primaryType"));
         assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
@@ -42,7 +42,7 @@ public class ContentFileParserTest {
     @Test
     public void testParseInvalidJson() {
         File file = new File("src/test/resources/invalid-test/invalid.json");
-        Map<String,Object> content = ContentFileParser.parse(file);
+        Map<String,Object> content = ContentFileParserUtil.parse(file);
         assertNull(content);
     }
 
@@ -50,7 +50,7 @@ public class ContentFileParserTest {
     @Test
     public void testParseJcrXml() {
         File file = new File("src/test/resources/fs-test/folder3/content.jcr.xml");
-        Map<String,Object> content = ContentFileParser.parse(file);
+        Map<String,Object> content = ContentFileParserUtil.parse(file);
         assertNotNull(content);
         assertEquals("app:Page", content.get("jcr:primaryType"));
         assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
@@ -59,7 +59,7 @@ public class ContentFileParserTest {
     @Test
     public void testParseInvalidJcrXml() {
         File file = new File("src/test/resources/invalid-test/invalid.jcr.xml");
-        Map<String,Object> content = ContentFileParser.parse(file);
+        Map<String,Object> content = ContentFileParserUtil.parse(file);
         assertNull(content);
     }
 
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParserTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParserTest.java
deleted file mode 100644
index 7cccbcf..0000000
--- a/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParserTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.parser;
-
-import static org.junit.Assert.assertEquals;
-
-import org.apache.jackrabbit.util.ISO9075;
-import org.junit.Test;
-
-public class JcrXmlFileParserTest {
-
-    @Test
-    public void testDecodeName() {
-        assertEquals("jcr:title", JcrXmlFileParser.decodeName("jcr:" + ISO9075.encode("title")));
-        assertEquals("sling:123", JcrXmlFileParser.decodeName("sling:" + ISO9075.encode("123")));
-    }
-
-}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverterTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverterTest.java
deleted file mode 100644
index 15fe989..0000000
--- a/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverterTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.parser;
-
-import static org.apache.sling.fsprovider.internal.parser.JcrXmlValueConverter.parseValue;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import java.util.Calendar;
-
-import org.junit.Test;
-
-public class JcrXmlValueConverterTest {
-
-    @Test
-    public void testNull() {
-        assertNull(parseValue(null));
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testInvalid() {
-        parseValue("{InvalidType}xyz");
-    }
-
-    @Test
-    public void testString() {
-        assertEquals("myString", parseValue("myString"));
-        assertEquals("prop", "myString [ ] { } \\ ,", parseValue("myString [ ] { } \\\\ ,"));
-        assertEquals("{myString}", parseValue("\\{myString}"));
-        assertEquals("aaa{myString}", parseValue("aaa{myString}"));
-        assertEquals("[myString]", parseValue("\\[myString]"));
-        assertEquals("aaa[myString]", parseValue("aaa[myString]"));
-    }
-
-    @Test
-    public void testStringArray() {
-        assertArrayEquals(new Object[] { "myString1", "myString2" }, (Object[]) parseValue("[myString1,myString2]"));
-        assertArrayEquals(new Object[] { "myString1,[]\\äöü߀", "myString2", "myString3 [ ] { } \\ ,", "", "[myString5]", "{myString6}" },
-                (Object[]) parseValue("[myString1\\,[]\\\\äöü߀,myString2,myString3 [ ] { } \\\\ \\,,,[myString5],{myString6}]"));
-    }
-
-    @Test
-    public void testBoolean() {
-        assertEquals(true, parseValue("{Boolean}true"));
-        assertEquals(false, parseValue("{Boolean}false"));
-    }
-
-    @Test
-    public void testBooleanArray() {
-        assertArrayEquals(new Object[] { true, false }, (Object[]) parseValue("{Boolean}[true,false]"));
-    }
-
-    @Test
-    public void testLong() {
-        assertEquals(1L, parseValue("{Long}1"));
-        assertEquals(10000000000L, parseValue("{Long}10000000000"));
-    }
-
-    @Test
-    public void testLongArray() {
-        assertArrayEquals(new Object[] { 1L, 2L }, (Object[]) parseValue("{Long}[1,2]"));
-        assertArrayEquals(new Object[] { 10000000000L, 20000000000L }, (Object[]) parseValue("{Long}[10000000000,20000000000]"));
-    }
-
-    @Test
-    public void testDouble() {
-        assertEquals(1.234d, parseValue("{Decimal}1.234"));
-    }
-
-    @Test
-    public void testDoubleArray() {
-        assertArrayEquals(new Object[] { 1.234d, 2.345d }, (Object[]) parseValue("{Decimal}[1.234,2.345]"));
-    }
-
-    @Test
-    public void testCalendar() {
-        Calendar value = (Calendar)parseValue("{Date}2010-09-05T15:10:20.000Z");
-        assertEquals(2010, value.get(Calendar.YEAR));
-        assertEquals(8, value.get(Calendar.MONTH));
-        assertEquals(5, value.get(Calendar.DAY_OF_MONTH));
-    }
-
-    @Test
-    public void testStringArrayRepPrivileges() {
-        assertArrayEquals(new Object[] { "rep:write", "crx:replicate", "jcr:read" }, (Object[]) parseValue("{Name}[rep:write,crx:replicate,jcr:read]"));
-    }
-
-}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 18/29: SLING-6537 FileVault XML support

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 37acacbc7e8c0db696b4ff830bd860df99b73ce7
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Thu Mar 9 17:25:36 2017 +0000

    SLING-6537 FileVault XML support
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1786206 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/fsprovider/internal/FileMonitor.java     |  4 +---
 .../fsprovider/internal/FsResourceProvider.java    | 10 +++++++++-
 .../internal/mapper/FileVaultResourceMapper.java   | 22 ++++++++++------------
 .../fsprovider/internal/FileVaultContentTest.java  |  6 ++++--
 .../internal/FileVaultFileMonitorTest.java         |  6 ++++--
 5 files changed, 28 insertions(+), 20 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
index d8f7795..dc1e65d 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -18,8 +18,6 @@
  */
 package org.apache.sling.fsprovider.internal;
 
-import static org.apache.jackrabbit.vault.util.Constants.ROOT_DIR;
-
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
@@ -71,7 +69,7 @@ public final class FileMonitor extends TimerTask {
         
         File rootFile = this.provider.getRootFile();
         if (fsMode == FsMode.FILEVAULT_XML) {
-            rootFile = new File(this.provider.getRootFile(), ROOT_DIR + PlatformNameFormat.getPlatformPath(this.provider.getProviderRoot()));
+            rootFile = new File(this.provider.getRootFile(), "." + PlatformNameFormat.getPlatformPath(this.provider.getProviderRoot()));
         }
         this.root = new Monitorable(this.provider.getProviderRoot(), rootFile, null);
         
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index 2c8384e..1e2c53b 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -126,6 +126,10 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
                 description = "Import options for Sling-Initial-Content filesystem layout. Supported options: overwrite, ignoreImportProviders.")
         String provider_initial_content_import_options();
         
+        @AttributeDefinition(name = "FileVault Filter",
+                description = "Path to META-INF/vault/filter.xml when using FileVault XML filesystem layout.")
+        String provider_filevault_filterxml_path();
+        
         @AttributeDefinition(name = "Cache Size",
                 description = "Max. number of content files cached in memory.")
         int provider_cache_size() default 10000;
@@ -311,10 +315,14 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
         this.overlayParentResourceProvider = false;
         
         InitialContentImportOptions options = new InitialContentImportOptions(config.provider_initial_content_import_options());
+        File filterXmlFile = null;
                 
         List<String> contentFileSuffixes = new ArrayList<>();
         if (fsMode == FsMode.FILEVAULT_XML) {
             contentFileSuffixes.add("/" + DOT_CONTENT_XML);
+            if (StringUtils.isNotBlank(config.provider_filevault_filterxml_path())) {
+                filterXmlFile = new File(config.provider_filevault_filterxml_path());
+            }
         }
         else if (fsMode == FsMode.INITIAL_CONTENT_FILES_FOLDERS) {
             overlayParentResourceProvider = true;
@@ -332,7 +340,7 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
         
         this.contentFileCache = new ContentFileCache(config.provider_cache_size());
         if (fsMode == FsMode.FILEVAULT_XML) {
-            this.fileVaultMapper = new FileVaultResourceMapper(this.providerFile, this.contentFileCache);
+            this.fileVaultMapper = new FileVaultResourceMapper(this.providerFile, filterXmlFile, this.contentFileCache);
         }
         else {
             this.fileMapper = new FileResourceMapper(this.providerRoot, this.providerFile, contentFileExtensions);
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
index 88e001e..cb25230 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
@@ -19,9 +19,6 @@
 package org.apache.sling.fsprovider.internal.mapper;
 
 import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
-import static org.apache.jackrabbit.vault.util.Constants.FILTER_XML;
-import static org.apache.jackrabbit.vault.util.Constants.META_DIR;
-import static org.apache.jackrabbit.vault.util.Constants.ROOT_DIR;
 
 import java.io.File;
 import java.io.IOException;
@@ -51,13 +48,15 @@ public final class FileVaultResourceMapper implements FsResourceMapper {
     private static final String DOT_DIR_SUFFIX = "/.dir";
 
     private final File providerFile;
+    private final File filterXmlFile;
     private final ContentFileCache contentFileCache;
     private final WorkspaceFilter workspaceFilter;
     
     private static final Logger log = LoggerFactory.getLogger(FileVaultResourceMapper.class);
     
-    public FileVaultResourceMapper(File providerFile, ContentFileCache contentFileCache) {
+    public FileVaultResourceMapper(File providerFile, File filterXmlFile, ContentFileCache contentFileCache) {
         this.providerFile = providerFile;
+        this.filterXmlFile = filterXmlFile;
         this.contentFileCache = contentFileCache;
         this.workspaceFilter = getWorkspaceFilter();
     }
@@ -134,18 +133,17 @@ public final class FileVaultResourceMapper implements FsResourceMapper {
      * @return Workspace filter or null if none found.
      */
     private WorkspaceFilter getWorkspaceFilter() {
-        File filter = new File(providerFile, META_DIR + "/" + FILTER_XML);
-        if (filter.exists()) {
+        if (filterXmlFile != null && filterXmlFile.exists()) {
             try {
                 DefaultWorkspaceFilter workspaceFilter = new DefaultWorkspaceFilter();
-                workspaceFilter.load(filter);
+                workspaceFilter.load(filterXmlFile);
                 return workspaceFilter;
             } catch (IOException | ConfigurationException ex) {
-                log.error("Unable to parse workspace filter: " + filter.getPath(), ex);
+                log.error("Unable to parse workspace filter: " + filterXmlFile.getPath(), ex);
             }
         }
         else {
-            log.warn("Workspace filter not found: " + filter.getPath());
+            log.debug("Workspace filter not found: " + filterXmlFile.getPath());
         }
         return null;
     }
@@ -161,7 +159,7 @@ public final class FileVaultResourceMapper implements FsResourceMapper {
             return false;
         }
         if (workspaceFilter == null) {
-            return false;
+            return true;
         }
         else {
             return workspaceFilter.contains(path);
@@ -172,7 +170,7 @@ public final class FileVaultResourceMapper implements FsResourceMapper {
         if (StringUtils.endsWith(path, DOT_CONTENT_XML_SUFFIX)) {
             return null;
         }
-        File file = new File(providerFile, ROOT_DIR + PlatformNameFormat.getPlatformPath(path));
+        File file = new File(providerFile, "." + PlatformNameFormat.getPlatformPath(path));
         if (file.exists()) {
             return file;
         }
@@ -180,7 +178,7 @@ public final class FileVaultResourceMapper implements FsResourceMapper {
     }
     
     private ContentFile getContentFile(String path, String subPath) {
-        File file = new File(providerFile, ROOT_DIR + PlatformNameFormat.getPlatformPath(path) + DOT_CONTENT_XML_SUFFIX);
+        File file = new File(providerFile, "." + PlatformNameFormat.getPlatformPath(path) + DOT_CONTENT_XML_SUFFIX);
         if (file.exists()) {
             ContentFile contentFile = new ContentFile(file, path, subPath, contentFileCache);
             if (contentFile.hasContent()) {
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java
index a6f6744..b2f1514 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.java
@@ -57,12 +57,14 @@ public class FileVaultContentTest {
     public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
         .plugin(new RegisterFsResourcePlugin(
                 "provider.fs.mode", FsMode.FILEVAULT_XML.name(),
-                "provider.file", "src/test/resources/vaultfs-test",
+                "provider.file", "src/test/resources/vaultfs-test/jcr_root",
+                "provider.filevault.filterxml.path", "src/test/resources/vaultfs-test/META-INF/vault/filter.xml",
                 "provider.root", "/content/dam/talk.png"
                 ))
         .plugin(new RegisterFsResourcePlugin(
                 "provider.fs.mode", FsMode.FILEVAULT_XML.name(),
-                "provider.file", "src/test/resources/vaultfs-test",
+                "provider.file", "src/test/resources/vaultfs-test/jcr_root",
+                "provider.filevault.filterxml.path", "src/test/resources/vaultfs-test/META-INF/vault/filter.xml",
                 "provider.root", "/content/samples"
                 ))
         .build();
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java
index 8fe5f3b..9743027 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java
@@ -65,12 +65,14 @@ public class FileVaultFileMonitorTest {
                 
                 // mount temp. directory
                 context.registerInjectActivateService(new FsResourceProvider(),
-                        "provider.file", tempDir.getPath(),
+                        "provider.file", tempDir.getPath() + "/jcr_root",
+                        "provider.filevault.filterxml.path", tempDir.getPath() + "/META-INF/vault/filter.xml",
                         "provider.root", "/content/dam/talk.png",
                         "provider.checkinterval", CHECK_INTERVAL,
                         "provider.fs.mode", FsMode.FILEVAULT_XML.name());
                 context.registerInjectActivateService(new FsResourceProvider(),
-                        "provider.file", tempDir.getPath(),
+                        "provider.file", tempDir.getPath() + "/jcr_root",
+                        "provider.filevault.filterxml.path", tempDir.getPath() + "/META-INF/vault/filter.xml",
                         "provider.root", "/content/samples",
                         "provider.checkinterval", CHECK_INTERVAL,
                         "provider.fs.mode", FsMode.FILEVAULT_XML.name());

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 07/29: SLING-6537 add support for .jcr.xml files

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 94e04e04aed63c7010c670cd1199340a1abf512e
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Feb 24 21:04:25 2017 +0000

    SLING-6537 add support for .jcr.xml files
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1784329 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  31 ++--
 .../fsprovider/internal/FsResourceProvider.java    |  12 +-
 .../internal/parser/ContentFileParser.java         |   6 +-
 .../internal/parser/ContentFileTypes.java          |   5 +
 .../internal/parser/JcrXmlFileParser.java          | 147 ++++++++++++++++
 .../internal/parser/JcrXmlValueConverter.java      | 168 ++++++++++++++++++
 .../fsprovider/internal/parser/JsonFileParser.java |  13 +-
 .../sling/fsprovider/internal/FileMonitorTest.java |   6 +-
 .../sling/fsprovider/internal/FilesFolderTest.java |   4 +-
 .../sling/fsprovider/internal/JcrMixedTest.java    |   6 +-
 .../fsprovider/internal/JcrXmlContentTest.java     | 165 ++++++++++++++++++
 .../sling/fsprovider/internal/JsonContentTest.java |   5 +-
 .../internal/parser/ContentFileParserTest.java     |  19 +-
 .../internal/parser/JcrXmlFileParserTest.java}     |  23 ++-
 .../internal/parser/JcrXmlValueConverterTest.java  | 105 +++++++++++
 src/test/resources/fs-test/folder2/content.json    |   6 +-
 src/test/resources/fs-test/folder3/content.jcr.xml | 192 +++++++++++++++++++++
 .../fs-test/folder3/content/content2.jcr.xml       |  29 ++++
 .../resources/fs-test/folder3/folder31/file31a.txt |   1 +
 src/test/resources/invalid-test/invalid.jcr.xml    |   1 +
 src/test/resources/invalid-test/invalid.json       |   1 +
 .../vaultfs-test/META-INF/vault/filter.xml         |  23 +++
 .../vaultfs-test/META-INF/vault/settings.xml       |  23 +++
 .../resources/vaultfs-test/jcr_root/.content.xml   |  24 +++
 .../vaultfs-test/jcr_root/content/.content.xml     |  28 +++
 .../vaultfs-test/jcr_root/content/dam/.content.xml |  24 +++
 .../jcr_root/content/dam/talk.png/.content.xml     |  46 +++++
 .../renditions/original.dir/.content.xml           |  25 +++
 .../jcr_root/content/samples/.content.xml          |  23 +++
 .../jcr_root/content/samples/en/.content.xml       | 191 ++++++++++++++++++++
 .../content/samples/en/conference/.content.xml     |  89 ++++++++++
 .../jcr_root/content/samples/en/tools/.content.xml |  29 ++++
 .../samples/en/tools/navigation/.content.xml       |  27 +++
 33 files changed, 1451 insertions(+), 46 deletions(-)

diff --git a/pom.xml b/pom.xml
index fa7233a..ea0200f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,10 +77,17 @@
                     <!-- Export SCR metadata to classpath to have them available in unit tests -->
                     <exportScr>true</exportScr>
                     <instructions>
+                        <!-- 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>
@@ -90,6 +97,8 @@
                 <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>
@@ -131,22 +140,22 @@
             <scope>compile</scope>
         </dependency>
         <dependency>
-            <groupId>commons-io</groupId>
-            <artifactId>commons-io</artifactId>
-            <version>2.4</version>
+            <groupId>org.apache.johnzon</groupId>
+            <artifactId>johnzon-core</artifactId>
+            <version>1.0.0</version>
             <scope>compile</scope>
         </dependency>
         <dependency>
-          <groupId>org.apache.johnzon</groupId>
-          <artifactId>johnzon-core</artifactId>
-          <version>1.0.0</version>
-          <scope>compile</scope>
+            <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.geronimo.specs</groupId>
-          <artifactId>geronimo-json_1.0_spec</artifactId>
-          <version>1.0-alpha-1</version>
-          <scope>compile</scope>
+            <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>
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index 5d1ca9b..65246c4 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -106,10 +106,14 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
                 "filesystem resources are mapped in. This property must not be an empty string.")
         String provider_root();
         
-        @AttributeDefinition(name = "Mount JSON",
+        @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;
@@ -254,6 +258,10 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
             contentFileSuffixes.add(ContentFileTypes.JSON_SUFFIX);
             this.overlayParentResourceProvider = false;
         }
+        if (config.provider_jcrxml_content()) {
+            contentFileSuffixes.add(ContentFileTypes.JCR_XML_SUFFIX);
+            this.overlayParentResourceProvider = false;
+        }
         ContentFileExtensions contentFileExtensions = new ContentFileExtensions(contentFileSuffixes);
         
         this.contentFileCache = new ContentFileCache(config.provider_cache_size());
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
index 8f7a11f..81a69a3 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
@@ -18,6 +18,7 @@
  */
 package org.apache.sling.fsprovider.internal.parser;
 
+import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JCR_XML_SUFFIX;
 import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JSON_SUFFIX;
 
 import java.io.File;
@@ -28,7 +29,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Parses file that contains content fragments (e.g. JSON, JCR XML).
+ * Parses files that contains content fragments (e.g. JSON, JCR XML).
  */
 class ContentFileParser {
     
@@ -48,6 +49,9 @@ class ContentFileParser {
             if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) {
                 return JsonFileParser.parse(file);
             }
+            else if (StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) {
+                return JcrXmlFileParser.parse(file);
+            }
         }
         catch (Throwable ex) {
             log.warn("Error parsing content from " + file.getPath(), ex);
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
index 9d995e6..85e6445 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
@@ -28,6 +28,11 @@ public final class ContentFileTypes {
      */
     public static final String JSON_SUFFIX = ".json";
 
+    /**
+     * JCR XML content files.
+     */
+    public static final String JCR_XML_SUFFIX = ".jcr.xml";
+        
     private ContentFileTypes() {
         // static methods only
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParser.java
new file mode 100644
index 0000000..6174bf9
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParser.java
@@ -0,0 +1,147 @@
+/*
+ * 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.parser;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Stack;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.jackrabbit.util.ISO9075;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Parses JCR XML files that contains content fragments.
+ */
+class JcrXmlFileParser {
+    
+    private static final Logger log = LoggerFactory.getLogger(JcrXmlFileParser.class);
+    
+    private static final SAXParserFactory SAX_PARSER_FACTORY;
+    static {
+        SAX_PARSER_FACTORY = SAXParserFactory.newInstance();
+        SAX_PARSER_FACTORY.setNamespaceAware(true);
+    }
+    
+    private JcrXmlFileParser() {
+        // static methods only
+    }
+    
+    /**
+     * Parse JSON file.
+     * @param file File
+     * @return Content
+     */
+    public static Map<String,Object> parse(File file) {
+        log.debug("Parse JCR XML content from {}", file.getPath());
+        try (FileInputStream fis = new FileInputStream(file)) {
+            XmlHandler xmlHandler = new XmlHandler();
+            SAXParser parser = SAX_PARSER_FACTORY.newSAXParser();
+            parser.parse(fis, xmlHandler);
+            if (xmlHandler.hasError()) {
+                throw xmlHandler.getError();
+            }
+            return xmlHandler.getContent();
+        }
+        catch (IOException | ParserConfigurationException | SAXException ex) {
+            log.warn("Error parsing JCR XML content from " + file.getPath(), ex);
+            return null;
+        }
+    }
+    
+    /**
+     * Decodes element or attribute names.
+     * @param qname qname
+     * @return Decoded name
+     */
+    static String decodeName(String qname) {
+        return ISO9075.decode(qname);
+    }
+    
+    /**
+     * Parses XML stream to Map.
+     */
+    static class XmlHandler extends DefaultHandler {
+        private final Map<String,Object> content = new LinkedHashMap<>();
+        private final Stack<Map<String,Object>> elements = new Stack<>();
+        private SAXParseException error;
+        
+        public Map<String,Object> getContent() {
+            return content;
+        }
+        
+        public boolean hasError() {
+            return error != null;
+        }
+        
+        public SAXParseException getError() {
+            return error;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attributes)
+                throws SAXException {
+            
+            // prepare map for element
+            Map<String,Object> element;
+            if (elements.isEmpty()) {
+                element = content;
+            }
+            else {
+                element = new HashMap<>();
+                elements.peek().put(decodeName(qName), element);
+            }
+            elements.push(element);
+            
+            // get attributes
+            for (int i=0; i<attributes.getLength(); i++) {
+                element.put(decodeName(attributes.getQName(i)), JcrXmlValueConverter.parseValue(attributes.getValue(i)));
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) throws SAXException {
+            elements.pop();
+        }
+
+        @Override
+        public void error(SAXParseException ex) throws SAXException {
+            this.error = ex;
+        }
+
+        @Override
+        public void fatalError(SAXParseException ex) throws SAXException {
+            this.error = ex;
+        }
+        
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverter.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverter.java
new file mode 100644
index 0000000..8a0d0db
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverter.java
@@ -0,0 +1,168 @@
+/*
+ * 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.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jackrabbit.util.ISO8601;
+
+/**
+ * Parses JCR XML files that contains content fragments.
+ */
+class JcrXmlValueConverter {
+    
+    private static final Pattern TYPE_PREFIX = Pattern.compile("^\\{([^\\{\\}]+)\\}(.+)$");
+    private static final Pattern VALUE_ARRAY = Pattern.compile("^\\[(.*)\\]$");
+    
+    private JcrXmlValueConverter() {
+        // static methods only
+    }
+    
+    /**
+     * Parse JSON value from XML Attribute.
+     * @param value XML attribute value
+     * @return Value object
+     */
+    public static Object parseValue(final String rawValue) {
+        String value = rawValue;
+        String[] valueArray = null;
+        
+        if (rawValue == null) {
+            return null;
+        }
+        
+        // detect type prefix
+        String typePrefix = null;
+        Matcher typePrefixMatcher = TYPE_PREFIX.matcher(value);
+        if (typePrefixMatcher.matches()) {
+            typePrefix = typePrefixMatcher.group(1);
+            value = typePrefixMatcher.group(2);
+        }
+        
+        // check for array
+        Matcher arrayMatcher = VALUE_ARRAY.matcher(value);
+        if (arrayMatcher.matches()) {
+            value = null;
+            valueArray = splitPreserveAllTokens(arrayMatcher.group(1), ',');
+        }
+
+        // convert values
+        if (valueArray != null) {
+            Object[] result = new Object[valueArray.length];
+            for (int i=0; i<valueArray.length; i++) {
+                result[i] = convertValue(valueArray[i], typePrefix, true);
+            }
+            return result;
+        }
+        else {
+            return convertValue(value, typePrefix, false);
+        }
+    }
+    
+    /**
+     * Split string preserving all tokens - but ignore separators that are escaped with \.
+     * @param str Combined string
+     * @param sep Separator
+     * @return Tokens
+     */
+    private static String[] splitPreserveAllTokens(String str, char sep) {
+        final int len = str.length();
+        if (len == 0) {
+            return ArrayUtils.EMPTY_STRING_ARRAY;
+        }
+        final List<String> list = new ArrayList<String>();
+        int i = 0, start = 0;
+        boolean match = false;
+        boolean lastMatch = false;
+        boolean escaped = false;
+        while (i < len) {
+            if (str.charAt(i) == '\\' && !escaped) {
+                escaped = true;
+            }
+            else {
+                if (str.charAt(i) == sep && !escaped) {
+                    lastMatch = true;
+                    list.add(str.substring(start, i));
+                    match = false;
+                    start = ++i;
+                    continue;
+                }
+                lastMatch = false;
+                match = true;
+                escaped = false;
+            }
+            i++;
+        }
+        if (match || lastMatch) {
+            list.add(str.substring(start, i));
+        }
+        return list.toArray(new String[list.size()]);        
+    }
+    
+    /**
+     * Parse value depending on type prefix.
+     * @param value Value
+     * @param typePrefix Type prefix
+     * @param inArray Value is in array
+     * @return Value object
+     */
+    private static Object convertValue(final String value, final String typePrefix, final boolean inArray) {
+        if (typePrefix == null || StringUtils.equals(typePrefix, "Name")) {
+            return deescapeStringValue(value, inArray);
+        }
+        else if (StringUtils.equals(typePrefix, "Boolean")) {
+            return Boolean.valueOf(value);
+        }
+        else if (StringUtils.equals(typePrefix, "Long")) {
+            return Long.valueOf(value);
+        }
+        else if (StringUtils.equals(typePrefix, "Decimal")) {
+            return Double.valueOf(value);
+        }
+        else if (StringUtils.equals(typePrefix, "Date")) {
+            return ISO8601.parse(value);
+        }
+        else {
+            throw new IllegalArgumentException("Unexpected type prefix: " + typePrefix);
+        }
+    }
+    
+    /**
+     * De-escape string value.
+     * @param value Escaped string value
+     * @param inArray In array
+     * @return De-escaped string value
+     */
+    private static String deescapeStringValue(final String value, final boolean inArray) {
+        String descapedValue = value;
+        if (inArray) {
+          descapedValue = StringUtils.replace(descapedValue, "\\,", ",");
+        }
+        else if (StringUtils.startsWith(descapedValue, "\\{") || StringUtils.startsWith(descapedValue, "\\[")) {
+            descapedValue = descapedValue.substring(1);
+        }
+        return StringUtils.replace(descapedValue, "\\\\", "\\");
+    }
+        
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java
index c9eddc5..8070e0d 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/JsonFileParser.java
@@ -33,12 +33,13 @@ import javax.json.JsonReader;
 import javax.json.JsonReaderFactory;
 import javax.json.JsonString;
 import javax.json.JsonValue;
+import javax.json.stream.JsonParsingException;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Parses JSON file that contains content fragments.
+ * Parses JSON files that contains content fragments.
  */
 class JsonFileParser {
     
@@ -71,13 +72,11 @@ class JsonFileParser {
      */
     public static Map<String,Object> parse(File file) {
         log.debug("Parse JSON content from {}", file.getPath());
-        try {
-            try (FileInputStream fis = new FileInputStream(file);
-                    JsonReader reader = JSON_READER_FACTORY.createReader(fis)) {
-                return toMap(reader.readObject());
-            }
+        try (FileInputStream fis = new FileInputStream(file);
+                JsonReader reader = JSON_READER_FACTORY.createReader(fis)) {
+            return toMap(reader.readObject());
         }
-        catch (IOException ex) {
+        catch (IOException | JsonParsingException ex) {
             log.warn("Error parsing JSON content from " + file.getPath(), ex);
             return null;
         }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
index 30012ef..591a85c 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
@@ -131,14 +131,14 @@ public class FileMonitorTest {
         List<ResourceChange> changes = resourceListener.getChanges();
         assertTrue(changes.isEmpty());
         
-        File folder3 = new File(tempDir, "folder3");
-        folder3.mkdir();
+        File folder99 = new File(tempDir, "folder99");
+        folder99.mkdir();
         
         Thread.sleep(250);
 
         assertEquals(2, changes.size());
         assertChange(changes, 0, "/fs-test", ChangeType.CHANGED);
-        assertChange(changes, 1, "/fs-test/folder3", ChangeType.ADDED);
+        assertChange(changes, 1, "/fs-test/folder99", ChangeType.ADDED);
     }
     
     @Test
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
index 403592a..1930a94 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
@@ -57,6 +57,7 @@ public class FilesFolderTest {
         assertFolder(fsroot, "folder1");
         assertFolder(fsroot, "folder1/folder11");
         assertFolder(fsroot, "folder2");
+        assertFolder(fsroot, "folder3");
     }
 
     @Test
@@ -66,12 +67,13 @@ public class FilesFolderTest {
         assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
         assertFile(fsroot, "folder2/content.json", null);
         assertFile(fsroot, "folder2/content/file2content.txt", "file2content");
+        assertFile(fsroot, "folder3/content.jcr.xml", null);
     }
 
     @Test
     public void testListChildren() {
         assertThat(root, ResourceMatchers.containsChildren("fs-test"));
-        assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2"));
+        assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2", "folder3"));
         assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("folder11", "file1a.txt", "file1b.txt"));
         assertThat(fsroot.getChild("folder2"), ResourceMatchers.hasChildren("folder21", "content.json"));
         assertFalse(fsroot.getChild("folder1/file1a.txt").listChildren().hasNext());
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java
index 1231090..9a220d9 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JcrMixedTest.java
@@ -66,8 +66,8 @@ public class JcrMixedTest {
         file1a.setProperty("prop2", 234L);
         // folder1/file1c.txt
         folder1.addNode("file1c.txt", "nt:file");
-        // folder3
-        fstest.addNode("folder3", "nt:folder");
+        // folder99
+        fstest.addNode("folder99", "nt:folder");
     }
 
     @Test
@@ -96,7 +96,7 @@ public class JcrMixedTest {
     @Test
     public void testListChildren() {
         assertThat(root, ResourceMatchers.containsChildren("fs-test"));
-        assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2", "folder3"));
+        assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2", "folder99"));
         assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("file1a.txt", "file1b.txt", "file1c.txt"));
     }
 
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
new file mode 100644
index 0000000..39f18d4
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
@@ -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;
+
+import static org.apache.sling.fsprovider.internal.TestUtils.assertFile;
+import static org.apache.sling.fsprovider.internal.TestUtils.assertFolder;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin;
+import org.apache.sling.hamcrest.ResourceMatchers;
+import org.apache.sling.testing.mock.sling.ResourceResolverType;
+import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Test access to files and folders from filesystem.
+ */
+public class JcrXmlContentTest {
+
+    private Resource root;
+    private Resource fsroot;
+
+    @Rule
+    public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
+        .plugin(new RegisterFsResourcePlugin("provider.jcrxml.content", true))
+        .build();
+
+    @Before
+    public void setUp() {
+        root = context.resourceResolver().getResource("/");
+        fsroot = context.resourceResolver().getResource("/fs-test");
+    }
+
+    @Test
+    public void testFolders() {
+        assertFolder(fsroot, "folder1");
+        assertFolder(fsroot, "folder1/folder11");
+        assertFolder(fsroot, "folder2");
+        assertFolder(fsroot, "folder3");
+    }
+
+    @Test
+    public void testFiles() {
+        assertFile(fsroot, "folder1/file1a.txt", "file1a");
+        assertFile(fsroot, "folder1/file1b.txt", "file1b");
+        assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
+        assertFile(fsroot, "folder2/content.json", null);
+        assertNull(fsroot.getChild("folder3/content.jcr.xml"));
+    }
+
+    @Test
+    public void testListChildren() {
+        assertThat(root, ResourceMatchers.containsChildren("fs-test"));
+        assertThat(fsroot, ResourceMatchers.hasChildren("folder1", "folder2"));
+        assertThat(fsroot.getChild("folder1"), ResourceMatchers.hasChildren("folder11", "file1a.txt", "file1b.txt"));
+        assertThat(fsroot.getChild("folder2"), ResourceMatchers.hasChildren("folder21", "content"));
+    }
+
+    @Test
+    public void testJsonContent_Root() {
+        Resource underTest = fsroot.getChild("folder3/content");
+        assertNotNull(underTest);
+        assertEquals("app:Page", underTest.getValueMap().get("jcr:primaryType", String.class));
+        assertEquals("app:Page", underTest.getResourceType());
+        assertThat(underTest, ResourceMatchers.hasChildren("jcr:content"));
+    }
+
+    @Test
+    public void testJsonContent_Level1() {
+        Resource underTest = fsroot.getChild("folder3/content/jcr:content");
+        assertNotNull(underTest);
+        assertEquals("app:PageContent", underTest.getValueMap().get("jcr:primaryType", String.class));
+        assertEquals("samples/sample-app/components/content/page/homepage", underTest.getResourceType());
+        assertThat(underTest, ResourceMatchers.hasChildren("teaserbar", "aside", "content"));
+    }
+
+    @Test
+    public void testJsonContent_Level3() {
+        Resource underTest = fsroot.getChild("folder3/content/jcr:content/content/contentheadline");
+        assertNotNull(underTest);
+        assertEquals("nt:unstructured", underTest.getValueMap().get("jcr:primaryType", String.class));
+        assertEquals("samples/sample-app/components/content/common/contentHeadline", underTest.getResourceType());
+        assertFalse(underTest.listChildren().hasNext());
+    }
+
+    @Test
+    public void testJsonContent_Datatypes() {
+        Resource underTest = fsroot.getChild("folder3/content/jcr:content");
+        ValueMap props = underTest.getValueMap();
+        
+        assertEquals("en", props.get("jcr:title", String.class));
+        assertEquals(true, props.get("includeAside", false));
+        assertEquals((Long)1234567890123L, props.get("longProp", Long.class));
+        assertEquals((Double)1.2345d, props.get("decimalProp", Double.class), 0.00001d);
+        
+        assertArrayEquals(new String[] { "aa", "bb", "cc" }, props.get("stringPropMulti", String[].class));
+        assertArrayEquals(new Long[] { 1234567890123L, 55L }, props.get("longPropMulti", Long[].class));
+    }
+
+    @Test
+    public void testJsonContent_InvalidPath() {
+        Resource underTest = fsroot.getChild("folder2/content/jcr:content/xyz");
+        assertNull(underTest);
+    }
+
+    @Test
+    public void testJcrMixedContent() throws RepositoryException {
+        // prepare mixed JCR content
+        Node node = root.adaptTo(Node.class);
+        Node fstest = node.addNode("fs-test", "nt:folder");
+        fstest.addNode("folder99", "nt:folder");
+
+        assertNull(fsroot.getChild("folder99"));
+    }
+
+    @Test
+    public void testFolder3ChildNodes() throws RepositoryException {
+        Resource folder3 = fsroot.getChild("folder3");
+        List<Resource> children = ImmutableList.copyOf(folder3.listChildren());
+        
+        assertEquals(2, children.size());
+        Resource child1 = children.get(0);
+        assertEquals("content", child1.getName());
+        assertEquals("app:Page", child1.getResourceType());
+        assertEquals("app:Page", child1.getValueMap().get("jcr:primaryType", String.class));
+
+        Resource child2 = children.get(1);
+        assertEquals("folder31", child2.getName());
+        assertEquals("nt:folder", child2.getValueMap().get("jcr:primaryType", String.class));
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
index 34240a8..a78d601 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
@@ -87,6 +87,7 @@ public class JsonContentTest {
         assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
         assertNull(fsroot.getChild("folder2/content.json"));
         assertFile(fsroot, "folder2/content/file2content.txt", "file2content");
+        assertFile(fsroot, "folder3/content.jcr.xml", null);
     }
 
     @Test
@@ -229,9 +230,9 @@ public class JsonContentTest {
         // prepare mixed JCR content
         Node node = root.adaptTo(Node.class);
         Node fstest = node.addNode("fs-test", "nt:folder");
-        fstest.addNode("folder3", "nt:folder");
+        fstest.addNode("folder99", "nt:folder");
 
-        assertNull(fsroot.getChild("folder3"));
+        assertNull(fsroot.getChild("folder99"));
     }
 
     @Test
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java
index 0bc14a4..290ea21 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserTest.java
@@ -41,7 +41,24 @@ public class ContentFileParserTest {
 
     @Test
     public void testParseInvalidJson() {
-        File file = new File("src/test/resources/fs-test/folder1/file1a.txt");
+        File file = new File("src/test/resources/invalid-test/invalid.json");
+        Map<String,Object> content = ContentFileParser.parse(file);
+        assertNull(content);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testParseJcrXml() {
+        File file = new File("src/test/resources/fs-test/folder3/content.jcr.xml");
+        Map<String,Object> content = ContentFileParser.parse(file);
+        assertNotNull(content);
+        assertEquals("app:Page", content.get("jcr:primaryType"));
+        assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
+    }
+
+    @Test
+    public void testParseInvalidJcrXml() {
+        File file = new File("src/test/resources/invalid-test/invalid.jcr.xml");
         Map<String,Object> content = ContentFileParser.parse(file);
         assertNull(content);
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParserTest.java
similarity index 69%
copy from src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
copy to src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParserTest.java
index 9d995e6..7cccbcf 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlFileParserTest.java
@@ -18,18 +18,17 @@
  */
 package org.apache.sling.fsprovider.internal.parser;
 
-/**
- * Content file types.
- */
-public final class ContentFileTypes {
-    
-    /**
-     * JSON content files.
-     */
-    public static final String JSON_SUFFIX = ".json";
+import static org.junit.Assert.assertEquals;
+
+import org.apache.jackrabbit.util.ISO9075;
+import org.junit.Test;
 
-    private ContentFileTypes() {
-        // static methods only
+public class JcrXmlFileParserTest {
+
+    @Test
+    public void testDecodeName() {
+        assertEquals("jcr:title", JcrXmlFileParser.decodeName("jcr:" + ISO9075.encode("title")));
+        assertEquals("sling:123", JcrXmlFileParser.decodeName("sling:" + ISO9075.encode("123")));
     }
-    
+
 }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverterTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverterTest.java
new file mode 100644
index 0000000..15fe989
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/JcrXmlValueConverterTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.parser;
+
+import static org.apache.sling.fsprovider.internal.parser.JcrXmlValueConverter.parseValue;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Calendar;
+
+import org.junit.Test;
+
+public class JcrXmlValueConverterTest {
+
+    @Test
+    public void testNull() {
+        assertNull(parseValue(null));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalid() {
+        parseValue("{InvalidType}xyz");
+    }
+
+    @Test
+    public void testString() {
+        assertEquals("myString", parseValue("myString"));
+        assertEquals("prop", "myString [ ] { } \\ ,", parseValue("myString [ ] { } \\\\ ,"));
+        assertEquals("{myString}", parseValue("\\{myString}"));
+        assertEquals("aaa{myString}", parseValue("aaa{myString}"));
+        assertEquals("[myString]", parseValue("\\[myString]"));
+        assertEquals("aaa[myString]", parseValue("aaa[myString]"));
+    }
+
+    @Test
+    public void testStringArray() {
+        assertArrayEquals(new Object[] { "myString1", "myString2" }, (Object[]) parseValue("[myString1,myString2]"));
+        assertArrayEquals(new Object[] { "myString1,[]\\äöü߀", "myString2", "myString3 [ ] { } \\ ,", "", "[myString5]", "{myString6}" },
+                (Object[]) parseValue("[myString1\\,[]\\\\äöü߀,myString2,myString3 [ ] { } \\\\ \\,,,[myString5],{myString6}]"));
+    }
+
+    @Test
+    public void testBoolean() {
+        assertEquals(true, parseValue("{Boolean}true"));
+        assertEquals(false, parseValue("{Boolean}false"));
+    }
+
+    @Test
+    public void testBooleanArray() {
+        assertArrayEquals(new Object[] { true, false }, (Object[]) parseValue("{Boolean}[true,false]"));
+    }
+
+    @Test
+    public void testLong() {
+        assertEquals(1L, parseValue("{Long}1"));
+        assertEquals(10000000000L, parseValue("{Long}10000000000"));
+    }
+
+    @Test
+    public void testLongArray() {
+        assertArrayEquals(new Object[] { 1L, 2L }, (Object[]) parseValue("{Long}[1,2]"));
+        assertArrayEquals(new Object[] { 10000000000L, 20000000000L }, (Object[]) parseValue("{Long}[10000000000,20000000000]"));
+    }
+
+    @Test
+    public void testDouble() {
+        assertEquals(1.234d, parseValue("{Decimal}1.234"));
+    }
+
+    @Test
+    public void testDoubleArray() {
+        assertArrayEquals(new Object[] { 1.234d, 2.345d }, (Object[]) parseValue("{Decimal}[1.234,2.345]"));
+    }
+
+    @Test
+    public void testCalendar() {
+        Calendar value = (Calendar)parseValue("{Date}2010-09-05T15:10:20.000Z");
+        assertEquals(2010, value.get(Calendar.YEAR));
+        assertEquals(8, value.get(Calendar.MONTH));
+        assertEquals(5, value.get(Calendar.DAY_OF_MONTH));
+    }
+
+    @Test
+    public void testStringArrayRepPrivileges() {
+        assertArrayEquals(new Object[] { "rep:write", "crx:replicate", "jcr:read" }, (Object[]) parseValue("{Name}[rep:write,crx:replicate,jcr:read]"));
+    }
+
+}
diff --git a/src/test/resources/fs-test/folder2/content.json b/src/test/resources/fs-test/folder2/content.json
index f35baf8..b0fc78d 100644
--- a/src/test/resources/fs-test/folder2/content.json
+++ b/src/test/resources/fs-test/folder2/content.json
@@ -8,7 +8,7 @@
     "jcr:primaryType": "app:PageContent",  /* Comment example */
     "jcr:createdBy": "admin",
     "jcr:title": "English",
-    "app:template": "/apps/sample/templates/homepage",
+    "app:template": "sample/templates/homepage",
     "jcr:created": "Thu Aug 07 2014 16:32:59 GMT+0200",
     "app:lastModified": "Tue Apr 22 2014 15:11:24 GMT+0200",
     "dateISO8601String": "2014-04-22T15:11:24.000+02:00",
@@ -194,7 +194,7 @@
       "subtitle": "Contains the toolbar",
       "jcr:createdBy": "admin",
       "jcr:title": "Toolbar",
-      "app:template": "/apps/sample/templates/contentpage",
+      "app:template": "sample/templates/contentpage",
       "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
       "app:lastModified": "Wed Aug 25 2010 22:51:02 GMT+0200",
       "hideInNav": "true",
@@ -222,7 +222,7 @@
         "jcr:mixinTypes": ["type1","type2"],
         "jcr:createdBy": "admin",
         "jcr:title": "Profiles",
-        "app:template": "/apps/sample/templates/contentpage",
+        "app:template": "sample/templates/contentpage",
         "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
         "app:lastModified": "Thu Nov 05 2009 20:27:13 GMT+0100",
         "hideInNav": true,
diff --git a/src/test/resources/fs-test/folder3/content.jcr.xml b/src/test/resources/fs-test/folder3/content.jcr.xml
new file mode 100644
index 0000000..da111ee
--- /dev/null
+++ b/src/test/resources/fs-test/folder3/content.jcr.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:app="http://sample.com/jcr/app/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+    jcr:primaryType="app:Page">
+  <jcr:content
+      jcr:primaryType="app:PageContent"
+      jcr:title="en"
+      sling:resourceType="samples/sample-app/components/content/page/homepage"
+      includeAside="{Boolean}true"
+      includeAsideBar="{Boolean}true"
+      includeTeaserBar="{Boolean}true"
+      includeTeaserbar="{Boolean}true"
+      inheritAside="{Boolean}false"
+      inheritTeaserbar="{Boolean}false"
+      longProp="{Long}1234567890123"
+      decimalProp="{Decimal}1.2345"
+      longPropMulti="{Long}[1234567890123,55]"
+      stringPropMulti="[aa,bb,cc]"
+      navTitle="HOME"
+      pageTitle="Sample Site">
+    <teaserbar
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarParsys">
+      <teaserbaritem
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarItem"
+          linkContentRef="/content/samples/en/conference"
+          linkMediaDownload="{Boolean}false"
+          linkTitle="This should help you with your decision"
+          linkType="internal"
+          linkWindowFeatures="default"
+          linkWindowTarget="_self"
+          mediaRef="/content/dam/samples/content/user.png"
+          teaserContent="Still not convinced to attend? Need persuasion? Facts for your boss?"
+          title="Why to attend" />
+      <teaserbaritem_0
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarItem"
+          linkContentRef="/content/samples/en/venue"
+          linkMediaDownload="{Boolean}false"
+          linkTitle="More information"
+          linkType="internal"
+          linkWindowFeatures="default"
+          linkWindowTarget="_self"
+          mediaRef="/content/dam/samples/content/location.png"
+          teaserContent="Take a look at the new venue for 2013. The Kulturbrauerei in the Prenzlauer Berg district."
+          title="Location" />
+      <teaserbaritem_1
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarItem"
+          linkContentRef="/content/samples/en/conference/call-for-papers"
+          linkMediaDownload="{Boolean}false"
+          linkTitle="Submit your proposal here"
+          linkType="internal"
+          linkWindowFeatures="default"
+          linkWindowTarget="_self"
+          mediaRef="/content/dam/samples/content/talk.png"
+          teaserContent="If you have insight and experiences with Apache Sling and want to share them? We are actually asking for your participation!"
+          title="Want to share?" />
+      <teaserbaritem_2
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarItem"
+          linkContentRef="/content/samples/en/archive"
+          linkMediaDownload="{Boolean}false"
+          linkTitle="Dive into the archive"
+          linkType="internal"
+          linkWindowFeatures="default"
+          linkWindowTarget="_self"
+          mediaRef="/content/dam/samples/content/archive.png"
+          teaserContent="adaptTo() is not a new event. Take a look at what was said and done previously."
+          title="Take a look back" />
+    </teaserbar>
+    <aside
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="samples/sample-app/components/content/aside/asideParsys">
+      <asidesponsorteaser
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaser"
+          title="Sponsors">
+        <images
+            jcr:primaryType="nt:unstructured"
+            sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaserParsys">
+          <asidesponsorteaserit_0
+              jcr:primaryType="nt:unstructured"
+              sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaserItem"
+              imageHeight="41"
+              imageWidth="200"
+              linkExternalRef="http://www.pro-vision.de"
+              linkType="external"
+              linkWindowFeatures="default"
+              linkWindowTarget="_blank"
+              mediaRef="/content/dam/samples/content/provision-logo.png" />
+        </images>
+      </asidesponsorteaser>
+      <asidesocialteaser
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/aside/asideSocialTeaser"
+          title="Follow us">
+        <images
+            jcr:primaryType="nt:unstructured"
+            sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaserParsys">
+          <asidesocialteaserite
+              jcr:primaryType="nt:unstructured"
+              sling:resourceType="samples/sample-app/components/content/aside/asideSocialTeaserItem"
+              linkExternalRef="http://twitter.com/adaptto"
+              linkMediaDownload="{Boolean}false"
+              linkTitle="@adaptTo"
+              linkType="external"
+              linkWindowFeatures="default"
+              linkWindowTarget="_blank"
+              mediaRef="/content/dam/samples/content/twitter-icon.png"
+              title="on twitter" />
+        </images>
+      </asidesocialteaser>
+    </aside>
+    <content
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="sample/wcm/parsys/components/parsys">
+      <contentrichtext
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/common/contentRichText"
+          text="&lt;p&gt;adaptTo() is a meetup in Berlin focused on Apache Sling including Apache Jackrabbit and Apache Felix and is addressed to all using this stack or parts of it.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a data=&quot;{&amp;quot;linkType&amp;quot;:&amp;quot;internal&amp;quot;,&amp;quot;linkContentRef&amp;quot;:&amp;quot;/content/samples/handler/en/conference&amp;quot;,&amp;quot;linkWindowTarget&amp;quot;:&amp;quot;_self&amp;quot;,&amp;quot;linkWindowFeatures&amp;quot;:&amp;quot;defa [...]
+      <contentheadline
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/common/contentHeadline"
+          headline="Extended Call for Papers"
+          smaller="{Boolean}true" />
+      <contentrichtext_0
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/common/contentRichText"
+          text="&lt;p&gt;Although we got some great submissions for adaptTo() 2013, we still have some slots for further sessions. Therefore we extend the timeslot for submissions to the call for papers and for feedback by two weeks. This means you still can submit you submissions till 06.05.2013. We're looking forward to get more of your great talks.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a data=&quot;{&amp;quot;linkType&amp;quot;:&amp;quot;internal&amp;quot;,&amp;quot;linkContentRef&amp;quot;:&amp [...]
+    </content>
+    <stage
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="sample/wcm/parsys/components/parsys">
+      <stageheader
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/stage/stageheader"
+          linkMediaDownload="{Boolean}false"
+          linkType="internal"
+          linkWindowFeatures="default"
+          linkWindowTarget="_self"
+          mediaRef="/content/dam/samples/content/stageheader-outside2.jpg"
+          subtitle="23.–25. September 2013&#xA;Kulturbrauerei Berlin"
+          title="adaptTo() 2013">
+        <links
+            jcr:primaryType="nt:unstructured"
+            sling:resourceType="samples/sample-app/components/content/stage/stageheaderParsys">
+          <stageheaderlinkitem
+              jcr:primaryType="nt:unstructured"
+              sling:resourceType="samples/sample-app/components/content/stage/stageheaderLinkItem"
+              linkContentRef="/content/samples/en/tickets"
+              linkMediaDownload="{Boolean}false"
+              linkTitle="Get tickets now"
+              linkType="internal"
+              linkWindowFeatures="default"
+              linkWindowTarget="_self" />
+          <stageheaderlinkitem_0
+              jcr:primaryType="nt:unstructured"
+              sling:resourceType="samples/sample-app/components/content/stage/stageheaderLinkItem"
+              linkContentRef="/content/samples/en/conference/call-for-papers"
+              linkMediaDownload="{Boolean}false"
+              linkTitle="Submit paper"
+              linkType="internal"
+              linkWindowFeatures="default"
+              linkWindowTarget="_self" />
+        </links>
+      </stageheader>
+    </stage>
+    <image
+        jcr:primaryType="nt:unstructured" />
+  </jcr:content>
+  <tools />
+  <conference />
+</jcr:root>
diff --git a/src/test/resources/fs-test/folder3/content/content2.jcr.xml b/src/test/resources/fs-test/folder3/content/content2.jcr.xml
new file mode 100644
index 0000000..3964ca4
--- /dev/null
+++ b/src/test/resources/fs-test/folder3/content/content2.jcr.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:app="http://sample.com/jcr/app/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+    jcr:primaryType="app:Page">
+  <jcr:content
+      app:template="samples/sample-app/templates/admin/structureElement"
+      jcr:primaryType="app:PageContent"
+      jcr:title="tools"
+      sling:resourceType="samples/sample-app/components/admin/page/structureElement"
+      hideInNav="{Boolean}true" />
+  <navigation />
+</jcr:root>
diff --git a/src/test/resources/fs-test/folder3/folder31/file31a.txt b/src/test/resources/fs-test/folder3/folder31/file31a.txt
new file mode 100644
index 0000000..3d5becc
--- /dev/null
+++ b/src/test/resources/fs-test/folder3/folder31/file31a.txt
@@ -0,0 +1 @@
+file21a
\ No newline at end of file
diff --git a/src/test/resources/invalid-test/invalid.jcr.xml b/src/test/resources/invalid-test/invalid.jcr.xml
new file mode 100644
index 0000000..7df8a70
--- /dev/null
+++ b/src/test/resources/invalid-test/invalid.jcr.xml
@@ -0,0 +1 @@
+This is invalid xml.
diff --git a/src/test/resources/invalid-test/invalid.json b/src/test/resources/invalid-test/invalid.json
new file mode 100644
index 0000000..59fc86c
--- /dev/null
+++ b/src/test/resources/invalid-test/invalid.json
@@ -0,0 +1 @@
+This is invalid json.
diff --git a/src/test/resources/vaultfs-test/META-INF/vault/filter.xml b/src/test/resources/vaultfs-test/META-INF/vault/filter.xml
new file mode 100644
index 0000000..20be2d8
--- /dev/null
+++ b/src/test/resources/vaultfs-test/META-INF/vault/filter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<workspaceFilter version="1.0">
+    <filter root="/content/dam/talk.png" />
+    <filter root="/content/samples" />
+</workspaceFilter>
diff --git a/src/test/resources/vaultfs-test/META-INF/vault/settings.xml b/src/test/resources/vaultfs-test/META-INF/vault/settings.xml
new file mode 100644
index 0000000..3f8e13a
--- /dev/null
+++ b/src/test/resources/vaultfs-test/META-INF/vault/settings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<vault version="1.0">
+  <ignore name=".svn"/>
+  <ignore name=".DS_Store"/>
+</vault>
diff --git a/src/test/resources/vaultfs-test/jcr_root/.content.xml b/src/test/resources/vaultfs-test/jcr_root/.content.xml
new file mode 100644
index 0000000..b264022
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/.content.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+    jcr:mixinTypes="[rep:AccessControllable,rep:RepoAccessControllable]"
+    jcr:primaryType="rep:root"
+    sling:resourceType="sling:redirect"
+    sling:target="/index.html" />
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/.content.xml
new file mode 100644
index 0000000..115c72c
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/.content.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:rep="internal" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+    jcr:mixinTypes="[mix:lockable,rep:AccessControllable,sling:Redirect]"
+    jcr:primaryType="sling:OrderedFolder"
+    jcr:title="Content Root"
+    sling:resourceType="sling:redirect"
+    sling:target="/geohome">
+  <dam />
+  <samples />
+</jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/dam/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/dam/.content.xml
new file mode 100644
index 0000000..64f25b1
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/dam/.content.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:rep="internal" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+    jcr:mixinTypes="[mix:lockable,rep:AccessControllable]"
+    jcr:primaryType="sling:OrderedFolder">
+  <talk.png />
+</jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml
new file mode 100644
index 0000000..4f8312a
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/.content.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:app="http://sample.com/app/1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
+    jcr:primaryType="app:Asset">
+  <jcr:content
+      jcr:primaryType="app:AssetContent">
+    <metadata
+        app:Bitsperpixel="{Long}4"
+        app:extracted="{Date}2015-09-19T14:33:36.078+02:00"
+        app:Fileformat="PNG"
+        app:MIMEtype="image/png"
+        app:Numberofimages="{Long}1"
+        app:Numberoftextualcomments="{Long}3"
+        app:Physicalheightindpi="{Long}72"
+        app:Physicalheightininches="{Decimal}3.750854253768921"
+        app:Physicalwidthindpi="{Long}72"
+        app:Physicalwidthininches="{Decimal}6.668185710906982"
+        app:Progressive="no"
+        app:sha1="29e02b493473c2beaf851002b67b6f1b700be978"
+        app:size="{Long}6652"
+        app:writebackEnable="False"
+        dc:format="image/png"
+        dc:modified="{Date}2014-09-19T21:20:26.812+02:00"
+        jcr:primaryType="nt:unstructured"
+        tiff:ImageLength="{Long}270"
+        tiff:ImageWidth="{Long}480"
+        writebackEnable="{Boolean}true" />
+  </jcr:content>
+</jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original.dir/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original.dir/.content.xml
new file mode 100644
index 0000000..1813b25
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/dam/talk.png/_jcr_content/renditions/original.dir/.content.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
+    jcr:primaryType="nt:file">
+  <jcr:content
+      jcr:mimeType="image/png"
+      jcr:primaryType="nt:resource" />
+</jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/samples/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/samples/.content.xml
new file mode 100644
index 0000000..c96141e
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/samples/.content.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+    jcr:primaryType="sling:OrderedFolder">
+  <en />
+</jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml
new file mode 100644
index 0000000..83b8626
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/.content.xml
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:app="http://sample.com/jcr/app/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+    jcr:primaryType="app:Page">
+  <jcr:content
+      app:cloudserviceconfigs="[[]]"
+      app:deviceGroups="[/etc/mobile/groups/responsive]"
+      app:template="samples/sample-app/templates/content/homepage"
+      jcr:primaryType="app:PageContent"
+      jcr:title="en"
+      sling:resourceType="samples/sample-app/components/content/page/homepage"
+      includeAside="{Boolean}true"
+      includeAsideBar="{Boolean}true"
+      includeTeaserBar="{Boolean}true"
+      includeTeaserbar="{Boolean}true"
+      inheritAside="{Boolean}false"
+      inheritTeaserbar="{Boolean}false"
+      navTitle="HOME"
+      pageTitle="Sample Site">
+    <teaserbar
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarParsys">
+      <teaserbaritem
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarItem"
+          linkContentRef="/content/samples/en/conference"
+          linkMediaDownload="{Boolean}false"
+          linkTitle="This should help you with your decision"
+          linkType="internal"
+          linkWindowFeatures="default"
+          linkWindowTarget="_self"
+          mediaRef="/content/dam/samples/content/user.png"
+          teaserContent="Still not convinced to attend? Need persuasion? Facts for your boss?"
+          title="Why to attend" />
+      <teaserbaritem_0
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarItem"
+          linkContentRef="/content/samples/en/venue"
+          linkMediaDownload="{Boolean}false"
+          linkTitle="More information"
+          linkType="internal"
+          linkWindowFeatures="default"
+          linkWindowTarget="_self"
+          mediaRef="/content/dam/samples/content/location.png"
+          teaserContent="Take a look at the new venue for 2013. The Kulturbrauerei in the Prenzlauer Berg district."
+          title="Location" />
+      <teaserbaritem_1
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarItem"
+          linkContentRef="/content/samples/en/conference/call-for-papers"
+          linkMediaDownload="{Boolean}false"
+          linkTitle="Submit your proposal here"
+          linkType="internal"
+          linkWindowFeatures="default"
+          linkWindowTarget="_self"
+          mediaRef="/content/dam/samples/content/talk.png"
+          teaserContent="If you have insight and experiences with Apache Sling and want to share them? We are actually asking for your participation!"
+          title="Want to share?" />
+      <teaserbaritem_2
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/teaserbar/teaserbarItem"
+          linkContentRef="/content/samples/en/archive"
+          linkMediaDownload="{Boolean}false"
+          linkTitle="Dive into the archive"
+          linkType="internal"
+          linkWindowFeatures="default"
+          linkWindowTarget="_self"
+          mediaRef="/content/dam/samples/content/archive.png"
+          teaserContent="adaptTo() is not a new event. Take a look at what was said and done previously."
+          title="Take a look back" />
+    </teaserbar>
+    <aside
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="samples/sample-app/components/content/aside/asideParsys">
+      <asidesponsorteaser
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaser"
+          title="Sponsors">
+        <images
+            jcr:primaryType="nt:unstructured"
+            sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaserParsys">
+          <asidesponsorteaserit_0
+              jcr:primaryType="nt:unstructured"
+              sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaserItem"
+              imageHeight="41"
+              imageWidth="200"
+              linkExternalRef="http://www.pro-vision.de"
+              linkType="external"
+              linkWindowFeatures="default"
+              linkWindowTarget="_blank"
+              mediaRef="/content/dam/samples/content/provision-logo.png" />
+        </images>
+      </asidesponsorteaser>
+      <asidesocialteaser
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/aside/asideSocialTeaser"
+          title="Follow us">
+        <images
+            jcr:primaryType="nt:unstructured"
+            sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaserParsys">
+          <asidesocialteaserite
+              jcr:primaryType="nt:unstructured"
+              sling:resourceType="samples/sample-app/components/content/aside/asideSocialTeaserItem"
+              linkExternalRef="http://twitter.com/adaptto"
+              linkMediaDownload="{Boolean}false"
+              linkTitle="@adaptTo"
+              linkType="external"
+              linkWindowFeatures="default"
+              linkWindowTarget="_blank"
+              mediaRef="/content/dam/samples/content/twitter-icon.png"
+              title="on twitter" />
+        </images>
+      </asidesocialteaser>
+    </aside>
+    <content
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="sample/wcm/parsys/components/parsys">
+      <contentrichtext
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/common/contentRichText"
+          text="&lt;p&gt;adaptTo() is a meetup in Berlin focused on Apache Sling including Apache Jackrabbit and Apache Felix and is addressed to all using this stack or parts of it.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a data=&quot;{&amp;quot;linkType&amp;quot;:&amp;quot;internal&amp;quot;,&amp;quot;linkContentRef&amp;quot;:&amp;quot;/content/samples/handler/en/conference&amp;quot;,&amp;quot;linkWindowTarget&amp;quot;:&amp;quot;_self&amp;quot;,&amp;quot;linkWindowFeatures&amp;quot;:&amp;quot;defa [...]
+      <contentheadline
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/common/contentHeadline"
+          headline="Extended Call for Papers"
+          smaller="{Boolean}true" />
+      <contentrichtext_0
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/common/contentRichText"
+          text="&lt;p&gt;Although we got some great submissions for adaptTo() 2013, we still have some slots for further sessions. Therefore we extend the timeslot for submissions to the call for papers and for feedback by two weeks. This means you still can submit you submissions till 06.05.2013. We're looking forward to get more of your great talks.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a data=&quot;{&amp;quot;linkType&amp;quot;:&amp;quot;internal&amp;quot;,&amp;quot;linkContentRef&amp;quot;:&amp [...]
+    </content>
+    <stage
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="sample/wcm/parsys/components/parsys">
+      <stageheader
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/stage/stageheader"
+          linkMediaDownload="{Boolean}false"
+          linkType="internal"
+          linkWindowFeatures="default"
+          linkWindowTarget="_self"
+          mediaRef="/content/dam/samples/content/stageheader-outside2.jpg"
+          subtitle="23.–25. September 2013&#xA;Kulturbrauerei Berlin"
+          title="adaptTo() 2013">
+        <links
+            jcr:primaryType="nt:unstructured"
+            sling:resourceType="samples/sample-app/components/content/stage/stageheaderParsys">
+          <stageheaderlinkitem
+              jcr:primaryType="nt:unstructured"
+              sling:resourceType="samples/sample-app/components/content/stage/stageheaderLinkItem"
+              linkContentRef="/content/samples/en/tickets"
+              linkMediaDownload="{Boolean}false"
+              linkTitle="Get tickets now"
+              linkType="internal"
+              linkWindowFeatures="default"
+              linkWindowTarget="_self" />
+          <stageheaderlinkitem_0
+              jcr:primaryType="nt:unstructured"
+              sling:resourceType="samples/sample-app/components/content/stage/stageheaderLinkItem"
+              linkContentRef="/content/samples/en/conference/call-for-papers"
+              linkMediaDownload="{Boolean}false"
+              linkTitle="Submit paper"
+              linkType="internal"
+              linkWindowFeatures="default"
+              linkWindowTarget="_self" />
+        </links>
+      </stageheader>
+    </stage>
+    <image
+        jcr:primaryType="nt:unstructured" />
+  </jcr:content>
+  <tools />
+  <conference />
+</jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/samples/en/conference/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/conference/.content.xml
new file mode 100644
index 0000000..2c2ef86
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/conference/.content.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:app="http://sample.com/jcr/app/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+    jcr:primaryType="app:Page">
+  <jcr:content
+      app:template="samples/sample-app/templates/content/content"
+      jcr:primaryType="app:PageContent"
+      jcr:title="Conference"
+      sling:resourceType="samples/sample-app/components/content/page/content"
+      includeAside="{Boolean}true"
+      includeAsideBar="{Boolean}true"
+      includeTeaserBar="{Boolean}true"
+      includeTeaserbar="{Boolean}false"
+      inheritAside="{Boolean}false"
+      inheritTeaserBar="{Boolean}true"
+      inheritTeaserbar="{Boolean}false"
+      navTitle="CONFERENCE">
+    <content
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="sample/wcm/parsys/components/parsys">
+      <contentheadline
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/common/contentHeadline"
+          headline="About adaptTo() 2013" />
+      <contentrichtext
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/common/contentRichText"
+          text="&lt;p&gt;adaptTo() is a technical meetup focused on the technical stack of &lt;a data=&quot;{&amp;quot;linkType&amp;quot;:&amp;quot;external&amp;quot;,&amp;quot;linkExternalRef&amp;quot;:&amp;quot;http://sling.apache.org/&amp;quot;,&amp;quot;linkWindowTarget&amp;quot;:&amp;quot;_blank&amp;quot;,&amp;quot;linkWindowFeatures&amp;quot;:&amp;quot;default&amp;quot;}&quot; href=&quot;#&quot;&gt;Apache Sling&lt;/a&gt; including &lt;a data=&quot;{&amp;quot;linkType&amp;quot;:&amp [...]
+    </content>
+    <aside
+        jcr:primaryType="nt:unstructured"
+        sling:resourceType="samples/sample-app/components/content/aside/asideParsys">
+      <asideteaser
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/aside/asideTeaser"
+          teaserContent="&lt;p&gt;Submit your paper to attend the conference as speaker&lt;/p&gt;&#xA;"
+          title="Call for Papers">
+        <links
+            jcr:primaryType="nt:unstructured"
+            sling:resourceType="samples/sample-app/components/framework/parsys/linkListParsys">
+          <linkItem
+              jcr:primaryType="nt:unstructured"
+              sling:resourceType="samples/sample-app/components/framework/item/linkItem"
+              linkContentRef="/content/samples/en/conference/call-for-papers"
+              linkMediaDownload="{Boolean}false"
+              linkTitle="Call for Papers"
+              linkType="internal"
+              linkWindowFeatures="default"
+              linkWindowTarget="_self" />
+        </links>
+      </asideteaser>
+      <asidesponsorteaser
+          jcr:primaryType="nt:unstructured"
+          sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaser"
+          title="Sponsors">
+        <images
+            jcr:primaryType="nt:unstructured"
+            sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaserParsys">
+          <asidesponsorteaserit_0
+              jcr:primaryType="nt:unstructured"
+              sling:resourceType="samples/sample-app/components/content/aside/asideSponsorTeaserItem"
+              linkExternalRef="http://www.pro-vision.de"
+              linkMediaDownload="{Boolean}false"
+              linkType="external"
+              linkWindowFeatures="default"
+              linkWindowTarget="_blank"
+              mediaRef="/content/dam/samples/content/provision-logo.png" />
+        </images>
+      </asidesponsorteaser>
+    </aside>
+  </jcr:content>
+</jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/.content.xml
new file mode 100644
index 0000000..3964ca4
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/.content.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:app="http://sample.com/jcr/app/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+    jcr:primaryType="app:Page">
+  <jcr:content
+      app:template="samples/sample-app/templates/admin/structureElement"
+      jcr:primaryType="app:PageContent"
+      jcr:title="tools"
+      sling:resourceType="samples/sample-app/components/admin/page/structureElement"
+      hideInNav="{Boolean}true" />
+  <navigation />
+</jcr:root>
diff --git a/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/navigation/.content.xml b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/navigation/.content.xml
new file mode 100644
index 0000000..676d8b9
--- /dev/null
+++ b/src/test/resources/vaultfs-test/jcr_root/content/samples/en/tools/navigation/.content.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:app="http://sample.com/jcr/app/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
+    jcr:primaryType="app:Page">
+  <jcr:content
+      app:template="samples/sample-app/templates/admin/structureElement"
+      jcr:primaryType="app:PageContent"
+      jcr:title="navigation"
+      sling:resourceType="samples/sample-app/components/admin/page/structureElement" />
+</jcr:root>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 10/29: SLING-6440 simplify FileMonitor

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 299edbd8103826d59a3ea32c2ce2e5ee45bea01d
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Tue Feb 28 16:21:54 2017 +0000

    SLING-6440 simplify FileMonitor
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1784773 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/fsprovider/internal/FileMonitor.java     | 32 +++++++++-------------
 .../sling/fsprovider/internal/FileMonitorTest.java | 12 ++------
 2 files changed, 16 insertions(+), 28 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
index 05d01b7..7d77482 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -20,10 +20,8 @@ package org.apache.sling.fsprovider.internal;
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
 
@@ -208,17 +206,22 @@ public final class FileMonitor extends TimerTask {
             log.debug("Detected change for resource {} : {}", monitorable.path, changeType);
         }
 
+        List<ResourceChange> changes = null;
         for (final ObserverConfiguration config : reporter.getObserverConfigurations()) {
-            if ( config.matches(monitorable.path) ) {
-                List<ResourceChange> changes = collectResourceChanges(monitorable, changeType);
+            if (config.matches(monitorable.path)) {
+                if (changes == null) {
+                    changes = collectResourceChanges(monitorable, changeType);
+                }
                 if (log.isTraceEnabled()) {
                     for (ResourceChange change : changes) {
                         log.debug("Send change for resource {}: {} to {}", change.getPath(), change.getType(), config);
                     }
                 }
-                reporter.reportChanges(changes, false);
             }
         }
+        if (changes != null) {
+            reporter.reportChanges(changes, false);
+        }
     }
     
     @SuppressWarnings("unchecked")
@@ -230,7 +233,7 @@ public final class FileMonitor extends TimerTask {
                 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(ChangeType.REMOVED, content, monitorable.path));
+                changes.add(buildContentResourceChange(ChangeType.REMOVED,  monitorable.path));
                 addContentResourceChanges(changes, ChangeType.ADDED, content, monitorable.path);
             }
             else {
@@ -238,14 +241,14 @@ public final class FileMonitor extends TimerTask {
             }
         }
         else {
-            changes.add(new ResourceChange(changeType, monitorable.path, false, null, null, null));
+            changes.add(buildContentResourceChange(changeType, monitorable.path));
         }
         return changes;
     }
     @SuppressWarnings("unchecked")
     private void addContentResourceChanges(final List<ResourceChange> changes, final ChangeType changeType,
             final Map<String,Object> content, final String path) {
-        changes.add(buildContentResourceChange(changeType, content, path));
+        changes.add(buildContentResourceChange(changeType,  path));
         if (content != null) {
             for (Map.Entry<String,Object> entry : content.entrySet()) {
                 if (entry.getValue() instanceof Map) {
@@ -255,17 +258,8 @@ public final class FileMonitor extends TimerTask {
             }
         }
     }
-    private ResourceChange buildContentResourceChange(final ChangeType changeType, final Map<String,Object> content, final String path) {
-        Set<String> addedPropertyNames = null;
-        if (content != null && changeType == ChangeType.ADDED) {
-            addedPropertyNames = new HashSet<>();
-            for (Map.Entry<String,Object> entry : content.entrySet()) {
-                if (!(entry.getValue() instanceof Map)) {
-                    addedPropertyNames.add(entry.getKey());
-                }
-            }
-        }
-        return new ResourceChange(changeType, path, false, addedPropertyNames, null, null);
+    private ResourceChange buildContentResourceChange(final ChangeType changeType, final String path) {
+        return new ResourceChange(changeType, path, false, null, null, null);
     }
 
     /**
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
index f9cc25c..020405d 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
@@ -38,8 +38,6 @@ import org.apache.sling.testing.mock.sling.junit.SlingContextCallback;
 import org.junit.Rule;
 import org.junit.Test;
 
-import com.google.common.collect.ImmutableSet;
-
 /**
  * Test events when changing filesystem content.
  */
@@ -185,8 +183,8 @@ public class FileMonitorTest {
 
         assertEquals(3, changes.size());
         assertChange(changes, "/fs-test/folder1", ChangeType.CHANGED);
-        assertChange(changes, "/fs-test/folder1/file1c", ChangeType.ADDED, "prop1");
-        assertChange(changes, "/fs-test/folder1/file1c/child1", ChangeType.ADDED, "prop2");
+        assertChange(changes, "/fs-test/folder1/file1c", ChangeType.ADDED);
+        assertChange(changes, "/fs-test/folder1/file1c/child1", ChangeType.ADDED);
     }
     
     @Test
@@ -205,19 +203,15 @@ public class FileMonitorTest {
     }
     
     
-    private void assertChange(List<ResourceChange> changes, String path, ChangeType changeType, String... addedPropertyNames) {
+    private void assertChange(List<ResourceChange> changes, String path, ChangeType changeType) {
         boolean found = false;
         for (ResourceChange change : changes) {
             if (StringUtils.equals(change.getPath(), path) && change.getType() == changeType) {
                 found = true;
-                if (addedPropertyNames.length > 0) {
-                    assertEquals(ImmutableSet.copyOf(addedPropertyNames), change.getAddedPropertyNames());
-                }
                 break;
             }
         }
         assertTrue("Change with path=" + path + ", changeType=" + changeType, found);
-
     }
     
     static class ResourceListener implements ResourceChangeListener {

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 01/29: [maven-release-plugin] prepare for next development iteration

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 2a1916a491d60f20c147b064830e237ae6d77995
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Thu Jan 19 21:58:34 2017 +0000

    [maven-release-plugin] prepare for next development iteration
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1779535 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index cdfdc7d..c7e99ac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
 
     <artifactId>org.apache.sling.fsresource</artifactId>
     <packaging>bundle</packaging>
-    <version>1.2.2</version>
+    <version>1.2.3-SNAPSHOT</version>
 
     <name>Apache Sling Filesystem Resource Provider</name>
     <description>
@@ -37,9 +37,9 @@
     </description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.fsresource-1.2.2</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.fsresource-1.2.2</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.fsresource-1.2.2</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource</connection>
+        <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>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 25/29: SLING-6440 switch to latest contentparser API

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 69333f256552ffa84ec120ee1fbddc27f41bda9d
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Mar 17 21:49:38 2017 +0000

    SLING-6440 switch to latest contentparser API
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1787507 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/fsprovider/internal/FileMonitor.java     | 17 +++---
 .../fsprovider/internal/mapper/ContentFile.java    | 70 +++++-----------------
 .../internal/mapper/ContentFileResource.java       |  2 +-
 .../internal/mapper/ContentFileResourceMapper.java |  7 ++-
 .../internal/mapper/FileVaultResourceMapper.java   |  5 +-
 .../internal/mapper/jcr/FsNodeIterator.java        | 18 ++----
 .../internal/mapper/valuemap/ValueMapUtil.java     |  8 +--
 .../fsprovider/internal/parser/ContentElement.java | 52 ++++++++++++++++
 .../internal/parser/ContentElementHandler.java     | 69 +++++++++++++++++++++
 .../internal/parser/ContentElementImpl.java        | 68 +++++++++++++++++++++
 .../internal/parser/ContentFileCache.java          | 12 ++--
 .../internal/parser/ContentFileParserUtil.java     |  9 +--
 .../internal/mapper/ContentFileTest.java           | 25 +++-----
 .../internal/mapper/valuemap/ValueMapUtilTest.java |  4 --
 .../internal/parser/ContentFileCacheTest.java      |  5 +-
 .../internal/parser/ContentFileParserUtilTest.java | 19 +++---
 16 files changed, 256 insertions(+), 134 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
index dc1e65d..41fed5f 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -30,6 +30,7 @@ import org.apache.jackrabbit.vault.util.PlatformNameFormat;
 import org.apache.sling.api.resource.observation.ResourceChange;
 import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
 import org.apache.sling.fsprovider.internal.mapper.ContentFile;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.apache.sling.spi.resource.provider.ObservationReporter;
 import org.apache.sling.spi.resource.provider.ObserverConfiguration;
@@ -258,20 +259,19 @@ public final class FileMonitor extends TimerTask {
         }
     }
     
-    @SuppressWarnings("unchecked")
     private List<ResourceChange> collectResourceChanges(final Monitorable monitorable, final ChangeType changeType) {
         List<ResourceChange> changes = new ArrayList<>();
         if (monitorable.status instanceof ContentFileStatus) {
             ContentFile contentFile = ((ContentFileStatus)monitorable.status).contentFile;
             if (changeType == ChangeType.CHANGED) {
-                Map<String,Object> content = (Map<String,Object>)contentFile.getContent();
+                ContentElement content = 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(ChangeType.REMOVED,  transformPath(monitorable.path)));
                 addContentResourceChanges(changes, ChangeType.ADDED, content, transformPath(monitorable.path));
             }
             else {
-                addContentResourceChanges(changes, changeType, (Map<String,Object>)contentFile.getContent(), transformPath(monitorable.path));
+                addContentResourceChanges(changes, changeType, contentFile.getContent(), transformPath(monitorable.path));
             }
         }
         else {
@@ -279,16 +279,13 @@ public final class FileMonitor extends TimerTask {
         }
         return changes;
     }
-    @SuppressWarnings("unchecked")
     private void addContentResourceChanges(final List<ResourceChange> changes, final ChangeType changeType,
-            final Map<String,Object> content, final String path) {
+            final ContentElement content, final String path) {
         changes.add(buildContentResourceChange(changeType,  path));
         if (content != null) {
-            for (Map.Entry<String,Object> entry : content.entrySet()) {
-                if (entry.getValue() instanceof Map) {
-                    String childPath = path + "/" + entry.getKey();
-                    addContentResourceChanges(changes, changeType, (Map<String,Object>)entry.getValue(), childPath);
-                }
+            for (Map.Entry<String,ContentElement> entry : content.getChildren().entrySet()) {
+                String childPath = path + "/" + entry.getKey();
+                addContentResourceChanges(changes, changeType, entry.getValue(), childPath);
             }
         }
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
index 00c238a..dd31346 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
@@ -22,10 +22,9 @@ import java.io.File;
 import java.util.Iterator;
 import java.util.Map;
 
-import org.apache.commons.collections.IteratorUtils;
-import org.apache.commons.collections.Predicate;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.fsprovider.internal.mapper.valuemap.ValueMapUtil;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 
 /**
@@ -38,7 +37,7 @@ public final class ContentFile {
     private final String subPath;
     private final ContentFileCache contentFileCache;
     private boolean contentInitialized;
-    private Object content;
+    private ContentElement content;
     private ValueMap valueMap;
     
     /**
@@ -79,10 +78,15 @@ public final class ContentFile {
      * Content object referenced by sub path.
      * @return Map if resource, property value if property.
      */
-    public Object getContent() {
+    public ContentElement getContent() {
         if (!contentInitialized) {
-            Map<String,Object> rootContent = contentFileCache.get(path, file);
-            content = getDeepContent(rootContent, subPath);
+            ContentElement rootContent = contentFileCache.get(path, file);
+            if (subPath == null) {
+                content = rootContent;
+            }
+            else {
+                content = rootContent.getChild(subPath);
+            }
             contentInitialized = true;
         }
         return content;
@@ -96,21 +100,13 @@ public final class ContentFile {
     }
     
     /**
-     * @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);
+            ContentElement currentContent = getContent();
+            if (currentContent != null) {
+                valueMap = ValueMapUtil.toValueMap(currentContent.getProperties());
             }
             else {
                 valueMap = ValueMap.EMPTY;
@@ -122,18 +118,8 @@ public final class ContentFile {
     /**
      * @return Child maps.
      */
-    @SuppressWarnings("unchecked")
-    public Iterator<Map.Entry<String,Map<String,Object>>> getChildren() {
-        if (!isResource()) {
-            return IteratorUtils.emptyIterator();
-        }
-        return IteratorUtils.filteredIterator(((Map)getContent()).entrySet().iterator(), new Predicate() {
-            @Override
-            public boolean evaluate(Object object) {
-                Map.Entry<String,Object> entry = (Map.Entry<String,Object>)object;
-                return entry.getValue() instanceof Map;
-            }
-        });
+    public Iterator<Map.Entry<String,ContentElement>> getChildren() {
+        return getContent().getChildren().entrySet().iterator();
     }
     
     /**
@@ -161,30 +147,4 @@ public final class ContentFile {
         return new ContentFile(file, path, absoluteSubPath, 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);
-    }
-    
 }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
index dbbb5d0..40549c5 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
@@ -106,7 +106,7 @@ public final class ContentFileResource extends AbstractResource {
         else if (type == ValueMap.class) {
             return (AdapterType)contentFile.getValueMap();
         }
-        else if (type == Node.class && contentFile.isResource()) {
+        else if (type == Node.class) {
             // support a subset of JCR API for content file resources
             return (AdapterType)new FsNode(contentFile, getResourceResolver());
         }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
index 09b6ffe..b44926a 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
@@ -32,6 +32,7 @@ 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.ContentElement;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 
 public final class ContentFileResourceMapper implements FsResourceMapper {
@@ -105,10 +106,10 @@ public final class ContentFileResourceMapper implements FsResourceMapper {
 
         // get child resources from content fragments in content file
         List<ContentFile> children = new ArrayList<>();
-        if (parentContentFile.hasContent() && parentContentFile.isResource()) {
-            Iterator<Map.Entry<String,Map<String,Object>>> childMaps = parentContentFile.getChildren();
+        if (parentContentFile.hasContent()) {
+            Iterator<Map.Entry<String,ContentElement>> childMaps = parentContentFile.getChildren();
             while (childMaps.hasNext()) {
-                Map.Entry<String,Map<String,Object>> entry = childMaps.next();
+                Map.Entry<String,ContentElement> entry = childMaps.next();
                 children.add(parentContentFile.navigateToRelative(entry.getKey()));
             }
         }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
index 6585d1e..14f6146 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
@@ -38,6 +38,7 @@ 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.FsResourceMapper;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -94,9 +95,9 @@ public final class FileVaultResourceMapper implements FsResourceMapper {
         // get children from content resource of parent
         ContentFile parentContentFile = getContentFile(parentPath, null);
         if (parentContentFile != null) {
-            Iterator<Map.Entry<String,Map<String,Object>>> childMaps = parentContentFile.getChildren();
+            Iterator<Map.Entry<String,ContentElement>> childMaps = parentContentFile.getChildren();
             while (childMaps.hasNext()) {
-                Map.Entry<String,Map<String,Object>> entry = childMaps.next();
+                Map.Entry<String,ContentElement> entry = childMaps.next();
                 String childPath = parentPath + "/" + entry.getKey();
                 if (pathMatches(childPath)) {
                     childPaths.add(childPath);
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
index 09db7d4..7565994 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
@@ -24,10 +24,9 @@ import java.util.Map;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 
-import org.apache.commons.collections.IteratorUtils;
-import org.apache.commons.collections.Predicate;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.fsprovider.internal.mapper.ContentFile;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
 
 /**
  * Simplified implementation of read-only content access via the JCR API.
@@ -36,20 +35,13 @@ class FsNodeIterator implements NodeIterator {
     
     private final ContentFile contentFile;
     private final ResourceResolver resolver;
-    private final Iterator<Map.Entry<String,Map<String,Object>>> children;
+    private final Iterator<Map.Entry<String,ContentElement>> children;
 
-    @SuppressWarnings("unchecked")
     public FsNodeIterator(ContentFile contentFile, ResourceResolver resolver) {
         this.contentFile = contentFile;
         this.resolver = resolver;
-        Map<String,Object> content = (Map<String,Object>)contentFile.getContent();
-        this.children = IteratorUtils.filteredIterator(content.entrySet().iterator(), new Predicate() {
-            @Override
-            public boolean evaluate(Object object) {
-                Map.Entry<String,Object> entry = (Map.Entry<String,Object>)object;
-                return (entry.getValue() instanceof Map);
-            }
-        });
+        ContentElement content = contentFile.getContent();
+        this.children = content.getChildren().entrySet().iterator();
     }
 
     public boolean hasNext() {
@@ -62,7 +54,7 @@ class FsNodeIterator implements NodeIterator {
 
     @Override
     public Node nextNode() {
-        Map.Entry<String,Map<String,Object>> nextEntry = children.next();
+        Map.Entry<String,ContentElement> nextEntry = children.next();
         return new FsNode(contentFile.navigateToRelative(nextEntry.getKey()), resolver);
     }
 
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java
index b0a079c..ce69941 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java
@@ -38,12 +38,8 @@ public final class ValueMapUtil {
     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) {
+        for (Map.Entry<String, Object> entry : content.entrySet()) {
+            if (entry.getValue() instanceof Collection) {
                 // convert lists to arrays
                 props.put(entry.getKey(), ((Collection)entry.getValue()).toArray());
             }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElement.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElement.java
new file mode 100644
index 0000000..85ead62
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElement.java
@@ -0,0 +1,52 @@
+/*
+ * 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.parser;
+
+import java.util.Map;
+
+/**
+ * Represents a resource or node in the content hierarchy.
+ */
+public interface ContentElement {
+
+    /**
+     * @return Resource name. The root resource has no name (null).
+     */
+    String getName();
+    
+    /**
+     * Properties of this resource.
+     * @return Properties (keys, values)
+     */
+    Map<String, Object> getProperties();
+    
+    /**
+     * Get children of current resource. The Map preserves the ordering of children.
+     * @return Children (child names, child objects)
+     */
+    Map<String, ContentElement> getChildren();
+    
+    /**
+     * Get child or descendant
+     * @param path Relative path to address child or one of it's descendants (use "/" as hierarchy separator).
+     * @return Child or null if no child found with this path
+     */
+    ContentElement getChild(String path);
+    
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementHandler.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementHandler.java
new file mode 100644
index 0000000..e128943
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementHandler.java
@@ -0,0 +1,69 @@
+/*
+ * 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.parser;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.jcr.contentparser.ContentHandler;
+
+/**
+ * {@link ContentHandler} implementation that produces a tree of {@link ContentElement} items.
+ */
+final class ContentElementHandler implements ContentHandler {
+    
+    private ContentElement root;
+    private Pattern PATH_PATTERN = Pattern.compile("^((/[^/]+)*)(/([^/]+))$"); 
+
+    @Override
+    public void resource(String path, Map<String, Object> properties) {
+        if (StringUtils.equals(path, "/")) {
+            root = new ContentElementImpl(null, properties);
+        }
+        else {
+            if (root == null) {
+                throw new RuntimeException("Root resource not set.");
+            }
+            Matcher matcher = PATH_PATTERN.matcher(path);
+            if (!matcher.matches()) {
+                throw new RuntimeException("Unexpected path:" + path);
+            }
+            String relativeParentPath = StringUtils.stripStart(matcher.group(1), "/");
+            String name = matcher.group(4);
+            ContentElement parent;
+            if (StringUtils.isEmpty(relativeParentPath)) {
+                parent = root;
+            }
+            else {
+                parent = root.getChild(relativeParentPath);
+            }
+            if (parent == null) {
+                throw new RuntimeException("Parent '" + relativeParentPath + "' does not exist.");
+            }
+            parent.getChildren().put(name, new ContentElementImpl(name, properties));
+        }
+    }
+    
+    public ContentElement getRoot() {
+        return root;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementImpl.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementImpl.java
new file mode 100644
index 0000000..e7d6dd3
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentElementImpl.java
@@ -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.parser;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+
+final class ContentElementImpl implements ContentElement {
+    
+    private final String name;
+    private final Map<String, Object> properties;
+    private final Map<String, ContentElement> children = new LinkedHashMap<>();
+    
+    public ContentElementImpl(String name, Map<String, Object> properties) {
+        this.name = name;
+        this.properties = properties;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public Map<String, ContentElement> getChildren() {
+        return children;
+    }
+
+    @Override
+    public ContentElement getChild(String path) {
+        String name = StringUtils.substringBefore(path, "/");
+        ContentElement child = children.get(name);
+        if (child == null) {
+          return null;
+        }
+        String remainingPath = StringUtils.substringAfter(path, "/");
+        if (StringUtils.isEmpty(remainingPath)) {
+          return child;
+        }
+        else {
+          return child.getChild(remainingPath);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
index 745ae60..4222e98 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
@@ -29,8 +29,8 @@ import org.apache.commons.collections.map.LRUMap;
  */
 public final class ContentFileCache {
 
-    private final Map<String,Map<String,Object>> contentCache;
-    private final Map<String,Object> NULL_MAP = Collections.emptyMap();
+    private final Map<String,ContentElement> contentCache;
+    private final ContentElement NULL_ELEMENT = new ContentElementImpl(null, Collections.<String,Object>emptyMap());
     
     /**
      * @param maxSize Cache size. 0 = caching disabled.
@@ -51,21 +51,21 @@ public final class ContentFileCache {
      * @param file File
      * @return Content or null
      */
-    public Map<String,Object> get(String path, File file) {
-        Map<String,Object> content = null;
+    public ContentElement get(String path, File file) {
+        ContentElement content = null;
         if (contentCache != null) {
             content = contentCache.get(path);
         }
         if (content == null) {
             content = ContentFileParserUtil.parse(file);
             if (content == null) {
-                content = NULL_MAP;
+                content = NULL_ELEMENT;
             }
             if (contentCache != null) {
                 contentCache.put(path, content);
             }
         }
-        if (content == NULL_MAP) {
+        if (content == NULL_ELEMENT) {
             return null;
         }
         else {
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
index b32decf..dbb638a 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
@@ -26,7 +26,6 @@ import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.jcr.contentparser.ContentParser;
@@ -65,7 +64,7 @@ class ContentFileParserUtil {
      * @param file File. Type is detected automatically.
      * @return Content or null if content could not be parsed.
      */
-    public static Map<String,Object> parse(File file) {
+    public static ContentElement parse(File file) {
         if (!file.exists()) {
             return null;
         }
@@ -83,10 +82,12 @@ class ContentFileParserUtil {
         return null;
     }
     
-    private static Map<String,Object> parse(ContentParser contentParser, File file) throws IOException {
+    private static ContentElement parse(ContentParser contentParser, File file) throws IOException {
         try (FileInputStream fis = new FileInputStream(file);
                 BufferedInputStream bis = new BufferedInputStream(fis)) {
-            return contentParser.parse(bis);
+            ContentElementHandler handler = new ContentElementHandler();
+            contentParser.parse(handler, bis);
+            return handler.getRoot();
         }
     }
 
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
index 4659991..3a6a747 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
@@ -24,9 +24,9 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
-import java.util.Map;
 
 import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.fsprovider.internal.parser.ContentElement;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.junit.Test;
 
@@ -34,7 +34,6 @@ public class ContentFileTest {
     
     private ContentFileCache contentFileCache = new ContentFileCache(0);
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testRootContent() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
@@ -45,16 +44,15 @@ public class ContentFileTest {
         
         assertTrue(underTest.hasContent());
 
-        Map<String,Object> content = (Map<String,Object>)underTest.getContent();
-        assertEquals("app:Page", content.get("jcr:primaryType"));
-        assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
+        ContentElement content = underTest.getContent();
+        assertEquals("app:Page", content.getProperties().get("jcr:primaryType"));
+        assertEquals("app:PageContent", content.getChild("jcr:content").getProperties().get("jcr:primaryType"));
 
         ValueMap props = underTest.getValueMap();
         assertEquals("app:Page", props.get("jcr:primaryType"));
         assertNull(props.get("jcr:content"));
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testContentLevel1() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
@@ -65,14 +63,13 @@ public class ContentFileTest {
         
         assertTrue(underTest.hasContent());
 
-        Map<String,Object> content = (Map<String,Object>)underTest.getContent();
-        assertEquals("app:PageContent", content.get("jcr:primaryType"));
+        ContentElement content = underTest.getContent();
+        assertEquals("app:PageContent", content.getProperties().get("jcr:primaryType"));
 
         ValueMap props = underTest.getValueMap();
         assertEquals("app:PageContent", props.get("jcr:primaryType"));
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testContentLevel5() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
@@ -83,8 +80,8 @@ public class ContentFileTest {
         
         assertTrue(underTest.hasContent());
 
-        Map<String,Object> content = (Map<String,Object>)underTest.getContent();
-        assertEquals("nt:resource", content.get("jcr:primaryType"));
+        ContentElement content = underTest.getContent();
+        assertEquals("nt:resource", content.getProperties().get("jcr:primaryType"));
 
         ValueMap props = underTest.getValueMap();
         assertEquals("nt:resource", props.get("jcr:primaryType"));
@@ -98,11 +95,7 @@ public class ContentFileTest {
         assertEquals(file, underTest.getFile());
         assertEquals("jcr:content/jcr:title", underTest.getSubPath());
         
-        assertTrue(underTest.hasContent());
-
-        assertEquals("English", underTest.getContent());
-
-        assertTrue(underTest.getValueMap().isEmpty());
+        assertFalse(underTest.hasContent());
     }
 
     @Test
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java
index 87f2bfd..841ef00 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java
@@ -20,7 +20,6 @@ package org.apache.sling.fsprovider.internal.mapper.valuemap;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -29,7 +28,6 @@ import org.apache.sling.api.resource.ValueMap;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 
 public class ValueMapUtilTest {
 
@@ -38,7 +36,6 @@ public class ValueMapUtilTest {
         Map<String,Object> content = new HashMap<>();
         content.put("stringProp", "abc");
         content.put("intProp", 123);
-        content.put("childNode", ImmutableMap.<String,Object>of());
         content.put("stringArray", new String[] { "a", "b", "c" });
         content.put("stringList", ImmutableList.of("ab", "cd"));
         content.put("intList", ImmutableList.of(12, 34));
@@ -46,7 +43,6 @@ public class ValueMapUtilTest {
         ValueMap props = ValueMapUtil.toValueMap(content);
         assertEquals("abc", props.get("stringProp", String.class));
         assertEquals((Integer)123, props.get("intProp", 0));
-        assertNull(props.get("childNode"));
         assertArrayEquals(new String[] { "a", "b", "c" }, props.get("stringArray", String[].class));
         assertArrayEquals(new String[] { "ab", "cd" }, props.get("stringList", String[].class));
         assertArrayEquals(new Integer[] { 12, 34 }, props.get("intList", Integer[].class));
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java
index 1eaf1e1..236959a 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java
@@ -23,7 +23,6 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import java.io.File;
-import java.util.Map;
 
 import org.junit.experimental.theories.DataPoint;
 import org.junit.experimental.theories.Theories;
@@ -44,7 +43,7 @@ public class ContentFileCacheTest {
     public void testCache(int cacheSize) {
         ContentFileCache underTest = new ContentFileCache(cacheSize);
         
-        Map<String,Object> content1 = underTest.get("/fs-test/folder2/content", new File("src/test/resources/fs-test/folder2/content.json"));
+        ContentElement content1 = underTest.get("/fs-test/folder2/content", new File("src/test/resources/fs-test/folder2/content.json"));
         assertNotNull(content1);
         
         switch (cacheSize) {
@@ -57,7 +56,7 @@ public class ContentFileCacheTest {
             break;
         }
 
-        Map<String,Object> content2 = underTest.get("/fs-test/folder1/file1a", new File("src/test/resources/fs-test/folder1/file1a.txt"));
+        ContentElement content2 = underTest.get("/fs-test/folder1/file1a", new File("src/test/resources/fs-test/folder1/file1a.txt"));
         assertNull(content2);
 
         switch (cacheSize) {
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java
index 9bdaa51..b775c81 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtilTest.java
@@ -23,43 +23,40 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import java.io.File;
-import java.util.Map;
 
 import org.junit.Test;
 
 public class ContentFileParserUtilTest {
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testParseJson() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
-        Map<String,Object> content = ContentFileParserUtil.parse(file);
+        ContentElement content = ContentFileParserUtil.parse(file);
         assertNotNull(content);
-        assertEquals("app:Page", content.get("jcr:primaryType"));
-        assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
+        assertEquals("app:Page", content.getProperties().get("jcr:primaryType"));
+        assertEquals("app:PageContent", content.getChild("jcr:content").getProperties().get("jcr:primaryType"));
     }
 
     @Test
     public void testParseInvalidJson() {
         File file = new File("src/test/resources/invalid-test/invalid.json");
-        Map<String,Object> content = ContentFileParserUtil.parse(file);
+        ContentElement content = ContentFileParserUtil.parse(file);
         assertNull(content);
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void testParseJcrXml() {
         File file = new File("src/test/resources/fs-test/folder3/content.jcr.xml");
-        Map<String,Object> content = ContentFileParserUtil.parse(file);
+        ContentElement content = ContentFileParserUtil.parse(file);
         assertNotNull(content);
-        assertEquals("app:Page", content.get("jcr:primaryType"));
-        assertEquals("app:PageContent", ((Map<String,Object>)content.get("jcr:content")).get("jcr:primaryType"));
+        assertEquals("app:Page", content.getProperties().get("jcr:primaryType"));
+        assertEquals("app:PageContent", content.getChild("jcr:content").getProperties().get("jcr:primaryType"));
     }
 
     @Test
     public void testParseInvalidJcrXml() {
         File file = new File("src/test/resources/invalid-test/invalid.jcr.xml");
-        Map<String,Object> content = ContentFileParserUtil.parse(file);
+        ContentElement content = ContentFileParserUtil.parse(file);
         assertNull(content);
     }
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 17/29: SLING-6440 use ManifestHeader to parse directives

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 94499336c2bc1a393a94ebe97b26dc4b8ef14041
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Wed Mar 8 16:36:17 2017 +0000

    SLING-6440 use ManifestHeader to parse directives
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1786016 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  6 ++++
 .../internal/InitialContentImportOptions.java      | 33 +++++++++-------------
 .../internal/InitialContentImportOptionsTest.java  |  4 +--
 3 files changed, 22 insertions(+), 21 deletions(-)

diff --git a/pom.xml b/pom.xml
index def0725..f7f45c8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -139,6 +139,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.2.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.fscontentparser</artifactId>
             <version>1.0.0-SNAPSHOT</version>
             <scope>compile</scope>
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java b/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java
index 5353d0f..52de538 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/InitialContentImportOptions.java
@@ -19,13 +19,14 @@
 package org.apache.sling.fsprovider.internal;
 
 import java.util.Arrays;
-import java.util.HashMap;
+import java.util.Collections;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 
 import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.commons.osgi.ManifestHeader;
+import org.apache.sling.commons.osgi.ManifestHeader.Entry;
 
 class InitialContentImportOptions {
 
@@ -46,25 +47,19 @@ class InitialContentImportOptions {
     private final Set<String> ignoreImportProviders;
     
     public InitialContentImportOptions(String optionsString) {
-        Map<String,String> options = parseOptions(optionsString);
-        overwrite = BooleanUtils.toBoolean(options.get(OVERWRITE_DIRECTIVE));
-        ignoreImportProviders = new HashSet<>(Arrays.asList(StringUtils.split(StringUtils.defaultString(options.get(IGNORE_CONTENT_READERS_DIRECTIVE)))));
-    }
-    
-    private static Map<String,String> parseOptions(String optionsString) {
-        Map<String,String> options = new HashMap<>();
-        String[] optionsList = StringUtils.split(optionsString, ";");
-        if (optionsList != null) {
-            for (String keyValueString : optionsList) {
-                String[] keyValue = StringUtils.splitByWholeSeparator(keyValueString, ":=");
-                if (keyValue.length == 2) {
-                    options.put(StringUtils.trim(keyValue[0]), StringUtils.trim(keyValue[1]));
-                }
-            }
+        ManifestHeader header = ManifestHeader.parse("/dummy/path;" + optionsString);
+        Entry[] entries = header.getEntries();
+        if (entries.length > 0) {
+            overwrite = BooleanUtils.toBoolean(entries[0].getDirectiveValue(OVERWRITE_DIRECTIVE));
+            String ignoreImportProvidersString = StringUtils.defaultString(entries[0].getDirectiveValue(IGNORE_CONTENT_READERS_DIRECTIVE));
+            ignoreImportProviders = new HashSet<>(Arrays.asList(StringUtils.split(ignoreImportProvidersString, ",")));
+        }
+        else {
+            overwrite = false;
+            ignoreImportProviders = Collections.emptySet();
         }
-        return options;
     }
-
+    
     public boolean isOverwrite() {
         return overwrite;
     }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java b/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java
index 513c51f..3a0d166 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/InitialContentImportOptionsTest.java
@@ -44,9 +44,9 @@ public class InitialContentImportOptionsTest {
 
     @Test
     public void testOptions1() {
-        InitialContentImportOptions underTest = new InitialContentImportOptions("overwrite:=true;ignoreImportProviders:=xml,json");
+        InitialContentImportOptions underTest = new InitialContentImportOptions("overwrite:=true;ignoreImportProviders:=\"xml,json\"");
         assertTrue(underTest.isOverwrite());
-        assertEquals(ImmutableSet.of("xml,json"), underTest.getIgnoreImportProviders());
+        assertEquals(ImmutableSet.of("xml","json"), underTest.getIgnoreImportProviders());
     }
 
     @Test

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 27/29: fix javadoc errors

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 80a7f039ed2f464d4c67b6278f9b3037671e5493
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Mon Mar 27 16:11:51 2017 +0000

    fix javadoc errors
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1788967 13f79535-47bb-0310-9956-ffa450edef68
---
 .../java/org/apache/sling/fsprovider/internal/FileMonitor.java     | 3 +++
 .../org/apache/sling/fsprovider/internal/FsResourceProvider.java   | 7 +++----
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
index 41fed5f..a164d4f 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -60,6 +60,9 @@ public final class FileMonitor extends TimerTask {
      * Creates a new instance of this class.
      * @param provider The resource provider.
      * @param interval The interval between executions of the task, in milliseconds.
+     * @param fsMode FS mode
+     * @param contentFileExtensions Content file extensions
+     * @param contentFileCache Content file cache
      */
     public FileMonitor(final FsResourceProvider provider, final long interval, FsMode fsMode,
             final ContentFileExtensions contentFileExtensions, final ContentFileCache contentFileCache) {
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index bb6a6e5..9497127 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -75,7 +75,8 @@ import org.osgi.service.metatype.annotations.Option;
 public final class FsResourceProvider extends ResourceProvider<Object> {
     
     /**
-     * Resource metadata property set by {@link FsResource} if the underlying file reference is a directory.
+     * Resource metadata property set by {@link org.apache.sling.fsprovider.internal.mapper.FileResource}
+     * if the underlying file reference is a directory.
      */
     public static final String RESOURCE_METADATA_FILE_DIRECTORY = ":org.apache.sling.fsprovider.file.directory";
     
@@ -128,9 +129,7 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
                 description = "Max. number of content files cached in memory.")
         int provider_cache_size() default 10000;
 
-        /**
-         * Internal Name hint for web console.
-         */
+        // Internal Name hint for web console.
         String webconsole_configurationFactory_nameHint() default "{provider.fs.mode}: {" + ResourceProvider.PROPERTY_ROOT + "}";
     }
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 09/29: SLING-6440 refactor fs jcr implementation to avoid cyclic dependencies to resource API and some further improvements

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 0115c1a694f71f4f0493e7c4cc2d517eff872c59
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Tue Feb 28 15:28:31 2017 +0000

    SLING-6440 refactor fs jcr implementation to avoid cyclic dependencies to resource API and some further improvements
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1784754 13f79535-47bb-0310-9956-ffa450edef68
---
 .../fsprovider/internal/ContentFileExtensions.java |  7 ++
 .../fsprovider/internal/mapper/ContentFile.java    |  9 +++
 .../internal/mapper/ContentFileResource.java       |  2 +-
 .../internal/mapper/ContentFileResourceMapper.java |  6 ++
 .../fsprovider/internal/mapper/jcr/FsItem.java     | 56 ++++++++--------
 .../fsprovider/internal/mapper/jcr/FsNode.java     | 77 ++++++++++++++++++----
 .../internal/mapper/jcr/FsNodeIterator.java        | 36 ++++++++--
 .../fsprovider/internal/mapper/jcr/FsProperty.java |  9 +--
 .../internal/mapper/jcr/FsPropertyIterator.java    | 13 ++--
 .../sling/fsprovider/internal/FileMonitorTest.java | 53 ++++++++-------
 .../sling/fsprovider/internal/JsonContentTest.java |  4 +-
 11 files changed, 191 insertions(+), 81 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java b/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
index 3711ac9..5097750 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/ContentFileExtensions.java
@@ -65,4 +65,11 @@ public final class ContentFileExtensions {
         return contentFileSuffixes;
     }
     
+    /**
+     * @return true if not suffixes are defined.
+     */
+    public boolean isEmpty() {
+        return contentFileSuffixes.isEmpty();
+    }
+    
 }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
index 2e60b1f..d31a851 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
@@ -128,6 +128,15 @@ public final class ContentFile {
         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) {
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
index 410419e..dbbb5d0 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
@@ -108,7 +108,7 @@ public final class ContentFileResource extends AbstractResource {
         }
         else if (type == Node.class && contentFile.isResource()) {
             // support a subset of JCR API for content file resources
-            return (AdapterType)new FsNode(this);
+            return (AdapterType)new FsNode(contentFile, getResourceResolver());
         }
         return super.adaptTo(type);
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
index b5306b9..e6edd04 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
@@ -55,6 +55,9 @@ public final class ContentFileResourceMapper implements FsResourceMapper {
     
     @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);
@@ -67,6 +70,9 @@ public final class ContentFileResourceMapper implements FsResourceMapper {
     @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);
 
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java
index 11e1836..d5689a9 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsItem.java
@@ -38,53 +38,49 @@ 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;
+import org.apache.sling.fsprovider.internal.mapper.ContentFile;
 
 /**
  * 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 ContentFile contentFile;
     protected final ResourceResolver resolver;
+    protected final ValueMap props;
     
-    public FsItem(Resource resource) {
-        this.resource = resource;
-        this.props = resource.getValueMap();
-        this.resolver = resource.getResourceResolver();
+    public FsItem(ContentFile contentFile, ResourceResolver resolver) {
+        this.contentFile = contentFile;
+        this.resolver = resolver;
+        this.props = contentFile.getValueMap();
     }
 
     @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);
-            }
+        if (contentFile.getSubPath() == null) {
+            return contentFile.getPath();
+        }
+        else {
+            return contentFile.getPath() + "/" + contentFile.getSubPath();
         }
-        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;
+    public Item getAncestor(int depth) throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+        String path;
+        if (depth == 0) {
+            path = "/";
+        }
+        else {
+            String[] pathParts = StringUtils.splitPreserveAllTokens(getPath(), "/");
+            path = StringUtils.join(pathParts, "/", 0, depth + 1);
+        }
+        Resource resource = resolver.getResource(path);
+        if (resource != null) {
+            Node refNode = resource.adaptTo(Node.class);
+            if (refNode != null) {
+                return refNode;
             }
         }
         throw new ItemNotFoundException();
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
index 1d4b842..6f52691 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
@@ -53,18 +53,21 @@ import javax.jcr.version.VersionHistory;
 
 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.mapper.ContentFile;
 
 /**
  * Simplified implementation of read-only content access via the JCR API.
  */
 public final class FsNode extends FsItem implements Node {
     
-    public FsNode(Resource resource) {
-        super(resource);
+    public FsNode(ContentFile contentFile, ResourceResolver resolver) {
+        super(contentFile, resolver);
     }
     
     private String getPrimaryTypeName() {
-        return  props.get("jcr:primaryType", String.class);
+        return props.get("jcr:primaryType", String.class);
     }
     
     private String[] getMixinTypeNames() {
@@ -72,30 +75,76 @@ public final class FsNode extends FsItem implements Node {
     }
     
     @Override
+    public String getName() throws RepositoryException {
+        if (contentFile.getSubPath() == null) {
+            return ResourceUtil.getName(contentFile.getPath());
+        }
+        else {
+            return ResourceUtil.getName(contentFile.getSubPath());
+        }
+    }
+
+    @Override
+    public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException {
+        return getNode(ResourceUtil.getParent(getPath()));
+    }
+    
+    @Override
     public Node getNode(String relPath) throws PathNotFoundException, RepositoryException {
-        Resource child = resource.getChild(relPath);
-        if (child != null) {
-            return new FsNode(child);
+        if (relPath == null) {
+            throw new PathNotFoundException();
+        }
+        
+        // get absolute node path
+        String path = relPath;
+        if (!StringUtils.startsWith(path,  "/")) {
+            path = ResourceUtil.normalize(getPath() + "/" + relPath);
+        }
+
+        if (StringUtils.equals(path, contentFile.getPath()) || StringUtils.startsWith(path, contentFile.getPath() + "/")) {
+            // node is contained in content file
+            String subPath;
+            if (StringUtils.equals(path, contentFile.getPath())) {
+                subPath = null;
+            }
+            else {
+                subPath = path.substring(contentFile.getPath().length() + 1);
+            }
+            ContentFile referencedFile = contentFile.navigateTo(subPath);
+            if (referencedFile.hasContent()) {
+                return new FsNode(referencedFile, resolver);
+            }
+        }
+        else {
+            // node is outside content file
+            Node refNode = null;
+            Resource resource = resolver.getResource(path);
+            if (resource != null) {
+                refNode = resource.adaptTo(Node.class);
+                if (refNode != null) {
+                    return refNode;
+                }
+            }
         }
         throw new PathNotFoundException(relPath);
     }
 
     @Override
     public NodeIterator getNodes() throws RepositoryException {
-        return new FsNodeIterator(resource.listChildren());
+        return new FsNodeIterator(contentFile, resolver);
     }
 
     @Override
     public Property getProperty(String relPath) throws PathNotFoundException, RepositoryException {
         if (props.containsKey(relPath)) {
-            return new FsProperty(resource, relPath, this);
+            return new FsProperty(contentFile, resolver, relPath, this);
         }
         throw new PathNotFoundException(relPath);
     }
 
     @Override
     public PropertyIterator getProperties() throws RepositoryException {
-        return new FsPropertyIterator(props.keySet().iterator(), resource, this);
+        return new FsPropertyIterator(props.keySet().iterator(), contentFile, resolver, this);
     }
 
     @Override
@@ -111,7 +160,13 @@ public final class FsNode extends FsItem implements Node {
 
     @Override
     public boolean hasNode(String relPath) throws RepositoryException {
-        return resource.getChild(relPath) != null;
+        try {
+            getNode(relPath);
+            return true;
+        }
+        catch (RepositoryException ex) {
+            return false;
+        }
     }
 
     @Override
@@ -121,7 +176,7 @@ public final class FsNode extends FsItem implements Node {
 
     @Override
     public boolean hasNodes() throws RepositoryException {
-        return resource.listChildren().hasNext();
+        return getNodes().hasNext();
     }
 
     @Override
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
index a2ef2fe..f03b0a7 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeIterator.java
@@ -19,25 +19,41 @@
 package org.apache.sling.fsprovider.internal.mapper.jcr;
 
 import java.util.Iterator;
+import java.util.Map;
 
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 
-import org.apache.sling.api.resource.Resource;
+import org.apache.commons.collections.IteratorUtils;
+import org.apache.commons.collections.Predicate;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.fsprovider.internal.mapper.ContentFile;
 
 /**
  * Simplified implementation of read-only content access via the JCR API.
  */
 class FsNodeIterator implements NodeIterator {
     
-    private final Iterator<Resource> resources;
+    private final ContentFile contentFile;
+    private final ResourceResolver resolver;
+    private final Iterator<Map.Entry<String,Map<String,Object>>> children;
 
-    public FsNodeIterator(Iterator<Resource> resources) {
-        this.resources = resources;
+    @SuppressWarnings("unchecked")
+    public FsNodeIterator(ContentFile contentFile, ResourceResolver resolver) {
+        this.contentFile = contentFile;
+        this.resolver = resolver;
+        Map<String,Object> content = (Map<String,Object>)contentFile.getContent();
+        this.children = IteratorUtils.filteredIterator(content.entrySet().iterator(), new Predicate() {
+            @Override
+            public boolean evaluate(Object object) {
+                Map.Entry<String,Object> entry = (Map.Entry<String,Object>)object;
+                return (entry.getValue() instanceof Map);
+            }
+        });
     }
 
     public boolean hasNext() {
-        return resources.hasNext();
+        return children.hasNext();
     }
 
     public Object next() {
@@ -46,7 +62,15 @@ class FsNodeIterator implements NodeIterator {
 
     @Override
     public Node nextNode() {
-        return resources.next().adaptTo(Node.class);
+        Map.Entry<String,Map<String,Object>> nextEntry = children.next();
+        String subPath;
+        if (contentFile.getSubPath() == null) {
+            subPath = nextEntry.getKey();
+        }
+        else {
+            subPath = contentFile.getSubPath() + "/" + nextEntry.getKey();
+        }
+        return new FsNode(contentFile.navigateTo(subPath), resolver);
     }
 
     
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java
index 469bd11..bf24aa7 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java
@@ -36,7 +36,8 @@ import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.PropertyDefinition;
 import javax.jcr.version.VersionException;
 
-import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.fsprovider.internal.mapper.ContentFile;
 
 /**
  * Simplified implementation of read-only content access via the JCR API.
@@ -46,8 +47,8 @@ class FsProperty extends FsItem implements Property {
     private final String propertyName;
     private final Node node;
     
-    public FsProperty(Resource resource, String propertyName, Node node) {
-        super(resource);
+    public FsProperty(ContentFile contentFile, ResourceResolver resolver, String propertyName, Node node) {
+        super(contentFile, resolver);
         this.propertyName = propertyName;
         this.node = node;
     }
@@ -69,7 +70,7 @@ class FsProperty extends FsItem implements Property {
     
     @Override
     public String getPath() throws RepositoryException {
-        return resource.getPath() + "/" + propertyName;
+        return super.getPath() + "/" + propertyName;
     }
 
     @Override
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyIterator.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyIterator.java
index de2d572..335472f 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyIterator.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyIterator.java
@@ -24,7 +24,8 @@ import javax.jcr.Node;
 import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
 
-import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.fsprovider.internal.mapper.ContentFile;
 
 /**
  * Simplified implementation of read-only content access via the JCR API.
@@ -32,12 +33,14 @@ import org.apache.sling.api.resource.Resource;
 class FsPropertyIterator implements PropertyIterator {
     
     private final Iterator<String> propertyNames;
-    private final Resource resource;
+    private final ContentFile contentFile;
+    private final ResourceResolver resolver;
     private final Node node;
     
-    public FsPropertyIterator(Iterator<String> propertyNames, Resource resource, Node node) {
+    public FsPropertyIterator(Iterator<String> propertyNames, ContentFile contentFile, ResourceResolver resolver, Node node) {
         this.propertyNames = propertyNames;
-        this.resource = resource;
+        this.contentFile = contentFile;
+        this.resolver = resolver;
         this.node = node;
     }
 
@@ -51,7 +54,7 @@ class FsPropertyIterator implements PropertyIterator {
 
     @Override
     public Property nextProperty() {
-        return new FsProperty(resource, propertyNames.next(), node);
+        return new FsProperty(contentFile, resolver, propertyNames.next(), node);
     }
 
     
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
index 591a85c..f9cc25c 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.commons.io.FileUtils;
+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.api.resource.observation.ResourceChangeListener;
@@ -93,7 +94,7 @@ public class FileMonitorTest {
         Thread.sleep(250);
 
         assertEquals(1, changes.size());
-        assertChange(changes, 0, "/fs-test/folder1/file1a.txt", ChangeType.CHANGED);
+        assertChange(changes, "/fs-test/folder1/file1a.txt", ChangeType.CHANGED);
     }
     
     @Test
@@ -107,8 +108,8 @@ public class FileMonitorTest {
         Thread.sleep(250);
 
         assertEquals(2, changes.size());
-        assertChange(changes, 0, "/fs-test/folder1", ChangeType.CHANGED);
-        assertChange(changes, 1, "/fs-test/folder1/file1c.txt", ChangeType.ADDED);
+        assertChange(changes, "/fs-test/folder1", ChangeType.CHANGED);
+        assertChange(changes, "/fs-test/folder1/file1c.txt", ChangeType.ADDED);
     }
     
     @Test
@@ -122,8 +123,8 @@ public class FileMonitorTest {
         Thread.sleep(250);
 
         assertEquals(2, changes.size());
-        assertChange(changes, 0, "/fs-test/folder1", ChangeType.CHANGED);
-        assertChange(changes, 1, "/fs-test/folder1/file1a.txt", ChangeType.REMOVED);
+        assertChange(changes, "/fs-test/folder1", ChangeType.CHANGED);
+        assertChange(changes, "/fs-test/folder1/file1a.txt", ChangeType.REMOVED);
     }
     
     @Test
@@ -137,8 +138,8 @@ public class FileMonitorTest {
         Thread.sleep(250);
 
         assertEquals(2, changes.size());
-        assertChange(changes, 0, "/fs-test", ChangeType.CHANGED);
-        assertChange(changes, 1, "/fs-test/folder99", ChangeType.ADDED);
+        assertChange(changes, "/fs-test", ChangeType.CHANGED);
+        assertChange(changes, "/fs-test/folder99", ChangeType.ADDED);
     }
     
     @Test
@@ -152,8 +153,8 @@ public class FileMonitorTest {
         Thread.sleep(250);
 
         assertEquals(2, changes.size());
-        assertChange(changes, 0, "/fs-test", ChangeType.CHANGED);
-        assertChange(changes, 1, "/fs-test/folder1", ChangeType.REMOVED);
+        assertChange(changes, "/fs-test", ChangeType.CHANGED);
+        assertChange(changes, "/fs-test/folder1", ChangeType.REMOVED);
     }
 
     @Test
@@ -167,9 +168,9 @@ public class FileMonitorTest {
         Thread.sleep(250);
 
         assertTrue(changes.size() > 1);
-        assertChange(changes, 0, "/fs-test/folder2/content", ChangeType.REMOVED);
-        assertChange(changes, 1, "/fs-test/folder2/content", ChangeType.ADDED);
-        assertChange(changes, 2, "/fs-test/folder2/content/jcr:content", ChangeType.ADDED);
+        assertChange(changes, "/fs-test/folder2/content", ChangeType.REMOVED);
+        assertChange(changes, "/fs-test/folder2/content", ChangeType.ADDED);
+        assertChange(changes, "/fs-test/folder2/content/jcr:content", ChangeType.ADDED);
     }
     
     @Test
@@ -183,9 +184,9 @@ public class FileMonitorTest {
         Thread.sleep(250);
 
         assertEquals(3, changes.size());
-        assertChange(changes, 0, "/fs-test/folder1", ChangeType.CHANGED);
-        assertChange(changes, 1, "/fs-test/folder1/file1c", ChangeType.ADDED, "prop1");
-        assertChange(changes, 2, "/fs-test/folder1/file1c/child1", ChangeType.ADDED, "prop2");
+        assertChange(changes, "/fs-test/folder1", ChangeType.CHANGED);
+        assertChange(changes, "/fs-test/folder1/file1c", ChangeType.ADDED, "prop1");
+        assertChange(changes, "/fs-test/folder1/file1c/child1", ChangeType.ADDED, "prop2");
     }
     
     @Test
@@ -199,18 +200,24 @@ public class FileMonitorTest {
         Thread.sleep(250);
 
         assertEquals(2, changes.size());
-        assertChange(changes, 0, "/fs-test/folder2", ChangeType.CHANGED);
-        assertChange(changes, 1, "/fs-test/folder2/content", ChangeType.REMOVED);
+        assertChange(changes, "/fs-test/folder2", ChangeType.CHANGED);
+        assertChange(changes, "/fs-test/folder2/content", ChangeType.REMOVED);
     }
     
     
-    private void assertChange(List<ResourceChange> changes, int index, String path, ChangeType changeType, String... addedPropertyNames) {
-        ResourceChange change = changes.get(index);
-        assertEquals(path, change.getPath());
-        assertEquals(changeType, change.getType());
-        if (addedPropertyNames.length > 0) {
-            assertEquals(ImmutableSet.copyOf(addedPropertyNames), change.getAddedPropertyNames());
+    private void assertChange(List<ResourceChange> changes, String path, ChangeType changeType, String... addedPropertyNames) {
+        boolean found = false;
+        for (ResourceChange change : changes) {
+            if (StringUtils.equals(change.getPath(), path) && change.getType() == changeType) {
+                found = true;
+                if (addedPropertyNames.length > 0) {
+                    assertEquals(ImmutableSet.copyOf(addedPropertyNames), change.getAddedPropertyNames());
+                }
+                break;
+            }
         }
+        assertTrue("Change with path=" + path + ", changeType=" + changeType, found);
+
     }
     
     static class ResourceListener implements ResourceChangeListener {
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
index a78d601..797575b 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
@@ -198,8 +198,10 @@ public class JsonContentTest {
         assertEquals(7, rightpar.getDepth());
         Node parent = rightpar.getParent();
         assertTrue(node.isSame(parent));
-        Node ancestor = (Node)rightpar.getAncestor(4);
+        Node ancestor = (Node)rightpar.getAncestor(5);
         assertEquals(underTest.getParent().getPath(), ancestor.getPath());
+        Node root = (Node)rightpar.getAncestor(0);
+        assertEquals("/", root.getPath());
         
         // node types
         assertTrue(node.isNodeType("app:PageContent"));

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 23/29: SLING-6440 switch to latest jcr/contentparser API

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 9f5cdf6a4eb0773f67c73d640a8740b1138e0e50
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Tue Mar 14 10:44:14 2017 +0000

    SLING-6440 switch to latest jcr/contentparser API
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1786872 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                                      |  4 ++--
 .../fsprovider/internal/parser/ContentFileParserUtil.java    | 12 ++++++------
 .../sling/fsprovider/internal/parser/ContentFileTypes.java   |  6 +++---
 .../apache/sling/fsprovider/internal/JsonContentTest.java    |  2 +-
 4 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/pom.xml b/pom.xml
index f7f45c8..efb1f26 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,7 +66,7 @@
                         <Embed-Dependency>
                             johnzon-core;scope=compile;inline=false,
                             geronimo-json_1.0_spec;scope=compile;inline=false,
-                            org.apache.sling.commons.fscontentparser;scope=compile;inline=false
+                            org.apache.sling.jcr.contentparser;scope=compile;inline=false
                         </Embed-Dependency>
                     </instructions>
                 </configuration>
@@ -145,7 +145,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.commons.fscontentparser</artifactId>
+            <artifactId>org.apache.sling.jcr.contentparser</artifactId>
             <version>1.0.0-SNAPSHOT</version>
             <scope>compile</scope>
         </dependency>
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
index 6ac72c5..ff83c00 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
@@ -26,9 +26,9 @@ import java.io.File;
 import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.fscontentparser.ContentFileParser;
-import org.apache.sling.fscontentparser.ContentFileParserFactory;
-import org.apache.sling.fscontentparser.ContentFileType;
+import org.apache.sling.jcr.contentparser.ContentParser;
+import org.apache.sling.jcr.contentparser.ContentParserFactory;
+import org.apache.sling.jcr.contentparser.ContentType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,19 +39,19 @@ class ContentFileParserUtil {
     
     private static final Logger log = LoggerFactory.getLogger(ContentFileParserUtil.class);
     
-    private static final ContentFileParser JSON_PARSER;
+    private static final ContentParser JSON_PARSER;
     static {
         // workaround for JsonProvider classloader issue until https://issues.apache.org/jira/browse/GERONIMO-6560 is fixed
         ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
         try {
             Thread.currentThread().setContextClassLoader(ContentFileParserUtil.class.getClassLoader());
-            JSON_PARSER = ContentFileParserFactory.create(ContentFileType.JSON);
+            JSON_PARSER = ContentParserFactory.create(ContentType.JSON);
         }
         finally {
             Thread.currentThread().setContextClassLoader(oldClassLoader);
         }
     }
-    private static final ContentFileParser JCR_XML_PARSER = ContentFileParserFactory.create(ContentFileType.JCR_XML);
+    private static final ContentParser JCR_XML_PARSER = ContentParserFactory.create(ContentType.JCR_XML);
     
     private ContentFileParserUtil() {
         // static methods only
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
index f3e2c22..8e6a9be 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
@@ -18,7 +18,7 @@
  */
 package org.apache.sling.fsprovider.internal.parser;
 
-import org.apache.sling.fscontentparser.ContentFileType;
+import org.apache.sling.jcr.contentparser.ContentType;
 
 /**
  * Content file types.
@@ -28,12 +28,12 @@ public final class ContentFileTypes {
     /**
      * JSON content files.
      */
-    public static final String JSON_SUFFIX = "." + ContentFileType.JSON.getExtension();
+    public static final String JSON_SUFFIX = "." + ContentType.JSON.getExtension();
 
     /**
      * JCR XML content files.
      */
-    public static final String JCR_XML_SUFFIX = "." + ContentFileType.JCR_XML.getExtension();
+    public static final String JCR_XML_SUFFIX = "." + ContentType.JCR_XML.getExtension();
         
     private ContentFileTypes() {
         // static methods only
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
index 32fa3ca..50150c7 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
@@ -43,9 +43,9 @@ import javax.jcr.nodetype.NodeType;
 
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.fscontentparser.ParserOptions;
 import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin;
 import org.apache.sling.hamcrest.ResourceMatchers;
+import org.apache.sling.jcr.contentparser.ParserOptions;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 14/29: use Sling Parent 30

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 8290b1b3f9f1589e14a2bcb1b8b78fb40a3eb8c2
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Mon Mar 6 10:22:30 2017 +0000

    use Sling Parent 30
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1785621 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 3a525ce..def0725 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>30-SNAPSHOT</version>
+        <version>30</version>
         <relativePath />
     </parent>
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 24/29: SLING-6440 switch to latest jcr/contentparser API

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 259fc5804cce0d6c64bd784186d6ca199be97042
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Tue Mar 14 10:57:57 2017 +0000

    SLING-6440 switch to latest jcr/contentparser API
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1786877 13f79535-47bb-0310-9956-ffa450edef68
---
 .../fsprovider/internal/parser/ContentFileParserUtil.java  | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
index ff83c00..b32decf 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParserUtil.java
@@ -22,7 +22,10 @@ import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
 import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JCR_XML_SUFFIX;
 import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JSON_SUFFIX;
 
+import java.io.BufferedInputStream;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
@@ -68,10 +71,10 @@ class ContentFileParserUtil {
         }
         try {
             if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) {
-                return JSON_PARSER.parse(file);
+                return parse(JSON_PARSER, file);
             }
             else if (StringUtils.equals(file.getName(), DOT_CONTENT_XML) || StringUtils.endsWith(file.getName(), JCR_XML_SUFFIX)) {
-                return JCR_XML_PARSER.parse(file);
+                return parse(JCR_XML_PARSER, file);
             }
         }
         catch (Throwable ex) {
@@ -79,5 +82,12 @@ class ContentFileParserUtil {
         }
         return null;
     }
+    
+    private static Map<String,Object> parse(ContentParser contentParser, File file) throws IOException {
+        try (FileInputStream fis = new FileInputStream(file);
+                BufferedInputStream bis = new BufferedInputStream(fis)) {
+            return contentParser.parse(bis);
+        }
+    }
 
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 05/29: SLING-6440 fix invalid path in unit test

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 110f28bafe9e25408c5c0809b65b9a5676896862
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Tue Feb 21 14:21:15 2017 +0000

    SLING-6440 fix invalid path in unit test
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1783893 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/fsprovider/internal/InvalidRootFolderTest.java     | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java b/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java
index 9f38c1a..6d080b1 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/InvalidRootFolderTest.java
@@ -21,11 +21,14 @@ package org.apache.sling.fsprovider.internal;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 
+import java.io.File;
+
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.fsprovider.internal.TestUtils.RegisterFsResourcePlugin;
 import org.apache.sling.testing.mock.sling.ResourceResolverType;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.apache.sling.testing.mock.sling.junit.SlingContextBuilder;
+import org.apache.sling.testing.mock.sling.junit.SlingContextCallback;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -39,7 +42,14 @@ public class InvalidRootFolderTest {
 
     @Rule
     public SlingContext context = new SlingContextBuilder(ResourceResolverType.JCR_MOCK)
-        .plugin(new RegisterFsResourcePlugin("provider.file", "/invalid-folder"))
+        .plugin(new RegisterFsResourcePlugin("provider.file", "target/temp/invalid-folder"))
+        .afterTearDown(new SlingContextCallback() {
+            @Override
+            public void execute(SlingContext context) throws Exception {
+                File file = new File("target/temp/invalid-folder");
+                file.delete();
+            }
+        })
         .build();
 
     @Before

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 04/29: SLING-6440 Filesystem Resource Provider: Support "mounting" content resources from JSON files

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit fe118cc3aa74001d1b5acafa3ce3d7c21940d3a1
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Tue Feb 21 14:01:51 2017 +0000

    SLING-6440 Filesystem Resource Provider: Support "mounting" content resources from JSON files
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1783890 13f79535-47bb-0310-9956-ffa450edef68
---
 .../fsprovider/internal/{FsResource.java => mapper/FileResource.java}     | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java
similarity index 100%
rename from src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
rename to src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 19/29: SLING-6537 BigDecimal support, use copy of enhanced ValueMap with proper type converstion from Sling API 2.17.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit 32e4272f5cc62e353d7f4360ba452b4efcca0134
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Mar 10 14:58:17 2017 +0000

    SLING-6537 BigDecimal support, use copy of enhanced ValueMap with proper type converstion from Sling API 2.17.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1786361 13f79535-47bb-0310-9956-ffa450edef68
---
 .../fsprovider/internal/FsResourceProvider.java    |   3 +-
 .../fsprovider/internal/mapper/ContentFile.java    |   1 +
 .../fsprovider/internal/mapper/FileResource.java   |   2 +-
 .../internal/mapper/valuemap/DateUtils.java        |  99 +++++++++
 .../internal/mapper/valuemap/ObjectConverter.java  | 185 ++++++++++++++++
 .../mapper/valuemap/ValueMapDecorator.java         | 179 +++++++++++++++
 .../mapper/{ => valuemap}/ValueMapUtil.java        |   5 +-
 .../sling/fsprovider/internal/JsonContentTest.java |   5 +-
 .../sling/fsprovider/internal/TestUtils.java       |   2 +-
 .../internal/mapper/valuemap/Convert.java          | 242 +++++++++++++++++++++
 .../mapper/valuemap/ObjectConverterTest.java       | 233 ++++++++++++++++++++
 .../mapper/valuemap/ValueMapDecoratorTest.java     | 157 +++++++++++++
 .../mapper/{ => valuemap}/ValueMapUtilTest.java    |   2 +-
 13 files changed, 1107 insertions(+), 8 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index 1e2c53b..2fa1e3e 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -18,13 +18,14 @@
  */
 package org.apache.sling.fsprovider.internal;
 
+import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
-import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
 
 import org.apache.commons.collections.IteratorUtils;
 import org.apache.commons.collections.Predicate;
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
index 27e09d5..00c238a 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
@@ -25,6 +25,7 @@ import java.util.Map;
 import org.apache.commons.collections.IteratorUtils;
 import org.apache.commons.collections.Predicate;
 import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.fsprovider.internal.mapper.valuemap.ValueMapUtil;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 
 /**
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java
index 83f345e..ff276d9 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileResource.java
@@ -37,8 +37,8 @@ import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.api.wrappers.ValueMapDecorator;
 import org.apache.sling.fsprovider.internal.FsResourceProvider;
+import org.apache.sling.fsprovider.internal.mapper.valuemap.ValueMapDecorator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/DateUtils.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/DateUtils.java
new file mode 100644
index 0000000..cf985f5
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/DateUtils.java
@@ -0,0 +1,99 @@
+/*
+ * 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.valuemap;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.jackrabbit.util.ISO8601;
+
+/**
+ * This is copied from org.apache.sling.api.wrappers.impl.DateUtils
+ * to avoid dependency to latest Sling API.
+ * This can be removed when Sling API 2.17.0 or higher is referenced.
+ */
+final class DateUtils {
+    
+    private DateUtils() {
+        // static methods only
+    }
+
+    /**
+     * @param date Date value
+     * @return Calendar value or null
+     */
+    public static Calendar toCalendar(Date input) {
+        if (input == null) {
+            return null;
+        }
+        Calendar result = Calendar.getInstance();
+        result.setTime(input);
+        return result;
+    }
+
+    /**
+     * @param calendar Calendar value
+     * @return Date value or null
+     */
+    public static Date toDate(Calendar input) {
+        if (input == null) {
+            return null;
+        }
+        return input.getTime();
+    }
+    
+    /**
+     * @param input Date value
+     * @return ISO8601 string representation or null
+     */
+    public static String dateToString(Date input) {
+        return calendarToString(toCalendar(input));
+    }
+
+    /**
+     * @param input Calendar value
+     * @return ISO8601 string representation or null
+     */
+    public static String calendarToString(Calendar input) {
+        if (input == null) {
+            return null;
+        }
+        return ISO8601.format(input);
+    }
+
+    /**
+     * @param input ISO8601 string representation
+     * @return Date value or null
+     */
+    public static Date dateFromString(String input) {
+        return toDate(calendarFromString(input));
+    }
+
+    /**
+     * @param input ISO8601 string representation
+     * @return Calendar value or null
+     */
+    public static Calendar calendarFromString(String input) {
+        if (input == null) {
+            return null;
+        }
+        return ISO8601.parse(input);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ObjectConverter.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ObjectConverter.java
new file mode 100644
index 0000000..5d91e06
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ObjectConverter.java
@@ -0,0 +1,185 @@
+/*
+ * 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.valuemap;
+
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * This is copied from org.apache.sling.api.wrappers.impl.ObjectConverter
+ * to avoid dependency to latest Sling API.
+ * This can be removed when Sling API 2.17.0 or higher is referenced.
+ */
+final class ObjectConverter {
+    
+    private ObjectConverter() {
+        // static methods only
+    }
+
+    /**
+     * Converts the object to the given type.
+     * @param obj object
+     * @param type type
+     * @return the converted object
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T convert(Object obj, Class<T> type) {
+        if (obj == null) {
+            return null;
+        }
+        
+        // check if direct assignment is possible
+        if (type.isAssignableFrom(obj.getClass())) {
+            return (T)obj;
+        }
+        
+        // convert array elements individually
+        if (type.isArray()) {
+            return (T)convertToArray(obj, type.getComponentType());
+        }
+        
+        // convert Calendar in Date and vice versa
+        if (Calendar.class.isAssignableFrom(type) && obj instanceof Date) {
+            return (T)DateUtils.toCalendar((Date)obj);
+        }
+        if (type == Date.class && obj instanceof Calendar) {
+            return (T)DateUtils.toDate((Calendar)obj);
+        }
+
+        // no direct conversion - format to string and try to parse to target type 
+        String result = getSingleValue(obj);
+        if (result == null) {
+            return null;
+        }
+        if (type == String.class) {
+            return (T)result.toString();
+        }
+        if (type == Boolean.class) {
+            // do not rely on Boolean.parseBoolean to avoid converting nonsense to "false" without noticing
+            if ("true".equalsIgnoreCase(result)) {
+                return (T)Boolean.TRUE;
+            }
+            else if ("false".equalsIgnoreCase(result)) {
+                return (T)Boolean.FALSE;
+            }
+            else {
+                return null;
+            }
+        }
+        try {
+            if (type == Byte.class) {
+                return (T)(Byte)Byte.parseByte(result);
+            }
+            if (type == Short.class) {
+                return (T)(Short)Short.parseShort(result);
+            }
+            if (type == Integer.class) {
+                return (T)(Integer)Integer.parseInt(result);
+            }
+            if (type == Long.class) {
+                return (T)(Long)Long.parseLong(result);
+            }
+            if (type == Float.class) {
+                return (T)(Float)Float.parseFloat(result);
+            }
+            if (type == Double.class) {
+                return (T)(Double)Double.parseDouble(result);
+            }
+            if (type == BigDecimal.class) {
+                return (T)new BigDecimal(result);
+            }
+        }
+        catch (NumberFormatException e) {
+            return null;
+        }
+        if (Calendar.class.isAssignableFrom(type)) {
+            return (T)DateUtils.calendarFromString(result);
+        }
+        if (type == Date.class) {
+            return (T)DateUtils.dateFromString(result);
+        }
+        return null;
+    }
+
+    /**
+     * Gets a single value of String from the object. If the object is an array it returns it's first element.
+     * @param obj object or object array.
+     * @return result of <code>toString()</code> on object or first element of an object array. If @param obj is null
+     * or it's an array with first element that is null, then null is returned.
+     */
+    private static String getSingleValue(Object obj) {
+        final String result;
+        if (obj == null) {
+            result = null;
+        }
+        else if (obj.getClass().isArray()) {
+            if (Array.getLength(obj) == 0) {
+                result = null;
+            }
+            else {
+                result = getSingleValue(Array.get(obj, 0));
+            }
+        }
+        else if (obj instanceof Calendar) {
+            result = DateUtils.calendarToString((Calendar)obj);
+        }
+        else if (obj instanceof Date) {
+            result = DateUtils.dateToString((Date)obj);
+        }
+        else {
+            result = obj.toString();
+        }
+        return result;
+    }
+
+    /**
+     * Converts the object to an array of the given type
+     * @param obj the object or object array
+     * @param type the component type of the array
+     * @return and array of type T
+     */
+    @SuppressWarnings("unchecked")
+    private static <T> T[] convertToArray(Object obj, Class<T> type) {
+        if (obj.getClass().isArray()) {
+            List<Object> resultList = new ArrayList<Object>();
+            for (int i = 0; i < Array.getLength(obj); i++) {
+                T singleValueResult = convert(Array.get(obj, i), type);
+                if (singleValueResult != null) {
+                    resultList.add(singleValueResult);
+                }
+            }
+            return resultList.toArray((T[])Array.newInstance(type, resultList.size()));
+        }
+        else {
+            final T singleValueResult = convert(obj, type);
+            // return null for type conversion errors instead of single element array with value null
+            if (singleValueResult == null) {
+                return (T[])Array.newInstance(type, 0);
+            }
+            final T[] arrayResult = (T[])Array.newInstance(type, 1);
+            arrayResult[0] = singleValueResult;
+            return arrayResult;
+        }
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecorator.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecorator.java
new file mode 100644
index 0000000..4b0b5a5
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecorator.java
@@ -0,0 +1,179 @@
+/*
+ * 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.valuemap;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.resource.ValueMap;
+
+/**
+ * This is copied from org.apache.sling.api.wrappers.ValueMapDectorator
+ * to avoid dependency to latest Sling API.
+ * This can be removed when Sling API 2.17.0 or higher is referenced.
+ */
+public final class ValueMapDecorator implements ValueMap {
+
+    /**
+     * underlying map
+     */
+    private final Map<String, Object> base;
+
+    /**
+     * Creates a new wrapper around a given map.
+     * @param base wrapped object
+     */
+    public ValueMapDecorator(Map<String, Object> base) {
+        this.base = base;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public <T> T get(String name, Class<T> type) {
+        if (base instanceof ValueMap) {
+            // shortcut if decorated map is ValueMap
+            return ((ValueMap)base).get(name, type);
+        }
+        return ObjectConverter.convert(get(name), type);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @SuppressWarnings("unchecked")
+    public <T> T get(String name, T defaultValue) {
+        if (base instanceof ValueMap) {
+            // shortcut if decorated map is ValueMap
+            return ((ValueMap)base).get(name, defaultValue);
+        }
+        if (defaultValue == null) {
+            return (T)get(name);
+        }
+        T value = get(name, (Class<T>) defaultValue.getClass());
+        return value == null ? defaultValue : value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int size() {
+        return base.size();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isEmpty() {
+        return base.isEmpty();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsKey(Object key) {
+        return base.containsKey(key);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsValue(Object value) {
+        return base.containsValue(value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object get(Object key) {
+        return base.get(key);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object put(String key, Object value) {
+        return base.put(key, value);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object remove(Object key) {
+        return base.remove(key);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void putAll(Map<? extends String, ?> t) {
+        base.putAll(t);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void clear() {
+        base.clear();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<String> keySet() {
+        return base.keySet();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<Object> values() {
+        return base.values();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<Entry<String, Object>> entrySet() {
+        return base.entrySet();
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " : " + this.base.toString();
+    }
+
+    @Override
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode() {
+        return base.hashCode();
+    }
+
+    @Override
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object obj) {
+        return base.equals(obj);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java
similarity index 93%
rename from src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
rename to src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java
index 0d12ee1..b0a079c 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtil.java
@@ -16,16 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.fsprovider.internal.mapper;
+package org.apache.sling.fsprovider.internal.mapper.valuemap;
 
 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 {
+public final class ValueMapUtil {
     
     private ValueMapUtil() {
         // static methods only
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
index 74c7d81..cb5338d 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
@@ -28,6 +28,7 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import java.math.BigDecimal;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -138,6 +139,7 @@ public class JsonContentTest {
         assertEquals(true, props.get("booleanProp", false));
         assertEquals((Long)1234567890123L, props.get("longProp", Long.class));
         assertEquals((Double)1.2345d, props.get("decimalProp", Double.class), 0.00001d);
+        assertEquals(new BigDecimal("1.2345"), props.get("decimalProp", BigDecimal.class));
         
         assertArrayEquals(new String[] { "aa", "bb", "cc" }, props.get("stringPropMulti", String[].class));
         assertArrayEquals(new Long[] { 1234567890123L, 55L }, props.get("longPropMulti", Long[].class));
@@ -162,8 +164,9 @@ public class JsonContentTest {
         assertEquals(true, node.getProperty("booleanProp").getBoolean());
         assertEquals(PropertyType.LONG, node.getProperty("longProp").getType());
         assertEquals(1234567890123L, node.getProperty("longProp").getLong());
-        assertEquals(PropertyType.DOUBLE, node.getProperty("decimalProp").getType());
+        assertEquals(PropertyType.DECIMAL, node.getProperty("decimalProp").getType());
         assertEquals(1.2345d, node.getProperty("decimalProp").getDouble(), 0.00001d);
+        assertEquals(new BigDecimal("1.2345"), node.getProperty("decimalProp").getDecimal());
         
         assertEquals(PropertyType.STRING, node.getProperty("stringPropMulti").getType());
         assertTrue(node.getProperty("stringPropMulti").isMultiple());
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
index bbaf34b..6fed2b2 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
@@ -38,8 +38,8 @@ import org.apache.commons.lang3.CharEncoding;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.observation.ResourceChange;
-import org.apache.sling.api.resource.observation.ResourceChangeListener;
 import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
 import org.apache.sling.hamcrest.ResourceMatchers;
 import org.apache.sling.testing.mock.osgi.MapUtil;
 import org.apache.sling.testing.mock.osgi.context.AbstractContextPlugin;
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/Convert.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/Convert.java
new file mode 100644
index 0000000..15657cc
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/Convert.java
@@ -0,0 +1,242 @@
+/*
+ * 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.valuemap;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.lang.reflect.Array;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.commons.lang3.ClassUtils;
+
+/**
+ * This is copied from org.apache.sling.api.wrappers.impl.Convert
+ */
+final class Convert {
+    
+    private Convert() {
+        // static methods only
+    }
+    
+    @SuppressWarnings("unchecked")
+    public static class ConversionAssert<T,U> {
+        private final T input1;
+        private final T input2;
+        private Class<T> inputType;
+        private U expected1;
+        private U expected2;
+        private U nullValue;
+        private Class<U> expectedType;
+        
+        private ConversionAssert(T input1, T input2, boolean inputTypePrimitive) {
+            this.input1 = input1;
+            this.input2 = input2;
+            this.inputType = (Class<T>)input1.getClass();
+            if (inputTypePrimitive) {
+                this.inputType = (Class<T>)ClassUtils.wrapperToPrimitive(this.inputType);
+            }
+        }
+        
+        private void expected(U expected1, U expected2, boolean expectedTypePrimitive) {
+            this.expected1 = expected1;
+            this.expected2 = expected2;
+            this.expectedType = (Class<U>)expected1.getClass();
+            if (expectedTypePrimitive) {
+                expectedType = (Class<U>)ClassUtils.wrapperToPrimitive(this.expectedType);
+            }
+        }
+        
+        /**
+         * @param expected1 Singleton or first array expected result value
+         * @param expected2 Second array expected result value
+         * @return this
+         */
+        public ConversionAssert<T,U> to(U expected1, U expected2) {
+            expected(expected1, expected2, false);
+            return this;
+        }
+
+        /**
+         * @param expected1 Singleton or first array expected result value
+         * @param expected2 Second array expected result value
+         * @return this
+         */
+        public ConversionAssert<T,U> toPrimitive(U expected1, U expected2) {
+            expected(expected1, expected2, true);
+            return this;
+        }
+        
+        /**
+         * @param expected1 Singleton or first array expected result value
+         * @param expected2 Second array expected result value
+         * @return this
+         */
+        public ConversionAssert<T,U> toNull(Class<U> expectedType) {
+            expected1 = null;
+            expected2 = null;
+            this.expectedType = expectedType;
+            return this;
+        }
+        
+        /**
+         * @param nullValue Result value in case of null
+         */
+        public ConversionAssert<T,U> nullValue(U nullValue) {
+            this.nullValue = nullValue;
+            return this;
+        }
+        
+        /**
+         * Do assertion
+         */
+        public void test() {
+            Class<U[]> expectedArrayType = (Class<U[]>)Array.newInstance(this.expectedType, 0).getClass();
+            assertPermuations(input1, input2, inputType, expected1, expected2, nullValue, expectedType, expectedArrayType);
+        }
+    }
+
+    /**
+     * @param input1 Singleton or first array input value
+     * @param input2 Second array input value
+     */
+    public static <T,U> ConversionAssert<T,U> from(T input1, T input2) {
+        return new ConversionAssert<T,U>(input1, input2, false);
+    }
+
+    /**
+     * @param input1 Singleton or first array input value
+     * @param input2 Second array input value
+     */
+    public static <T,U> ConversionAssert<T,U> fromPrimitive(T input1, T input2) {
+        return new ConversionAssert<T,U>(input1, input2, true);
+    }
+
+    private static <T,U> void assertPermuations(T input1, T input2, Class<T> inputType,
+            U expected1, U expected2, U nullValue, Class<U> expectedType, Class<U[]> expectedArrayType) {
+        
+        // single value to single value
+        assertConversion(expected1, input1, expectedType);
+        
+        // single value to array
+        Object expectedSingletonArray;
+        if (expected1 == null && expected2 == null) {
+            expectedSingletonArray = Array.newInstance(expectedType, 0);
+        }
+        else {
+            expectedSingletonArray = Array.newInstance(expectedType, 1);
+            Array.set(expectedSingletonArray, 0, expected1);
+        }
+        assertConversion(expectedSingletonArray, input1, expectedArrayType);
+        
+        // array to array
+        Object inputDoubleArray = Array.newInstance(inputType, 2);
+        Array.set(inputDoubleArray, 0, input1);
+        Array.set(inputDoubleArray, 1, input2);
+        Object expectedDoubleArray;
+        if (expected1 == null && expected2 == null) {
+            expectedDoubleArray = Array.newInstance(expectedType, 0);
+        }
+        else {
+            expectedDoubleArray = Array.newInstance(expectedType, 2);
+            Array.set(expectedDoubleArray, 0,  expected1);
+            Array.set(expectedDoubleArray, 1,  expected2);
+        }
+        assertConversion(expectedDoubleArray, inputDoubleArray, expectedArrayType);
+        
+        // array to single (first) value
+        assertConversion(expected1, inputDoubleArray, expectedType);
+        
+        // null to single value
+        assertConversion(nullValue, null, expectedType);
+        
+        // null to array
+        assertConversion(null, null, expectedArrayType);
+        
+        // empty array to single value
+        Object inputEmptyArray = Array.newInstance(inputType, 0);
+        assertConversion(nullValue, inputEmptyArray, expectedType);
+
+        // empty array to array
+        Object expectedEmptyArray = Array.newInstance(expectedType, 0);
+        assertConversion(expectedEmptyArray, inputEmptyArray, expectedArrayType);
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static <T,U> void assertConversion(Object expected, Object input, Class<U> type) {
+        U result = ObjectConverter.convert(input, type);
+        String msg = "Convert '" + toString(input) + "' to " + type.getSimpleName();
+        if (expected == null) {
+            assertNull(msg, result);
+        }
+        else if (expected.getClass().isArray()) {
+            assertArrayEquals(msg, (U[])toStringIfDate(expected), (U[])toStringIfDate(result));
+        }
+        else {
+            assertEquals(msg, toStringIfDate(expected), toStringIfDate(result));
+        }
+    }
+    
+    private static String toString(Object input) {
+        if (input == null) {
+            return "null";
+        }
+        else if (input.getClass().isArray()) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("[");
+            for (int i=0; i<Array.getLength(input); i++) {
+                if (i > 0) {
+                    sb.append(",");
+                }
+                sb.append(toString(Array.get(input, i)));
+            }
+            sb.append("]");
+            return sb.toString();
+        }
+        else {
+            return toStringIfDate(input).toString();
+        }
+    }
+    
+    private static Object toStringIfDate(Object input) {
+        if (input == null) {
+            return null;
+        }
+        if (input instanceof Calendar) {
+            return "(Calendar)" + DateUtils.calendarToString((Calendar)input);
+        }
+        if (input instanceof Date) {
+            return "(Date)" + DateUtils.dateToString((Date)input);
+        }
+        if (input.getClass().isArray()) {
+            if (Calendar.class.isAssignableFrom(input.getClass().getComponentType())
+                    || input.getClass().getComponentType() == Date.class) {
+                Object[] resultArray = new String[Array.getLength(input)];
+                for (int i=0; i<Array.getLength(input); i++) {
+                    resultArray[i] = toStringIfDate(Array.get(input, i));
+                }
+                return resultArray;
+            }
+        }
+        return input;
+    }
+        
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ObjectConverterTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ObjectConverterTest.java
new file mode 100644
index 0000000..8c21a70
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ObjectConverterTest.java
@@ -0,0 +1,233 @@
+/*
+ * 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.valuemap;
+
+import static org.apache.sling.fsprovider.internal.mapper.valuemap.DateUtils.calendarToString;
+import static org.apache.sling.fsprovider.internal.mapper.valuemap.DateUtils.toCalendar;
+import static org.apache.sling.fsprovider.internal.mapper.valuemap.DateUtils.toDate;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertNull;
+
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Test;
+
+/**
+ * This is copied from org.apache.sling.api.wrappers.impl.ObjectConverterTest
+ */
+public class ObjectConverterTest {
+    
+    private static final String STRING_1 = "item1";
+    private static final String STRING_2 = "item2";
+    private static final boolean BOOLEAN_1 = true;
+    private static final boolean BOOLEAN_2 = false;
+    private static final byte BYTE_1 = (byte)0x01;
+    private static final byte BYTE_2 = (byte)0x02;
+    private static final short SHORT_1 = (short)12;
+    private static final short SHORT_2 = (short)34;
+    private static final int INT_1 = 55;
+    private static final int INT_2 = -123;
+    private static final long LONG_1 = 1234L;
+    private static final long LONG_2 = -4567L;
+    private static final float FLOAT_1 = 1.23f;
+    private static final float FLOAT_2 = -4.56f;
+    private static final double DOUBLE_1 = 12.34d;
+    private static final double DOUBLE_2 = -45.67d;
+    private static final BigDecimal BIGDECIMAL_1 = new BigDecimal("12345.67");
+    private static final BigDecimal BIGDECIMAL_2 = new BigDecimal("-23456.78");
+    private static final Calendar CALENDAR_1 = Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.US);
+    private static final Calendar CALENDAR_2 = Calendar.getInstance(TimeZone.getTimeZone("GMT"), Locale.US);
+    {
+        CALENDAR_1.set(2016, 10, 15, 8, 20, 30);
+        CALENDAR_2.set(2015, 6, 31, 19, 10, 20);
+    }
+    private static final Date DATE_1 = toDate(CALENDAR_1);
+    private static final Date DATE_2 = toDate(CALENDAR_2);
+
+    @Test
+    public void testDateToString() {
+        Convert.from(STRING_1, STRING_2).to(STRING_1, STRING_2).test();
+        Convert.fromPrimitive(BOOLEAN_1, BOOLEAN_2).to(Boolean.toString(BOOLEAN_1), Boolean.toString(BOOLEAN_2)).test();
+        Convert.from(BOOLEAN_1, BOOLEAN_2).to(Boolean.toString(BOOLEAN_1), Boolean.toString(BOOLEAN_2)).test();
+        Convert.fromPrimitive(BYTE_1, BYTE_2).to(Byte.toString(BYTE_1), Byte.toString(BYTE_2)).test();
+        Convert.from(BYTE_1, BYTE_2).to(Byte.toString(BYTE_1), Byte.toString(BYTE_2)).test();
+        Convert.fromPrimitive(SHORT_1, SHORT_2).to(Short.toString(SHORT_1), Short.toString(SHORT_2)).test();
+        Convert.from(SHORT_1, SHORT_2).to(Short.toString(SHORT_1), Short.toString(SHORT_2)).test();
+        Convert.fromPrimitive(INT_1, INT_2).to(Integer.toString(INT_1), Integer.toString(INT_2)).test();
+        Convert.from(INT_1, INT_2).to(Integer.toString(INT_1), Integer.toString(INT_2)).test();
+        Convert.fromPrimitive(LONG_1, LONG_2).to(Long.toString(LONG_1), Long.toString(LONG_2)).test();
+        Convert.from(LONG_1, LONG_2).to(Long.toString(LONG_1), Long.toString(LONG_2)).test();
+        Convert.fromPrimitive(FLOAT_1, FLOAT_2).to(Float.toString(FLOAT_1), Float.toString(FLOAT_2)).test();
+        Convert.from(FLOAT_1, FLOAT_2).to(Float.toString(FLOAT_1), Float.toString(FLOAT_2)).test();
+        Convert.fromPrimitive(DOUBLE_1, DOUBLE_2).to(Double.toString(DOUBLE_1), Double.toString(DOUBLE_2)).test();
+        Convert.from(DOUBLE_1, DOUBLE_2).to(Double.toString(DOUBLE_1), Double.toString(DOUBLE_2)).test();
+        Convert.from(BIGDECIMAL_1, BIGDECIMAL_2).to(BIGDECIMAL_1.toString(), BIGDECIMAL_2.toString()).test();
+        Convert.from(CALENDAR_1, CALENDAR_2).to(calendarToString(CALENDAR_1), calendarToString(CALENDAR_2)).test();
+        Convert.from(DATE_1, DATE_2).to(calendarToString(toCalendar(DATE_1)), calendarToString(toCalendar(DATE_2))).test();
+    }
+    
+    @Test
+    public void testToBoolean() {
+        Convert.fromPrimitive(BOOLEAN_1, BOOLEAN_2).to(BOOLEAN_1, BOOLEAN_2).test();
+        Convert.from(BOOLEAN_1, BOOLEAN_2).to(BOOLEAN_1, BOOLEAN_2).test();
+        Convert.from(Boolean.toString(BOOLEAN_1), Boolean.toString(BOOLEAN_2)).to(BOOLEAN_1, BOOLEAN_2).test();
+        
+        // test other types that should not be converted
+        Convert.<Integer,Boolean>from(INT_1, INT_2).toNull(Boolean.class).test();
+        Convert.<Date,Boolean>from(DATE_1, DATE_2).toNull(Boolean.class).test();
+    }
+    
+    @Test
+    public void testToByte() {
+        Convert.fromPrimitive(BYTE_1, BYTE_2).to(BYTE_1, BYTE_2).test();
+        Convert.from(BYTE_1, BYTE_2).to(BYTE_1, BYTE_2).test();
+        Convert.from(Byte.toString(BYTE_1), Byte.toString(BYTE_2)).to(BYTE_1, BYTE_2).test();
+        
+        // test conversion from other number types
+        Convert.from(INT_1, INT_2).to((byte)INT_1, (byte)INT_2).test();
+        Convert.fromPrimitive(INT_1, INT_2).to((byte)INT_1, (byte)INT_2).test();
+
+        // test other types that should not be converted
+        Convert.<Date,Byte>from(DATE_1, DATE_2).toNull(Byte.class).test();
+    }
+    
+    @Test
+    public void testToShort() {
+        Convert.fromPrimitive(SHORT_1, SHORT_2).to(SHORT_1, SHORT_2).test();
+        Convert.from(SHORT_1, SHORT_2).to(SHORT_1, SHORT_2).test();
+        Convert.from(Short.toString(SHORT_1), Short.toString(SHORT_2)).to(SHORT_1, SHORT_2).test();
+        
+        // test conversion from other number types
+        Convert.from(INT_1, INT_2).to((short)INT_1, (short)INT_2).test();
+        Convert.fromPrimitive(INT_1, INT_2).to((short)INT_1, (short)INT_2).test();
+
+        // test other types that should not be converted
+        Convert.<Date,Short>from(DATE_1, DATE_2).toNull(Short.class).test();
+    }
+    
+    @Test
+    public void testToInteger() {
+        Convert.fromPrimitive(INT_1, INT_2).to(INT_1, INT_2).test();
+        Convert.from(INT_1, INT_2).to(INT_1, INT_2).test();
+        Convert.from(Integer.toString(INT_1), Integer.toString(INT_2)).to(INT_1, INT_2).test();
+        
+        // test conversion from other number types
+        Convert.from(SHORT_1, SHORT_2).to((int)SHORT_1, (int)SHORT_2).test();
+        Convert.fromPrimitive(SHORT_1, SHORT_2).to((int)SHORT_1, (int)SHORT_2).test();
+
+        // test other types that should not be converted
+        Convert.<Date,Integer>from(DATE_1, DATE_2).toNull(Integer.class).test();
+    }
+    
+    @Test
+    public void testToLong() {
+        Convert.fromPrimitive(LONG_1, LONG_2).to(LONG_1, LONG_2).test();
+        Convert.from(LONG_1, LONG_2).to(LONG_1, LONG_2).test();
+        Convert.from(Long.toString(LONG_1), Long.toString(LONG_2)).to(LONG_1, LONG_2).test();
+        
+        // test conversion from other number types
+        Convert.from(SHORT_1, SHORT_2).to((long)SHORT_1, (long)SHORT_2).test();
+        Convert.fromPrimitive(SHORT_1, SHORT_2).to((long)SHORT_1, (long)SHORT_2).test();
+
+        // test other types that should not be converted
+        Convert.<Date,Long>from(DATE_1, DATE_2).toNull(Long.class).test();
+    }
+    
+    @Test
+    public void testToFloat() {
+        Convert.fromPrimitive(FLOAT_1, FLOAT_2).to(FLOAT_1, FLOAT_2).test();
+        Convert.from(FLOAT_1, FLOAT_2).to(FLOAT_1, FLOAT_2).test();
+        Convert.from(Float.toString(FLOAT_1), Float.toString(FLOAT_2)).to(FLOAT_1, FLOAT_2).test();
+        
+        // test conversion from other number types
+        Convert.from(SHORT_1, SHORT_2).to((float)SHORT_1, (float)SHORT_2).test();
+        Convert.fromPrimitive(SHORT_1, SHORT_2).to((float)SHORT_1, (float)SHORT_2).test();
+
+        // test other types that should not be converted
+        Convert.<Date,Float>from(DATE_1, DATE_2).toNull(Float.class).test();
+    }
+    
+    @Test
+    public void testToDouble() {
+        Convert.fromPrimitive(DOUBLE_1, DOUBLE_2).to(DOUBLE_1, DOUBLE_2).test();
+        Convert.from(DOUBLE_1, DOUBLE_2).to(DOUBLE_1, DOUBLE_2).test();
+        Convert.from(Double.toString(DOUBLE_1), Double.toString(DOUBLE_2)).to(DOUBLE_1, DOUBLE_2).test();
+        
+        // test conversion from other number types
+        Convert.from(SHORT_1, SHORT_2).to((double)SHORT_1, (double)SHORT_2).test();
+        Convert.fromPrimitive(SHORT_1, SHORT_2).to((double)SHORT_1, (double)SHORT_2).test();
+
+        // test other types that should not be converted
+        Convert.<Date,Double>from(DATE_1, DATE_2).toNull(Double.class).test();
+    }
+    
+    @Test
+    public void testToBigDecimal() {
+        Convert.from(BIGDECIMAL_1, BIGDECIMAL_2).to(BIGDECIMAL_1, BIGDECIMAL_2).test();
+        Convert.from(BIGDECIMAL_1.toString(), BIGDECIMAL_2.toString()).to(BIGDECIMAL_1, BIGDECIMAL_2).test();
+        
+        // test conversion from other number types
+        Convert.from(LONG_1, LONG_2).to(BigDecimal.valueOf(LONG_1), BigDecimal.valueOf(LONG_2)).test();
+        Convert.fromPrimitive(LONG_1, LONG_2).to(BigDecimal.valueOf(LONG_1), BigDecimal.valueOf(LONG_2)).test();
+        Convert.from(DOUBLE_1, DOUBLE_2).to(BigDecimal.valueOf(DOUBLE_1), BigDecimal.valueOf(DOUBLE_2)).test();
+        Convert.fromPrimitive(DOUBLE_1, DOUBLE_2).to(BigDecimal.valueOf(DOUBLE_1), BigDecimal.valueOf(DOUBLE_2)).test();
+
+        // test other types that should not be converted
+        Convert.<Date,BigDecimal>from(DATE_1, DATE_2).toNull(BigDecimal.class).test();
+    }
+    
+    @Test
+    public void testToCalendar() {
+        Convert.from(CALENDAR_1, CALENDAR_2).to(CALENDAR_1, CALENDAR_2).test();
+        Convert.from(DateUtils.calendarToString(CALENDAR_1), DateUtils.calendarToString(CALENDAR_2)).to(CALENDAR_1, CALENDAR_2).test();
+        
+        // test conversion from other date types
+        Convert.from(DATE_1, DATE_2).to(toCalendar(DATE_1), toCalendar(DATE_2)).test();
+
+        // test other types that should not be converted
+        Convert.<String,Calendar>from(STRING_1, STRING_2).toNull(Calendar.class).test();
+        Convert.<Boolean,Calendar>from(BOOLEAN_1, BOOLEAN_2).toNull(Calendar.class).test();
+    }
+    
+    @Test
+    public void testToDate() {
+        Convert.from(DATE_1, DATE_2).to(DATE_1, DATE_2).test();
+        Convert.from(DateUtils.dateToString(DATE_1), DateUtils.dateToString(DATE_2)).to(DATE_1, DATE_2).test();
+        
+        // test conversion from other date types
+        Convert.from(CALENDAR_1, CALENDAR_2).to(toDate(CALENDAR_1), toDate(CALENDAR_2)).test();
+
+        // test other types that should not be converted
+        Convert.<String,Date>from(STRING_1, STRING_2).toNull(Date.class).test();
+        Convert.<Boolean,Date>from(BOOLEAN_1, BOOLEAN_2).toNull(Date.class).test();
+    }
+    
+    @Test
+    public void testPrimitiveByteArray() {
+        byte[] array = new byte[] { 0x01, 0x02, 0x03 };
+        assertArrayEquals(array, ObjectConverter.convert(array, byte[].class));
+        assertArrayEquals(new byte[0], ObjectConverter.convert(new byte[0], byte[].class));
+        assertNull(ObjectConverter.convert(null, byte[].class));
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecoratorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecoratorTest.java
new file mode 100644
index 0000000..88827e0
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapDecoratorTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.valuemap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.api.resource.ValueMap;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * This is copied from org.apache.sling.api.wrappers.ValueMapDecoratorTest
+ */
+public class ValueMapDecoratorTest {
+
+    private Map<String, Object> map;
+    private ValueMap valueMap;
+
+    @Before
+    public void setUp() {
+        map = new HashMap<String, Object>();
+        valueMap = new ValueMapDecorator(map);
+    }
+
+    // SLING-4178
+    @Test
+    public void testIncompatibleTypeInArray() {
+        map.put("prop1", new String[] { "test", "test2" });
+        map.put("prop2", "test");
+        Assert.assertArrayEquals("Not convertible type should return empty array", new Integer[0], valueMap.get("prop1", Integer[].class));
+        Assert.assertArrayEquals("Not convertible type should return empt array", new Integer[0], valueMap.get("prop2", Integer[].class));
+    }
+
+    // SLING-662
+    @Test
+    public void testGettingArraysFromSingleValueEntries() {
+        map.put("prop1", "test");
+        map.put("prop2", "1");
+        Assert.assertArrayEquals(
+                "Even though the underlying entry is single-value if should be enclosed in a single element array",
+                new String[] { "test" }, valueMap.get("prop1", String[].class));
+        Assert.assertArrayEquals(
+                "Even though the underlying entry is single-value if should be enclosed in a single element array",
+                new Integer[] { 1 }, valueMap.get("prop2", Integer[].class));
+    }
+
+    @Test
+    public void testGettingArraysFromMultiValueEntries() {
+        map.put("prop1", new String[] { "test", "test2" });
+        map.put("prop2", new String[] { "1", "2" });
+        Assert.assertArrayEquals("Could not get values from underlying array", new String[] { "test", "test2" },
+                valueMap.get("prop1", String[].class));
+        Assert.assertArrayEquals("Conversion to Integer was not possible", new Integer[] { 1, 2 },
+                valueMap.get("prop2", Integer[].class));
+    }
+
+    @Test
+    public void testGettingSingleValuesFromMultiValueEntries() {
+        map.put("prop1", new String[] { "test", "test2" });
+        map.put("prop2", new String[] { "1", "2" });
+        Assert.assertEquals("First element from underlying array should be returned", "test",
+                valueMap.get("prop1", String.class));
+        Assert.assertEquals("First element from underlying array should be returned", Integer.valueOf(1),
+                valueMap.get("prop2", Integer.class));
+    }
+
+    @Test
+    public void testGettingInvalidEntryWithDefaultValue() {
+        Assert.assertEquals(Integer.valueOf(1), valueMap.get("prop1", 1));
+        Assert.assertEquals("test", valueMap.get("prop1", "test"));
+    }
+
+    @Test
+    public void testPrimitiveTypes() {
+        map.put("prop1", new String[] { "1", "2" });
+        Assert.assertNull("ValueMap should not support conversion to primitive type", valueMap.get("prop1", int.class));
+    }
+    @Test(expected=ClassCastException.class)
+    public void testPrimitiveTypesArray() {
+        map.put("prop1", new String[] { "1", "2" });
+        Assert.assertArrayEquals("ValueMap should not support conversion to array of primitive type",
+                new int[0], valueMap.get("prop1", int[].class));
+    }
+
+    @Test
+    public void testEqualsAndHashCodeOfEqualValueMapsWithNonArrayTypes() {
+        map.put("prop1", "some string");
+        ValueMapDecorator valueMap2 = new ValueMapDecorator(map);
+        Assert.assertTrue("Two ValueMapDecorators based on the same map should be equal", valueMap.equals(valueMap2));
+        Assert.assertEquals("Two equal ValueMapDecorators should have the same hash code", valueMap.hashCode(),
+                valueMap2.hashCode());
+
+        ValueMapDecorator valueMap3 = new ValueMapDecorator(new HashMap<String, Object>());
+        valueMap3.put("prop1", "some string");
+        Assert.assertEquals(valueMap, valueMap3);
+        Assert.assertEquals("Two equal ValueMapDecorators should have the same hash code", valueMap.hashCode(),
+                valueMap3.hashCode());
+
+        Assert.assertEquals(map, valueMap);
+        Assert.assertEquals(valueMap, map);
+    }
+
+    @Ignore("SLING-4784")
+    @Test
+    public void testEqualsAndHashCodeOfEqualValueMapsWithArrayTypes() {
+        map.put("prop1", new String[] { "1", "2" });
+        ValueMapDecorator valueMap2 = new ValueMapDecorator(map);
+        Assert.assertTrue("Two ValueMapDecorators based on the same map should be equal", valueMap.equals(valueMap2));
+        Assert.assertEquals("Two equal ValueMapDecorators should have the same hash code", valueMap.hashCode(),
+                valueMap2.hashCode());
+
+        ValueMapDecorator valueMap3 = new ValueMapDecorator(new HashMap<String, Object>());
+        valueMap3.put("prop1", new String[] { "1", "2" });
+        Assert.assertEquals(valueMap, valueMap3);
+        Assert.assertEquals("Two equal ValueMapDecorators should have the same hash code", valueMap.hashCode(),
+                valueMap3.hashCode());
+    }
+
+    @Test
+    public void testEqualsOfInequalValueMapsWithNonArrayTypes() {
+        valueMap.put("prop", "value");
+        ValueMapDecorator valueMap2 = new ValueMapDecorator(new HashMap<String, Object>());
+        valueMap2.put("prop", "value2");
+        Assert.assertFalse("Two ValueMapDecorators based on maps with different entries should not be equal",
+                valueMap.equals(valueMap2));
+
+    }
+
+    @Test
+    public void testEqualsOfInequalValueMapsWithArrayTypes() {
+        valueMap.put("prop", new String[] { "1", "2" });
+        ValueMapDecorator valueMap2 = new ValueMapDecorator(new HashMap<String, Object>());
+        valueMap2.put("prop", new String[] { "3", "4" });
+        Assert.assertFalse("Two ValueMapDecorators based on maps with different entries should not be equal",
+                valueMap.equals(valueMap2));
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtilTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java
similarity index 97%
rename from src/test/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtilTest.java
rename to src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java
index 71d6a38..87f2bfd 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtilTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/valuemap/ValueMapUtilTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.fsprovider.internal.mapper;
+package org.apache.sling.fsprovider.internal.mapper.valuemap;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 22/29: cosmetic: use consistent wording, reorder config attributes

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit ee0c02d4d86b217e80b2dba7a33c004828157fc1
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Mar 10 16:29:33 2017 +0000

    cosmetic: use consistent wording, reorder config attributes
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1786379 13f79535-47bb-0310-9956-ffa450edef68
---
 .../apache/sling/fsprovider/internal/FsMode.java   |  2 +-
 .../fsprovider/internal/FsResourceProvider.java    | 43 +++++++++-------------
 .../internal/mapper/FileVaultResourceMapper.java   |  2 +-
 .../sling/fsprovider/internal/FileMonitorTest.java |  2 +-
 .../internal/FileVaultFileMonitorTest.java         |  2 +-
 .../sling/fsprovider/internal/FilesFolderTest.java |  2 +-
 .../fsprovider/internal/JcrXmlContentTest.java     |  2 +-
 .../sling/fsprovider/internal/JsonContentTest.java |  2 +-
 8 files changed, 25 insertions(+), 32 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java b/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
index 50eeebd..b255bb2 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsMode.java
@@ -29,7 +29,7 @@ public enum FsMode {
     FILES_FOLDERS,
 
     /**
-     * Sling-Initial-Content filesystem layout, supports file and folders ant content files in JSON and jcr.xml format.
+     * Sling-Initial-Content file system layout, supports file and folders ant content files in JSON and jcr.xml format.
      */
     INITIAL_CONTENT,
     
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index 8863bdf..bb6a6e5 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -80,14 +80,10 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
     public static final String RESOURCE_METADATA_FILE_DIRECTORY = ":org.apache.sling.fsprovider.file.directory";
     
     @ObjectClassDefinition(name = "Apache Sling File System Resource Provider",
-            description = "Configure an instance of the filesystem " +
-                          "resource provider in terms of provider root and filesystem location")
+            description = "Configure an instance of the file system " +
+                          "resource provider in terms of provider root and file system 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 = "File System Root",
                 description = "File system directory mapped to the virtual " +
                         "resource tree. This property must not be an empty string. If the path is " +
@@ -96,41 +92,38 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
                         "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 Root",
                 description = "Location in the virtual resource tree where the " +
-                "filesystem resources are mapped in. This property must not be an empty string.")
+                "file system resources are mapped in. This property must not be an empty string.")
         String provider_root();
         
-        @AttributeDefinition(name = "Filesystem layout",
-                description = "Filesystem layout mode for files, folders and content.",
+        @AttributeDefinition(name = "File system layout",
+                description = "File system layout mode for files, folders and content.",
                 options={
                         @Option(value="FILES_FOLDERS", label="FILES_FOLDERS - "
                                 + "Support only files and folders (classic mode)"),
                         @Option(value="INITIAL_CONTENT", label="INITIAL_CONTENT - "
-                                + "Sling-Initial-Content filesystem layout, supports file and folders ant content files in JSON and jcr.xml format"),
+                                + "Sling-Initial-Content file system layout, supports file and folders ant content files in JSON and jcr.xml format"),
                         @Option(value="FILEVAULT_XML", label="FILEVAULT_XML - "
                                 + "FileVault XML format (expanded content package)"),
                 })
         FsMode provider_fs_mode() default FsMode.FILES_FOLDERS;
         
         @AttributeDefinition(name = "Init. Content Options",
-                description = "Import options for Sling-Initial-Content filesystem layout. Supported options: overwrite, ignoreImportProviders.")
+                description = "Import options for Sling-Initial-Content file system layout. Supported options: overwrite, ignoreImportProviders.")
         String provider_initial_content_import_options();
         
         @AttributeDefinition(name = "FileVault Filter",
-                description = "Path to META-INF/vault/filter.xml when using FileVault XML filesystem layout.")
+                description = "Path to META-INF/vault/filter.xml when using FileVault XML file system layout.")
         String provider_filevault_filterxml_path();
         
+
+        @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 = "Cache Size",
                 description = "Max. number of content files cached in memory.")
         int provider_cache_size() default 10000;
@@ -150,13 +143,13 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
     // The monitor to detect file changes.
     private FileMonitor monitor;
     
-    // maps filesystem to resources
+    // maps file system to resources
     private FsMode fsMode;
     private FsResourceMapper fileMapper;
     private FsResourceMapper contentFileMapper;
     private FileVaultResourceMapper fileVaultMapper;
     
-    // if true resources from filesystem are only "overlayed" to JCR resources, serving JCR as fallback within the same path
+    // if true resources from file system are only "overlayed" to JCR resources, serving JCR as fallback within the same path
     private boolean overlayParentResourceProvider;
     
     // cache for parsed content files
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
index cb25230..6585d1e 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/FileVaultResourceMapper.java
@@ -104,7 +104,7 @@ public final class FileVaultResourceMapper implements FsResourceMapper {
             }
         }
         
-        // additional check for children in filesystem
+        // additional check for children in file system
         File parentFile = getFile(parentPath);
         if (parentFile != null && parentFile.isDirectory()) {
             for (File childFile : parentFile.listFiles()) {
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
index bc4892f..d2b1143 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
@@ -39,7 +39,7 @@ import org.junit.Rule;
 import org.junit.Test;
 
 /**
- * Test events when changing filesystem content (Sling-Initial-Content).
+ * Test events when changing file system content (Sling-Initial-Content).
  */
 public class FileMonitorTest {
 
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java
index 9743027..76e0525 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java
@@ -39,7 +39,7 @@ import org.junit.Rule;
 import org.junit.Test;
 
 /**
- * Test events when changing filesystem content (FileVault XML).
+ * Test events when changing file system content (FileVault XML).
  */
 public class FileVaultFileMonitorTest {
     
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
index 1930a94..c1f3470 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
@@ -34,7 +34,7 @@ import org.junit.Rule;
 import org.junit.Test;
 
 /**
- * Test access to files and folders from filesystem.
+ * Test access to files and folders from file system.
  */
 public class FilesFolderTest {
 
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
index c1dc1c2..5306566 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
@@ -46,7 +46,7 @@ import org.junit.Test;
 import com.google.common.collect.ImmutableList;
 
 /**
- * Test access to files and folders from filesystem.
+ * Test access to files and folders from file system.
  */
 public class JcrXmlContentTest {
 
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
index cb5338d..32fa3ca 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
@@ -57,7 +57,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 /**
- * Test access to files and folders and JSON content from filesystem.
+ * Test access to files and folders and JSON content from file system.
  */
 public class JsonContentTest {
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-fsresource] 06/29: SLING-6440 further improvements for content file/json support: - enhance jcr api layer - make sure jcr primary type is always defined - send events for all nodes in content files - introduce simple LRU memory cache for parsed content files

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.fsresource-2.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git

commit eadcbb2fe9cd27feb4e1e6049ef78e2eef4ff3da
Author: Stefan Seifert <ss...@apache.org>
AuthorDate: Fri Feb 24 20:45:26 2017 +0000

    SLING-6440 further improvements for content file/json support:
    - enhance jcr api layer
    - make sure jcr primary type is always defined
    - send events for all nodes in content files
    - introduce simple LRU memory cache for parsed content files
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@1784324 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/fsprovider/internal/FileMonitor.java     | 130 +++++++++++++----
 .../fsprovider/internal/FsResourceProvider.java    |  25 +++-
 .../fsprovider/internal/mapper/ContentFile.java    |  26 +++-
 .../internal/mapper/ContentFileResource.java       |   7 +-
 .../internal/mapper/ContentFileResourceMapper.java |  19 +--
 .../fsprovider/internal/mapper/ValueMapUtil.java   |   9 ++
 .../fsprovider/internal/mapper/jcr/FsNode.java     |  35 +++--
 .../fsprovider/internal/mapper/jcr/FsNodeType.java | 157 +++++++++++++++++++++
 .../fsprovider/internal/mapper/jcr/FsProperty.java |  10 +-
 .../internal/mapper/jcr/FsPropertyDefinition.java  | 100 +++++++++++++
 .../internal/parser/ContentFileCache.java          | 107 ++++++++++++++
 .../internal/parser/ContentFileParser.java         |  20 ++-
 ...ontentFileParser.java => ContentFileTypes.java} |  25 +---
 .../sling/fsprovider/internal/FileMonitorTest.java |  20 ++-
 .../sling/fsprovider/internal/FilesFolderTest.java |   1 +
 .../sling/fsprovider/internal/JsonContentTest.java |  23 ++-
 .../internal/mapper/ContentFileTest.java           |  13 +-
 .../internal/parser/ContentFileCacheTest.java      |  92 ++++++++++++
 src/test/resources/fs-test/folder2/content.json    |   2 +-
 .../fs-test/folder2/content/file2content.txt       |   1 +
 20 files changed, 713 insertions(+), 109 deletions(-)

diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
index 7249912..05d01b7 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -19,13 +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.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.resource.observation.ResourceChange;
 import org.apache.sling.api.resource.observation.ResourceChange.ChangeType;
+import org.apache.sling.fsprovider.internal.mapper.ContentFile;
+import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.apache.sling.spi.resource.provider.ObservationReporter;
 import org.apache.sling.spi.resource.provider.ObserverConfiguration;
 import org.slf4j.Logger;
@@ -37,8 +43,7 @@ import org.slf4j.LoggerFactory;
  */
 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;
@@ -49,18 +54,21 @@ public final class FileMonitor extends TimerTask {
     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, final ContentFileExtensions contentFileExtensions) {
+    public FileMonitor(final FsResourceProvider provider, final long interval,
+            final ContentFileExtensions contentFileExtensions, final ContentFileCache contentFileCache) {
         this.provider = provider;
         this.contentFileExtensions = contentFileExtensions;
+        this.contentFileCache = contentFileCache;
         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);
+        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);
     }
 
@@ -90,7 +98,7 @@ public final class FileMonitor extends TimerTask {
                 Thread.currentThread().interrupt();
             }
         }
-        logger.debug("Stopped file monitor for {}", this.root.file);
+        log.debug("Stopped file monitor for {}", this.root.file);
     }
 
     /**
@@ -129,24 +137,21 @@ public final class FileMonitor extends TimerTask {
      * @param reporter The ObservationReporter
      */
     private void check(final Monitorable monitorable, final ObservationReporter reporter) {
-        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, contentFileExtensions);
-                sendEvents(monitorable,
-                           ChangeType.ADDED,
-                           reporter);
+                createStatus(monitorable, contentFileExtensions, contentFileCache);
+                sendEvents(monitorable, ChangeType.ADDED, reporter);
             }
         } else {
             // check if the file has been removed
             if ( !monitorable.file.exists() ) {
                 // removed file and update status
-                sendEvents(monitorable,
-                           ChangeType.REMOVED,
-                           reporter);
+                sendEvents(monitorable, ChangeType.REMOVED, reporter);
                 monitorable.status = NonExistingStatus.SINGLETON;
+                contentFileCache.remove(monitorable.path);
             } else {
                 // check for changes
                 final FileStatus fs = (FileStatus)monitorable.status;
@@ -154,10 +159,9 @@ public final class FileMonitor extends TimerTask {
                 if ( fs.lastModified < monitorable.file.lastModified() ) {
                     fs.lastModified = monitorable.file.lastModified();
                     // changed
-                    sendEvents(monitorable,
-                               ChangeType.CHANGED,
-                               reporter);
+                    sendEvents(monitorable, ChangeType.CHANGED, reporter);
                     changed = true;
+                    contentFileCache.remove(monitorable.path);
                 }
                 if ( fs instanceof DirStatus ) {
                     // directory
@@ -200,28 +204,86 @@ public final class FileMonitor extends TimerTask {
      * Send the event async via the event admin.
      */
     private void sendEvents(final Monitorable monitorable, final ChangeType changeType, final ObservationReporter reporter) {
-        if ( logger.isDebugEnabled() ) {
-            logger.debug("Detected change for resource {} : {}", monitorable.path, changeType);
+        if (log.isDebugEnabled()) {
+            log.debug("Detected change for resource {} : {}", monitorable.path, changeType);
         }
 
-        for(final ObserverConfiguration config : reporter.getObserverConfigurations()) {
+        for (final ObserverConfiguration config : reporter.getObserverConfigurations()) {
             if ( config.matches(monitorable.path) ) {
-                final ResourceChange change = new ResourceChange(changeType, monitorable.path, false, null, null, null);
-                reporter.reportChanges(Collections.singleton(change), false);
+                List<ResourceChange> changes = collectResourceChanges(monitorable, changeType);
+                if (log.isTraceEnabled()) {
+                    for (ResourceChange change : changes) {
+                        log.debug("Send change for resource {}: {} to {}", change.getPath(), change.getType(), config);
+                    }
+                }
+                reporter.reportChanges(changes, false);
+            }
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    private List<ResourceChange> collectResourceChanges(final Monitorable monitorable, final ChangeType changeType) {
+        List<ResourceChange> changes = new ArrayList<>();
+        if (monitorable.status instanceof ContentFileStatus) {
+            ContentFile contentFile = ((ContentFileStatus)monitorable.status).contentFile;
+            if (changeType == ChangeType.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(ChangeType.REMOVED, content, monitorable.path));
+                addContentResourceChanges(changes, ChangeType.ADDED, content, monitorable.path);
+            }
+            else {
+                addContentResourceChanges(changes, changeType, (Map<String,Object>)contentFile.getContent(), monitorable.path);
+            }
+        }
+        else {
+            changes.add(new ResourceChange(changeType, monitorable.path, false, null, null, null));
+        }
+        return changes;
+    }
+    @SuppressWarnings("unchecked")
+    private void addContentResourceChanges(final List<ResourceChange> changes, final ChangeType changeType,
+            final Map<String,Object> content, final String path) {
+        changes.add(buildContentResourceChange(changeType, 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, changeType, (Map<String,Object>)entry.getValue(), childPath);
+                }
             }
         }
     }
+    private ResourceChange buildContentResourceChange(final ChangeType changeType, final Map<String,Object> content, final String path) {
+        Set<String> addedPropertyNames = null;
+        if (content != null && changeType == ChangeType.ADDED) {
+            addedPropertyNames = new HashSet<>();
+            for (Map.Entry<String,Object> entry : content.entrySet()) {
+                if (!(entry.getValue() instanceof Map)) {
+                    addedPropertyNames.add(entry.getKey());
+                }
+            }
+        }
+        return new ResourceChange(changeType, path, false, addedPropertyNames, null, null);
+    }
 
     /**
      * Create a status object for the monitorable
      */
-    private static void createStatus(final Monitorable monitorable, ContentFileExtensions contentFileExtensions) {
+    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, contentFileExtensions);
+            monitorable.status = new DirStatus(monitorable.file, monitorable.path, contentFileExtensions, contentFileCache);
         }
     }
 
@@ -248,12 +310,22 @@ public final class FileMonitor extends TimerTask {
             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, final ContentFileExtensions contentFileExtensions) {
+        public DirStatus(final File dir, final String path,
+                final ContentFileExtensions contentFileExtensions, final ContentFileCache contentFileCache) {
             super(dir);
             final File[] files = dir.listFiles();
             if (files != null) {
@@ -261,7 +333,7 @@ public final class FileMonitor extends TimerTask {
                 for (int i = 0; i < files.length; i++) {
                     this.children[i] = new Monitorable(path + '/' + files[i].getName(), files[i],
                             contentFileExtensions.getSuffix(files[i]));
-                    FileMonitor.createStatus(this.children[i], contentFileExtensions);
+                    FileMonitor.createStatus(this.children[i], contentFileExtensions, contentFileCache);
                 }
             } else {
                 this.children = new Monitorable[0];
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
index f379bad..5d1ca9b 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -31,7 +31,8 @@ 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.fsprovider.internal.parser.ContentFileCache;
+import org.apache.sling.fsprovider.internal.parser.ContentFileTypes;
 import org.apache.sling.spi.resource.provider.ObservationReporter;
 import org.apache.sling.spi.resource.provider.ProviderContext;
 import org.apache.sling.spi.resource.provider.ResolveContext;
@@ -108,7 +109,11 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
         @AttributeDefinition(name = "Mount JSON",
                 description = "Mount .json files as content in the resource hierarchy.")
         boolean provider_json_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.
          */
@@ -130,6 +135,9 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
     
     // if true resources from filesystem are only "overlayed" to JCR resources, serving JCR as fallback within the same path
     private boolean overlayParentResourceProvider;
+    
+    // cache for parsed content files
+    private ContentFileCache contentFileCache;
 
     /**
      * Returns a resource wrapping a file system file or folder for the given
@@ -243,17 +251,20 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
         
         List<String> contentFileSuffixes = new ArrayList<>();
         if (config.provider_json_content()) {
-            contentFileSuffixes.add(ContentFileParser.JSON_SUFFIX);
+            contentFileSuffixes.add(ContentFileTypes.JSON_SUFFIX);
             this.overlayParentResourceProvider = false;
         }
         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.contentFileMapper = new ContentFileResourceMapper(this.providerRoot, this.providerFile,
+                contentFileExtensions, this.contentFileCache);
         
         // start background monitor if check interval is higher than 100
         if ( config.provider_checkinterval() > 100 ) {
-            this.monitor = new FileMonitor(this, config.provider_checkinterval(), contentFileExtensions);
+            this.monitor = new FileMonitor(this, config.provider_checkinterval(),
+                    contentFileExtensions, this.contentFileCache);
         }
     }
 
@@ -268,6 +279,10 @@ public final class FsResourceProvider extends ResourceProvider<Object> {
         this.overlayParentResourceProvider = false;
         this.fileMapper = null;
         this.contentFileMapper = null;
+        if (this.contentFileCache != null) {
+            this.contentFileCache.clear();
+            this.contentFileCache = null;
+        }
     }
 
     File getRootFile() {
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
index 319a3de..2e60b1f 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFile.java
@@ -22,7 +22,7 @@ import java.io.File;
 import java.util.Map;
 
 import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.fsprovider.internal.parser.ContentFileParser;
+import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 
 /**
  * Reference to a file that contains a content fragment (e.g. JSON, JCR XML).
@@ -30,28 +30,35 @@ import org.apache.sling.fsprovider.internal.parser.ContentFileParser;
 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 subPath) {
+    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 subPath, Object content) {
-        this.file = file;
-        this.subPath = subPath;
+    public ContentFile(File file, String path, String subPath, ContentFileCache contentFileCache, Object content) {
+        this(file, path, subPath, contentFileCache);
         this.contentInitialized = true;
         this.content = content;
     }
@@ -62,6 +69,13 @@ public final class ContentFile {
     public File getFile() {
         return file;
     }
+    
+    /**
+     * @return Root path of content file
+     */
+    public String getPath() {
+        return path;
+    }
 
     /**
      * @return Relative path addressing content fragment inside file
@@ -76,7 +90,7 @@ public final class ContentFile {
      */
     public Object getContent() {
         if (!contentInitialized) {
-            Map<String,Object> rootContent = ContentFileParser.parse(file);
+            Map<String,Object> rootContent = contentFileCache.get(path, file);
             content = getDeepContent(rootContent, subPath);
             contentInitialized = true;
         }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
index 11bfcbb..410419e 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResource.java
@@ -54,10 +54,11 @@ public final class ContentFileResource extends AbstractResource {
      * @param resourcePath The resource path in the resource tree
      * @param contentFile Content file with sub path
      */
-    ContentFileResource(ResourceResolver resolver, String resourcePath, ContentFile contentFile) {
+    ContentFileResource(ResourceResolver resolver, ContentFile contentFile) {
         this.resolver = resolver;
-        this.resourcePath = resourcePath;
         this.contentFile = contentFile;
+        this.resourcePath = contentFile.getPath()
+                + (contentFile.getSubPath() != null ? "/" + contentFile.getSubPath() : "");
     }
 
     public String getPath() {
@@ -79,7 +80,7 @@ public final class ContentFileResource extends AbstractResource {
 
     public String getResourceSuperType() {
         if (resourceSuperType == null) {
-            resourceSuperType = contentFile.getValueMap().get("sling:resourceSuperType", String.class);
+            resourceSuperType = getValueMap().get("sling:resourceSuperType", String.class);
         }
         return resourceSuperType;
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
index 1fe257c..b5306b9 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ContentFileResourceMapper.java
@@ -32,6 +32,7 @@ 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 {
     
@@ -42,18 +43,21 @@ public final class ContentFileResourceMapper implements FsResourceMapper {
     private final File providerFile;
     
     private final ContentFileExtensions contentFileExtensions;
+    private final ContentFileCache contentFileCache;
     
-    public ContentFileResourceMapper(String providerRoot, File providerFile, ContentFileExtensions contentFileExtensions) {
+    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) {
         ContentFile contentFile = getFile(resourcePath, null);
         if (contentFile != null && contentFile.hasContent()) {
-            return new ContentFileResource(resolver, resourcePath, contentFile);
+            return new ContentFileResource(resolver, contentFile);
         }
         else {
             return null;
@@ -78,9 +82,9 @@ public final class ContentFileResourceMapper implements FsResourceMapper {
                     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));
+                            ContentFile contentFile = new ContentFile(file, path, null, contentFileCache);
+                            childResources.add(new ContentFileResource(resolver, contentFile));
                         }
                     }
                     if (!childResources.isEmpty()) {
@@ -106,7 +110,7 @@ public final class ContentFileResourceMapper implements FsResourceMapper {
                     else {
                         subPath = parentContentFile.getSubPath() + "/" + entry.getKey();
                     }
-                    children.add(new ContentFile(parentContentFile.getFile(), subPath, entry.getValue()));
+                    children.add(new ContentFile(parentContentFile.getFile(), parentContentFile.getPath(), subPath, contentFileCache, entry.getValue()));
                 }
             }
         }
@@ -118,8 +122,7 @@ public final class ContentFileResourceMapper implements FsResourceMapper {
                 @Override
                 public Object transform(Object input) {
                     ContentFile contentFile = (ContentFile)input;
-                    String path = parentPath + "/" + ResourceUtil.getName(contentFile.getSubPath());
-                    return new ContentFileResource(resolver, path, contentFile);
+                    return new ContentFileResource(resolver, contentFile);
                 }
             });
         }
@@ -133,7 +136,7 @@ public final class ContentFileResourceMapper implements FsResourceMapper {
         for (String filenameSuffix : contentFileExtensions.getSuffixes()) {
             File file = new File(providerFile, relPath + filenameSuffix);
             if (file.exists()) {
-                return new ContentFile(file, subPath);
+                return new ContentFile(file, path, subPath, contentFileCache);
             }
         }
         // try to find in parent path which contains content fragment
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
index 67122b1..8ddbda7 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/ValueMapUtil.java
@@ -22,6 +22,8 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.jcr.nodetype.NodeType;
+
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.api.wrappers.ValueMapDecorator;
 
@@ -38,6 +40,7 @@ final class ValueMapUtil {
      */
     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
@@ -51,6 +54,12 @@ final class ValueMapUtil {
                 props.put(entry.getKey(), entry.getValue());
             }
         }
+        
+        // fallback to default jcr:primaryType is none is set
+        if (!props.containsKey("jcr:primaryType")) {
+            props.put("jcr:primaryType", NodeType.NT_UNSTRUCTURED);
+        }
+        
         return new ValueMapDecorator(props);
     }
 
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
index 308701c..1d4b842 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNode.java
@@ -63,6 +63,14 @@ public final class FsNode extends FsItem implements Node {
         super(resource);
     }
     
+    private String getPrimaryTypeName() {
+        return  props.get("jcr:primaryType", String.class);
+    }
+    
+    private String[] getMixinTypeNames() {
+        return props.get("jcr:mixinTypes", new String[0]);
+    }
+    
     @Override
     public Node getNode(String relPath) throws PathNotFoundException, RepositoryException {
         Resource child = resource.getChild(relPath);
@@ -123,7 +131,7 @@ public final class FsNode extends FsItem implements Node {
 
     @Override
     public boolean isNodeType(String nodeTypeName) throws RepositoryException {
-        return StringUtils.equals(nodeTypeName, props.get("jcr:primaryType", String.class));
+        return StringUtils.equals(nodeTypeName, getPrimaryTypeName());
     }
 
     @Override
@@ -146,6 +154,21 @@ public final class FsNode extends FsItem implements Node {
         return false;
     }
 
+    @Override
+    public NodeType getPrimaryNodeType() throws RepositoryException {
+        return new FsNodeType(getPrimaryTypeName(), false);
+    }
+
+    @Override
+    public NodeType[] getMixinNodeTypes() throws RepositoryException {
+        String[] mixinTypeNames = getMixinTypeNames();
+        NodeType[] mixinTypes = new NodeType[mixinTypeNames.length];
+        for (int i=0; i<mixinTypeNames.length; i++) {
+            mixinTypes[i] = new FsNodeType(mixinTypeNames[i], true);
+        }
+        return mixinTypes;
+    }
+    
 
     // --- unsupported methods ---
     
@@ -444,11 +467,6 @@ public final class FsNode extends FsItem implements Node {
     }
 
     @Override
-    public NodeType getPrimaryNodeType() throws RepositoryException {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public String getIdentifier() throws RepositoryException {
         throw new UnsupportedOperationException();
     }
@@ -459,11 +477,6 @@ public final class FsNode extends FsItem implements Node {
     }
 
     @Override
-    public NodeType[] getMixinNodeTypes() throws RepositoryException {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public Item getPrimaryItem() throws ItemNotFoundException, RepositoryException {
         throw new UnsupportedOperationException();
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeType.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeType.java
new file mode 100644
index 0000000..8d60812
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsNodeType.java
@@ -0,0 +1,157 @@
+/*
+ * 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.Value;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeIterator;
+import javax.jcr.nodetype.PropertyDefinition;
+
+import org.apache.commons.lang3.StringUtils;
+
+class FsNodeType implements NodeType {
+    
+    private final String name;
+    private final boolean mixin;
+    
+    public FsNodeType(String name, boolean mixin) {
+        this.name = name;
+        this.mixin = mixin;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String[] getDeclaredSupertypeNames() {
+        return new String[0];
+    }
+
+    @Override
+    public boolean isAbstract() {
+        return false;
+    }
+
+    @Override
+    public boolean isMixin() {
+        return mixin;
+    }
+
+    @Override
+    public boolean hasOrderableChildNodes() {
+        return false;
+    }
+
+    @Override
+    public boolean isQueryable() {
+        return false;
+    }
+
+    @Override
+    public String getPrimaryItemName() {
+        return null;
+    }
+
+    @Override
+    public NodeType[] getSupertypes() {
+        return new NodeType[0];
+    }
+
+    @Override
+    public NodeType[] getDeclaredSupertypes() {
+        return new NodeType[0];
+    }
+
+    @Override
+    public boolean isNodeType(String nodeTypeName) {
+        return StringUtils.equals(name, nodeTypeName);
+    }
+
+
+    // --- unsupported methods ---    
+    
+    @Override
+    public PropertyDefinition[] getDeclaredPropertyDefinitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeDefinition[] getDeclaredChildNodeDefinitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeTypeIterator getSubtypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeTypeIterator getDeclaredSubtypes() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public PropertyDefinition[] getPropertyDefinitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public NodeDefinition[] getChildNodeDefinitions() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canSetProperty(String propertyName, Value value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canSetProperty(String propertyName, Value[] values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canAddChildNode(String childNodeName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canAddChildNode(String childNodeName, String nodeTypeName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canRemoveItem(String itemName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canRemoveNode(String nodeName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean canRemoveProperty(String propertyName) {
+        throw new UnsupportedOperationException();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java
index 4d1d138..469bd11 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsProperty.java
@@ -143,7 +143,12 @@ class FsProperty extends FsItem implements Property {
         return getValue().getType();
     }
     
+    @Override
+    public PropertyDefinition getDefinition() throws RepositoryException {
+        return new FsPropertyDefinition(propertyName);
+    }
 
+    
     // --- unsupported methods ---
     
     @Override
@@ -219,11 +224,6 @@ class FsProperty extends FsItem implements Property {
     }
 
     @Override
-    public PropertyDefinition getDefinition() throws RepositoryException {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public Property getProperty() throws ItemNotFoundException, ValueFormatException, RepositoryException {
         throw new UnsupportedOperationException();
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyDefinition.java b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyDefinition.java
new file mode 100644
index 0000000..85920cb
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/mapper/jcr/FsPropertyDefinition.java
@@ -0,0 +1,100 @@
+/*
+ * 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.PropertyType;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.PropertyDefinition;
+import javax.jcr.version.OnParentVersionAction;
+
+class FsPropertyDefinition implements PropertyDefinition {
+    
+    private final String name;
+    
+    public FsPropertyDefinition(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public NodeType getDeclaringNodeType() {
+        return null;
+    }
+
+    @Override
+    public boolean isAutoCreated() {
+        return false;
+    }
+
+    @Override
+    public boolean isMandatory() {
+        return false;
+    }
+
+    @Override
+    public int getOnParentVersion() {
+        return OnParentVersionAction.COPY;
+    }
+
+    @Override
+    public boolean isProtected() {
+        return false;
+    }
+
+    @Override
+    public int getRequiredType() {
+        return PropertyType.UNDEFINED;
+    }
+
+    @Override
+    public String[] getValueConstraints() {
+        return new String[0];
+    }
+
+    @Override
+    public Value[] getDefaultValues() {
+        return new Value[0];
+    }
+
+    @Override
+    public boolean isMultiple() {
+        return false;
+    }
+
+    @Override
+    public String[] getAvailableQueryOperators() {
+        return new String[0];
+    }
+
+    @Override
+    public boolean isFullTextSearchable() {
+        return false;
+    }
+
+    @Override
+    public boolean isQueryOrderable() {
+        return false;
+    }    
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
new file mode 100644
index 0000000..289fb67
--- /dev/null
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileCache.java
@@ -0,0 +1,107 @@
+/*
+ * 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.parser;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.commons.collections.map.LRUMap;
+
+/**
+ * Cache for parsed content from content files (e.g. JSON, JCR XML).
+ */
+public final class ContentFileCache {
+
+    private final Map<String,Map<String,Object>> contentCache;
+    private final Map<String,Object> NULL_MAP = Collections.emptyMap();
+    
+    /**
+     * @param maxSize Cache size. 0 = caching disabled.
+     */
+    @SuppressWarnings("unchecked")
+    public ContentFileCache(int maxSize) {
+        if (maxSize > 0) {
+            this.contentCache = Collections.synchronizedMap(new LRUMap(maxSize));
+        }
+        else {
+            this.contentCache = null;
+        }
+    }
+    
+    /**
+     * Get content.
+     * @param path Path (used as cache key).
+     * @param file File
+     * @return Content or null
+     */
+    public Map<String,Object> get(String path, File file) {
+        Map<String,Object> content = null;
+        if (contentCache != null) {
+            content = contentCache.get(path);
+        }
+        if (content == null) {
+            content = ContentFileParser.parse(file);
+            if (content == null) {
+                content = NULL_MAP;
+            }
+            if (contentCache != null) {
+                contentCache.put(path, content);
+            }
+        }
+        if (content == NULL_MAP) {
+            return null;
+        }
+        else {
+            return content;
+        }
+    }
+    
+    /**
+     * Remove content from cache.
+     * @param path Path (used as cache key)
+     */
+    public void remove(String path) {
+        if (contentCache != null) {
+            contentCache.remove(path);
+        }
+    }
+
+    /**
+     * Clear whole cache
+     */
+    public void clear() {
+        if (contentCache != null) {
+            contentCache.clear();
+        }
+    }
+    
+    /**
+     * @return Current cache size
+     */
+    public int size() {
+        if (contentCache != null) {
+            return contentCache.size();
+        }
+        else {
+            return 0;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
index 46d5b85..8f7a11f 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
@@ -18,20 +18,21 @@
  */
 package org.apache.sling.fsprovider.internal.parser;
 
+import static org.apache.sling.fsprovider.internal.parser.ContentFileTypes.JSON_SUFFIX;
+
 import java.io.File;
 import java.util.Map;
 
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Parses file that contains content fragments (e.g. JSON, JCR XML).
  */
-public final class ContentFileParser {
+class ContentFileParser {
     
-    /**
-     * JSON content files.
-     */
-    public static final String JSON_SUFFIX = ".json";
+    private static final Logger log = LoggerFactory.getLogger(ContentFileParser.class);
     
     private ContentFileParser() {
         // static methods only
@@ -43,8 +44,13 @@ public final class ContentFileParser {
      * @return Content or null if content could not be parsed.
      */
     public static Map<String,Object> parse(File file) {
-        if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) {
-            return JsonFileParser.parse(file);
+        try {
+            if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) {
+                return JsonFileParser.parse(file);
+            }
+        }
+        catch (Throwable ex) {
+            log.warn("Error parsing content from " + file.getPath(), ex);
         }
         return null;
     }
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
similarity index 62%
copy from src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
copy to src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
index 46d5b85..9d995e6 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileParser.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/parser/ContentFileTypes.java
@@ -18,35 +18,18 @@
  */
 package org.apache.sling.fsprovider.internal.parser;
 
-import java.io.File;
-import java.util.Map;
-
-import org.apache.commons.lang3.StringUtils;
-
 /**
- * Parses file that contains content fragments (e.g. JSON, JCR XML).
+ * Content file types.
  */
-public final class ContentFileParser {
+public final class ContentFileTypes {
     
     /**
      * JSON content files.
      */
     public static final String JSON_SUFFIX = ".json";
-    
-    private ContentFileParser() {
+
+    private ContentFileTypes() {
         // static methods only
     }
     
-    /**
-     * Parse content from file.
-     * @param file File. Type is detected automatically.
-     * @return Content or null if content could not be parsed.
-     */
-    public static Map<String,Object> parse(File file) {
-        if (StringUtils.endsWith(file.getName(), JSON_SUFFIX)) {
-            return JsonFileParser.parse(file);
-        }
-        return null;
-    }
-
 }
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
index a6f7568..30012ef 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
@@ -37,6 +37,8 @@ import org.apache.sling.testing.mock.sling.junit.SlingContextCallback;
 import org.junit.Rule;
 import org.junit.Test;
 
+import com.google.common.collect.ImmutableSet;
+
 /**
  * Test events when changing filesystem content.
  */
@@ -164,8 +166,10 @@ public class FileMonitorTest {
         
         Thread.sleep(250);
 
-        assertEquals(1, changes.size());
-        assertChange(changes, 0, "/fs-test/folder2/content", ChangeType.CHANGED);
+        assertTrue(changes.size() > 1);
+        assertChange(changes, 0, "/fs-test/folder2/content", ChangeType.REMOVED);
+        assertChange(changes, 1, "/fs-test/folder2/content", ChangeType.ADDED);
+        assertChange(changes, 2, "/fs-test/folder2/content/jcr:content", ChangeType.ADDED);
     }
     
     @Test
@@ -174,13 +178,14 @@ public class FileMonitorTest {
         assertTrue(changes.isEmpty());
         
         File file1c = new File(tempDir, "folder1/file1c.json");
-        FileUtils.write(file1c, "{'prop1':'value1'}");
+        FileUtils.write(file1c, "{\"prop1\":\"value1\",\"child1\":{\"prop2\":\"value1\"}}");
         
         Thread.sleep(250);
 
-        assertEquals(2, changes.size());
+        assertEquals(3, changes.size());
         assertChange(changes, 0, "/fs-test/folder1", ChangeType.CHANGED);
-        assertChange(changes, 1, "/fs-test/folder1/file1c", ChangeType.ADDED);
+        assertChange(changes, 1, "/fs-test/folder1/file1c", ChangeType.ADDED, "prop1");
+        assertChange(changes, 2, "/fs-test/folder1/file1c/child1", ChangeType.ADDED, "prop2");
     }
     
     @Test
@@ -199,10 +204,13 @@ public class FileMonitorTest {
     }
     
     
-    private void assertChange(List<ResourceChange> changes, int index, String path, ChangeType changeType) {
+    private void assertChange(List<ResourceChange> changes, int index, String path, ChangeType changeType, String... addedPropertyNames) {
         ResourceChange change = changes.get(index);
         assertEquals(path, change.getPath());
         assertEquals(changeType, change.getType());
+        if (addedPropertyNames.length > 0) {
+            assertEquals(ImmutableSet.copyOf(addedPropertyNames), change.getAddedPropertyNames());
+        }
     }
     
     static class ResourceListener implements ResourceChangeListener {
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
index 578befd..403592a 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FilesFolderTest.java
@@ -65,6 +65,7 @@ public class FilesFolderTest {
         assertFile(fsroot, "folder1/file1b.txt", "file1b");
         assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
         assertFile(fsroot, "folder2/content.json", null);
+        assertFile(fsroot, "folder2/content/file2content.txt", "file2content");
     }
 
     @Test
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
index 1dcf7a4..34240a8 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
@@ -38,6 +38,7 @@ import javax.jcr.PropertyIterator;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
 
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ValueMap;
@@ -85,6 +86,7 @@ public class JsonContentTest {
         assertFile(fsroot, "folder1/file1b.txt", "file1b");
         assertFile(fsroot, "folder1/folder11/file11a.txt", "file11a");
         assertNull(fsroot.getChild("folder2/content.json"));
+        assertFile(fsroot, "folder2/content/file2content.txt", "file2content");
     }
 
     @Test
@@ -144,11 +146,11 @@ public class JsonContentTest {
         
         assertEquals("/fs-test/folder2/content/toolbar/profiles/jcr:content", node.getPath());
         assertEquals(6, node.getDepth());
-        assertTrue(node.isNodeType("app:PageContent"));
         
         assertTrue(node.hasProperty("jcr:title"));
         assertEquals(PropertyType.STRING, node.getProperty("jcr:title").getType());
         assertFalse(node.getProperty("jcr:title").isMultiple());
+        assertEquals("jcr:title", node.getProperty("jcr:title").getDefinition().getName());
         assertEquals("/fs-test/folder2/content/toolbar/profiles/jcr:content/jcr:title", node.getProperty("jcr:title").getPath());
         assertEquals("Profiles", node.getProperty("jcr:title").getString());
         assertEquals(PropertyType.BOOLEAN, node.getProperty("booleanProp").getType());
@@ -196,10 +198,27 @@ public class JsonContentTest {
         Node parent = rightpar.getParent();
         assertTrue(node.isSame(parent));
         Node ancestor = (Node)rightpar.getAncestor(4);
-        assertEquals(underTest.getParent().getPath(), ancestor.getPath());        
+        assertEquals(underTest.getParent().getPath(), ancestor.getPath());
+        
+        // node types
+        assertTrue(node.isNodeType("app:PageContent"));
+        assertEquals("app:PageContent", node.getPrimaryNodeType().getName());
+        assertFalse(node.getPrimaryNodeType().isMixin());
+        NodeType[] mixinTypes = node.getMixinNodeTypes();
+        assertEquals(2, mixinTypes.length);
+        assertEquals("type1", mixinTypes[0].getName());
+        assertEquals("type2", mixinTypes[1].getName());
+        assertTrue(mixinTypes[0].isMixin());
+        assertTrue(mixinTypes[1].isMixin());
     }
 
     @Test
+    public void testFallbackNodeType() throws RepositoryException {
+        Resource underTest = fsroot.getChild("folder2/content/jcr:content/par/title_2");
+        assertEquals(NodeType.NT_UNSTRUCTURED, underTest.adaptTo(Node.class).getPrimaryNodeType().getName());
+    }
+    
+    @Test
     public void testJsonContent_InvalidPath() {
         Resource underTest = fsroot.getChild("folder2/content/jcr:content/xyz");
         assertNull(underTest);
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
index b3c18ee..4659991 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/mapper/ContentFileTest.java
@@ -27,16 +27,19 @@ import java.io.File;
 import java.util.Map;
 
 import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.junit.Test;
 
 public class ContentFileTest {
+    
+    private ContentFileCache contentFileCache = new ContentFileCache(0);
 
     @SuppressWarnings("unchecked")
     @Test
     public void testRootContent() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
         
-        ContentFile underTest = new ContentFile(file, null);
+        ContentFile underTest = new ContentFile(file, "/fs-test/folder2/content", null, contentFileCache);
         assertEquals(file, underTest.getFile());
         assertNull(underTest.getSubPath());
         
@@ -56,7 +59,7 @@ public class ContentFileTest {
     public void testContentLevel1() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
         
-        ContentFile underTest = new ContentFile(file, "jcr:content");
+        ContentFile underTest = new ContentFile(file, "/fs-test/folder2/content", "jcr:content", contentFileCache);
         assertEquals(file, underTest.getFile());
         assertEquals("jcr:content", underTest.getSubPath());
         
@@ -74,7 +77,7 @@ public class ContentFileTest {
     public void testContentLevel5() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
         
-        ContentFile underTest = new ContentFile(file, "jcr:content/par/image/file/jcr:content");
+        ContentFile underTest = new ContentFile(file, "/fs-test/folder2/content", "jcr:content/par/image/file/jcr:content", contentFileCache);
         assertEquals(file, underTest.getFile());
         assertEquals("jcr:content/par/image/file/jcr:content", underTest.getSubPath());
         
@@ -91,7 +94,7 @@ public class ContentFileTest {
     public void testContentProperty() {
         File file = new File("src/test/resources/fs-test/folder2/content.json");
         
-        ContentFile underTest = new ContentFile(file, "jcr:content/jcr:title");
+        ContentFile underTest = new ContentFile(file, "/fs-test/folder2/content", "jcr:content/jcr:title", contentFileCache);
         assertEquals(file, underTest.getFile());
         assertEquals("jcr:content/jcr:title", underTest.getSubPath());
         
@@ -105,7 +108,7 @@ public class ContentFileTest {
     @Test
     public void testInvalidFile() {
         File file = new File("src/test/resources/fs-test/folder1/file1a.txt");
-        ContentFile underTest = new ContentFile(file, null);
+        ContentFile underTest = new ContentFile(file, "/fs-test/folder1/file1a", null, contentFileCache);
         assertFalse(underTest.hasContent());
     }
 
diff --git a/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java
new file mode 100644
index 0000000..1eaf1e1
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/parser/ContentFileCacheTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.parser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.File;
+import java.util.Map;
+
+import org.junit.experimental.theories.DataPoint;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.runner.RunWith;
+
+@RunWith(Theories.class)
+public class ContentFileCacheTest {
+    
+    @DataPoint
+    public static final int NO_CACHE = 0;
+    @DataPoint
+    public static final int SMALL_CACHE = 1;
+    @DataPoint
+    public static final int HUGE_CACHE = 1000;
+
+    @Theory
+    public void testCache(int cacheSize) {
+        ContentFileCache underTest = new ContentFileCache(cacheSize);
+        
+        Map<String,Object> content1 = underTest.get("/fs-test/folder2/content", new File("src/test/resources/fs-test/folder2/content.json"));
+        assertNotNull(content1);
+        
+        switch (cacheSize) {
+        case NO_CACHE:
+            assertEquals(0, underTest.size());
+            break;
+        case SMALL_CACHE:
+        case HUGE_CACHE:
+            assertEquals(1, underTest.size());
+            break;
+        }
+
+        Map<String,Object> content2 = underTest.get("/fs-test/folder1/file1a", new File("src/test/resources/fs-test/folder1/file1a.txt"));
+        assertNull(content2);
+
+        switch (cacheSize) {
+        case NO_CACHE:
+            assertEquals(0, underTest.size());
+            break;
+        case SMALL_CACHE:
+            assertEquals(1, underTest.size());
+            break;
+        case HUGE_CACHE:
+            assertEquals(2, underTest.size());
+            break;
+        }
+
+        underTest.remove("/fs-test/folder1/file1a");
+
+        switch (cacheSize) {
+        case NO_CACHE:
+        case SMALL_CACHE:
+            assertEquals(0, underTest.size());
+            break;
+        case HUGE_CACHE:
+            assertEquals(1, underTest.size());
+            break;
+        }
+        
+        underTest.clear();
+
+        assertEquals(0, underTest.size());        
+    }
+
+}
diff --git a/src/test/resources/fs-test/folder2/content.json b/src/test/resources/fs-test/folder2/content.json
index e808ef8..f35baf8 100644
--- a/src/test/resources/fs-test/folder2/content.json
+++ b/src/test/resources/fs-test/folder2/content.json
@@ -102,7 +102,6 @@
         }
       },
       "title_2": {
-        "jcr:primaryType": "nt:unstructured",
         "jcr:createdBy": "admin",
         "jcr:title": "Shape Technology",
         "jcr:lastModifiedBy": "admin",
@@ -220,6 +219,7 @@
       "jcr:created": "Thu Aug 07 2014 16:33:00 GMT+0200",
       "jcr:content": {
         "jcr:primaryType": "app:PageContent",
+        "jcr:mixinTypes": ["type1","type2"],
         "jcr:createdBy": "admin",
         "jcr:title": "Profiles",
         "app:template": "/apps/sample/templates/contentpage",
diff --git a/src/test/resources/fs-test/folder2/content/file2content.txt b/src/test/resources/fs-test/folder2/content/file2content.txt
new file mode 100644
index 0000000..667b547
--- /dev/null
+++ b/src/test/resources/fs-test/folder2/content/file2content.txt
@@ -0,0 +1 @@
+file2content
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.