Package er.extensions.foundation

Source Code of er.extensions.foundation.ERXFileNotificationCenter$_ObserverSelectorHolder

/*
* Copyright (C) NetStruxr, Inc. All rights reserved.
*
* This software is published under the terms of the NetStruxr
* Public Software License version 0.5, a copy of which has been
* included with this distribution in the LICENSE.NPL file.  */
package er.extensions.foundation;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;

import org.apache.log4j.Logger;

import com.webobjects.appserver.WOApplication;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSMutableSet;
import com.webobjects.foundation.NSNotification;
import com.webobjects.foundation.NSNotificationCenter;
import com.webobjects.foundation.NSSelector;

import er.extensions.appserver.ERXApplication;
import er.extensions.eof.ERXConstant;

/**
* The file notification center is only used in development systems. It provides a nice repository about
* files and their last modified dates.  So instead of every dynamic spot having to keep track of
* the files' dates, register and check at the end of every request-response loop, instead you
* can just add an observer to this center and be notified when the file changes. Files' last modification
* dates are checked at the end of every request-response loop.
*
* <p>It should be noted that the current version of the file notification center will retain a
* reference to each registered observer. This is not ideal and will be corrected in the
* future.</p>
*/
public class ERXFileNotificationCenter {

    /** Logging support */
    public static final Logger log = Logger.getLogger(ERXFileNotificationCenter.class);

    /** Contains the name of the notification that is posted when a file changes. */
    public static final String FileDidChange = "FileDidChange";

    /** holds a reference to the default file notification center */
    private static ERXFileNotificationCenter _defaultCenter;

    /**
     * @return the singleton instance of file notification center
     */
    public static ERXFileNotificationCenter defaultCenter() {
        if (_defaultCenter == null)
            _defaultCenter = new ERXFileNotificationCenter();
        return _defaultCenter;
    }
   
    /** In seconds.  0 means we will not regularly check files. */
    private static int checkFilesPeriod() {
        return ERXProperties.intForKeyWithDefault("er.extensions.ERXFileNotificationCenter.CheckFilesPeriod", 0);
    }

    /** collections of observers by file path */
    private NSMutableDictionary _observersByFilePath = new NSMutableDictionary();
    /** cache for last modified dates of files by file path */
    private NSMutableDictionary _lastModifiedByFilePath = new NSMutableDictionary();
    /** flag to tell if caching is enabled, set in the object constructor */
    private boolean developmentMode;
    /** The last time we checked files.  We only check if !WOCachingEnabled or if there is a CheckFilesPeriod set */
    private long lastCheckMillis = System.currentTimeMillis();
    private boolean symlinkSupport;
   
    /**
     * Default constructor. If you are in development mode
     * then this object will register for the notification
     * {@link com.webobjects.appserver.WOApplication#ApplicationWillDispatchRequestNotification}
     * which will enable it to check if files have changed at the end of every request-response
     * loop. If WOCaching is enabled then this object will not register for anything and will generate
     * warning messages if observers are registered with caching enabled.
     */
    public ERXFileNotificationCenter() {
      developmentMode = ERXApplication.isDevelopmentModeSafe();

        if (developmentMode || checkFilesPeriod() > 0) {
            ERXRetainer.retain(this);
            log.debug("Caching disabled.  Registering for notification: " + WOApplication.ApplicationWillDispatchRequestNotification);
            NSNotificationCenter.defaultCenter().addObserver(this, new NSSelector("checkIfFilesHaveChanged", ERXConstant.NotificationClassArray), WOApplication.ApplicationWillDispatchRequestNotification, null);           
        }
       
        // MS: In case we are touching properties before they're fully materialized or messed up from a failed reload, lets use System.props here
        symlinkSupport = Boolean.valueOf(System.getProperty("ERXFileNotificationCenter.symlinkSupport", "true"));
    }

    /**
     * When the file notification center is garbage collected it removes itself
     * as an observer from the
     * {@link com.webobjects.foundation.NSNotificationCenter NSNotificationCenter}.
     * Not doing this will cause exceptions.
     */
    @Override
    public void finalize() throws Throwable {
        NSNotificationCenter.defaultCenter().removeObserver(this);
        super.finalize();
    }

    /**
     * Used to register file observers for a particular file.
     * @param observer object to be notified when a file changes
     * @param selector selector to be invoked on the observer when
     *        the file changes.
     * @param filePath location of the file
     */
    public void addObserver(Object observer, NSSelector selector, String filePath) {
        if (filePath == null)
            throw new RuntimeException("Attempting to register observer for null filePath.");
        addObserver(observer, selector, new File(filePath));       
    }

    /**
     * Used to register file observers for a particular file.
     * @param observer object to be notified when a file changes
     * @param selector selector to be invoked on the observer when
     *        the file changes.
     * @param file file to watch for changes
     */
    public void addObserver(Object observer, NSSelector selector, File file) {
        if (file == null)
            throw new RuntimeException("Attempting to register a null file.");
        if (observer == null)
            throw new RuntimeException("Attempting to register null observer for file: " + file);
        if (selector == null)
            throw new RuntimeException("Attempting to register null selector for file: " + file);
        if (!developmentMode && checkFilesPeriod() == 0) {
            log.info("Registering an observer when file checking is disabled (WOCaching must be " +
                     "disabled or the er.extensions.ERXFileNotificationCenter.CheckFilesPeriod " +
                     "property must be set).  This observer will not ever by default be called: " + file);
        }
        String filePath = cacheKeyForFile(file);
        if (log.isDebugEnabled())
            log.debug("Registering Observer for file at path: " + filePath);
        // Register last modified date.
        registerLastModifiedDateForFile(file);
        // FIXME: This retains the observer.  This is not ideal.  With the 1.3 JDK we can use a ReferenceQueue to maintain weak references.
        NSMutableSet observerSet = (NSMutableSet)_observersByFilePath.objectForKey(filePath);
        if (observerSet == null) {
            observerSet = new NSMutableSet();
            _observersByFilePath.setObjectForKey(observerSet, filePath);
        }
        observerSet.addObject(new _ObserverSelectorHolder(observer, selector));
    }

    /**
     * Returns the path that should be used as the cache key for the given file. This
     * will return the absolute path of the file (specifically NOT the canonical path)
     * so that we make sure to lookup files using their original sym links rather than
     * resolving them at registration time.
     * 
     * @param file the file to lookup a cache key for
     * @return the absolute path of the file
     */
    protected String cacheKeyForFile(File file) {
      return file.getAbsolutePath();
    }
   
    /**
     * Returns the value to cache to detect changes to this file. Currently this returns
     * the lastModified date of the canonicalized version of this file, meaning that we
     * compare the lastModified of the target of symlinks.
     *  
     * @param file the file to lookup a cache value for
     * @return a value representing the current version of this file
     */
    protected Object cacheValueForFile(File file) {
      if (symlinkSupport) {
        try {
          // MS: We want to compute the last modified time on the destination of a (possibly)
          // symlinked file. On OS X, the lastModified of the sym link itself matches the
          // lastModified of the referenced file, but I didn't want to presume that behavior.
          File canonicalizedFile = file.getCanonicalFile();
          return canonicalizedFile.getPath() + ":" + Long.valueOf(canonicalizedFile.lastModified());
        }
        catch (IOException e) {
          // MS: return a zero to match the previous semantics from calling file.lastModified() on a missing file.
          ERXFileNotificationCenter.log.warn("Failed to determine the lastModified time on '" + file + "': " + e.getMessage());
          return Long.valueOf(0);
        }
      }
      return Long.valueOf(file.lastModified());
    }
   
    /**
     * Records the last modified date of the file for future comparison.
     * @param file file to record the last modified date
     */
    public void registerLastModifiedDateForFile(File file) {
        if (file != null) {
            // Note that if the file doesn't exist, it will be registered with a 0
            // lastModified time by virtue of the semantics of File.lastModified.
            _lastModifiedByFilePath.setObjectForKey(cacheValueForFile(file), cacheKeyForFile(file));
        }
    }

    /**
     * Compares the last modified date of the file with the last recorded modification date.
     * @param file file to compare last modified date.
     * @return if the file has changed since the last time the <code>lastModified</code> value
     *         was recorded.
     */
    public boolean hasFileChanged(File file) {
        if (file == null)
            throw new RuntimeException("Attempting to check if a null file has been changed");
        Object previousCacheValue = _lastModifiedByFilePath.objectForKey(cacheKeyForFile(file));
        return previousCacheValue == null || !previousCacheValue.equals(cacheValueForFile(file));
    }

    /**
     * Only used internally. Notifies all of the observers who have been registered for the
     * given file.
     * @param file file that has changed
     */
    protected void fileHasChanged(File file) {
        NSMutableSet observers = (NSMutableSet)_observersByFilePath.objectForKey(cacheKeyForFile(file));
        if (observers == null)
            log.warn("Unable to find observers for file: " + file);
        else {
            NSNotification notification = new NSNotification(FileDidChange, file);
            for (Enumeration e = observers.objectEnumerator(); e.hasMoreElements();) {
                _ObserverSelectorHolder holder = (_ObserverSelectorHolder)e.nextElement();
                try {
                    holder.selector.invoke(holder.observer, notification);
                } catch (Exception ex) {
                    log.error("Catching exception when invoking method on observer: " + ex.toString()+" - "+ERXUtilities.stackTrace(ex));
                }
            }
            registerLastModifiedDateForFile(file);           
        }
    }
   
    /**
     * Notified by the NSNotificationCenter at the end of every request-response
     * loop. It is here that all of the currently watched files are checked to
     * see if they have any changes.
     * @param n NSNotification notification posted from the NSNotificationCenter.
     */
    public void checkIfFilesHaveChanged(NSNotification n) {
        int checkPeriod = checkFilesPeriod();
       
        if (!developmentMode && (checkPeriod == 0 || System.currentTimeMillis() - lastCheckMillis < 1000 * checkPeriod)) {
            return;
        }
       
        lastCheckMillis = System.currentTimeMillis();
       
        if (log.isDebugEnabled()) log.debug("Checking if files have changed");
        for (Enumeration e = _lastModifiedByFilePath.keyEnumerator(); e.hasMoreElements();) {
            File file = new File((String)e.nextElement());
            if (file.exists() && hasFileChanged(file)) {
                fileHasChanged(file);
            }
        }
    }

    /**
     * Simple observer-selector holder class.
     */
    public static class _ObserverSelectorHolder {
        /** Observing object */
        // FIXME: Should be a weak reference
        public Object observer;
        /** Selector to call on observer */
        public NSSelector selector;
        /** Constructs a holder given an observer and a selector */
        public _ObserverSelectorHolder(Object obs, NSSelector sel) {
            observer = obs;
            selector = sel;           
        }

        @Override
        public int hashCode() {
          return (observer == null ? 1 : observer.hashCode()) * (selector == null ? 1 : selector.hashCode());
        }

        /**
         * Overridden to return true if the object being compared has the same observer-selector pair.
         * @param osh object to be compared
         * @return result of comparison
         */
        @Override
        public boolean equals(Object osh) {
            return osh != null && osh instanceof _ObserverSelectorHolder && ((_ObserverSelectorHolder)osh).selector.equals(selector) &&
            ((_ObserverSelectorHolder)osh).observer.equals(observer);
        }
    }
}
TOP

Related Classes of er.extensions.foundation.ERXFileNotificationCenter$_ObserverSelectorHolder

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.