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