This is an automated email from the ASF dual-hosted git repository.
cziegeler pushed a commit to branch simple
in repository
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git
The following commit(s) were added to refs/heads/simple by this push:
new 9862897 Move code to different package, merge with code from
whiteboard
9862897 is described below
commit 9862897399fed8804dd752415e0325f0005cc4a4
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Wed Oct 30 17:19:25 2024 -0700
Move code to different package, merge with code from whiteboard
---
.../fsprovider/internal/FsResourceProvider.java | 374 ---------------------
.../internal/FileMonitor.java | 33 +-
.../internal/FsResource.java | 142 +++-----
.../internal/FsResourceProvider.java | 271 +++++++++++++++
4 files changed, 341 insertions(+), 479 deletions(-)
diff --git
a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
deleted file mode 100644
index db0a2fe..0000000
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.fsprovider.internal;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-import java.util.Set;
-
-import org.apache.sling.api.resource.Resource;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.spi.resource.provider.ObservationReporter;
-import org.apache.sling.spi.resource.provider.ProviderContext;
-import org.apache.sling.spi.resource.provider.ResolveContext;
-import org.apache.sling.spi.resource.provider.ResourceContext;
-import org.apache.sling.spi.resource.provider.ResourceProvider;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.ConfigurationPolicy;
-import org.osgi.service.component.annotations.Deactivate;
-import org.osgi.service.metatype.annotations.AttributeDefinition;
-import org.osgi.service.metatype.annotations.Designate;
-import org.osgi.service.metatype.annotations.ObjectClassDefinition;
-
-/**
- * The <code>FsResourceProvider</code> is a resource provider which maps
- * file system files and folders into the virtual resource tree. The provider
is
- * implemented in terms of a component factory, that is multiple instances of
- * this provider may be created by creating respective configuration.
- * <p>
- * Each provider instance is configured with two properties: The location in
the
- * resource tree where resources are provided (provider.root)
- * and the file system path from where files and folders are mapped into the
- * resource (provider.file).
- */
-@Component(
- name = "org.apache.sling.fsprovider.internal.FsResourceProvider",
- service = ResourceProvider.class,
- configurationPolicy = ConfigurationPolicy.REQUIRE,
- property = {
- Constants.SERVICE_DESCRIPTION + "=Sling Filesystem Resource
Provider",
- Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
- })
-@Designate(ocd = FsResourceProvider.Config.class, factory = true)
-public class FsResourceProvider extends ResourceProvider<Object> {
-
- /**
- * Resource metadata property set by {@link FsResource} if the underlying
file reference is a directory.
- */
- static final String RESOURCE_METADATA_FILE_DIRECTORY =
":org.apache.sling.fsprovider.file.directory";
-
- @ObjectClassDefinition(
- name = "Apache Sling Filesystem Resource Provider",
- description = "Configure an instance of the filesystem "
- + "resource provider in terms of provider root and
filesystem location")
- public @interface Config {
- /**
- * The name of the configuration property providing file system path of
- * files and folders mapped into the resource tree (value is
- * "provider.file").
- */
- @AttributeDefinition(
- name = "Filesystem Root",
- description = "Filesystem directory mapped to the virtual "
- + "resource tree. This property must not be an empty
string. If the path is "
- + "relative it is resolved against sling.home or the
current working directory. "
- + "The path may be a file or folder. If the path does
not address an existing "
- + "file or folder, an empty folder is created.")
- String provider_file();
-
- /**
- * The name of the configuration property providing the check interval
- * for file changes (value is "provider.checkinterval").
- */
- @AttributeDefinition(
- name = "Check Interval",
- description =
- "If the interval has a value higher than 100, the
provider will "
- + "check the file system for changes
periodically. This interval defines the period in milliseconds "
- + "(the default is 1000). If a change is
detected, resource events are sent through the event admin.")
- long provider_checkinterval() default 1000;
-
- @AttributeDefinition(
- name = "Provider Root",
- description = "Location in the virtual resource tree where the
"
- + "filesystem resources are mapped in. This property
must not be an empty string.")
- String provider_root();
-
- /**
- * Internal Name hint for web console.
- */
- String webconsole_configurationFactory_nameHint() default "Root path:
{" + ResourceProvider.PROPERTY_ROOT + "}";
- }
-
- // The location in the resource tree where the resources are mapped
- private String providerRoot;
-
- // providerRoot + "/" to be used for prefix matching of paths
- private String providerRootPrefix;
-
- // The "root" file or folder in the file system
- private File providerFile;
-
- /** The monitor to detect file changes. */
- private FileMonitor monitor;
-
- /**
- * Returns a resource wrapping a file system file or folder for the given
- * path. If the <code>path</code> is equal to the configured resource tree
- * location of this provider, the configured file system file or folder is
- * used for the resource. Otherwise the configured resource tree location
- * prefix is removed from the path and the remaining relative path is used
- * to access the file or folder. If no such file or folder exists, this
- * method returns <code>null</code>.
- */
- @SuppressWarnings({"rawtypes", "unchecked"})
- @Override
- public Resource getResource(
- final ResolveContext<Object> ctx,
- final String path,
- final ResourceContext resourceContext,
- final Resource parent) {
- Resource rsrc = getResource(ctx.getResourceResolver(), path,
getFile(path));
- // make sure directory resources from parent resource provider have
higher precedence than from this provider
- // this allows properties like sling:resourceSuperType to take effect
- if (rsrc == null ||
rsrc.getResourceMetadata().containsKey(RESOURCE_METADATA_FILE_DIRECTORY)) {
- // get resource from shadowed provider
- final ResourceProvider rp = ctx.getParentResourceProvider();
- if (rp != null) {
- Resource resourceFromParentResourceProvider =
- rp.getResource((ResolveContext)
ctx.getParentResolveContext(), path, resourceContext, parent);
- if (resourceFromParentResourceProvider != null) {
- rsrc = resourceFromParentResourceProvider;
- }
- }
- }
- return rsrc;
- }
-
- /**
- * Returns an iterator of resources.
- */
- @SuppressWarnings("unchecked")
- @Override
- public Iterator<Resource> listChildren(final ResolveContext<Object> ctx,
final Resource parent) {
- File parentFile = parent.adaptTo(File.class);
-
- // not a FsResource, try to create one from the resource
- if (parentFile == null) {
- // if the parent path is at or below the provider root, get
- // the respective file
- parentFile = getFile(parent.getPath());
-
- // if the parent path is actually the parent of the provider
- // root, return a single element iterator just containing the
- // provider file, unless the provider file is a directory and
- // a repository item with the same path actually exists
- if (parentFile == null) {
-
- String parentPath = parent.getPath().concat("/");
- if (providerRoot.startsWith(parentPath)) {
- String relPath =
providerRoot.substring(parentPath.length());
- if (relPath.indexOf('/') < 0) {
- Resource res =
getResource(parent.getResourceResolver(), providerRoot, providerFile);
- if (res != null) {
- return Collections.singletonList(res).iterator();
- }
- }
- }
-
- // no children here
- return null;
- }
- }
-
- // get children from from shadowed provider
- @SuppressWarnings("rawtypes")
- final ResourceProvider rp = ctx.getParentResourceProvider();
- final Iterator<Resource> parentChildrenIterator;
- if (rp != null) {
- parentChildrenIterator =
rp.listChildren(ctx.getParentResolveContext(), parent);
- } else {
- parentChildrenIterator = null;
- }
- final File[] children = parentFile.listFiles();
-
- final ResourceResolver resolver = ctx.getResourceResolver();
- final String parentPath = parent.getPath();
- return new Iterator<Resource>() {
-
- final Set<String> names = new HashSet<>();
-
- int index = 0;
-
- Resource next = seek();
-
- @Override
- public boolean hasNext() {
- return next != null;
- }
-
- @Override
- public Resource next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
-
- Resource result = next;
- next = seek();
- return result;
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("remove");
- }
-
- private Resource seek() {
- while (children != null && index < children.length) {
- File file = children[index++];
- String path = parentPath + "/" + file.getName();
- Resource result = getResource(resolver, path, file);
- if (result != null) {
- names.add(file.getName());
- return result;
- }
- }
- if (parentChildrenIterator != null) {
- while (parentChildrenIterator.hasNext()) {
- final Resource result = parentChildrenIterator.next();
- if (!names.contains(result.getName())) {
- names.add(result.getName());
- return result;
- }
- }
- }
- // nothing found any more
- return null;
- }
- };
- }
-
- // ---------- SCR Integration
- @Activate
- protected void activate(BundleContext bundleContext, final Config config) {
- String providerRoot = config.provider_root();
- if (providerRoot == null || providerRoot.length() == 0) {
- throw new IllegalArgumentException("provider.root property must be
set");
- }
-
- String providerFileName = config.provider_file();
- if (providerFileName == null || providerFileName.length() == 0) {
- throw new IllegalArgumentException("provider.file property must be
set");
- }
-
- this.providerRoot = providerRoot;
- this.providerRootPrefix = providerRoot.concat("/");
- this.providerFile = getProviderFile(providerFileName, bundleContext);
- // start background monitor if check interval is higher than 100
- if (config.provider_checkinterval() > 100) {
- this.monitor = new FileMonitor(this,
config.provider_checkinterval());
- }
- }
-
- @Deactivate
- protected void deactivate() {
- if (this.monitor != null) {
- this.monitor.stop();
- this.monitor = null;
- }
- this.providerRoot = null;
- this.providerRootPrefix = null;
- this.providerFile = null;
- }
-
- File getRootFile() {
- return this.providerFile;
- }
-
- String getProviderRoot() {
- return this.providerRoot;
- }
-
- // ---------- internal
-
- private File getProviderFile(String providerFileName, BundleContext
bundleContext) {
-
- // the file object from the plain name
- File providerFile = new File(providerFileName);
-
- // resolve relative file name against sling.home or current
- // working directory
- if (!providerFile.isAbsolute()) {
- String home = bundleContext.getProperty("sling.home");
- if (home != null && home.length() > 0) {
- providerFile = new File(home, providerFileName);
- }
- }
-
- // resolve the path
- providerFile = providerFile.getAbsoluteFile();
-
- // if the provider file does not exist, create an empty new folder
- if (!providerFile.exists() && !providerFile.mkdirs()) {
- throw new IllegalArgumentException("Cannot create provider file
root " + providerFile);
- }
-
- return providerFile;
- }
-
- /**
- * Returns a file corresponding to the given absolute resource tree path.
If
- * the path equals the configured provider root, the provider root file is
- * returned. If the path starts with the configured provider root, a file
is
- * returned relative to the provider root file whose relative path is the
- * remains of the resource tree path without the provider root path.
- * Otherwise <code>null</code> is returned.
- */
- private File getFile(String path) {
- if (path.equals(providerRoot)) {
- return providerFile;
- }
-
- if (path.startsWith(providerRootPrefix)) {
- String relPath = path.substring(providerRootPrefix.length());
- return new File(providerFile, relPath);
- }
-
- return null;
- }
-
- private Resource getResource(final ResourceResolver resolver, final String
resourcePath, final File file) {
-
- if (file != null) {
-
- // if the file exists, but is not a directory or no repository
entry
- // exists, return it as a resource
- if (file.exists()) {
- return new FsResource(resolver, resourcePath, file);
- }
- }
-
- // not applicable or not an existing file path
- return null;
- }
-
- public ObservationReporter getObservationReporter() {
- final ProviderContext ctx = this.getProviderContext();
- if (ctx != null) {
- return ctx.getObservationReporter();
- }
- return null;
- }
-}
diff --git
a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
b/src/main/java/org/apache/sling/simplefsprovider/internal/FileMonitor.java
similarity index 89%
rename from src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
rename to
src/main/java/org/apache/sling/simplefsprovider/internal/FileMonitor.java
index db4f236..29fd24d 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
+++ b/src/main/java/org/apache/sling/simplefsprovider/internal/FileMonitor.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.sling.fsprovider.internal;
+package org.apache.sling.simplefsprovider.internal;
import java.io.File;
import java.util.Collections;
@@ -40,8 +40,8 @@ public class FileMonitor extends TimerTask {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Timer timer = new Timer();
- private boolean stop = false;
- private boolean stopped = true;
+ private volatile boolean stop = false;
+ private volatile boolean stopped = true;
private final Monitorable root;
@@ -50,11 +50,14 @@ public class FileMonitor extends TimerTask {
/**
* Creates a new instance of this class.
* @param provider The resource provider.
+ * @param resourcePath The resource path.
+ * @param homeDir The home directory to monitor.
* @param interval The interval between executions of the task, in
milliseconds.
*/
- public FileMonitor(final FsResourceProvider provider, final long interval)
{
+ public FileMonitor(
+ final FsResourceProvider provider, final String resourcePath,
final File homeDir, final long interval) {
this.provider = provider;
- this.root = new Monitorable(this.provider.getProviderRoot(),
this.provider.getRootFile());
+ this.root = new Monitorable(resourcePath, homeDir);
createStatus(this.root);
logger.debug("Starting file monitor for {} with an interval of {}ms",
this.root.file, interval);
timer.schedule(this, 0, interval);
@@ -169,8 +172,8 @@ public class FileMonitor extends TimerTask {
}
}
if (children[i] == null) {
- children[i] =
- new Monitorable(monitorable.path +
'/' + files[i].getName(), files[i]);
+ children[i] = new Monitorable(
+ monitorable.resourcePath + '/' +
files[i].getName(), files[i]);
children[i].status =
NonExistingStatus.SINGLETON;
check(children[i], reporter);
}
@@ -190,13 +193,11 @@ public class FileMonitor extends TimerTask {
*/
private void sendEvents(
final Monitorable monitorable, final ChangeType changeType, final
ObservationReporter reporter) {
- if (logger.isDebugEnabled()) {
- logger.debug("Detected change for resource {} : {}",
monitorable.path, changeType);
- }
+ logger.debug("Detected change for resource {} : {}",
monitorable.resourcePath, changeType);
for (final ObserverConfiguration config :
reporter.getObserverConfigurations()) {
- if (config.matches(monitorable.path)) {
- final ResourceChange change = new ResourceChange(changeType,
monitorable.path, false);
+ if (config.matches(monitorable.resourcePath)) {
+ final ResourceChange change = new ResourceChange(changeType,
monitorable.resourcePath, false);
reporter.reportChanges(config, Collections.singleton(change),
false);
}
}
@@ -211,18 +212,18 @@ public class FileMonitor extends TimerTask {
} else if (monitorable.file.isFile()) {
monitorable.status = new FileStatus(monitorable.file);
} else {
- monitorable.status = new DirStatus(monitorable.file,
monitorable.path);
+ monitorable.status = new DirStatus(monitorable.file,
monitorable.resourcePath);
}
}
/** The monitorable to hold the resource path, the file and the status. */
private static final class Monitorable {
- public final String path;
+ public final String resourcePath;
public final File file;
public Object status;
- public Monitorable(final String path, final File file) {
- this.path = path;
+ public Monitorable(final String resourcePath, final File file) {
+ this.resourcePath = resourcePath;
this.file = file;
}
}
diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
b/src/main/java/org/apache/sling/simplefsprovider/internal/FsResource.java
similarity index 56%
rename from src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
rename to
src/main/java/org/apache/sling/simplefsprovider/internal/FsResource.java
index 0f380ad..d91f378 100644
--- a/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
+++ b/src/main/java/org/apache/sling/simplefsprovider/internal/FsResource.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.sling.fsprovider.internal;
+package org.apache.sling.simplefsprovider.internal;
import java.io.File;
import java.io.FileInputStream;
@@ -24,7 +24,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
-import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
@@ -51,6 +50,8 @@ import org.slf4j.LoggerFactory;
})
public class FsResource extends AbstractResource {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(FsResource.class);
+
/**
* The resource type for file system files mapped into the resource tree by
* the {@link FsResourceProvider} (value is "nt:file").
@@ -63,9 +64,6 @@ public class FsResource extends AbstractResource {
*/
static final String RESOURCE_TYPE_FOLDER = "nt:folder";
- // default log, assigned on demand
- private Logger log;
-
// the owning resource resolver
private final ResourceResolver resolver;
@@ -75,11 +73,10 @@ public class FsResource extends AbstractResource {
// the file wrapped by this instance
private final File file;
- // the resource type, assigned on demand
- private String resourceType;
-
// the resource metadata, assigned on demand
- private ResourceMetadata metaData;
+ private final ResourceMetadata metadata;
+
+ private ValueMap valueMap;
/**
* Creates an instance of this Filesystem resource.
@@ -88,124 +85,91 @@ public class FsResource extends AbstractResource {
* @param resourcePath The resource path in the resource tree
* @param file The wrapped file
*/
- FsResource(ResourceResolver resolver, String resourcePath, File file) {
+ FsResource(final ResourceResolver resolver, final String resourcePath,
final File file) {
this.resolver = resolver;
this.resourcePath = resourcePath;
this.file = file;
+ this.metadata = new ResourceMetadata();
+ this.metadata.setModificationTime(file.lastModified());
+ this.metadata.setResolutionPath(resourcePath);
+ if (file.isFile()) {
+ this.metadata.setContentLength(file.length());
+ }
}
- /**
- * Returns the path of this resource
- */
+ @Override
public String getPath() {
return resourcePath;
}
- /**
- * Returns the resource meta data for this resource containing the file
- * length, last modification time and the resource path (same as
- * {@link #getPath()}).
- */
- public ResourceMetadata getResourceMetadata() {
- if (metaData == null) {
- metaData = new ResourceMetadata();
- metaData.setContentLength(file.length());
- metaData.setModificationTime(file.lastModified());
- metaData.setResolutionPath(resourcePath);
- if (this.file.isDirectory()) {
-
metaData.put(FsResourceProvider.RESOURCE_METADATA_FILE_DIRECTORY, Boolean.TRUE);
- }
- }
- return metaData;
- }
-
- /**
- * Returns the resource resolver which cause this resource object to be
- * created.
- */
- public ResourceResolver getResourceResolver() {
- return resolver;
+ @Override
+ public String getResourceType() {
+ return this.file.isDirectory() ? RESOURCE_TYPE_FOLDER :
RESOURCE_TYPE_FILE;
}
- /**
- * Returns <code>null</code>}
- */
+ @Override
public String getResourceSuperType() {
return null;
}
- /**
- * Returns {@link #RESOURCE_TYPE_FILE} if this resource
- * wraps a file. Otherwise {@link #RESOURCE_TYPE_FOLDER}
- * is returned.
- */
- public String getResourceType() {
- if (resourceType == null) {
- resourceType = file.isFile() ? RESOURCE_TYPE_FILE :
RESOURCE_TYPE_FOLDER;
- }
+ @Override
+ public ResourceMetadata getResourceMetadata() {
+ return metadata;
+ }
- return resourceType;
+ @Override
+ public ResourceResolver getResourceResolver() {
+ return resolver;
}
/**
* Returns an adapter for this resource. This implementation supports
- * <code>File</code>, <code>InputStream</code> and <code>URL</code>
- * plus those supported by the adapter manager.
+ * <code>File</code>, <code>InputStream</code> and <code>URL</code> plus
those
+ * supported by the adapter manager.
*/
@Override
- @SuppressWarnings("unchecked")
- public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ public <AdapterType> AdapterType adaptTo(final Class<AdapterType> type) {
if (type == File.class) {
-
- return (AdapterType) file;
-
+ return type.cast(this.file);
} else if (type == InputStream.class) {
-
- if (!file.isDirectory() && file.canRead()) {
-
+ if (file.isFile() && file.canRead()) {
try {
- return (AdapterType) new FileInputStream(file);
- } catch (IOException ioe) {
- getLog().info("adaptTo: Cannot open a stream on the file "
+ file, ioe);
+ return type.cast(new FileInputStream(file));
+ } catch (final IOException ioe) {
+ LOGGER.info("adaptTo: Cannot open a stream on the file " +
file, ioe);
}
-
} else {
-
- getLog().debug("adaptTo: File {} is not a readable file",
file);
+ LOGGER.debug("adaptTo: File {} is not a readable file", file);
}
-
} else if (type == URL.class) {
-
try {
- return (AdapterType) file.toURI().toURL();
+ return type.cast(file.toURI().toURL());
} catch (MalformedURLException mue) {
- getLog().info("adaptTo: Cannot convert the file path " + file
+ " to an URL", mue);
+ LOGGER.info("adaptTo: Cannot convert the file path " + file +
" to an URL", mue);
}
-
} else if (type == ValueMap.class) {
-
- // this resource simulates nt:file/nt:folder behavior by returning
it as resource type
- // we should simulate the corresponding JCR properties in a value
map as well
- if (file.exists() && file.canRead()) {
- Map<String, Object> props = new HashMap<String, Object>();
- props.put("jcr:primaryType", getResourceType());
- props.put("jcr:createdBy", "system");
- Calendar lastModifed = Calendar.getInstance();
- lastModifed.setTimeInMillis(file.lastModified());
- props.put("jcr:created", lastModifed);
- return (AdapterType) new ValueMapDecorator(props);
- }
+ return type.cast(getValueMap());
}
return super.adaptTo(type);
}
- // ---------- internal
-
- private Logger getLog() {
- if (log == null) {
- log = LoggerFactory.getLogger(getClass());
+ @Override
+ public ValueMap getValueMap() {
+ if (this.valueMap == null) {
+ final Map<String, Object> props = new HashMap<>();
+ props.put("sling:resourceType", getResourceType());
+ props.put(ResourceMetadata.MODIFICATION_TIME,
this.file.lastModified());
+ if (this.metadata.getContentLength() > 0) {
+ props.put(ResourceMetadata.CONTENT_LENGTH,
this.metadata.getContentLength());
+ }
+ this.valueMap = new ValueMapDecorator(props);
}
- return log;
+ return this.valueMap;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + ", path: " + resourcePath + ",
file: " + file;
}
}
diff --git
a/src/main/java/org/apache/sling/simplefsprovider/internal/FsResourceProvider.java
b/src/main/java/org/apache/sling/simplefsprovider/internal/FsResourceProvider.java
new file mode 100644
index 0000000..726bea0
--- /dev/null
+++
b/src/main/java/org/apache/sling/simplefsprovider/internal/FsResourceProvider.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.simplefsprovider.internal;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.spi.resource.provider.ObservationReporter;
+import org.apache.sling.spi.resource.provider.ProviderContext;
+import org.apache.sling.spi.resource.provider.ResolveContext;
+import org.apache.sling.spi.resource.provider.ResourceContext;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+/**
+ * The <code>FsResourceProvider</code> is a resource provider which maps
+ * file system files and folders into the virtual resource tree. The provider
is
+ * implemented in terms of a component factory, that is multiple instances of
+ * this provider may be created by creating respective configuration.
+ * <p>
+ * Each provider instance is configured with two properties: The location in
the
+ * resource tree where resources are provided (provider.root)
+ * and the file system path from where files and folders are mapped into the
+ * resource (provider.file).
+ */
+@Component(
+ name = "org.apache.sling.simplefsprovider.FsResourceProvider",
+ service = ResourceProvider.class,
+ configurationPolicy = ConfigurationPolicy.REQUIRE)
+@Designate(ocd = FsResourceProvider.Config.class, factory = true)
+public class FsResourceProvider extends ResourceProvider<Object> {
+
+ @ObjectClassDefinition(
+ name = "Apache Sling Simple Filesystem Resource Provider",
+ description = "Configure an instance of the filesystem "
+ + "resource provider in terms of provider root and
filesystem location")
+ public @interface Config {
+ /**
+ * The name of the configuration property providing file system path of
+ * files and folders mapped into the resource tree (value is
+ * "provider.file").
+ */
+ @AttributeDefinition(
+ name = "Filesystem Root",
+ description = "Filesystem directory mapped to the virtual "
+ + "resource tree. This property must not be an empty
string. If the path is "
+ + "relative it is resolved against sling.home or the
current working directory. "
+ + "The path may be a file or folder. If the path does
not address an existing "
+ + "file or folder, an empty folder is created.")
+ String provider_file();
+
+ /**
+ * The name of the configuration property providing the check interval
+ * for file changes (value is "provider.checkinterval").
+ */
+ @AttributeDefinition(
+ name = "Check Interval",
+ description =
+ "If the interval has a value higher than 100, the
provider will "
+ + "check the file system for changes
periodically. This interval defines the period in milliseconds "
+ + "(the default is 1000). If a change is
detected, resource events are sent through the event admin.")
+ long provider_checkinterval() default 1000;
+
+ @AttributeDefinition(
+ name = "Provider Root",
+ description = "Location in the virtual resource tree where the
"
+ + "filesystem resources are mapped in. This property
must not be an empty string.")
+ String provider_root();
+
+ @AttributeDefinition(
+ name = "Exclude Files",
+ description = "Specify a list of simple patterns to exclude
files.")
+ String[] provider_exclude_files() default {
+ "\\..*", ".*~", ".*\\.bak", ".*\\.swp", ".*\\.swx", ".*\\.swpx",
".*\\.tmp", ".*\\.log"
+ };
+
+ /**
+ * Internal Name hint for web console.
+ */
+ String webconsole_configurationFactory_nameHint() default "Root path:
{" + ResourceProvider.PROPERTY_ROOT + "}";
+ }
+
+ // Resource path prefix with trailing slash
+ private final String resourcePathPrefix;
+
+ // The "root" file or folder in the file system
+ private final String home;
+
+ /** The monitor to detect file changes. */
+ private final FileMonitor monitor;
+
+ private final List<Pattern> excludePatterns = new ArrayList<>();
+
+ @Activate
+ public FsResourceProvider(final Config config, final BundleContext
bundleContext) {
+ if (config.provider_root() == null || config.provider_root().length()
== 0) {
+ throw new IllegalArgumentException("provider.root property must be
set");
+ }
+ if (config.provider_root().endsWith("/")) {
+ this.resourcePathPrefix = config.provider_root();
+ } else {
+ this.resourcePathPrefix = config.provider_root().concat("/");
+ }
+ final String providerFileName = config.provider_file();
+ if (providerFileName == null || providerFileName.length() == 0) {
+ throw new IllegalArgumentException("provider.file property must be
set");
+ }
+ final File homeDir = getProviderFile(providerFileName, bundleContext);
+ this.home = homeDir.getAbsolutePath();
+ // start background monitor if check interval is higher than 100
+ if (config.provider_checkinterval() > 100) {
+ this.monitor = new FileMonitor(
+ this,
+ this.resourcePathPrefix.substring(0,
this.resourcePathPrefix.length() - 1),
+ homeDir,
+ config.provider_checkinterval());
+ } else {
+ this.monitor = null;
+ }
+ if (config.provider_exclude_files() != null) {
+ for (final String pattern : config.provider_exclude_files()) {
+ this.excludePatterns.add(Pattern.compile(pattern));
+ }
+ }
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ if (this.monitor != null) {
+ this.monitor.stop();
+ }
+ }
+
+ private boolean include(final File file) {
+ boolean include = false;
+ if (file.exists() && (file.isDirectory() || file.canRead())) {
+ include = true;
+ for (final Pattern pattern : this.excludePatterns) {
+ if (pattern.matcher(file.getName()).matches()) {
+ include = false;
+ break;
+ }
+ }
+ }
+ return include;
+ }
+
+ /**
+ * Returns a resource wrapping a file system file or folder for the given
+ * path. If the <code>path</code> is equal to the configured resource tree
+ * location of this provider, the configured file system file or folder is
+ * used for the resource. Otherwise the configured resource tree location
+ * prefix is removed from the path and the remaining relative path is used
+ * to access the file or folder. If no such file or folder exists, this
+ * method returns <code>null</code>.
+ */
+ @Override
+ public Resource getResource(
+ final ResolveContext<Object> ctx,
+ final String resourcePath,
+ final ResourceContext resourceContext,
+ final Resource parent) {
+ final String filePath = resourcePath.length() <
this.resourcePathPrefix.length()
+ ? ""
+ : resourcePath.substring(this.resourcePathPrefix.length());
+
+ // try one to one mapping
+ final Path path = Paths.get(this.home, filePath.replace('/',
File.separatorChar));
+ final File file = path.toFile();
+
+ if (include(file)) {
+ return new FsResource(ctx.getResourceResolver(), resourcePath,
file);
+ }
+ return null;
+ }
+
+ public Iterator<Resource> listChildren(final ResolveContext<Object> ctx,
final Resource parent) {
+ if (FsResource.RESOURCE_TYPE_FOLDER.equals(parent.getResourceType())) {
+ final File file = parent.adaptTo(File.class);
+ if (file != null && file.isDirectory()) {
+ final List<File> children = new ArrayList<>();
+ for (final File c : file.listFiles()) {
+ if (include(c)) {
+ children.add(c);
+ }
+ }
+ Collections.sort(children);
+ final Iterator<File> i = children.iterator();
+ return new Iterator<Resource>() {
+
+ @Override
+ public boolean hasNext() {
+ return i.hasNext();
+ }
+
+ @Override
+ public Resource next() {
+ final File file = i.next();
+ return new FsResource(
+ ctx.getResourceResolver(),
+
parent.getPath().concat("/").concat(file.getName()),
+ file);
+ }
+ };
+ }
+ }
+ return null;
+ }
+
+ private File getProviderFile(final String providerFileName, final
BundleContext bundleContext) {
+ // the file object from the plain name
+ File providerFile = new File(providerFileName);
+
+ // resolve relative file name against sling.home or current
+ // working directory
+ if (!providerFile.isAbsolute()) {
+ final String home = bundleContext.getProperty("sling.home");
+ if (home != null && home.length() > 0) {
+ providerFile = new File(home, providerFileName);
+ }
+ }
+
+ // resolve the path
+ providerFile = providerFile.getAbsoluteFile();
+
+ // if the provider file does not exist, create an empty new folder
+ if (!providerFile.exists() && !providerFile.mkdirs()) {
+ throw new IllegalArgumentException("Cannot create provider file
root " + providerFile);
+ }
+
+ return providerFile;
+ }
+
+ public ObservationReporter getObservationReporter() {
+ final ProviderContext ctx = this.getProviderContext();
+ if (ctx != null) {
+ return ctx.getObservationReporter();
+ }
+ return null;
+ }
+}