Package org.geoserver.platform.resource

Source Code of org.geoserver.platform.resource.FileSystemWatcher$Delta

package org.geoserver.platform.resource;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.geoserver.platform.resource.ResourceNotification.Kind;


/**
* Active object (using a ScheduledExecutorService) used to watch file system for changes.
* <p>
* This implementation currently polls the file system and should be updated with Java 7 WatchService when available. The internal design is similar
* to WatchService, WatchKey and WatchEvent in order to facilitate this transition.
* <p>
* This implementation makes a few concessions to being associated with ResourceStore, reporting changes with resource paths rather than files.
*
* @author Jody Garnett (Boundless)
*/
public class FileSystemWatcher {
    /**
     * Change to file system
     */
    static class Delta {
        final File context;

        final Kind kind;

        final List<File> created;

        final List<File> removed;

        final List<File> modified;

        public Delta(File context, Kind kind) {
            this.context = context;
            this.kind = kind;
            this.created = null;
            this.removed = null;
            this.modified = null;
        }

        @SuppressWarnings("unchecked")
        public Delta(File context, Kind kind, List<File> created, List<File> removed,
                List<File> modified) {
            this.context = context;
            this.kind = Kind.ENTRY_MODIFY;
            this.created = created != null ? created : (List<File>) Collections.EMPTY_LIST;
            this.removed = removed != null ? removed : (List<File>) Collections.EMPTY_LIST;
            this.modified = modified != null ? modified : (List<File>) Collections.EMPTY_LIST;
        }

        @Override
        public String toString() {
            return "Delta [context=" + context + ", created=" + created + ", removed=" + removed
                    + ", modified=" + modified + "]";
        }

    }

    /**
     * Record of a ResourceListener that wishes to be notified of changes to a path.
     */
    private class Watch implements Comparable<Watch> {
        /** File being watched */
        final File file;

        /** Path to use during notification */
        final String path;

        List<ResourceListener> listeners = new CopyOnWriteArrayList<ResourceListener>();
       
        /** When last notification was sent */
        long last = 0;
       
        /** Used to track resource creation / deletion */
        boolean exsists;
       
        File[] contents; // directory contents at last check

        public Watch(File file, String path) {
            this.file = file;
            this.path = path;
            this.exsists = file.exists();
            this.last = exsists ? file.lastModified() : 0;           
            if (file.isDirectory()) {
                contents = file.listFiles();
            }
        }

        public void addListener(ResourceListener listener){
            listeners.add(listener);
        }
        public void removeListener(ResourceListener listener){
            listeners.remove(listener);
        }
       
        /** Path used for notification */
        public String getPath() {
            return path;
        }
       
        public List<ResourceListener> getListeners() {
            return listeners;
        }

        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((file == null) ? 0 : file.hashCode());
            result = prime * result + ((path == null) ? 0 : path.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Watch other = (Watch) obj;
            if (file == null) {
                if (other.file != null)
                    return false;
            } else if (!file.equals(other.file))
                return false;
            if (path == null) {
                if (other.path != null)
                    return false;
            } else if (!path.equals(other.path))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return "Watch [path=" + path + ", file=" + file + ", listeners="+listeners.size()+"]";
        }

        @Override
        public int compareTo(Watch other) {
            return path.compareTo(other.path);
        }

        public Delta changed(long now) {
            if (!file.exists()) {
                if (exsists) {
                    exsists = false;
                    if (contents != null) {
                        // delete directory
                        List<File> deleted = Arrays.asList(contents);
                        this.last = now;
                        this.contents = null;
                        return new Delta(file, Kind.ENTRY_DELETE, null, deleted, null);
                    } else {
                        // file has been deleted!
                        this.last = now;
                        return new Delta(file, Kind.ENTRY_DELETE);
                    }
                } else {
                    return null; // no change file still deleted!
                }
            }
            if (file.isFile()) {
                long fileModified = file.lastModified();
                if (fileModified > last || !exsists) {
                    if (exsists) {
                        this.last = fileModified;
                        return new Delta(file, Kind.ENTRY_MODIFY);
                    }
                    else {
                        exsists = true;
                        this.last = fileModified;
                        return new Delta(file, Kind.ENTRY_CREATE);
                    }
                } else {
                    return null; // no change!
                }
            }
            if (file.isDirectory()) {
                Kind kind = null;
                long fileModified = file.lastModified();
                if (fileModified > last || !exsists) {
                    kind = exsists ? Kind.ENTRY_MODIFY : Kind.ENTRY_CREATE;
                    exsists = true;
                } else {
                    // boolean win = System.getProperty("os.name").startsWith("Windows");
                    // if( !win ){
                    // // not windows - so no need to check directory contents
                    // return null; // no change
                    // }
                   
                }
                File[] files = file.listFiles();

                List<File> removed = new ArrayList<File>(files.length);
                List<File> created = new ArrayList<File>(files.length);
                List<File> modified = new ArrayList<File>(files.length);

                removed.addAll(Arrays.asList(this.contents));
                removed.removeAll(Arrays.asList(files));
                if( !removed.isEmpty() ){
                    fileModified = Math.max(fileModified, last+1);
                }               
               
                created.addAll(Arrays.asList(files));
                created.removeAll(Arrays.asList(this.contents));
                for(File check : created ){
                    long checkModified = check.lastModified();
                    fileModified = Math.max(fileModified, checkModified);
                }
                // check contents
                List<File> review = new ArrayList<File>(files.length);
                review.addAll(Arrays.asList(files));
                review.removeAll(created); // no need to check these they are new
                for (File check : review) {
                    long checkModified = check.lastModified();
                    if (checkModified > last) {
                        modified.add(check);
                        fileModified = Math.max(fileModified, checkModified);
                    }
                }
                if (kind == null) {
                    if (removed.isEmpty() && created.isEmpty() && modified.isEmpty()) {
                        // win only check of directory contents
                        return null; // no change to directory contents
                    } else {
                        kind = Kind.ENTRY_MODIFY;
                    }
                }
                this.last = fileModified;
                this.contents = files;
                return new Delta(file, kind, created, removed, modified);
            }
            return null; // no change
        }

        public boolean isMatch(File file, String path) {
            if (this.file == null) {
                if (file != null){
                    return false;
                }
            } else if (!this.file.equals(file)){
                return false;
            }
           
            if (this.path == null) {
                if (path != null){
                    return false;
                }
            } else if (!this.path.equals(path)){
                return false;
            }
            return true;
        }
    }

    private ScheduledExecutorService pool;

    //private FileSystemResourceStore store;

    protected long lastmodified;

    CopyOnWriteArrayList<Watch> watchers = new CopyOnWriteArrayList<Watch>();
   
    /**
     * Note we have a single runnable here to review all outstanding Watch instances.
     * The focus is on using minimal system resources while we wait for Java 7 WatchService
     * (to be more efficient).
     */
    private Runnable sync = new Runnable() {
        @Override
        public void run() {
            long now = System.currentTimeMillis();
            for (Watch watch : watchers) {
                if( watch.getListeners().isEmpty()){
                    watchers.remove(watch);
                    continue;
                }
                Delta delta = watch.changed(now);
                if (delta != null) {
                   
                    /** Created based on created/removed/modified files */
                    List<ResourceNotification.Event> events = ResourceNotification.delta(
                            watch.file, delta.created, delta.removed, delta.modified);
                   
                    ResourceNotification notify = new ResourceNotification( watch.getPath(),
                            delta.kind, watch.last, events);
                   
                    for (ResourceListener listener : watch.getListeners()) {
                        try {
                            listener.changed(notify);
                        } catch (Throwable t) {
                            Logger logger = Logger.getLogger(listener.getClass().getPackage()
                                    .getName());
                            logger.log(Level.FINE,
                                    "Unable to notify " + watch + ":" + t.getMessage(), t);
                        }
                    }
                }
            }
        }
    };

    private ScheduledFuture<?> monitor;

    private TimeUnit unit = TimeUnit.SECONDS;

    private long delay = 10;

    /**
     * FileSystemWatcher used to track file changes.
     * <p>
     * Internally a single threaded schedule executor is used to monitor files.
     */
    FileSystemWatcher() {
        this.pool = Executors.newSingleThreadScheduledExecutor();
    }
   
    private Watch watch(File file, String path ){
        if( file == null || path == null ){
            return null;
        }
        for( Watch watch : watchers ){
            if( watch.isMatch(file,path)){
                return watch;
            }
        }
        return null; // not found
    }
    public synchronized void addListener(File file, String path, ResourceListener listener) {
        if( file == null ){
            throw new NullPointerException("File to watch is required");
        }
        if( path == null ){
            throw new NullPointerException("Path for notification is required");
        }
        Watch watch = watch( file, path );
        if( watch == null ){
            watch = new Watch(file, path);
            watchers.add(watch);
            if( monitor == null){
                monitor = pool.scheduleWithFixedDelay(sync, delay, delay, unit);
            }               
        }
        watch.addListener(listener);
    }

    public synchronized void removeListener(File file, String path, ResourceListener listener) {
        if( file == null ){
            throw new NullPointerException("File to watch is required");
        }
        if( path == null ){
            throw new NullPointerException("Path for notification is required");
        }
        Watch watch = watch( file, path );
        boolean removed = false;
        if( watch != null ){
            watch.removeListener(listener);
            if( watch.getListeners().isEmpty()){
                removed = watchers.remove(watch);
            }
        }
        if (removed && watchers.isEmpty()) {
            if (monitor != null) {
                monitor.cancel(false); // stop watching nobody is looking
                monitor = null;
            }
        }
    }

    /**
     * Package visibility to allow test cases to set a shorter delay for testing.
     *
     * @param delay
     * @param unit
     */
    void schedule(long delay, TimeUnit unit) {
        this.delay = delay;
        this.unit = unit;
        if (monitor != null) {
            monitor.cancel(false);
            monitor = pool.scheduleWithFixedDelay(sync, delay, delay, unit);
        }
    }
}
TOP

Related Classes of org.geoserver.platform.resource.FileSystemWatcher$Delta

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.