You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by fm...@apache.org on 2014/10/23 09:50:37 UTC

svn commit: r1633762 - in /sling/whiteboard/fmeschbe/fsr: ./ src/main/java/org/apache/sling/fsprovider/internal/ src/main/resources/OSGI-INF/

Author: fmeschbe
Date: Thu Oct 23 07:50:37 2014
New Revision: 1633762

URL: http://svn.apache.org/r1633762
Log:
Fancy Filesystem ResourceProvider Prototype

Added:
    sling/whiteboard/fmeschbe/fsr/
      - copied from r1562756, sling/trunk/bundles/extensions/fsresource/
Removed:
    sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java
    sling/whiteboard/fmeschbe/fsr/src/main/resources/OSGI-INF/
Modified:
    sling/whiteboard/fmeschbe/fsr/pom.xml
    sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
    sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java

Modified: sling/whiteboard/fmeschbe/fsr/pom.xml
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/fsr/pom.xml?rev=1633762&r1=1562756&r2=1633762&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/fsr/pom.xml (original)
+++ sling/whiteboard/fmeschbe/fsr/pom.xml Thu Oct 23 07:50:37 2014
@@ -23,24 +23,26 @@
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
         <version>18</version>
-        <relativePath>../../../parent/pom.xml</relativePath>
+        <relativePath></relativePath>
     </parent>
 
-    <artifactId>org.apache.sling.fsresource</artifactId>
+    <artifactId>org.apache.sling.fsr</artifactId>
     <packaging>bundle</packaging>
-    <version>1.1.3-SNAPSHOT</version>
+    <version>0.0.1-SNAPSHOT</version>
 
-    <name>Apache Sling Filesystem Resource Provider</name>
+    <name>Apache Sling Filesystem Resource Provider++</name>
     <description>
         Provides a ResourceProvider implementation supporting filesystem
-        based resources.
+        based resources with support for metadata
     </description>
 
+    <!--
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/fsresource</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/  ---  TO FIX --- /fsr</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/  ---  TO FIX --- /fsr</url>
     </scm>
+    -->
 
     <build>
         <plugins>
@@ -70,9 +72,13 @@
                 <extensions>true</extensions>
                 <configuration>
                     <instructions>
-                        <Private-Package>
-                            org.apache.sling.fsprovider.internal
-                        </Private-Package>
+                        <Import-Package>
+                            !org.apache.tools.*,
+                            *
+                        </Import-Package>
+                        <Embed-Dependency>
+                            je
+                        </Embed-Dependency>
                     </instructions>
                 </configuration>
             </plugin>
@@ -83,10 +89,20 @@
             <groupId>javax.servlet</groupId>
             <artifactId>servlet-api</artifactId>
         </dependency>
+<dependency>
+    <groupId>com.sleepycat</groupId>
+    <artifactId>je</artifactId>
+    <version>5.0.73</version>
+</dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.3.0</version>
+            <version>2.8.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.serviceusermapper</artifactId>
+            <version>1.0.0</version>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
@@ -94,6 +110,16 @@
             <version>2.0.4</version>
         </dependency>
         <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>1.4</version>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
         </dependency>

Modified: sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java?rev=1633762&r1=1562756&r2=1633762&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java (original)
+++ sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java Thu Oct 23 07:50:37 2014
@@ -22,20 +22,17 @@ import java.io.File;
 import java.io.FileInputStream;
 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;
-
 import org.apache.sling.adapter.annotations.Adaptable;
 import org.apache.sling.adapter.annotations.Adapter;
 import org.apache.sling.api.resource.AbstractResource;
+import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.api.wrappers.ValueMapDecorator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -44,7 +41,7 @@ import org.slf4j.LoggerFactory;
  * a Sling Resource.
  */
 @Adaptable(adaptableClass=Resource.class, adapters={
-    @Adapter({File.class, URL.class}),
+    @Adapter({ValueMap.class, ModifiableValueMap.class, Map.class}),
     @Adapter(condition="If the resource is an FsResource and is a readable file.", value=InputStream.class)
 })
 public class FsResource extends AbstractResource {
@@ -67,11 +64,17 @@ public class FsResource extends Abstract
     // the owning resource resolver
     private final ResourceResolver resolver;
 
+    // the owning ResourceProvider
+    private final FsResourceProvider provider;
+
     // the path of this resource in the resource tree
     private final String resourcePath;
 
     // the file wrapped by this instance
-    private final File file;
+    private final File metaFile;
+
+    // the file wrapped by this instance
+    private final File dataFile;
 
     // the resource type, assigned on demand
     private String resourceType;
@@ -79,17 +82,34 @@ public class FsResource extends Abstract
     // the resource metadata, assigned on demand
     private ResourceMetadata metaData;
 
+    private FsModifiableValueMap properties;
+
+    private boolean modified = false;
+
+    private boolean removed = false;
+
     /**
      * Creates an instance of this Filesystem resource.
      *
      * @param resolver The owning resource resolver
+     * @param provider The FsResourceProvider creating this instance
      * @param resourcePath The resource path in the resource tree
-     * @param file The wrapped file
+     * @param metaFile The file providing the metadata (ValueMap properties) for
+     *            this resource
+     * @param dataFile The file providing the contents of this resource or
+     *            {@code null} if this resource only has properties
      */
-    FsResource(ResourceResolver resolver, String resourcePath, File file) {
+    FsResource(ResourceResolver resolver, FsResourceProvider provider, String resourcePath, File metaFile, File dataFile, Map<String, Object> properties) {
         this.resolver = resolver;
+        this.provider = provider;
         this.resourcePath = resourcePath;
-        this.file = file;
+        this.metaFile = metaFile;
+        this.dataFile = dataFile;
+
+        // initial properties (overwrite), causes this resource to be modified
+        if (properties != null && !properties.isEmpty()) {
+            this.getProperties().putAll(properties);
+        }
     }
 
     /**
@@ -107,11 +127,14 @@ public class FsResource extends Abstract
     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(ResourceMetadata.INTERNAL_CONTINUE_RESOLVING, Boolean.TRUE);
+
+            if (this.dataFile != null) {
+                metaData.setContentLength(dataFile.length());
+                metaData.setModificationTime(dataFile.lastModified());
+                metaData.setResolutionPath(resourcePath);
+                if (this.dataFile.isDirectory()) {
+                    metaData.put(ResourceMetadata.INTERNAL_CONTINUE_RESOLVING, Boolean.TRUE);
+                }
             }
         }
         return metaData;
@@ -139,9 +162,8 @@ public class FsResource extends Abstract
      */
     public String getResourceType() {
         if (resourceType == null) {
-            resourceType = file.isFile()
-                    ? RESOURCE_TYPE_FILE
-                            : RESOURCE_TYPE_FOLDER;
+            // TODO: PropertiesUtil
+            resourceType = (String) this.getProperties().get("sling:resourceType");
         }
 
         return resourceType;
@@ -155,59 +177,143 @@ public class FsResource extends Abstract
     @Override
     @SuppressWarnings("unchecked")
     public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
-        if (type == File.class) {
+        if (type == InputStream.class) {
 
-            return (AdapterType) file;
-
-        } else if (type == InputStream.class) {
-
-            if (!file.isDirectory() && file.canRead()) {
+            if (this.dataFile != null && !this.dataFile.isDirectory() && this.dataFile.canRead()) {
 
                 try {
-                    return (AdapterType) new FileInputStream(file);
+                    return (AdapterType) new FileInputStream(this.dataFile);
                 } catch (IOException ioe) {
                     getLog().info(
-                            "adaptTo: Cannot open a stream on the file " + file,
+                            "adaptTo: Cannot open a stream on the file " + this.dataFile,
                             ioe);
                 }
 
             } else {
 
-                getLog().debug("adaptTo: File {} is not a readable file", file);
+                getLog().debug("adaptTo: File {} is not a readable file", this.dataFile);
 
             }
 
-        } else if (type == URL.class) {
+        } else if (type == ValueMap.class || type == ModifiableValueMap.class || type == Map.class) {
+
+            return (AdapterType) this.getProperties();
+
+        }
+
+        return super.adaptTo(type);
+    }
+
+    // ---------- Object
+
+    @Override
+    public int hashCode() {
+        return (this.modified ? 17 : 0) + (this.removed ? 33 : 0) + this.getPath().hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        } else if (!(obj instanceof FsResource)) {
+            return false;
+        }
+
+        FsResource other = (FsResource) obj;
+        return this.getPath().equals(other.getPath()) && this.modified == other.modified
+            && this.removed == other.removed;
+    }
 
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + getResourceMetadata();
+    }
+
+    // modifiable support
+
+    private ModifiableValueMap getProperties() {
+        if (this.properties == null || this.properties.isEmpty()) {
+
+            Map<String, Object> props = null;
             try {
-                return (AdapterType) file.toURI().toURL();
-            } catch (MalformedURLException mue) {
-                getLog().info(
-                        "adaptTo: Cannot convert the file path " + file
-                        + " to an URL", mue);
+                props = FsUtil.readProperties(this.metaFile);
+            } catch (IOException e) {
+                // TODO: handle !!
             }
 
-        } 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());
+            if (props == null) {
+                props = new HashMap<String, Object>();
                 props.put("jcr:createdBy", "system");
                 Calendar lastModifed = Calendar.getInstance();
-                lastModifed.setTimeInMillis(file.lastModified());
+                if (this.dataFile != null) {
+                    lastModifed.setTimeInMillis(this.dataFile.lastModified());
+                }
                 props.put("jcr:created", lastModifed);
-                return (AdapterType) new ValueMapDecorator(props);
             }
 
+            // ensure primary and resource type !
+            if (!props.containsKey("jcr:primaryType")) {
+                props.put("jcr:primaryType", (this.dataFile == null) ? "nt:unstructured" : (this.dataFile.isFile()
+                        ? RESOURCE_TYPE_FILE
+                        : RESOURCE_TYPE_FOLDER));
+            }
+            if (!props.containsKey("sling:resourceType")) {
+                props.put("sling:resourceType", props.get("jcr:primaryType"));
+            }
+
+            this.properties = new FsModifiableValueMap(this, props);
         }
+        return this.properties;
+    }
 
-        return super.adaptTo(type);
+    void commit() throws IOException {
+        if (this.removed) {
+            this.metaFile.delete();
+            if (this.dataFile != null) {
+                // TODO: Check whether this is an empty directory first !
+                this.dataFile.delete();
+            }
+        } else if (this.modified && this.properties != null) {
+            FsUtil.writeProperties(this.metaFile, this.properties);
+        }
     }
 
+    void revert() {
+        this.modified = false;
+        this.removed = false;
+        if (this.properties != null) {
+            this.properties.clear();
+            this.getProperties();
+        }
+    }
+
+
     // ---------- internal
 
+    void unlockResourceMetadata() {
+        if (this.metaData != null) {
+            ResourceMetadata newMetaData = new ResourceMetadata();
+            newMetaData.putAll(this.metaData);
+            this.metaData = newMetaData;
+        }
+    }
+
+    File getDataFile() {
+        return this.dataFile;
+    }
+
+    void remove() {
+        this.removed = true;
+        this.provider.modified(this);
+    }
+
+    void modified() {
+        if (!this.modified && !this.removed) {
+            this.modified = true;
+            this.provider.modified(this);
+        }
+    }
+
     private Logger getLog() {
         if (log == null) {
             log = LoggerFactory.getLogger(getClass());

Modified: sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java?rev=1633762&r1=1562756&r2=1633762&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java (original)
+++ sling/whiteboard/fmeschbe/fsr/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java Thu Oct 23 07:50:37 2014
@@ -19,26 +19,29 @@
 package org.apache.sling.fsprovider.internal;
 
 import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
 
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.ConfigurationPolicy;
-import org.apache.felix.scr.annotations.Properties;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.apache.felix.scr.annotations.ReferencePolicy;
-import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.adapter.SlingAdaptable;
+import org.apache.sling.api.resource.AttributableResourceProvider;
+import org.apache.sling.api.resource.ModifyingResourceProvider;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.RefreshableResourceProvider;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceProvider;
 import org.apache.sling.api.resource.ResourceResolver;
-import org.osgi.framework.BundleContext;
-import org.osgi.service.event.EventAdmin;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.fsprovider.User;
 
 /**
  * The <code>FsResourceProvider</code> is a resource provider which maps
@@ -51,53 +54,20 @@ import org.osgi.service.event.EventAdmin
  * and the file system path from where files and folders are mapped into the
  * resource ({@link #PROP_PROVIDER_FILE}).
  */
-@Component(
-        name="org.apache.sling.fsprovider.internal.FsResourceProvider",
-        label="%resource.resolver.name",
-        description="%resource.resolver.description",
-        configurationFactory=true,
-        policy=ConfigurationPolicy.REQUIRE,
-        metatype=true
-        )
-@Service(ResourceProvider.class)
-@Properties({
-    @Property(name="service.description", value="Sling Filesystem Resource Provider"),
-    @Property(name="service.vendor", value="The Apache Software Foundation"),
-    @Property(name=ResourceProvider.ROOTS)
-})
-public class FsResourceProvider implements ResourceProvider {
+public class FsResourceProvider extends SlingAdaptable implements ResourceProvider, ModifyingResourceProvider, AttributableResourceProvider, RefreshableResourceProvider {
 
-    /**
-     * The name of the configuration property providing file system path of
-     * files and folders mapped into the resource tree (value is
-     * "provider.file").
-     */
-    @Property
-    public static final String PROP_PROVIDER_FILE = "provider.file";
-
-    /**
-     * The name of the configuration property providing the check interval
-     * for file changes (value is "provider.checkinterval").
-     */
-    @Property(longValue=FsResourceProvider.DEFAULT_CHECKINTERVAL)
-    public static final String PROP_PROVIDER_CHECKINTERVAL = "provider.checkinterval";
+    private final FsrResourceProviderFactory factory;
 
-    public static final long DEFAULT_CHECKINTERVAL = 1000;
+    private final User user;
 
-    // The location in the resource tree where the resources are mapped
-    private String providerRoot;
+    private final Set<FsResource> modifiedResources = new HashSet<FsResource>();
 
-    // providerRoot + "/" to be used for prefix matching of paths
-    private String providerRootPrefix;
+    private final Map<String, FsResource> cache = new HashMap<String, FsResource>();
 
-    // The "root" file or folder in the file system
-    private File providerFile;
-
-    /** The monitor to detect file changes. */
-    private FileMonitor monitor;
-
-    @Reference(cardinality=ReferenceCardinality.OPTIONAL_UNARY, policy=ReferencePolicy.DYNAMIC)
-    private EventAdmin eventAdmin;
+    FsResourceProvider(final FsrResourceProviderFactory factory, final User user) {
+        this.factory = factory;
+        this.user = user;
+    }
 
     /**
      * Same as {@link #getResource(ResourceResolver, String)}, i.e. the
@@ -105,8 +75,7 @@ public class FsResourceProvider implemen
      *
      * @see #getResource(ResourceResolver, String)
      */
-    public Resource getResource(ResourceResolver resourceResolver,
-            HttpServletRequest request, String path) {
+    public Resource getResource(ResourceResolver resourceResolver, HttpServletRequest request, String path) {
         return getResource(resourceResolver, path);
     }
 
@@ -120,34 +89,37 @@ public class FsResourceProvider implemen
      * method returns <code>null</code>.
      */
     public Resource getResource(ResourceResolver resourceResolver, String path) {
-        return getResource(resourceResolver, path, getFile(path));
+        return getResource(resourceResolver, path, this.factory.getFile(path));
     }
 
     /**
      * Returns an iterator of resources.
      */
     public Iterator<Resource> listChildren(Resource parent) {
-        File parentFile = parent.adaptTo(File.class);
 
-        // not a FsResource, try to create one from the resource
-        if (parentFile == null) {
+        File parentDataFile;
+        if (parent instanceof FsResource) {
+
+            parentDataFile = ((FsResource) parent).getDataFile();
+
+        } else {
+
             // if the parent path is at or below the provider root, get
             // the respective file
-            parentFile = getFile(parent.getPath());
+            File parentMetaFile = this.factory.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) {
+            if (parentMetaFile == null) {
 
                 String parentPath = parent.getPath().concat("/");
-                if (providerRoot.startsWith(parentPath)) {
-                    String relPath = providerRoot.substring(parentPath.length());
+                if (this.factory.getProviderRoot().startsWith(parentPath)) {
+                    String relPath = this.factory.getProviderRoot().substring(parentPath.length());
                     if (relPath.indexOf('/') < 0) {
-                        Resource res = getResource(
-                                parent.getResourceResolver(), providerRoot,
-                                providerFile);
+                        Resource res = getResource(parent.getResourceResolver(), this.factory.getProviderRoot(),
+                            this.factory.getRootFile());
                         if (res != null) {
                             return Collections.singletonList(res).iterator();
                         }
@@ -157,9 +129,15 @@ public class FsResourceProvider implemen
                 // no children here
                 return null;
             }
+
+            parentDataFile = getDataFile(parentMetaFile);
         }
 
-        final File[] children = parentFile.listFiles();
+        final File[] children = parentDataFile.listFiles(new FileFilter() {
+            public boolean accept(File file) {
+                return file.getName().endsWith(".meta");
+            }
+        });
 
         if (children != null && children.length > 0) {
             final ResourceResolver resolver = parent.getResourceResolver();
@@ -190,7 +168,8 @@ public class FsResourceProvider implemen
                 private Resource seek() {
                     while (index < children.length) {
                         File file = children[index++];
-                        String path = parentPath + "/" + file.getName();
+                        String path = parentPath + "/"
+                            + file.getName().substring(0, file.getName().length() - ".meta".length());
                         Resource result = getResource(resolver, path, file);
                         if (result != null) {
                             return result;
@@ -207,115 +186,97 @@ public class FsResourceProvider implemen
         return null;
     }
 
-    // ---------- SCR Integration
+    // ---------- ModifyingResourceProvider
 
-    protected void activate(BundleContext bundleContext, Map<?, ?> props) {
-        String providerRoot = (String) props.get(ROOTS);
-        if (providerRoot == null || providerRoot.length() == 0) {
-            throw new IllegalArgumentException(ROOTS + " property must be set");
+    public Resource create(final ResourceResolver resolver, final String path, final Map<String, Object> properties) {
+        File metaFile = this.factory.getFile(path);
+        // manage some properties
+        Map<String, Object> props = new HashMap<String, Object>();
+        if (properties != null) {
+            props.putAll(properties);
         }
+        props.put("jcr:created", System.currentTimeMillis());
+        props.put("jcr:createdBy", resolver.getUserID());
 
-        String providerFileName = (String) props.get(PROP_PROVIDER_FILE);
-        if (providerFileName == null || providerFileName.length() == 0) {
-            throw new IllegalArgumentException(PROP_PROVIDER_FILE
-                    + " property must be set");
-        }
+        return createResource(resolver, path, metaFile, props);
+    }
 
-        this.providerRoot = providerRoot;
-        this.providerRootPrefix = providerRoot.concat("/");
-        this.providerFile = getProviderFile(providerFileName, bundleContext);
-        // start background monitor if check interval is higher than 100
-        long checkInterval = DEFAULT_CHECKINTERVAL;
-        final Object interval = props.get(PROP_PROVIDER_CHECKINTERVAL);
-        if ( interval != null && interval instanceof Long ) {
-            checkInterval = (Long)interval;
-        }
-        if ( checkInterval > 100 ) {
-            this.monitor = new FileMonitor(this, checkInterval);
+    public void delete(ResourceResolver resolver, String path) {
+        Resource res = this.getResource(resolver, path);
+        if (res instanceof FsResource) {
+            ((FsResource) res).remove();
         }
     }
 
-    protected void deactivate() {
-        if ( this.monitor != null ) {
-            this.monitor.stop();
-            this.monitor = null;
+    public void revert(ResourceResolver resolver) {
+        for (FsResource fsResource : modifiedResources) {
+            fsResource.revert();
         }
-        this.providerRoot = null;
-        this.providerRootPrefix = null;
-        this.providerFile = null;
+        modifiedResources.clear();
     }
 
-    EventAdmin getEventAdmin() {
-        return this.eventAdmin;
+    public void commit(ResourceResolver resolver) throws PersistenceException {
+        for (FsResource fsResource : modifiedResources) {
+            try {
+                fsResource.commit();
+            } catch (IOException e) {
+                throw new PersistenceException(e.getMessage(), e);
+            }
+        }
+        modifiedResources.clear();
     }
 
-    File getRootFile() {
-        return this.providerFile;
+    public boolean hasChanges(ResourceResolver resolver) {
+        return !this.modifiedResources.isEmpty();
     }
 
-    String getProviderRoot() {
-        return this.providerRoot;
-    }
+    // ---------- AttributableResourceProvider
 
-    // ---------- internal
+    public Collection<String> getAttributeNames(ResourceResolver resolver) {
+        return Collections.singleton(ResourceResolverFactory.USER);
+    }
 
-    private File getProviderFile(String providerFileName,
-            BundleContext bundleContext) {
+    public Object getAttribute(ResourceResolver resolver, String name) {
+        return (ResourceResolverFactory.USER == name) ? this.user.getId() : null;
+    }
 
-        // the file object from the plain name
-        File providerFile = new File(providerFileName);
+    // ---------- RefreshableResourceProvider
 
-        // 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);
+    public void refresh() {
+        // drop all unmodified resources from the cache
+        for (Iterator<FsResource> fsri = this.cache.values().iterator(); fsri.hasNext(); ) {
+            if (!this.modifiedResources.contains(fsri.next())) {
+                fsri.remove();
             }
         }
+    }
 
-        // resolve the path
-        providerFile = providerFile.getAbsoluteFile();
+    // ---------- SlingAdaptable
 
-        // 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);
+    @SuppressWarnings("unchecked")
+    @Override
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        if (type == User.class) {
+            return (AdapterType) this.user;
         }
 
-        return providerFile;
+        return super.adaptTo(type);
     }
 
-    /**
-     * 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);
-        }
+    // ---------- internal
 
-        return null;
+    void modified(FsResource resource) {
+        this.modifiedResources.add(resource);
     }
 
-    private Resource getResource(ResourceResolver resourceResolver,
-            String resourcePath, File file) {
+    private Resource getResource(ResourceResolver resourceResolver, String resourcePath, File metaFile) {
 
-        if (file != null) {
+        if (metaFile != 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(resourceResolver, resourcePath, file);
+            if (metaFile.exists()) {
+                return createResource(resourceResolver, resourcePath, metaFile, null);
             }
 
         }
@@ -323,4 +284,35 @@ public class FsResourceProvider implemen
         // not applicable or not an existing file path
         return null;
     }
+
+    private Resource createResource(ResourceResolver resourceResolver, String resourcePath, File metaFile, Map<String, Object> properties) {
+        FsResource res = this.cache.get(resourcePath);
+        if (res == null) {
+            File dataFile = getDataFile(metaFile);
+            res = new FsResource(resourceResolver, this, resourcePath, metaFile, dataFile, properties);
+            this.cache.put(resourcePath, res);
+        } else {
+            // TODO: short-term fix to prevent exceptions since metadata
+            // is locked after it went through the ResourceResolver. Better
+            // would probably be to have a facade with ResourceMetadata
+            // and a backend with the rest
+            res.unlockResourceMetadata();
+        }
+        return res;
+    }
+
+    private File getDataFile(final File metaFile) {
+        String name = metaFile.getName();
+        if (name.endsWith(".meta")) {
+            name = name.substring(0, name.length()-".meta".length());
+        }
+
+        File dirFile = new File(metaFile.getParentFile(), name);
+        if (dirFile.isDirectory()) {
+            return dirFile;
+        }
+
+        return new File(metaFile.getParentFile(), name + ".data");
+    }
+
 }