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:37:22 UTC

[sling-org-apache-sling-fsresource] 07/23: SLING-6537 FileVault XML support

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

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

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

    SLING-6537 FileVault XML support
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/branches/fsresource-1.1.x@1785772 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |   2 +-
 .../fsprovider/internal/ContentFileExtensions.java |   3 +-
 .../sling/fsprovider/internal/FileMonitor.java     | 159 ++++++++------
 .../apache/sling/fsprovider/internal/FsMode.java   |  41 ++++
 .../fsprovider/internal/FsResourceProvider.java    | 116 +++++++---
 .../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 | 129 +++++------
 .../fsprovider/internal/FileVaultContentTest.java  | 143 ++++++++++++
 .../internal/FileVaultFileMonitorTest.java         | 239 +++++++++++++++++++++
 .../internal/InitialContentImportOptionsTest.java  |  59 +++++
 .../fsprovider/internal/JcrXmlContentTest.java     |   5 +-
 .../sling/fsprovider/internal/JsonContentTest.java |   7 +-
 .../sling/fsprovider/internal/TestUtils.java       |  33 +++
 .../vaultfs-test/META-INF/vault/filter.xml         |   4 +-
 .../jcr_root/content/dam/talk.png/.content.xml     |   2 -
 21 files changed, 1103 insertions(+), 220 deletions(-)

diff --git a/pom.xml b/pom.xml
index f7d4a3c..e530623 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>
 
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 609fe58..4ac056a 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
@@ -18,9 +18,17 @@
  */
 package org.apache.sling.fsprovider.internal;
 
+import static org.apache.jackrabbit.vault.util.Constants.ROOT_DIR;
+import static org.apache.sling.api.SlingConstants.PROPERTY_PATH;
+import static org.apache.sling.api.SlingConstants.PROPERTY_RESOURCE_TYPE;
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_ADDED;
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_CHANGED;
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_REMOVED;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Dictionary;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -28,7 +36,7 @@ import java.util.Timer;
 import java.util.TimerTask;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.api.SlingConstants;
+import org.apache.jackrabbit.vault.util.PlatformNameFormat;
 import org.apache.sling.fsprovider.internal.mapper.ContentFile;
 import org.apache.sling.fsprovider.internal.mapper.FileResource;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
@@ -51,7 +59,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;
 
@@ -60,12 +68,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);
@@ -116,9 +131,9 @@ public final class FileMonitor extends TimerTask {
         synchronized ( this ) {
             try {
                 // if we don't have an event admin, we just skip the check
-                final EventAdmin localEA = this.provider.getEventAdmin();
-                if ( localEA != null ) {
-                    this.check(this.root, localEA);
+                final EventAdmin reporter = this.provider.getEventAdmin();
+                if ( reporter != null ) {
+                    this.check(this.root, reporter);
                 }
             } catch (Exception e) {
                 // ignore this
@@ -133,22 +148,29 @@ public final class FileMonitor extends TimerTask {
     /**
      * Check the monitorable
      * @param monitorable The monitorable to check
-     * @param localEA The event admin
+     * @param reporter The EventAdmin
      */
-    private void check(final Monitorable monitorable, final EventAdmin localEA) {
+    private void check(final Monitorable monitorable, final EventAdmin reporter) {
         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, contentFileCache);
-                sendEvents(monitorable, SlingConstants.TOPIC_RESOURCE_ADDED, localEA);
+                sendEvents(monitorable, TOPIC_RESOURCE_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
             if ( !monitorable.file.exists() ) {
                 // removed file and update status
-                sendEvents(monitorable, SlingConstants.TOPIC_RESOURCE_REMOVED, localEA);
+                sendEvents(monitorable, TOPIC_RESOURCE_REMOVED, reporter);
                 monitorable.status = NonExistingStatus.SINGLETON;
                 contentFileCache.remove(monitorable.path);
             } else {
@@ -158,7 +180,7 @@ public final class FileMonitor extends TimerTask {
                 if ( fs.lastModified < monitorable.file.lastModified() ) {
                     fs.lastModified = monitorable.file.lastModified();
                     // changed
-                    sendEvents(monitorable, SlingConstants.TOPIC_RESOURCE_CHANGED, localEA);
+                    sendEvents(monitorable, TOPIC_RESOURCE_CHANGED, reporter);
                     changed = true;
                     contentFileCache.remove(monitorable.path);
                 }
@@ -166,105 +188,122 @@ public final class FileMonitor extends TimerTask {
                     // directory
                     final DirStatus ds = (DirStatus)fs;
                     for(int i=0; i<ds.children.length; i++) {
-                        check(ds.children[i], localEA);
+                        check(ds.children[i], reporter);
                     }
                     // 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], localEA);
-                                }
-                            }
-                            ds.children = children;
-                        } else {
-                            ds.children = new Monitorable[0];
-                        }
+                        checkDirStatusChildren(monitorable, reporter);
                     }
                 }
             }
         }
     }
+    
+    private void checkDirStatusChildren(final Monitorable dirMonitorable, final EventAdmin 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 String topic, final EventAdmin localEA) {
+    private void sendEvents(final Monitorable monitorable, final String changeType, final EventAdmin reporter) {
         if (log.isDebugEnabled()) {
-            log.debug("Detected change for resource {} : {}", monitorable.path, topic);
+            log.debug("Detected change for resource {} : {}", transformPath(monitorable.path), changeType);
         }
 
-        List<ResourceChange> changes = collectResourceChanges(monitorable, topic);
+        List<ResourceChange> changes = collectResourceChanges(monitorable, changeType);
         for (ResourceChange change : changes) {
             if (log.isTraceEnabled()) {
-                log.debug("Send change for resource {}: {}", change.path, change.topic);
+                log.debug("Send change for resource {}: {}", transformPath(change.path), change.topic);
             }
-            final Dictionary<String, String> properties = new Hashtable<String, String>();
-            properties.put(SlingConstants.PROPERTY_PATH, change.path);
+            final Dictionary<String, String> properties = new Hashtable<>();
+            properties.put(PROPERTY_PATH, transformPath(change.path));
             if (change.resourceType != null) {
-                properties.put(SlingConstants.PROPERTY_RESOURCE_TYPE, change.resourceType);
+                properties.put(PROPERTY_RESOURCE_TYPE, change.resourceType);
             }
-            localEA.postEvent(new org.osgi.service.event.Event(change.topic, properties));
+            reporter.postEvent(new org.osgi.service.event.Event(change.topic, properties));
         }        
     }
     
+    /**
+     * 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 String topic) {
+    private List<ResourceChange> collectResourceChanges(final Monitorable monitorable, final String changeType) {
         List<ResourceChange> changes = new ArrayList<>();
         if (monitorable.status instanceof ContentFileStatus) {
             ContentFile contentFile = ((ContentFileStatus)monitorable.status).contentFile;
-            if (StringUtils.equals(topic, SlingConstants.TOPIC_RESOURCE_CHANGED)) {
+            if (StringUtils.equals(changeType, TOPIC_RESOURCE_CHANGED)) {
                 Map<String,Object> content = (Map<String,Object>)contentFile.getContent();
                 // we cannot easily report the diff of resource changes between two content files
                 // so we simulate a removal of the toplevel node and then add all nodes contained in the current content file again.
-                changes.add(buildContentResourceChange(SlingConstants.TOPIC_RESOURCE_REMOVED, content, monitorable.path));
-                addContentResourceChanges(changes, SlingConstants.TOPIC_RESOURCE_ADDED, content, monitorable.path);
+                changes.add(buildContentResourceChange(TOPIC_RESOURCE_REMOVED, content, transformPath(monitorable.path)));
+                addContentResourceChanges(changes, TOPIC_RESOURCE_ADDED, content, transformPath(monitorable.path));
             }
             else {
-                addContentResourceChanges(changes, topic, (Map<String,Object>)contentFile.getContent(), monitorable.path);
+                addContentResourceChanges(changes, changeType, (Map<String,Object>)contentFile.getContent(), transformPath(monitorable.path));
             }
         }
         else {
-            ResourceChange change = new ResourceChange();
-            change.path = monitorable.path;
-            change.resourceType = monitorable.status instanceof FileStatus ?
-                    FileResource.RESOURCE_TYPE_FILE : FileResource.RESOURCE_TYPE_FOLDER;
-            change.topic = topic;
-            changes.add(change);
+            Map<String,Object> content = new HashMap<>();
+            content.put("sling:resourceType", monitorable.status instanceof FileStatus ?
+                    FileResource.RESOURCE_TYPE_FILE : FileResource.RESOURCE_TYPE_FOLDER);
+            changes.add(buildContentResourceChange(changeType, content, transformPath(monitorable.path)));
         }
         return changes;
     }
     @SuppressWarnings("unchecked")
-    private void addContentResourceChanges(final List<ResourceChange> changes, final String topic,
+    private void addContentResourceChanges(final List<ResourceChange> changes, final String changeType,
             final Map<String,Object> content, final String path) {
-        changes.add(buildContentResourceChange(topic, content, 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, topic, (Map<String,Object>)entry.getValue(), childPath);
+                    addContentResourceChanges(changes, changeType, (Map<String,Object>)entry.getValue(), childPath);
                 }
             }
         }
     }
-    private ResourceChange buildContentResourceChange(final String topic, final Map<String,Object> content, final String path) {
+    private ResourceChange buildContentResourceChange(final String changeType, final Map<String,Object> content, final String path) {
         ResourceChange change = new ResourceChange();
         change.path = path;
         change.resourceType = content != null ? (String)content.get("sling:resourceType") : null;
-        change.topic = topic;
+        change.topic = changeType;
         return change;
     }
 
@@ -351,5 +390,5 @@ public final class FileMonitor extends TimerTask {
         public String resourceType;
         public String topic;
     }
-    
+
 }
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 12e5bf2..6a9d45f 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
@@ -18,6 +18,8 @@
  */
 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;
@@ -35,6 +37,7 @@ import org.apache.sling.api.resource.ResourceProvider;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.fsprovider.internal.mapper.ContentFileResourceMapper;
 import org.apache.sling.fsprovider.internal.mapper.FileResourceMapper;
+import org.apache.sling.fsprovider.internal.mapper.FileVaultResourceMapper;
 import org.apache.sling.fsprovider.internal.parser.ContentFileCache;
 import org.apache.sling.fsprovider.internal.parser.ContentFileTypes;
 import org.osgi.framework.BundleContext;
@@ -50,6 +53,7 @@ import org.osgi.service.event.EventAdmin;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
 import org.osgi.service.metatype.annotations.Designate;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.osgi.service.metatype.annotations.Option;
 
 /**
  * The <code>FsResourceProvider</code> is a resource provider which maps
@@ -72,6 +76,11 @@ import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 @Designate(ocd=FsResourceProvider.Config.class, factory=true)
 public final class FsResourceProvider implements ResourceProvider {
     
+    /**
+     * Resource metadata property set by {@link FsResource} if the underlying file reference is a directory.
+     */
+    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")
@@ -104,17 +113,25 @@ public final class FsResourceProvider implements ResourceProvider {
                 "filesystem resources are mapped in. This property must contain at least one non-empty string.")
         String[] provider_roots();
         
-        @AttributeDefinition(name = "Mount json",
-                description = "Mount .json files as content in the resource hierarchy.")
-        boolean provider_json_content();
-       
-        @AttributeDefinition(name = "Mount jcr.xml",
-                description = "Mount .jcr.xml files as content in the resource hierarchy.")
-        boolean provider_jcrxml_content();
+        @AttributeDefinition(name = "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.
@@ -132,8 +149,10 @@ public final class FsResourceProvider implements ResourceProvider {
     private FileMonitor monitor;
     
     // maps filesystem to resources
+    private FsMode fsMode;
     private FsResourceMapper fileMapper;
     private FsResourceMapper contentFileMapper;
+    private FileVaultResourceMapper fileVaultMapper;
     
     // cache for parsed content files
     private ContentFileCache contentFileCache;
@@ -155,34 +174,56 @@ public final class FsResourceProvider implements ResourceProvider {
      * to access the file or folder. If no such file or folder exists, this
      * method returns <code>null</code>.
      */
+    @SuppressWarnings("rawtypes")
     @Override
     public Resource getResource(ResourceResolver resolver, String path) {
-        Resource rsrc = contentFileMapper.getResource(resolver, path);
-        if (rsrc == null) {
-            rsrc = fileMapper.getResource(resolver, path);
+        
+        Resource rsrc = null;
+
+        if (fsMode == FsMode.FILEVAULT_XML) {
+            // filevault: check if path matches, if not fallback to parent resource provider
+            if (fileVaultMapper.pathMatches(path)) {
+                rsrc = fileVaultMapper.getResource(resolver, path);
+            }
+        }
+        else {
+            // Sling-Initial-Content: mount folder/files an content files
+            rsrc = contentFileMapper.getResource(resolver, path);
+            if (rsrc == null) {
+                rsrc = fileMapper.getResource(resolver, path);
+            }
         }
+        
         return rsrc;
     }
-    
+
     /**
      * Returns an iterator of resources.
      */
     @SuppressWarnings("unchecked")
-    @Override
     public Iterator<Resource> listChildren(Resource parent) {
         ResourceResolver resolver = parent.getResourceResolver();
         
         List<Iterator<Resource>> allChildren = new ArrayList<>();
         Iterator<Resource> children;
         
-        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
+            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
+            children = contentFileMapper.getChildren(resolver, parent);
+            if (children != null) {
+                allChildren.add(children);
+            }
+            children = fileMapper.getChildren(resolver, parent);
+            if (children != null) {
+                allChildren.add(children);
+            }
         }
         
     	if (allChildren.isEmpty()) {
@@ -207,6 +248,7 @@ public final class FsResourceProvider implements ResourceProvider {
     // ---------- SCR Integration
     @Activate
     protected void activate(BundleContext bundleContext, final Config config) {
+        fsMode = config.provider_fs_mode();
         String[] providerRoots = config.provider_roots();
         if (providerRoots == null || providerRoots.length != 1 || StringUtils.isBlank(providerRoots[0])) {
             throw new IllegalArgumentException("provider.roots property must be set to exactly one entry.");
@@ -214,30 +256,42 @@ public final class FsResourceProvider implements ResourceProvider {
         String providerRoot = config.provider_roots()[0];
 
         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);
         
+        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);
+        if (fsMode == FsMode.FILEVAULT_XML) {
+            contentFileSuffixes.add("/" + DOT_CONTENT_XML);
         }
-        if (config.provider_jcrxml_content()) {
-            contentFileSuffixes.add(ContentFileTypes.JCR_XML_SUFFIX);
+        else if (fsMode == FsMode.INITIAL_CONTENT) {
+            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);
         }
     }
@@ -252,10 +306,12 @@ public final class FsResourceProvider implements ResourceProvider {
         this.providerFile = null;
         this.fileMapper = null;
         this.contentFileMapper = null;
+        this.fileVaultMapper = null;
         if (this.contentFileCache != null) {
             this.contentFileCache.clear();
             this.contentFileCache = null;
         }
+        this.fsMode = null;
     }
 
     EventAdmin getEventAdmin() {
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 5341ced..a9c842b 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileMonitorTest.java
@@ -18,35 +18,39 @@
  */
 package org.apache.sling.fsprovider.internal;
 
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_ADDED;
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_CHANGED;
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_REMOVED;
+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.SlingConstants;
 import org.apache.sling.fsprovider.internal.FileMonitor.ResourceChange;
+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;
-import org.osgi.service.event.Event;
 import org.osgi.service.event.EventConstants;
 import org.osgi.service.event.EventHandler;
 
 /**
- * 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 EventAdminListener eventListener = new EventAdminListener();
+    private final ResourceListener resourceListener = new ResourceListener();
     
     public FileMonitorTest() throws Exception {
         tempDir = Files.createTempDirectory(getClass().getName()).toFile();
@@ -66,15 +70,16 @@ public class FileMonitorTest {
                 context.registerInjectActivateService(new FsResourceProvider(),
                         "provider.file", tempDir.getPath(),
                         "provider.roots", "/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(EventHandler.class, eventListener,
+                context.registerService(EventHandler.class, resourceListener,
                         EventConstants.EVENT_TOPIC, new String[] {
-                                SlingConstants.TOPIC_RESOURCE_ADDED, 
-                                SlingConstants.TOPIC_RESOURCE_CHANGED,
-                                SlingConstants.TOPIC_RESOURCE_REMOVED
+                                TOPIC_RESOURCE_ADDED, 
+                                TOPIC_RESOURCE_CHANGED,
+                                TOPIC_RESOURCE_REMOVED
                         });
             }
         })
@@ -89,150 +94,122 @@ public class FileMonitorTest {
 
     @Test
     public void testUpdateFile() throws Exception {
-        List<ResourceChange> changes = eventListener.getChanges();
+        List<ResourceChange> changes = resourceListener.getChanges();
         assertTrue(changes.isEmpty());
         
         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", SlingConstants.TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/fs-test/folder1/file1a.txt", TOPIC_RESOURCE_CHANGED);
     }
     
     @Test
     public void testAddFile() throws Exception {
-        List<ResourceChange> changes = eventListener.getChanges();
+        List<ResourceChange> changes = resourceListener.getChanges();
         assertTrue(changes.isEmpty());
         
         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", SlingConstants.TOPIC_RESOURCE_CHANGED);
-        assertChange(changes, "/fs-test/folder1/file1c.txt", SlingConstants.TOPIC_RESOURCE_ADDED);
+        assertChange(changes, "/fs-test/folder1", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/fs-test/folder1/file1c.txt", TOPIC_RESOURCE_ADDED);
     }
     
     @Test
     public void testRemoveFile() throws Exception {
-        List<ResourceChange> changes = eventListener.getChanges();
+        List<ResourceChange> changes = resourceListener.getChanges();
         assertTrue(changes.isEmpty());
         
         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", SlingConstants.TOPIC_RESOURCE_CHANGED);
-        assertChange(changes, "/fs-test/folder1/file1a.txt", SlingConstants.TOPIC_RESOURCE_REMOVED);
+        assertChange(changes, "/fs-test/folder1", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/fs-test/folder1/file1a.txt", TOPIC_RESOURCE_REMOVED);
     }
     
     @Test
     public void testAddFolder() throws Exception {
-        List<ResourceChange> changes = eventListener.getChanges();
+        List<ResourceChange> changes = resourceListener.getChanges();
         assertTrue(changes.isEmpty());
         
         File folder99 = new File(tempDir, "folder99");
         folder99.mkdir();
         
-        Thread.sleep(250);
+        Thread.sleep(WAIT_INTERVAL);
 
         assertEquals(2, changes.size());
-        assertChange(changes, "/fs-test", SlingConstants.TOPIC_RESOURCE_CHANGED);
-        assertChange(changes, "/fs-test/folder99", SlingConstants.TOPIC_RESOURCE_ADDED);
+        assertChange(changes, "/fs-test", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/fs-test/folder99", TOPIC_RESOURCE_ADDED);
     }
     
     @Test
     public void testRemoveFolder() throws Exception {
-        List<ResourceChange> changes = eventListener.getChanges();
+        List<ResourceChange> changes = resourceListener.getChanges();
         assertTrue(changes.isEmpty());
         
         File folder1 = new File(tempDir, "folder1");
         FileUtils.deleteDirectory(folder1);
         
-        Thread.sleep(250);
+        Thread.sleep(WAIT_INTERVAL);
 
         assertEquals(2, changes.size());
-        assertChange(changes, "/fs-test", SlingConstants.TOPIC_RESOURCE_CHANGED);
-        assertChange(changes, "/fs-test/folder1", SlingConstants.TOPIC_RESOURCE_REMOVED);
+        assertChange(changes, "/fs-test", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/fs-test/folder1", TOPIC_RESOURCE_REMOVED);
     }
 
     @Test
-    public void testUpdateJsonContent() throws Exception {
-        List<ResourceChange> changes = eventListener.getChanges();
+    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", SlingConstants.TOPIC_RESOURCE_REMOVED);
-        assertChange(changes, "/fs-test/folder2/content", SlingConstants.TOPIC_RESOURCE_ADDED);
-        assertChange(changes, "/fs-test/folder2/content/jcr:content", SlingConstants.TOPIC_RESOURCE_ADDED);
+        assertChange(changes, "/fs-test/folder2/content", TOPIC_RESOURCE_REMOVED);
+        assertChange(changes, "/fs-test/folder2/content", TOPIC_RESOURCE_ADDED);
+        assertChange(changes, "/fs-test/folder2/content/jcr:content", TOPIC_RESOURCE_ADDED);
     }
     
     @Test
-    public void testAddJsonContent() throws Exception {
-        List<ResourceChange> changes = eventListener.getChanges();
+    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", SlingConstants.TOPIC_RESOURCE_CHANGED);
-        assertChange(changes, "/fs-test/folder1/file1c", SlingConstants.TOPIC_RESOURCE_ADDED);
-        assertChange(changes, "/fs-test/folder1/file1c/child1", SlingConstants.TOPIC_RESOURCE_ADDED);
+        assertChange(changes, "/fs-test/folder1", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/fs-test/folder1/file1c", TOPIC_RESOURCE_ADDED);
+        assertChange(changes, "/fs-test/folder1/file1c/child1", TOPIC_RESOURCE_ADDED);
     }
     
     @Test
-    public void testRemoveJsonContent() throws Exception {
-        List<ResourceChange> changes = eventListener.getChanges();
+    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", SlingConstants.TOPIC_RESOURCE_CHANGED);
-        assertChange(changes, "/fs-test/folder2/content", SlingConstants.TOPIC_RESOURCE_REMOVED);
+        assertChange(changes, "/fs-test/folder2", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/fs-test/folder2/content", TOPIC_RESOURCE_REMOVED);
     }
     
-    
-    private void assertChange(List<ResourceChange> changes, String path, String topic) {
-        boolean found = false;
-        for (ResourceChange change : changes) {
-            if (StringUtils.equals(change.path, path) && StringUtils.equals(change.topic,  topic)) {
-                found = true;
-                break;
-            }
-        }
-        assertTrue("Change with path=" + path + ", topic=" + topic, found);
-    }
-    
-    static class EventAdminListener implements EventHandler {
-        private final List<ResourceChange> allChanges = new ArrayList<>();
-        public List<ResourceChange> getChanges() {
-            return allChanges;
-        }
-        @Override
-        public void handleEvent(Event event) {
-            ResourceChange change = new ResourceChange();
-            change.path = (String)event.getProperty(SlingConstants.PROPERTY_PATH);
-            change.resourceType = (String)event.getProperty(SlingConstants.PROPERTY_RESOURCE_TYPE);
-            change.topic = event.getTopic();
-            allChanges.add(change);
-        }
-    }
-
 }
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..b6dda6a
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultContentTest.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;
+
+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.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.ResourceUtil;
+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.roots", "/content/dam/talk.png"
+                ))
+        .plugin(new RegisterFsResourcePlugin(
+                "provider.fs.mode", FsMode.FILEVAULT_XML.name(),
+                "provider.file", "src/test/resources/vaultfs-test",
+                "provider.roots", "/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 = ResourceUtil.getValueMap(metadata);
+        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" }, ResourceUtil.getValueMap(enContent).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"));
+        
+        // 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..7219033
--- /dev/null
+++ b/src/test/java/org/apache/sling/fsprovider/internal/FileVaultFileMonitorTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.api.SlingConstants.TOPIC_RESOURCE_ADDED;
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_CHANGED;
+import static org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_REMOVED;
+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.fsprovider.internal.FileMonitor.ResourceChange;
+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;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+
+/**
+ * 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.roots", "/content/dam/talk.png",
+                        "provider.checkinterval", CHECK_INTERVAL,
+                        "provider.fs.mode", FsMode.FILEVAULT_XML.name());
+                context.registerInjectActivateService(new FsResourceProvider(),
+                        "provider.file", tempDir.getPath(),
+                        "provider.roots", "/content/samples",
+                        "provider.checkinterval", CHECK_INTERVAL,
+                        "provider.fs.mode", FsMode.FILEVAULT_XML.name());
+                
+                // register resource change listener
+                context.registerService(EventHandler.class, resourceListener,
+                        EventConstants.EVENT_TOPIC, new String[] {
+                                TOPIC_RESOURCE_ADDED, 
+                                TOPIC_RESOURCE_CHANGED,
+                                TOPIC_RESOURCE_REMOVED
+                        });
+            }
+        })
+        .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", TOPIC_RESOURCE_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", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/content/dam/talk.png/jcr:content/renditions/text.txt", TOPIC_RESOURCE_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", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/content/dam/talk.png/jcr:content/renditions/web.1280.1280.png", TOPIC_RESOURCE_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", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/content/dam/talk.png/jcr:content/newfolder", TOPIC_RESOURCE_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", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/content/dam/talk.png/jcr:content/renditions", TOPIC_RESOURCE_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", TOPIC_RESOURCE_REMOVED);
+        assertChange(changes, "/content/samples/en", TOPIC_RESOURCE_ADDED);
+        assertChange(changes, "/content/samples/en/jcr:content", TOPIC_RESOURCE_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", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/content/samples/fr", TOPIC_RESOURCE_ADDED);
+        assertChange(changes, "/content/samples/fr/jcr:content", TOPIC_RESOURCE_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", TOPIC_RESOURCE_CHANGED);
+        assertChange(changes, "/content/samples/en", TOPIC_RESOURCE_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", TOPIC_RESOURCE_CHANGED);
+        // this second event is not fully correct, but this is a quite special case, accept it for now 
+        assertChange(changes, "/content/samples/en", TOPIC_RESOURCE_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 d928205..2a677ba 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JcrXmlContentTest.java
@@ -57,7 +57,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 04a9418..5ea4ea5 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/JsonContentTest.java
@@ -58,7 +58,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 {
 
@@ -67,7 +67,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 4d62076..0d12a06 100644
--- a/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
+++ b/src/test/java/org/apache/sling/fsprovider/internal/TestUtils.java
@@ -28,17 +28,23 @@ 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.SlingConstants;
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.fsprovider.internal.FileMonitor.ResourceChange;
 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.sling.context.SlingContextImpl;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
 
 class TestUtils {
 
@@ -53,6 +59,7 @@ class TestUtils {
             config.put("provider.file", "src/test/resources/fs-test");
             config.put("provider.roots", "/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 +101,30 @@ class TestUtils {
         }
     }    
 
+    public static void assertChange(List<ResourceChange> changes, String path, String topic) {
+        boolean found = false;
+        for (ResourceChange change : changes) {
+            if (StringUtils.equals(change.path, path) && StringUtils.equals(change.topic,  topic)) {
+                found = true;
+                break;
+            }
+        }
+        assertTrue("Change with path=" + path + ", topic=" + topic + " expected", found);
+    }
+    
+    public static class ResourceListener implements EventHandler {
+        private final List<ResourceChange> allChanges = new ArrayList<>();
+        public List<ResourceChange> getChanges() {
+            return allChanges;
+        }
+        @Override
+        public void handleEvent(Event event) {
+            ResourceChange change = new ResourceChange();
+            change.path = (String)event.getProperty(SlingConstants.PROPERTY_PATH);
+            change.resourceType = (String)event.getProperty(SlingConstants.PROPERTY_RESOURCE_TYPE);
+            change.topic = event.getTopic();
+            allChanges.add(change);
+        }
+    }
+
 }
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>.