You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by mr...@apache.org on 2016/08/22 09:57:45 UTC

svn commit: r1757129 [1/2] - in /jackrabbit/trunk: ./ jackrabbit-vfs-ext/ jackrabbit-vfs-ext/src/ jackrabbit-vfs-ext/src/main/ jackrabbit-vfs-ext/src/main/java/ jackrabbit-vfs-ext/src/main/java/org/ jackrabbit-vfs-ext/src/main/java/org/apache/ jackrabb...

Author: mreutegg
Date: Mon Aug 22 09:57:45 2016
New Revision: 1757129

URL: http://svn.apache.org/viewvc?rev=1757129&view=rev
Log:
JCR-3975: Commons-VFS Datastore implementation

Apply pull request from Woonsan Ko

Added:
    jackrabbit/trunk/jackrabbit-vfs-ext/   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/README.md
    jackrabbit/trunk/jackrabbit-vfs-ext/pom.xml   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/src/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/LazyFileContentInputStream.java   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSBackend.java   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSDataStore.java   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSUtils.java   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/ext/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/ext/TestAll.java   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/ext/ds/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/ext/ds/TestVFSDataStore.java   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/ext/ds/VFSTestUtils.java   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/resources/
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/resources/log4j.properties   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/resources/vfs-sftp.properties   (with props)
    jackrabbit/trunk/jackrabbit-vfs-ext/src/test/resources/vfs-webdav.properties   (with props)
Modified:
    jackrabbit/trunk/pom.xml

Propchange: jackrabbit/trunk/jackrabbit-vfs-ext/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Mon Aug 22 09:57:45 2016
@@ -0,0 +1 @@
+target

Added: jackrabbit/trunk/jackrabbit-vfs-ext/README.md
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-vfs-ext/README.md?rev=1757129&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-vfs-ext/README.md (added)
+++ jackrabbit/trunk/jackrabbit-vfs-ext/README.md Mon Aug 22 09:57:45 2016
@@ -0,0 +1,110 @@
+#Welcome to Jackrabbit Commons VFS Extension
+
+This is the Commons-VFS Extension component of the Apache Jackrabbit project.
+
+## Build Instructions
+
+To build the latest SNAPSHOT versions of all the components
+included here, run the following command with Maven 3:
+
+       mvn clean install
+
+## Unit Test Instructions
+
+### Testing with the default local file system
+
+By default, the unit tests use the local file system as backend storage.
+You can run the unit tests with the default temporary local file system like the following:
+
+        mvn clean test
+
+### Testing with WebDAV file system
+
+You can run the unit tests with WebDAV backend file system like the following:
+
+        mvn clean test -Dconfig=src/test/resources/vfs-webdav.properties
+
+*Tip*: You can install/run WsgiDAV server (http://wsgidav.readthedocs.io/en/latest/) like the following:
+
+        wsgidav --host=0.0.0.0 --port=8888 --root=/tmp/davroot
+
+### Testing with SFTP file system
+
+You can run the unit tests with WebDAV backend file system like the following:
+
+        mvn clean test -Dconfig=src/test/resources/vfs-sftp.properties
+
+## Configuration Instructions
+
+### With local file system
+
+          <DataStore class="org.apache.jackrabbit.vfs.ext.ds.VfsDataStore">
+            <!-- VFSDataStore specific parameters -->
+            <param name="baseFolderUri" value="file://${rep.home}/vfsds" />
+            <param name="asyncWritePoolSize" value="10" />
+            <!-- CachingDataStore specific parameters -->
+            <param name="secret" value="123456789"/>
+          </DataStore>
+
+### With WebDAV file system
+
+          <DataStore class="org.apache.jackrabbit.vfs.ext.ds.VFSDataStore">
+            <param name="config" value="${catalina.base}/conf/vfs2-datastore.properties" />
+            <!-- VFSDataStore specific parameters -->
+            <param name="asyncWritePoolSize" value="10" />
+            <!-- CachingDataStore specific parameters -->
+            <param name="secret" value="123456789"/>
+          </DataStore>
+
+vfs2-datastore.properties:
+
+```
+        baseFolderUri = webdav://tester:secret@localhost:8888/vfsds
+        # Properties to build org.apache.commons.vfs2.FileSystemOptions at runtime when resolving the base folder.
+        # Any properties, name of which is starting with 'fso.', are used to build FileSystemOptions
+        # after removing the 'fso.' prefix. See VFS2 documentation for the detail.
+        fso.http.maxTotalConnections = 200
+        fso.http.maxConnectionsPerHost = 200
+        fso.http.preemptiveAuth = false
+```
+
+### With SFTP file system
+
+          <DataStore class="org.apache.jackrabbit.vfs.ext.ds.VFSDataStore">
+            <param name="config" value="${catalina.base}/conf/vfs2-datastore.properties" />
+            <!-- VFSDataStore specific parameters -->
+            <param name="asyncWritePoolSize" value="10" />
+            <!-- CachingDataStore specific parameters -->
+            <param name="secret" value="123456789"/>
+          </DataStore>
+
+vfs2-datastore.properties:
+
+```
+        baseFolderUri = sftp://tester:secret@localhost/vfsds
+        # Properties to build org.apache.commons.vfs2.FileSystemOptions at runtime when resolving the base folder.
+        # Any properties, name of which is starting with 'fso.', are used to build FileSystemOptions
+        # after removing the 'fso.' prefix. See VFS2 documentation for the detail.
+```
+
+License
+-------
+
+(see the top-level [LICENSE.txt](../LICENSE.txt) for full license details)
+
+Collective work: Copyright 2012 The Apache Software Foundation.
+
+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.

Added: jackrabbit/trunk/jackrabbit-vfs-ext/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-vfs-ext/pom.xml?rev=1757129&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-vfs-ext/pom.xml (added)
+++ jackrabbit/trunk/jackrabbit-vfs-ext/pom.xml Mon Aug 22 09:57:45 2016
@@ -0,0 +1,129 @@
+<?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. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd ">
+    <modelVersion>4.0.0</modelVersion>
+
+    <!-- ====================================================================== -->
+    <!-- P R O J E C T D E S C R I P T I O N -->
+    <!-- ====================================================================== -->
+    <parent>
+        <groupId>org.apache.jackrabbit</groupId>
+        <artifactId>jackrabbit-parent</artifactId>
+        <version>2.13.2-SNAPSHOT</version>
+        <relativePath>../jackrabbit-parent/pom.xml</relativePath>
+    </parent>
+    <artifactId>jackrabbit-vfs-ext</artifactId>
+    <name>Jackrabbit VFS Extension</name>
+    <description>Jackrabbit extenstion to Commons VFS</description>
+    <packaging>bundle</packaging>
+
+    <!-- ====================================================================== -->
+    <!-- D E P E N D E N C I E S -->
+    <!-- ====================================================================== -->
+    <dependencies>
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.commons</groupId>
+          <artifactId>commons-vfs2</artifactId>
+          <version>2.1</version>
+          <exclusions>
+            <exclusion>
+              <groupId>commons-logging</groupId>
+              <artifactId>commons-logging</artifactId>
+            </exclusion>
+          </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-jcr-commons</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-data</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <!-- For WebDAV client against WebDAV server backend -->
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-webdav</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-data</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <!-- Test dependency for SFTP VFS2 File System -->
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <version>0.1.53</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>1.7.5</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <includes>
+                        <include>**/vfs/**/TestAll.java</include>
+                    </includes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>org.apache.jackrabbit.vfs.ext.ds</Export-Package>
+                        <DynamicImport-Package>sun.io</DynamicImport-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>.checkstyle</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

Propchange: jackrabbit/trunk/jackrabbit-vfs-ext/pom.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/LazyFileContentInputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/LazyFileContentInputStream.java?rev=1757129&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/LazyFileContentInputStream.java (added)
+++ jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/LazyFileContentInputStream.java Mon Aug 22 09:57:45 2016
@@ -0,0 +1,171 @@
+/*
+ * 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.jackrabbit.vfs.ext.ds;
+
+import java.io.IOException;
+
+import org.apache.commons.io.input.AutoCloseInputStream;
+import org.apache.commons.vfs2.FileNotFoundException;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+
+/**
+ * This input stream delays opening the file content until the first byte is read, and
+ * closes and discards the underlying stream as soon as the end of input has
+ * been reached or when the stream is explicitly closed.
+ */
+public class LazyFileContentInputStream extends AutoCloseInputStream {
+
+    /**
+     * The file object to read from.
+     */
+    protected final FileObject fileObject;
+
+    /**
+     * True if the input stream was opened. It is also set to true if the stream
+     * was closed without reading (to avoid opening the file content after the stream
+     * was closed).
+     */
+    protected boolean opened;
+
+    /**
+     * Creates a new <code>LazyFileInputStream</code> for the given file. If the
+     * file is unreadable, a FileSystemException is thrown.
+     * The file is not opened until the first byte is read from the stream.
+     *
+     * @param file the file
+     * @throws org.apache.commons.vfs2.FileNotFoundException
+     * @throws org.apache.commons.vfs2.FileSystemException
+     */
+    public LazyFileContentInputStream(FileObject fileObject) throws FileSystemException {
+        super(null);
+
+        if (!fileObject.isReadable()) {
+            throw new FileNotFoundException(fileObject.getName().getFriendlyURI());
+        }
+
+        this.fileObject = fileObject;
+    }
+
+    /**
+     * Open the stream if required.
+     *
+     * @throws java.io.IOException
+     */
+    protected void open() throws IOException {
+        if (!opened) {
+            opened = true;
+            in = fileObject.getContent().getInputStream();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int read() throws IOException {
+        open();
+        return super.read();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int available() throws IOException {
+        open();
+        return super.available();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close() throws IOException {
+        // make sure the file is not opened afterwards
+        opened = true;
+
+        // only close the file if it was in fact opened
+        if (in != null) {
+            super.close();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void reset() throws IOException {
+        open();
+        super.reset();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean markSupported() {
+        try {
+            open();
+        } catch (IOException e) {
+            throw new IllegalStateException(e.toString());
+        }
+
+        return super.markSupported();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void mark(int readlimit) {
+        try {
+            open();
+        } catch (IOException e) {
+            throw new IllegalStateException(e.toString());
+        }
+
+        super.mark(readlimit);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long skip(long n) throws IOException {
+        open();
+        return super.skip(n);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int read(byte[] b) throws IOException {
+        open();
+        return super.read(b, 0, b.length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        open();
+        return super.read(b, off, len);
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/LazyFileContentInputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSBackend.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSBackend.java?rev=1757129&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSBackend.java (added)
+++ jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSBackend.java Mon Aug 22 09:57:45 2016
@@ -0,0 +1,861 @@
+/*
+ * 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.jackrabbit.vfs.ext.ds;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.sql.Timestamp;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.jackrabbit.core.data.AsyncTouchCallback;
+import org.apache.jackrabbit.core.data.AsyncTouchResult;
+import org.apache.jackrabbit.core.data.AsyncUploadCallback;
+import org.apache.jackrabbit.core.data.AsyncUploadResult;
+import org.apache.jackrabbit.core.data.Backend;
+import org.apache.jackrabbit.core.data.CachingDataStore;
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.core.data.util.NamedThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A data store backend that stores data on VFS file system.
+ */
+public class VFSBackend implements Backend {
+
+    /**
+     * Logger instance.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(VFSBackend.class);
+
+    /**
+     * The default pool size of asynchronous write pooling executor.
+     */
+    static final int DEFAULT_ASYNC_WRITE_POOL_SIZE = 10;
+
+    /**
+     * The maximum last modified time resolution of the file system.
+     */
+    private static final int ACCESS_TIME_RESOLUTION = 2000;
+
+    /**
+     * Touch file name suffix.
+     * When {@link #isTouchFilePreferred()} returns true, this backend creates a separate file named by
+     * the original file base name followed by this touch file name suffix.
+     * So, this backend can set the last modified time on the separate touch file instead of trying to do it
+     * on the original entry file.
+     * For example, WebDAV file system doesn't allow to modify the last modified time on a file.
+     */
+    private static final String TOUCH_FILE_NAME_SUFFIX = ".touch";
+
+    /**
+     * {@link CachingDataStore} instance using this backend.
+     */
+    private CachingDataStore store;
+
+    /**
+     * VFS base folder object.
+     */
+    private FileObject baseFolder;
+
+    /**
+     * The pool size of asynchronous write pooling executor.
+     */
+    private int asyncWritePoolSize = DEFAULT_ASYNC_WRITE_POOL_SIZE;
+
+    /**
+     * Asynchronous write pooling executor.
+     */
+    private ThreadPoolExecutor asyncWriteExecuter;
+
+    /**
+     * Whether or not a touch file is preferred to set/get the last modified timestamp for a file object
+     * instead of setting/getting the last modified timestamp directly from the file object.
+     */
+    private boolean touchFilePreferred = true;
+
+    public VFSBackend(FileObject baseFolder) {
+        this.baseFolder = baseFolder;
+    }
+
+    /**
+     * Returns the pool size of the async write pool executor.
+     * @return the pool size of the async write pool executor
+     */
+    public int getAsyncWritePoolSize() {
+        return asyncWritePoolSize;
+    }
+
+    /**
+     * Sets the pool size of the async write pool executor.
+     * @param asyncWritePoolSize pool size of the async write pool executor
+     */
+    public void setAsyncWritePoolSize(int asyncWritePoolSize) {
+        this.asyncWritePoolSize = asyncWritePoolSize;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void init(CachingDataStore store, String homeDir, String config) throws DataStoreException {
+        this.store = store;
+
+        // When it's local file system, no need to use a separate touch file.
+        if ("file".equals(baseFolder.getName().getScheme())) {
+            touchFilePreferred = false;
+        }
+
+        asyncWriteExecuter = createAsyncWriteExecuter();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public InputStream read(DataIdentifier identifier) throws DataStoreException {
+        FileObject fileObject = getExistingFileObject(identifier);
+
+        if (fileObject == null) {
+            throw new DataStoreException("Could not find file object for: " + identifier);
+        }
+
+        try {
+            return new LazyFileContentInputStream(fileObject);
+        } catch (FileSystemException e) {
+            throw new DataStoreException("Could not get input stream from object: " + identifier, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long getLength(DataIdentifier identifier) throws DataStoreException {
+        FileObject fileObject = getExistingFileObject(identifier);
+
+        if (fileObject == null) {
+            throw new DataStoreException("Could not find file object for: " + identifier);
+        }
+
+        try {
+            return fileObject.getContent().getSize();
+        } catch (FileSystemException e) {
+            throw new DataStoreException("Could not get length from object: " + identifier, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public long getLastModified(DataIdentifier identifier) throws DataStoreException {
+        FileObject fileObject = getExistingFileObject(identifier);
+
+        if (fileObject == null) {
+            throw new DataStoreException("Could not find file object for: " + identifier);
+        }
+
+        return getLastModifiedTime(fileObject);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void write(DataIdentifier identifier, File file) throws DataStoreException {
+        write(identifier, file, false, null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void writeAsync(DataIdentifier identifier, File file, AsyncUploadCallback callback)
+            throws DataStoreException {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback parameter cannot be null in asyncUpload");
+        }
+
+        getAsyncWriteExecuter().execute(new AsyncUploadJob(identifier, file, callback));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterator<DataIdentifier> getAllIdentifiers() throws DataStoreException {
+        List<DataIdentifier> identifiers = new LinkedList<DataIdentifier>();
+
+        try {
+            for (FileObject fileObject : VFSUtils.getChildFolders(getBaseFolderObject())) { // skip top-level files
+                pushIdentifiersRecursively(identifiers, fileObject);
+            }
+        } catch (FileSystemException e) {
+            throw new DataStoreException("Object identifiers not resolved.", e);
+        }
+
+        LOG.debug("Found " + identifiers.size() + " identifiers.");
+
+        return identifiers.iterator();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean exists(DataIdentifier identifier, boolean touch) throws DataStoreException {
+        FileObject fileObject = getExistingFileObject(identifier);
+
+        if (fileObject == null) {
+            return false;
+        }
+
+        if (touch) {
+            touch(identifier, System.currentTimeMillis(), false, null);
+        }
+
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean exists(DataIdentifier identifier) throws DataStoreException {
+        return exists(identifier, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void touch(DataIdentifier identifier, long minModifiedDate) throws DataStoreException {
+        touch(identifier, minModifiedDate, false, null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void touchAsync(DataIdentifier identifier, long minModifiedDate, AsyncTouchCallback callback)
+            throws DataStoreException {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback parameter cannot be null in touchAsync");
+        }
+
+        getAsyncWriteExecuter().execute(new AsyncTouchJob(identifier, minModifiedDate, callback));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close() throws DataStoreException {
+        getAsyncWriteExecuter().shutdownNow();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Set<DataIdentifier> deleteAllOlderThan(long timestamp) throws DataStoreException {
+        Set<DataIdentifier> deleteIdSet = new HashSet<DataIdentifier>(30);
+
+        try {
+            for (FileObject folderObject : VFSUtils.getChildFolders(getBaseFolderObject())) {
+                deleteOlderRecursive(deleteIdSet, folderObject, timestamp);
+            }
+        } catch (FileSystemException e) {
+            throw new DataStoreException("Object deletion aborted.", e);
+        }
+
+        return deleteIdSet;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void deleteRecord(DataIdentifier identifier) throws DataStoreException {
+        FileObject fileObject = getExistingFileObject(identifier);
+
+        if (fileObject != null) {
+            deleteRecordFileObject(fileObject);
+            deleteEmptyParentFolders(fileObject);
+        }
+    }
+
+    /**
+     * Returns true if a touch file should be used to save/get the last modified time for a file object.
+     * True by default unless the {@link #getBaseFolderObject()} is representing a local file system folder (e.g, file://...).
+     * <P>
+     * When returns true, this backend creates a separate file named by the original file base name followed
+     * by this touch file name suffix. So, this backend can set the last modified time on the separate touch file
+     * instead of trying to do it on the original entry file.
+     * For example, WebDAV file system doesn't allow to modify the last modified time on a file.
+     * </P>
+     * @return true if a touch file should be used to save/get the last modified time for a file object
+     */
+    public boolean isTouchFilePreferred() {
+        return touchFilePreferred;
+    }
+
+    /**
+     * Sets whether or not a touch file should be used to save/get the last modified timestamp for a file object.
+     * @param touchFilePreferred whether or not a touch file should be used to save/get the last modified timestamp for a file object
+     */
+    public void setTouchFilePreferred(boolean touchFilePreferred) {
+        this.touchFilePreferred = touchFilePreferred;
+    }
+
+    /**
+     * Returns the VFS base folder object.
+     * @return the VFS base folder object
+     */
+    protected FileObject getBaseFolderObject() {
+        return baseFolder;
+    }
+
+    /**
+     * Returns a resolved identified file object. This method implements the pattern
+     * used to avoid problems with too many files in a single folder.
+     *
+     * @param identifier data identifier
+     * @return identified file object
+     * @throws FileSystemException if VFS file system exception occurs
+     * @throws DataStoreException if any file system exception occurs
+     */
+    protected FileObject resolveFileObject(DataIdentifier identifier) throws DataStoreException {
+        try {
+            String relPath = resolveFileObjectRelPath(identifier);
+            return getBaseFolderObject().resolveFile(relPath);
+        } catch (FileSystemException e) {
+            throw new DataStoreException("File object not resolved: " + identifier, e);
+        }
+    }
+
+    /**
+     * Returns a resolved relative file object path by the given entry identifier.
+     * @param identifier entry identifier
+     * @return a resolved relative file object path by the given entry identifier
+     */
+    protected String resolveFileObjectRelPath(DataIdentifier identifier) {
+        String idString = identifier.toString();
+        StringBuilder sb = new StringBuilder(80);
+        sb.append(idString.substring(0, 2)).append('/');
+        sb.append(idString.substring(2, 4)).append('/');
+        sb.append(idString.substring(4, 6)).append('/');
+        sb.append(idString);
+        return sb.toString();
+    }
+
+    /**
+     * Returns the identified file object. If not existing, returns null.
+     *
+     * @param identifier data identifier
+     * @return identified file object
+     * @throws FileSystemException if any file system exception occurs
+     * @throws DataStoreException if any file system exception occurs
+     */
+    protected FileObject getExistingFileObject(DataIdentifier identifier) throws DataStoreException {
+        String relPath = resolveFileObjectRelPath(identifier);
+        String [] segments = relPath.split("/");
+
+        FileObject tempFileObject = getBaseFolderObject();
+
+        try {
+            for (int i = 0; i < segments.length; i++) {
+                tempFileObject = tempFileObject.getChild(segments[i]);
+
+                if (tempFileObject == null) {
+                    return null;
+                }
+            }
+
+            return tempFileObject;
+        } catch (FileSystemException e) {
+            throw new DataStoreException("File object not resolved: " + identifier, e);
+        }
+    }
+
+    /**
+     * Returns true if the fileObject is used for touching purpose.
+     *
+     * @param fileObject file object
+     * @return true if the fileObject is used for touching purpose
+     */
+    protected boolean isTouchFileObject(FileObject fileObject) {
+        if (fileObject.getName().getBaseName().endsWith(TOUCH_FILE_NAME_SUFFIX)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the touch file for the fileObject.
+     * If there's no corresponding touch file existing, then returns null when {@code create} is false.
+     * When {@code create} is true, it creates a new touch file if no corresponding touch file exists.
+     *
+     * @param fileObject file object
+     * @param create create a touch file if not existing
+     * @return touch file object
+     * @throws DataStoreException if any file system exception occurs
+     */
+    protected FileObject getTouchFileObject(FileObject fileObject, boolean create) throws DataStoreException {
+        try {
+            FileObject folderObject = fileObject.getParent();
+            String touchFileName = fileObject.getName().getBaseName() + TOUCH_FILE_NAME_SUFFIX;
+            FileObject touchFileObject = folderObject.getChild(touchFileName);
+
+            if (touchFileObject == null && create) {
+                touchFileObject = folderObject.resolveFile(touchFileName);
+                touchFileObject.createFile();
+                touchFileObject = folderObject.getChild(touchFileName);
+            }
+
+            return touchFileObject;
+        } catch (FileSystemException e) {
+            throw new DataStoreException("Touch file object not resolved: " + fileObject.getName().getFriendlyURI(), e);
+        }
+    }
+
+    /**
+     * Creates a {@link ThreadPoolExecutor}.
+     * This method is invoked during the initialization for asynchronous write/touch job executions.
+     * @return a {@link ThreadPoolExecutor}
+     */
+    protected ThreadPoolExecutor createAsyncWriteExecuter() {
+        return (ThreadPoolExecutor) Executors.newFixedThreadPool(asyncWritePoolSize,
+                new NamedThreadFactory("vfs-write-worker"));
+    }
+
+    /**
+     * Returns ThreadPoolExecutor used to execute asynchronous write or touch jobs.
+     * @return ThreadPoolExecutor used to execute asynchronous write or touch jobs
+     */
+    protected ThreadPoolExecutor getAsyncWriteExecuter() {
+        return asyncWriteExecuter;
+    }
+
+    /**
+     * Copy the content of the local file ({@code srcFile}) to the record identified by the {@code identifier}.
+     * @param srcFile source local file
+     * @param identifier record identifier
+     * @throws IOException if any IO exception occurs
+     * @throws DataStoreException if any file system exception occurs
+     */
+    private void copyFileContentToRecord(File srcFile, DataIdentifier identifier) throws IOException, DataStoreException {
+        String relPath = resolveFileObjectRelPath(identifier);
+        String [] segments = relPath.split("/");
+
+        InputStream input = null;
+        OutputStream output = null;
+
+        try {
+            FileObject baseFolderObject = getBaseFolderObject();
+            FileObject folderObject = null;
+
+            for (int i = 0; i < segments.length - 1; i++) {
+                folderObject = VFSUtils.createChildFolder(baseFolderObject, segments[i]);
+                baseFolderObject = folderObject;
+            }
+
+            FileObject destFileObject = VFSUtils.createChildFile(folderObject, segments[segments.length - 1]);
+            input = new FileInputStream(srcFile);
+            output = destFileObject.getContent().getOutputStream();
+            IOUtils.copy(input, output);
+        } finally {
+            IOUtils.closeQuietly(output);
+            IOUtils.closeQuietly(input);
+        }
+    }
+
+    /**
+     * Set the last modified time of a fileObject, if the fileObject is writable.
+     * @param fileObject the file object
+     * @param time the new last modified date
+     * @throws DataStoreException if the fileObject is writable but modifying the date fails
+     */
+    private void updateLastModifiedTime(FileObject fileObject) throws DataStoreException {
+        try {
+            if (isTouchFilePreferred()) {
+                getTouchFileObject(fileObject, true);
+            } else {
+                long time = System.currentTimeMillis() + ACCESS_TIME_RESOLUTION;
+                fileObject.getContent().setLastModifiedTime(time);
+            }
+        } catch (FileSystemException e) {
+            throw new DataStoreException("An IO Exception occurred while trying to set the last modified date: "
+                    + fileObject.getName().getFriendlyURI(), e);
+        }
+    }
+
+    /**
+     * Get the last modified time of a file object.
+     * @param file the file object
+     * @return the last modified date
+     * @throws DataStoreException if reading fails
+     */
+    private long getLastModifiedTime(FileObject fileObject) throws DataStoreException {
+        long lastModified = 0;
+
+        try {
+            if (isTouchFilePreferred()) {
+                FileObject touchFile = getTouchFileObject(fileObject, false);
+
+                if (touchFile != null) {
+                    lastModified = touchFile.getContent().getLastModifiedTime();
+                } else {
+                    lastModified = fileObject.getContent().getLastModifiedTime();
+                }
+            } else {
+                lastModified = fileObject.getContent().getLastModifiedTime();
+            }
+
+            if (lastModified == 0) {
+                throw new DataStoreException("Failed to read record modified date: " + fileObject.getName().getFriendlyURI());
+            }
+        } catch (FileSystemException e) {
+            throw new DataStoreException("Failed to read record modified date: " + fileObject.getName().getFriendlyURI());
+        }
+
+        return lastModified;
+    }
+
+    /**
+     * Scans {@code folderObject} and all the descendant folders to find record entries and push the record entry
+     * identifiers to {@code identifiers}.
+     * @param identifiers identifier list
+     * @param folderObject folder object
+     * @throws FileSystemException if any file system exception occurs
+     * @throws DataStoreException if any file system exception occurs
+     */
+    private void pushIdentifiersRecursively(List<DataIdentifier> identifiers, FileObject folderObject)
+            throws FileSystemException, DataStoreException {
+        FileType type;
+
+        for (FileObject fileObject : VFSUtils.getChildFileOrFolders(folderObject)) {
+            type = fileObject.getType();
+
+            if (type == FileType.FOLDER) {
+                pushIdentifiersRecursively(identifiers, fileObject);
+            } else if (type == FileType.FILE) {
+                if (!isTouchFileObject(fileObject)) {
+                    identifiers.add(new DataIdentifier(fileObject.getName().getBaseName()));
+                }
+            }
+        }
+    }
+
+    /**
+     * Writes {@code file}'s content to the record entry identified by {@code identifier}.
+     * @param identifier record identifier
+     * @param file local file to copy from
+     * @param asyncUpload whether or not it should be done asynchronously
+     * @param callback asynchronous uploading callback instance
+     * @throws DataStoreException if any file system exception occurs
+     */
+    private void write(DataIdentifier identifier, File file, boolean asyncUpload, AsyncUploadCallback callback)
+            throws DataStoreException {
+        AsyncUploadResult asyncUpRes = null;
+
+        if (asyncUpload) {
+            asyncUpRes = new AsyncUploadResult(identifier, file);
+        }
+
+        synchronized (this) {
+            FileObject fileObject = getExistingFileObject(identifier);
+            FileObject resolvedFileObject = resolveFileObject(identifier);
+
+            try {
+                if (fileObject != null) {
+                    updateLastModifiedTime(resolvedFileObject);
+                } else {
+                    copyFileContentToRecord(file, identifier);
+                }
+
+                if (asyncUpRes != null && callback != null) {
+                    callback.onSuccess(asyncUpRes);
+                }
+            } catch (IOException e) {
+                DataStoreException e2 = new DataStoreException(
+                        "Could not get output stream to object: " + resolvedFileObject.getName().getFriendlyURI(), e);
+
+                if (asyncUpRes != null && callback != null) {
+                    asyncUpRes.setException(e2);
+                    callback.onFailure(asyncUpRes);
+                }
+
+                throw e2;
+            }
+        }
+    }
+
+    /**
+     * Touches the object entry file identified by {@code identifier}.
+     * @param identifier record identifier
+     * @param minModifiedDate minimum modified date time to be used in touching
+     * @param asyncTouch whether or not it should be done asynchronously
+     * @param callback asynchrounous touching callback instance
+     * @throws DataStoreException if any file system exception occurs
+     */
+    private void touch(DataIdentifier identifier, long minModifiedDate, boolean asyncTouch, AsyncTouchCallback callback)
+            throws DataStoreException {
+        AsyncTouchResult asyncTouchRes = null;
+
+        if (asyncTouch) {
+            asyncTouchRes = new AsyncTouchResult(identifier);
+        }
+
+        try {
+            FileObject fileObject = getExistingFileObject(identifier);
+
+            if (fileObject != null) {
+                if (minModifiedDate > 0 && minModifiedDate > getLastModifiedTime(fileObject)) {
+                    updateLastModifiedTime(fileObject);
+                }
+            } else {
+                LOG.debug("File doesn't exist for the identifier: {}.", identifier);
+            }
+        } catch (DataStoreException e) {
+            if (asyncTouchRes != null) {
+                asyncTouchRes.setException(e);
+            }
+
+            throw e;
+        } finally {
+            if (asyncTouchRes != null && callback != null) {
+                if (asyncTouchRes.getException() != null) {
+                    callback.onFailure(asyncTouchRes);
+                } else {
+                    callback.onSuccess(asyncTouchRes);
+                }
+            }
+        }
+    }
+
+    /**
+     * Deletes record file object.
+     * @param fileObject file object to delete
+     * @return true if deleted
+     * @throws DataStoreException if any file system exception occurs
+     */
+    private boolean deleteRecordFileObject(FileObject fileObject) throws DataStoreException {
+        if (isTouchFilePreferred()) {
+            try {
+                FileObject touchFile = getTouchFileObject(fileObject, false);
+
+                if (touchFile != null) {
+                    touchFile.delete();
+                }
+            } catch (FileSystemException e) {
+                LOG.warn("Could not delete touch file for " + fileObject.getName().getFriendlyURI(), e);
+            }
+        }
+
+        try {
+            return fileObject.delete();
+        } catch (FileSystemException e) {
+            throw new DataStoreException("Could not delete record file at " + fileObject.getName().getFriendlyURI(), e);
+        }
+    }
+
+    /**
+     * Deletes the parent folders of {@code fileObject} if a parent folder is empty.
+     * @param fileObject fileObject to start with
+     * @throws DataStoreException if any file system exception occurs
+     */
+    private void deleteEmptyParentFolders(FileObject fileObject) throws DataStoreException {
+        try {
+            String baseFolderUri = getBaseFolderObject().getName().getFriendlyURI() + "/";
+            FileObject parentFolder = fileObject.getParent();
+
+            // Only iterate & delete if parent folder of the blob file is
+            // child of the base directory and if it is empty
+            while (parentFolder.getName().getFriendlyURI().startsWith(baseFolderUri)) {
+                if (VFSUtils.hasAnyChildFileOrFolder(parentFolder)) {
+                    break;
+                }
+
+                boolean deleted = parentFolder.delete();
+                LOG.debug("Deleted parent folder [{}] of file [{}]: {}",
+                        new Object[] { parentFolder, fileObject.getName().getFriendlyURI(), deleted });
+                parentFolder = parentFolder.getParent();
+            }
+        } catch (IOException e) {
+            LOG.warn("Error in parents deletion for " + fileObject.getName().getFriendlyURI(), e);
+        }
+    }
+
+    /**
+     * Deletes any descendant record files under {@code folderObject} if the record files are older than {@code timestamp},
+     * and push all the deleted record identifiers into {@code deleteIdSet}.
+     * @param deleteIdSet set to store all the deleted record identifiers
+     * @param folderObject folder object to start with
+     * @param timestamp timestamp
+     * @throws FileSystemException if any file system exception occurs
+     * @throws DataStoreException if any file system exception occurs
+     */
+    private void deleteOlderRecursive(Set<DataIdentifier> deleteIdSet, FileObject folderObject, long timestamp)
+            throws FileSystemException, DataStoreException {
+        FileType type;
+        DataIdentifier identifier;
+
+        for (FileObject fileObject : VFSUtils.getChildFileOrFolders(folderObject)) {
+            type = fileObject.getType();
+
+            if (type == FileType.FOLDER) {
+                deleteOlderRecursive(deleteIdSet, fileObject, timestamp);
+
+                synchronized (this) {
+                    if (!VFSUtils.hasAnyChildFileOrFolder(fileObject)) {
+                        fileObject.delete();
+                    }
+                }
+
+            } else if (type == FileType.FILE) {
+                long lastModified = getLastModifiedTime(fileObject);
+
+                if (lastModified < timestamp) {
+                    identifier = new DataIdentifier(fileObject.getName().getBaseName());
+
+                    if (store.confirmDelete(identifier)) {
+                        store.deleteFromCache(identifier);
+
+                        if (LOG.isInfoEnabled()) {
+                            LOG.info("Deleting old file " + fileObject.getName().getFriendlyURI() + " modified: "
+                                    + new Timestamp(lastModified).toString() + " length: "
+                                    + fileObject.getContent().getSize());
+                        }
+
+                        if (deleteRecordFileObject(fileObject)) {
+                            deleteIdSet.add(identifier);
+                        } else {
+                            LOG.warn("Failed to delete old file " + fileObject.getName().getFriendlyURI());
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * This class implements {@link Runnable} interface to copy {@link File} to VFS file object asynchronously.
+     */
+    private class AsyncUploadJob implements Runnable {
+
+        /**
+         * Record data identifier.
+         */
+        private DataIdentifier identifier;
+
+        /**
+         * Source file to upload.
+         */
+        private File file;
+
+        /**
+         * Callback to handle events on completion, failure or abortion.
+         */
+        private AsyncUploadCallback callback;
+
+        /**
+         * Constructs an asynchronous file uploading job.
+         * @param identifier record data identifier
+         * @param file source file to upload
+         * @param callback callback to handle events on completion, failure or abortion.
+         */
+        public AsyncUploadJob(DataIdentifier identifier, File file, AsyncUploadCallback callback) {
+            super();
+            this.identifier = identifier;
+            this.file = file;
+            this.callback = callback;
+        }
+
+        /**
+         * Executes this job.
+         */
+        public void run() {
+            try {
+                write(identifier, file, true, callback);
+            } catch (DataStoreException e) {
+                LOG.error("Could not upload [" + identifier + "], file[" + file + "]", e);
+            }
+        }
+    }
+
+    /**
+     * This class implements {@link Runnable} interface to touch a VFS file object asynchronously.
+     */
+    private class AsyncTouchJob implements Runnable {
+
+        /**
+         * Record data identifier.
+         */
+        private DataIdentifier identifier;
+
+        /**
+         * Minimum modification time in milliseconds to be used in touching.
+         */
+        private long minModifiedDate;
+
+        /**
+         * Callback to handle events on completion, failure or abortion.
+         */
+        private AsyncTouchCallback callback;
+
+        /**
+         * Constructs an asynchronous record touching job.
+         * @param identifier record data identifier
+         * @param minModifiedDate minimum modification time in milliseconds to be used in touching
+         * @param callback callback to handle events on completion, failure or abortion
+         */
+        public AsyncTouchJob(DataIdentifier identifier, long minModifiedDate, AsyncTouchCallback callback) {
+            super();
+            this.identifier = identifier;
+            this.minModifiedDate = minModifiedDate;
+            this.callback = callback;
+        }
+
+        /**
+         * Executes this job.
+         */
+        public void run() {
+            try {
+                touch(identifier, minModifiedDate, true, callback);
+            } catch (DataStoreException e) {
+                LOG.error("Could not touch [" + identifier + "]", e);
+            }
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSBackend.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSDataStore.java?rev=1757129&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSDataStore.java (added)
+++ jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSDataStore.java Mon Aug 22 09:57:45 2016
@@ -0,0 +1,432 @@
+/*
+ * 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.jackrabbit.vfs.ext.ds;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
+import org.apache.commons.vfs2.impl.StandardFileSystemManager;
+import org.apache.commons.vfs2.util.DelegatingFileSystemOptionsBuilder;
+import org.apache.jackrabbit.core.data.Backend;
+import org.apache.jackrabbit.core.data.CachingDataStore;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Commons VFS based data store.
+ */
+public class VFSDataStore extends CachingDataStore {
+
+    /**
+     * Logger instance.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(VFSDataStore.class);
+
+    /**
+     * Property key name for maximum backend connection. e.g. max total http connections.
+     */
+    static final String PROP_MAX_TOTAL_CONNECTIONS = "maxTotalConnections";
+
+    /**
+     * Property key name for maximum backend connection. e.g. max http connections per host.
+     */
+    static final String PROP_MAX_CONNECTIONS_PER_HOST = "maxConnectionsPerHost";
+
+    /**
+     * Default maximum backend connection. e.g. max http connection.
+     */
+    static final int DEFAULT_MAX_CONNECTION = 200;
+
+    /**
+     * Property key prefix for FielSystemOptions.
+     */
+    private static final String FILE_SYSTEM_OPTIONS_PROP_PREFIX = "fso.";
+
+    /**
+     * The class name of the VFS {@link FileSystemManager} instance used in this VFS data store.
+     * If this is not set, then {@link StandardFileSystemManager} is used by default.
+     */
+    private String fileSystemManagerClassName;
+
+    /**
+     * The VFS {@link FileSystemManager} instance used in this VFS data store.
+     * If {@link #fileSystemManagerClassName} is not set, then a {@link StandardFileSystemManager} instance is created by default.
+     */
+    private FileSystemManager fileSystemManager;
+
+    /**
+     * {@link FileSystemOptions} used when resolving the {@link #baseFolder}.
+     */
+    private FileSystemOptions fileSystemOptions;
+
+    /**
+     * Properties used when building a {@link FileSystemOptions} using {@link DelegatingFileSystemOptionsBuilder}.
+     */
+    private Properties fileSystemOptionsProperties;
+
+    /**
+     * The VFS base folder URI where all the entry files are maintained.
+     */
+    private String baseFolderUri;
+
+    /**
+     * The VFS base folder object where all the entry files are maintained.
+     */
+    private FileObject baseFolder;
+
+    /**
+     * The pool size of asynchronous write pooling executor.
+     */
+    private int asyncWritePoolSize = VFSBackend.DEFAULT_ASYNC_WRITE_POOL_SIZE;
+
+    @Override
+    public void init(String homeDir) throws RepositoryException {
+        overridePropertiesFromConfig();
+
+        if (baseFolderUri == null) {
+            throw new RepositoryException("VFS base folder URI must be set.");
+        }
+
+        fileSystemManager = createFileSystemManager();
+
+        FileName baseFolderName = null;
+
+        try {
+            baseFolderName = fileSystemManager.resolveURI(baseFolderUri);
+
+            FileSystemOptions fso = getFileSystemOptions();
+
+            if (fso != null) {
+                baseFolder = fileSystemManager.resolveFile(baseFolderUri, fso);
+            } else {
+                baseFolder = fileSystemManager.resolveFile(baseFolderUri);
+            }
+
+            baseFolder.createFolder();
+        } catch (FileSystemException e) {
+            throw new RepositoryException("Could not initialize the VFS base folder at '"
+                    + (baseFolderName == null ? "" : baseFolderName.getFriendlyURI()) + "'.", e);
+        }
+
+        super.init(homeDir);
+    }
+
+    @Override
+    public void close() throws DataStoreException {
+        VFSBackend backend = (VFSBackend) getBackend();
+
+        try {
+            // Let's wait for 5 minutes at max if there are still execution jobs in the async writing executor's queue.
+            int seconds = 0;
+            while (backend.getAsyncWriteExecuter().getActiveCount() > 0 && seconds++ < 300) {
+                Thread.sleep(1000);
+            }
+        } catch (InterruptedException e) {
+            LOG.warn("Interrupted while waiting for the async write executor to complete.", e);
+        }
+
+        // Commenting out the following because the javadoc of FileSystemManager#closeFileSystem(FileSystem)
+        // says it is dangerous when singleton instance is being used, which is the case as VFSDataStore keeps
+        // single file system manager instance.
+        // Also, VFS seems to remove the related provider component on that invocation.
+//        if (fileSystemManager != null) {
+//            fileSystemManager.closeFileSystem(baseFolder.getFileSystem());
+//        }
+
+        super.close();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected Backend createBackend() {
+        VFSBackend backend = new VFSBackend(baseFolder);
+        backend.setAsyncWritePoolSize(getAsyncWritePoolSize());
+        return backend;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected String getMarkerFile() {
+        return "vfs.init.done";
+    }
+
+    /**
+     * Returns the class name of the VFS {@link FileSystemManager} instance used in this VFS data store.
+     * @return
+     */
+    public String getFileSystemManagerClassName() {
+        return fileSystemManagerClassName;
+    }
+
+    /**
+     * Sets the class name of the VFS {@link FileSystemManager} instance used in this VFS data store.
+     * If this is not set, then {@link StandardFileSystemManager} is used by default.
+     * @param fileSystemManagerClassName
+     */
+    public void setFileSystemManagerClassName(String fileSystemManagerClassName) {
+        this.fileSystemManagerClassName = fileSystemManagerClassName;
+    }
+
+    /**
+     * Returns the VFS {@link FileSystemManager} instance used in this VFS data store.
+     * @return the VFS {@link FileSystemManager} instance used in this VFS data store
+     */
+    public FileSystemManager getFileSystemManager() {
+        return fileSystemManager;
+    }
+
+    /**
+     * Returns {@link FileSystemOptions} instance used when resolving the {@link #baseFolder}.
+     * This may return null if {@link FileSystemOptions} instance was not injected or
+     * a {@link #fileSystemOptionsProperties} instance cannot be injected or created.
+     * Therefore, the caller should check whether or not this returns null.
+     * When returning null, the caller may not use a {@link FileSystemOptions} instance.
+     * @return {@link FileSystemOptions} instance used when resolving the {@link #baseFolder}
+     */
+    public FileSystemOptions getFileSystemOptions() throws RepositoryException {
+        if (fileSystemOptions == null) {
+            fileSystemOptions = createFileSystemOptions();
+        }
+
+        return fileSystemOptions;
+    }
+
+    /**
+     * Sets the {@link FileSystemOptions} instance used when resolving the {@link #baseFolder}.
+     * @param fileSystemOptions {@link FileSystemOptions} instance used when resolving the {@link #baseFolder}
+     */
+    public void setFileSystemOptions(FileSystemOptions fileSystemOptions) {
+        this.fileSystemOptions = fileSystemOptions;
+    }
+
+    /**
+     * Sets the properties used when building a {@link FileSystemOptions}, using {@link DelegatingFileSystemOptionsBuilder}.
+     * @param fileSystemOptionsProperties properties used when building a {@link FileSystemOptions}
+     */
+    public void setFileSystemOptionsProperties(Properties fileSystemOptionsProperties) {
+        this.fileSystemOptionsProperties = fileSystemOptionsProperties;
+    }
+
+    /**
+     * Sets the properties in a semi-colon delimited string used when building a {@link FileSystemOptions},
+     * using {@link DelegatingFileSystemOptionsBuilder}.
+     * @param fileSystemOptionsPropertiesInString properties in String
+     */
+    public void setFileSystemOptionsPropertiesInString(String fileSystemOptionsPropertiesInString) {
+        if (fileSystemOptionsPropertiesInString != null) {
+            try {
+                StringReader reader = new StringReader(fileSystemOptionsPropertiesInString);
+                Properties props = new Properties();
+                props.load(reader);
+                fileSystemOptionsProperties = props;
+            } catch (IOException e) {
+                throw new IllegalArgumentException("Could not load file system options properties.", e);
+            }
+        }
+    }
+
+    /**
+     * Sets the base VFS folder URI.
+     * @param baseFolderUri base VFS folder URI
+     */
+    public void setBaseFolderUri(String baseFolderUri) {
+        this.baseFolderUri = baseFolderUri;
+    }
+
+    /**
+     * Returns the pool size of the async write pool executor.
+     * @return the pool size of the async write pool executor
+     */
+    public int getAsyncWritePoolSize() {
+        return asyncWritePoolSize;
+    }
+
+    /**
+     * Sets the pool size of the async write pool executor.
+     * @param asyncWritePoolSize pool size of the async write pool executor
+     */
+    public void setAsyncWritePoolSize(int asyncWritePoolSize) {
+        this.asyncWritePoolSize = asyncWritePoolSize;
+    }
+
+    /**
+     * Creates a {@link FileSystemManager} instance.
+     * @return a {@link FileSystemManager} instance.
+     * @throws FileSystemException if an error occurs creating the manager.
+     */
+    protected FileSystemManager createFileSystemManager() throws RepositoryException {
+        FileSystemManager fileSystemManager = null;
+
+        try {
+            if (getFileSystemManagerClassName() == null) {
+                fileSystemManager = new StandardFileSystemManager();
+            } else {
+                final Class<?> mgrClass = Class.forName(getFileSystemManagerClassName());
+                fileSystemManager = (FileSystemManager) mgrClass.newInstance();
+            }
+
+            if (fileSystemManager instanceof DefaultFileSystemManager) {
+                ((DefaultFileSystemManager) fileSystemManager).init();
+            }
+        } catch (final FileSystemException e) {
+            throw new RepositoryException(
+                    "Could not initialize file system manager of class: " + getFileSystemManagerClassName(), e);
+        } catch (final Exception e) {
+            throw new RepositoryException(
+                    "Could not create file system manager of class: " + getFileSystemManagerClassName(), e);
+        }
+
+        return fileSystemManager;
+    }
+
+    /**
+     * Builds and returns {@link FileSystemOptions} instance which is used when resolving the {@link #vfsBaseFolder}
+     * during the initialization.
+     * If {@link #fileSystemOptionsProperties} is available, this scans all the property key names starting with {@link #FILE_SYSTEM_OPTIONS_PROP_PREFIX}
+     * and uses the rest of the key name after the {@link #FILE_SYSTEM_OPTIONS_PROP_PREFIX} as the combination of scheme and property name
+     * when building a {@link FileSystemOptions} using {@link DelegatingFileSystemOptionsBuilder}.
+     * @return {@link FileSystemOptions} instance which is used when resolving the {@link #vfsBaseFolder} during the initialization
+     * @throws RepositoryException if any file system exception occurs
+     */
+    protected FileSystemOptions createFileSystemOptions() throws RepositoryException {
+        FileSystemOptions fso = null;
+
+        if (fileSystemOptionsProperties != null) {
+            try {
+                fso = new FileSystemOptions();
+                DelegatingFileSystemOptionsBuilder delegate = new DelegatingFileSystemOptionsBuilder(getFileSystemManager());
+
+                String key;
+                String schemeDotPropName;
+                String scheme;
+                String propName;
+                String value;
+                int offset;
+
+                for (Enumeration<?> e = fileSystemOptionsProperties.propertyNames(); e.hasMoreElements(); ) {
+                    key = (String) e.nextElement();
+
+                    if (key.startsWith(FILE_SYSTEM_OPTIONS_PROP_PREFIX)) {
+                        value = fileSystemOptionsProperties.getProperty(key);
+                        schemeDotPropName = key.substring(FILE_SYSTEM_OPTIONS_PROP_PREFIX.length());
+                        offset = schemeDotPropName.indexOf('.');
+
+                        if (offset > 0) {
+                            scheme = schemeDotPropName.substring(0, offset);
+                            propName = schemeDotPropName.substring(offset + 1);
+                            delegate.setConfigString(fso, scheme, propName, value);
+                        } else {
+                            LOG.warn("Ignoring an FileSystemOptions property in invalid format. Key: {}, Value: {}", key, value);
+                        }
+                    }
+                }
+            } catch (FileSystemException e) {
+                throw new RepositoryException("Could not create File System Options.", e);
+            }
+        }
+
+        return fso;
+    }
+
+    /**
+     * Returns properties used when building a {@link FileSystemOptions} instance by the properties
+     * during the initialization.
+     * @return properties used when building a {@link FileSystemOptions} instance by the properties during the initialization
+     */
+    protected Properties getFileSystemOptionsProperties() {
+        return fileSystemOptionsProperties;
+    }
+
+    private void overridePropertiesFromConfig() throws RepositoryException {
+        final String config = getConfig();
+
+        // If config param provided, then override properties from the config file.
+        if (config != null && !"".equals(config)) {
+            try {
+                final Properties props = readConfig(config);
+
+                String propValue = props.getProperty("asyncWritePoolSize");
+                if (propValue != null && !"".equals(propValue)) {
+                    setAsyncWritePoolSize(Integer.parseInt(propValue));
+                }
+
+                propValue = props.getProperty("baseFolderUri");
+                if (propValue != null && !"".equals(propValue)) {
+                    setBaseFolderUri(propValue);
+                }
+
+                propValue = props.getProperty("fileSystemManagerClassName");
+                if (propValue != null && !"".equals(propValue)) {
+                    setFileSystemManagerClassName(propValue);
+                }
+
+                final Properties fsoProps = new Properties();
+                String propName;
+                for (Enumeration<?> propNames = props.propertyNames(); propNames.hasMoreElements(); ) {
+                    propName = (String) propNames.nextElement();
+                    if (propName.startsWith(FILE_SYSTEM_OPTIONS_PROP_PREFIX)) {
+                        fsoProps.setProperty(propName, props.getProperty(propName));
+                    }
+                }
+                if (!fsoProps.isEmpty()) {
+                    this.setFileSystemOptionsProperties(fsoProps);
+                }
+            } catch (IOException e) {
+                throw new RepositoryException("Configuration file doesn't exist at '" + config + "'.");
+            }
+        }
+    }
+
+    private Properties readConfig(String fileName) throws IOException {
+        if (!new File(fileName).exists()) {
+            throw new IOException("Config file not found: " + fileName);
+        }
+
+        Properties prop = new Properties();
+        InputStream in = null;
+
+        try {
+            in = new FileInputStream(fileName);
+            prop.load(in);
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+
+        return prop;
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSDataStore.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSUtils.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSUtils.java?rev=1757129&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSUtils.java (added)
+++ jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSUtils.java Mon Aug 22 09:57:45 2016
@@ -0,0 +1,184 @@
+/*
+ * 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.jackrabbit.vfs.ext.ds;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.jackrabbit.core.data.DataStoreException;
+
+/**
+ * VFS Utilities.
+ */
+class VFSUtils {
+
+    /**
+     * A set to filter out other files other than {@link FileType#FILE}.
+     */
+    static Set<FileType> FILE_ONLY_TYPES = Collections
+            .unmodifiableSet(new HashSet<FileType>(Arrays.asList(FileType.FILE)));
+
+    /**
+     * A set to filter out other files other than {@link FileType#FOLDER}.
+     */
+    static Set<FileType> FOLDER_ONLY_TYPES = Collections
+            .unmodifiableSet(new HashSet<FileType>(Arrays.asList(FileType.FOLDER)));
+
+    /**
+     * A set to filter out other files other than {@link FileType#FILE} or {@link FileType#FOLDER}.
+     */
+    static Set<FileType> FILE_OR_FOLDER_TYPES = Collections
+            .unmodifiableSet(new HashSet<FileType>(Arrays.asList(FileType.FILE, FileType.FOLDER)));
+
+    /**
+     * Creates a child folder by the {@code name} under the {@code baseFolder} and retrieves the created folder.
+     * @param baseFolder base folder object
+     * @param name child folder name
+     * @return a child folder by the {@code name} under the {@code baseFolder} and retrieves the created folder
+     * @throws DataStoreException if any file system exception occurs
+     */
+    static FileObject createChildFolder(FileObject baseFolder, String name) throws DataStoreException {
+        FileObject childFolder = null;
+
+        try {
+            childFolder = baseFolder.resolveFile(name);
+
+            if (!childFolder.exists()) {
+                childFolder.createFolder();
+                childFolder = baseFolder.getChild(childFolder.getName().getBaseName());
+            }
+        } catch (FileSystemException e) {
+            throw new DataStoreException(
+                    "Could not create a child folder, '" + name + "' under " + baseFolder.getName().getFriendlyURI(),
+                    e);
+        }
+
+        return childFolder;
+    }
+
+    /**
+     * Creates a child file by the {@code name} under the {@code baseFolder} and retrieves the created file.
+     * @param baseFolder base folder object
+     * @param name child file name
+     * @return a child file by the {@code name} under the {@code baseFolder} and retrieves the created file
+     * @throws DataStoreException if any file system exception occurs
+     */
+    static FileObject createChildFile(FileObject baseFolder, String name) throws DataStoreException {
+        FileObject childFile = null;
+
+        try {
+            childFile = baseFolder.resolveFile(name);
+
+            if (!childFile.exists()) {
+                childFile.createFile();
+                childFile = baseFolder.getChild(childFile.getName().getBaseName());
+            }
+        } catch (FileSystemException e) {
+            throw new DataStoreException(
+                    "Could not create a child file, '" + name + "' under " + baseFolder.getName().getFriendlyURI(),
+                    e);
+        }
+
+        return childFile;
+    }
+
+    /**
+     * Returns child files under {@code folderObject} after filtering out files other than {@link FileType#FILE}.
+     * @param folderObject folder object
+     * @return child files under {@code folderObject} after filtering out files other than {@link FileType#FILE}
+     * @throws DataStoreException if any file system exception occurs
+     */
+    static List<FileObject> getChildFiles(FileObject folderObject) throws DataStoreException {
+        return getChildrenOfTypes(folderObject, FILE_ONLY_TYPES);
+    }
+
+    /**
+     * Returns child folders under {@code folderObject} after filtering out files other than {@link FileType#FOLDER}.
+     * @param folderObject folder object
+     * @return child folders under {@code folderObject} after filtering out files other than {@link FileType#FOLDER}
+     * @throws DataStoreException if any file system exception occurs
+     */
+    static List<FileObject> getChildFolders(FileObject folderObject) throws DataStoreException {
+        return getChildrenOfTypes(folderObject, FOLDER_ONLY_TYPES);
+    }
+
+    /**
+     * Returns child file objects under {@code folderObject} after filtering out files other than {@link FileType#FILE} or {@link FileType#FOLDER}.
+     * @param folderObject folder object
+     * @return child file objects under {@code folderObject} after filtering out files other than {@link FileType#FILE} or {@link FileType#FOLDER}
+     * @throws DataStoreException if any file system exception occurs
+     */
+    static List<FileObject> getChildFileOrFolders(FileObject folderObject) throws DataStoreException {
+        return getChildrenOfTypes(folderObject, FILE_OR_FOLDER_TYPES);
+    }
+
+    /**
+     * Returns true if {@code folderObject} has any file or folder child objects.
+     * @param folderObject folder object
+     * @return true if {@code folderObject} has any file or folder child objects
+     * @throws DataStoreException if any file system exception occurs
+     */
+    static boolean hasAnyChildFileOrFolder(FileObject folderObject) throws DataStoreException {
+        return !getChildFileOrFolders(folderObject).isEmpty();
+    }
+
+    private static List<FileObject> getChildrenOfTypes(FileObject folderObject, Set<FileType> fileTypes) throws DataStoreException {
+        try {
+            String folderBaseName = folderObject.getName().getBaseName();
+            FileObject [] children = folderObject.getChildren();
+            List<FileObject> files = new ArrayList<FileObject>(children.length);
+            String childBaseName;
+
+            for (int i = 0; i < children.length; i++) {
+                childBaseName = children[i].getName().getBaseName();
+                FileType fileType = null;
+
+                try {
+                    fileType = children[i].getType();
+                } catch (FileSystemException notDetermineTypeEx) {
+                    if (folderBaseName.equals(childBaseName)) {
+                        // Ignore this case.
+                        // Some WebDAV server or VFS seems to include the folder itself as child as imaginary file type,
+                        // and throw FileSystemException saying "Could not determine the type of file" in this case.
+                    } else {
+                        throw notDetermineTypeEx;
+                    }
+                }
+
+                if (fileType != null && fileTypes.contains(fileType)) {
+                    files.add(children[i]);
+                }
+            }
+
+            return files;
+        } catch (FileSystemException e) {
+            throw new DataStoreException(
+                    "Could not find children under " + folderObject.getName().getFriendlyURI(), e);
+        }
+    }
+
+    private VFSUtils() {
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit-vfs-ext/src/main/java/org/apache/jackrabbit/vfs/ext/ds/VFSUtils.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/ext/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/ext/TestAll.java?rev=1757129&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/ext/TestAll.java (added)
+++ jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/ext/TestAll.java Mon Aug 22 09:57:45 2016
@@ -0,0 +1,38 @@
+/*
+ * 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.jackrabbit.vfs.ext;
+
+import org.apache.jackrabbit.vfs.ext.ds.TestVFSDataStore;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test suite that includes all test cases for the this module.
+ */
+public class TestAll extends TestCase {
+
+    /**
+     * <code>TestAll</code> suite that executes all tests inside this module.
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite("VFS tests");
+        suite.addTestSuite(TestVFSDataStore.class);
+        return suite;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-vfs-ext/src/test/java/org/apache/jackrabbit/vfs/ext/TestAll.java
------------------------------------------------------------------------------
    svn:eol-style = native