This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.fsresource-1.0.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-fsresource.git
commit 93cda71b4356d387c52bb9a1947117b76cf551e3 Author: Carsten Ziegeler <[email protected]> AuthorDate: Fri Feb 19 10:37:26 2010 +0000 SLING-1387 : File system provider should send resource events git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/fsresource@911777 13f79535-47bb-0310-9956-ffa450edef68 --- NOTICE | 2 +- pom.xml | 4 + .../sling/fsprovider/internal/FileMonitor.java | 256 +++++++++++++++++++++ .../sling/fsprovider/internal/FsResource.java | 4 +- .../fsprovider/internal/FsResourceProvider.java | 43 +++- src/main/resources/META-INF/NOTICE | 2 +- 6 files changed, 306 insertions(+), 5 deletions(-) diff --git a/NOTICE b/NOTICE index 7a727bf..9d9af52 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Apache Sling File System Resource Provider -Copyright 2008-2009 The Apache Software Foundation +Copyright 2008-2010 The Apache Software Foundation Apache Sling is based on source code originally developed by Day Software (http://www.day.com/). diff --git a/pom.xml b/pom.xml index ed57ad1..89aaae9 100644 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,10 @@ <artifactId>org.osgi.core</artifactId> </dependency> <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java new file mode 100644 index 0000000..5e03ea9 --- /dev/null +++ b/src/main/java/org/apache/sling/fsprovider/internal/FileMonitor.java @@ -0,0 +1,256 @@ +/* + * 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.Dictionary; +import java.util.Hashtable; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.sling.api.SlingConstants; +import org.osgi.service.event.EventAdmin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is a monitor for the file system + * that periodically checks for changes. + */ +public class FileMonitor extends TimerTask { + + /** The logger. */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final Timer timer = new Timer(); + private boolean stop = false; + private boolean stopped = true; + + private final Monitorable root; + + private final FsResourceProvider provider; + + /** + * Creates a new instance of this class. + * @param provider The resource provider. + * @param interval The interval between executions of the task, in milliseconds. + */ + public FileMonitor(final FsResourceProvider provider, final long interval) { + this.provider = provider; + this.root = new Monitorable(this.provider.getProviderRoot(), this.provider.getRootFile()); + createStatus(this.root); + logger.debug("Starting file monitor for {} with an interval of {}ms", this.root.file, interval); + timer.schedule(this, 0, interval); + } + + /** + * Stop periodically executing this task. If the task is currently executing it + * will never be run again after the current execution, otherwise it will simply + * never run (again). + */ + void stop() { + synchronized (timer) { + if (!stop) { + stop = true; + cancel(); + timer.cancel(); + } + + boolean interrupted = false; + while (!stopped) { + try { + timer.wait(); + } + catch (InterruptedException e) { + interrupted = true; + } + } + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + logger.debug("Stopped file monitor for {}", this.root.file); + } + + /** + * @see java.util.TimerTask#run() + */ + public void run() { + synchronized (timer) { + stopped = false; + if (stop) { + stopped = true; + timer.notifyAll(); + return; + } + } + synchronized ( this ) { + try { + // if we don't have an event admin, we just skip the check + final EventAdmin localEA = this.provider.getEventAdmin(); + if ( localEA != null ) { + this.check(this.root, localEA); + } + } catch (Exception e) { + // ignore this + } + } + synchronized (timer) { + stopped = true; + timer.notifyAll(); + } + } + + /** + * Check the monitorable + * @param monitorable The monitorable to check + * @param localEA The event admin + */ + private void check(final Monitorable monitorable, final EventAdmin localEA) { + logger.debug("Checking {}", monitorable.file); + // if the file is non existing, check if it has been readded + if ( monitorable.status instanceof NonExistingStatus ) { + if ( monitorable.file.exists() ) { + // new file and reset status + createStatus(monitorable); + sendEvents(monitorable, + SlingConstants.TOPIC_RESOURCE_ADDED, + localEA); + } + } else { + // check if the file has been removed + if ( !monitorable.file.exists() ) { + // removed file and update status + sendEvents(monitorable, + SlingConstants.TOPIC_RESOURCE_REMOVED, + localEA); + monitorable.status = NonExistingStatus.SINGLETON; + } else { + // check for changes + final FileStatus fs = (FileStatus)monitorable.status; + boolean changed = false; + if ( fs.lastModified < monitorable.file.lastModified() ) { + fs.lastModified = monitorable.file.lastModified(); + // changed + sendEvents(monitorable, + SlingConstants.TOPIC_RESOURCE_CHANGED, + localEA); + changed = true; + } + if ( fs instanceof DirStatus ) { + // directory + final DirStatus ds = (DirStatus)fs; + for(int i=0; i<ds.children.length; i++) { + check(ds.children[i], localEA); + } + // if the dir changed we have to update + if ( changed ) { + // and now update + final File[] files = monitorable.file.listFiles(); + final Monitorable[] children = new Monitorable[files.length]; + for(int i=0; i<files.length; i++) { + // search in old list + for(int m=0;m<ds.children.length;m++) { + if ( ds.children[m].file.equals(files[i]) ) { + children[i] = ds.children[m]; + break; + } + } + if ( children[i] == null ) { + children[i] = new Monitorable(monitorable.path + '/' + files[i].getName(), files[i]); + children[i].status = NonExistingStatus.SINGLETON; + check(children[i], localEA); + } + } + ds.children = children; + } + } + } + } + } + + /** + * Send the event async via the event admin. + */ + private void sendEvents(final Monitorable monitorable, final String topic, final EventAdmin localEA) { + if ( logger.isDebugEnabled() ) { + logger.debug("Detected change for resource {} : {}", monitorable.path, topic); + } + + final Dictionary<String, String> properties = new Hashtable<String, String>(); + properties.put(SlingConstants.PROPERTY_PATH, monitorable.path); + final String type = monitorable.status instanceof FileStatus ? + FsResource.RESOURCE_TYPE_FILE : FsResource.RESOURCE_TYPE_FOLDER; + properties.put(SlingConstants.PROPERTY_RESOURCE_TYPE, type); + localEA.postEvent(new org.osgi.service.event.Event(topic, properties)); + } + + /** + * Create a status object for the monitorable + */ + private static void createStatus(final Monitorable monitorable) { + if ( !monitorable.file.exists() ) { + monitorable.status = NonExistingStatus.SINGLETON; + } else if ( monitorable.file.isFile() ) { + monitorable.status = new FileStatus(monitorable.file); + } else { + monitorable.status = new DirStatus(monitorable.file, monitorable.path); + } + } + + /** The monitorable to hold the resource path, the file and the status. */ + private static final class Monitorable { + public final String path; + public final File file; + public Object status; + + public Monitorable(final String path, final File file) { + this.path = path; + this.file = file; + } + } + + /** Status for files. */ + private static class FileStatus { + public long lastModified; + public FileStatus(final File file) { + this.lastModified = file.lastModified(); + } + } + + /** Status for directories. */ + private static final class DirStatus extends FileStatus { + public Monitorable[] children; + + public DirStatus(final File dir, final String path) { + super(dir); + final File[] files = dir.listFiles(); + this.children = new Monitorable[files.length]; + for(int i=0; i<files.length; i++) { + this.children[i] = new Monitorable(path + '/' + files[i].getName(), files[i]); + FileMonitor.createStatus(this.children[i]); + } + } + } + + /** Status for non existing files. */ + private static final class NonExistingStatus { + public static NonExistingStatus SINGLETON = new NonExistingStatus(); + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java index 372d499..7ecd8bc 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResource.java @@ -42,13 +42,13 @@ public class FsResource extends SlingAdaptable implements Resource { * The resource type for file system files mapped into the resource tree by * the {@link FsResourceProvider} (value is "nt:file"). */ - private static final String RESOURCE_TYPE_FILE = "nt:file"; + static final String RESOURCE_TYPE_FILE = "nt:file"; /** * The resource type for file system folders mapped into the resource tree * by the {@link FsResourceProvider} (value is "nt:folder"). */ - private static final String RESOURCE_TYPE_FOLDER = "nt:folder"; + static final String RESOURCE_TYPE_FOLDER = "nt:folder"; // default log, assigned on demand private Logger log; diff --git a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java index 1cc0e18..dd7b740 100644 --- a/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java +++ b/src/main/java/org/apache/sling/fsprovider/internal/FsResourceProvider.java @@ -32,6 +32,7 @@ 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; /** * The <code>FsResourceProvider</code> is a resource provider which maps @@ -48,12 +49,13 @@ import org.osgi.framework.BundleContext; * label="%resource.resolver.name" * description="%resource.resolver.description" * configurationFactory="true" - * @scr.service + * @scr.service interface="ResourceProvider" * @scr.property name="service.description" value="Sling Filesystem Resource * Provider" * @scr.property name="service.vendor" value="The Apache Software Foundation" * @scr.property nameRef="ResourceProvider.ROOTS" * @scr.property nameRef="PROP_PROVIDER_FILE" + * @scr.property nameRef="PROP_PROVIDER_CHECKINTERVAL" valueRef="DEFAULT_CHECKINTERVAL" */ public class FsResourceProvider implements ResourceProvider { @@ -64,6 +66,14 @@ public class FsResourceProvider implements ResourceProvider { */ 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"). + */ + public static final String PROP_PROVIDER_CHECKINTERVAL = "provider.checkinterval"; + + public static long DEFAULT_CHECKINTERVAL = 1000; + // The location in the resource tree where the resources are mapped private String providerRoot; @@ -73,6 +83,12 @@ public class FsResourceProvider implements ResourceProvider { // The "root" file or folder in the file system private File providerFile; + /** The monitor to detect file changes. */ + private FileMonitor monitor; + + /** @scr.reference cardinality="0..1" policy="dynamic" */ + private EventAdmin eventAdmin; + /** * Same as {@link #getResource(ResourceResolver, String)}, i.e. the * <code>request</code> parameter is ignored. @@ -199,14 +215,39 @@ public class FsResourceProvider implements ResourceProvider { 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); + } } protected void deactivate() { + if ( this.monitor != null ) { + this.monitor.stop(); + this.monitor = null; + } this.providerRoot = null; this.providerRootPrefix = null; this.providerFile = null; } + EventAdmin getEventAdmin() { + return this.eventAdmin; + } + + File getRootFile() { + return this.providerFile; + } + + String getProviderRoot() { + return this.providerRoot; + } + // ---------- internal private File getProviderFile(String providerFileName, diff --git a/src/main/resources/META-INF/NOTICE b/src/main/resources/META-INF/NOTICE index 7a727bf..9d9af52 100644 --- a/src/main/resources/META-INF/NOTICE +++ b/src/main/resources/META-INF/NOTICE @@ -1,5 +1,5 @@ Apache Sling File System Resource Provider -Copyright 2008-2009 The Apache Software Foundation +Copyright 2008-2010 The Apache Software Foundation Apache Sling is based on source code originally developed by Day Software (http://www.day.com/). -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
