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;
+    }
+}


Reply via email to