All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBusx.io.EFileWatcher Maven / Gradle / Ivy

//
// Copyright 2013, 2014, 2019 Charles W. Rapp
//
// Licensed 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 net.sf.eBusx.io;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.eBus.client.EFeed;
import net.sf.eBus.client.EFeed.FeedScope;
import net.sf.eBus.client.EFeedState;
import net.sf.eBus.client.EPublishFeed;
import net.sf.eBus.client.EPublisher;
import net.sf.eBus.client.ERequestFeed;
import net.sf.eBus.client.ERequestFeed.ERequest;
import net.sf.eBus.client.ERequestor;
import net.sf.eBus.client.IEPublishFeed;
import net.sf.eBus.client.IERequestFeed;
import net.sf.eBus.messages.EMessageKey;
import net.sf.eBus.messages.EReplyMessage;
import net.sf.eBus.messages.InvalidMessageException;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBusx.util.ETimer;
import net.sf.eBusx.util.TimerRequest;

/**
 * This class provides an eBus file watcher service, allowing
 * asynchronous notification when a file or directory is created,
 * modified or deleted. This service only supports JVM-local
 * subscribers. This publisher advertises the
 * {@link EFileNotification} with the file name as subject. The
 * subject pattern is ".+" which supports any file name.
 * 

* The file watcher service starts its timer task with the first * subscriber and stops the timer task when there are no more * subscribers. This means the timer is running only when there * are watched files. * * @author Charles Rapp */ public final class EFileWatcher implements EPublisher, ERequestor { //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * The default watch rate is one minute. Value is in * milliseconds. */ public static final long DEFAULT_WATCH_RATE = 60000L; /** * The minimum watch rate is one half a second. Value is in * milliseconds. */ public static final long MIN_WATCH_RATE = 500L; /** * The maximum watch rate is one hour. Value is in * milliseconds. */ public static final long MAX_WATCH_RATE = 3600000L; /** * The thread name is "EFileWatcher". */ public static final String TIMER_NAME = "EFileWatcher"; //----------------------------------------------------------- // Statics. // /** * Maps the subject to its file watcher instance. */ private static final Map sWatchers = new HashMap<>(); /** * The singleton lock protecting access to * {@link #sWatchers}. */ private static final Lock sLock = new ReentrantLock(); /** * Logging subsystem interface. */ private static final Logger sLogger = Logger.getLogger(EFileWatcher.class.getName()); // Static initialization block. static { ETimer.startETimer(TIMER_NAME); try { DataType.findType(EFileNotification.class); } catch (IllegalArgumentException | InvalidMessageException jex) { if (sLogger.isLoggable(Level.WARNING)) { sLogger.log(Level.WARNING, "Cannot file EFileNotification", jex); } } } // end of static initialization block. //----------------------------------------------------------- // Locals. // /** * Watch this file to see if it is created, modified, or * deleted. */ private final File mFile; /** * Message key based on the the {@link EFileNotification} * message class and {@link #mFile} absolute name. */ private final EMessageKey mKey; /** * Check for file changes at this millisecond rate. */ private final long mWatchRate; /** * Set to {@code true} if {@link #mFile} exists and * {@code false} otherwise. */ private boolean mExistsFlag; /** * {@link #mFile} current last modify time. */ private long mModifyTime; /** * {@link #mFile} current size. */ private long mFileSize; /** * {@link ETimer} request feed. */ private ERequestFeed sTimerFeed; /** * The file watch timer request. */ private volatile ERequest mTimerTask; /** * The {@link EPublishFeed file watcher publisher feed}. */ private EPublishFeed mFeed; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new EFileWatcher instance for the given file or * directory name and watch rate. * @param file the file or directory to watch. * @param key message key based on {@code file}. * @param rate watch for changes at this millisecond rate. */ private EFileWatcher(final File file, final EMessageKey key, final long rate) { mFile = file; mKey = key; mWatchRate = rate; } // end of EFileWatcher(String, long) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // EObject Interface Implementation. // /** * Returns absolute file name as the eBus object name. * @return eBus object name. */ @Override public String name() { return (mKey.subject()); } // end of name() /** * Advertises the file watcher service to the local JVM only. * The timer task is started with the first subscriber and * stopped when there are no more subscribers. */ @Override public void startup() { if (sLogger.isLoggable(Level.FINE)) { sLogger.fine( String.format( "EFileWatcher: advertising %s.", mKey)); } // Advertise this service - but only within // this JVM. Do not send it to remote eBus apps. mFeed = (EPublishFeed.builder()).target(this) .messageKey(mKey) .scope(FeedScope.LOCAL_ONLY) .build(); mFeed.advertise(); // Subscribe to the ETimer request feed. // Note: the timer task is started when the first watch // entry is created. sTimerFeed = (ERequestFeed.builder()).target(this) .messageKey(ETimer.TIMER_KEY) .scope(FeedScope.LOCAL_ONLY) .build(); sTimerFeed.subscribe(); } // end of startup() /** * Shuts down the file watcher thread by stopping the watch * timer task, retracting the notification advertisement, and * clearing the file watchers collection. */ @Override public void shutdown() { // Firstly, stop the timer task. if (mTimerTask != null) { mTimerTask.close(); mTimerTask = null; } // Secondly, stop the timer feed. if (sTimerFeed != null) { sTimerFeed.close(); sTimerFeed = null; } // Thirdly, retract the file watch ad. if (mFeed != null) { mFeed.close(); mFeed = null; } // Thirdly, clear the watcher map. sWatchers.clear(); } // end of shutdown() // // end of EObject Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // EPublisher Interface Implementation. // /** * Starts or stops a file watcher entry depending on * {@code upFlag}. The notification subject is the file * name. * @param fs the f state is either up or down. * @param f the f state applies to this publish f. */ @Override public void publishStatus(final EFeedState fs, final IEPublishFeed f) { if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( String.format( "EFileWatcher: %s watch is %s.", mFile.getAbsolutePath(), fs)); } // Is this f being started? if (fs == EFeedState.UP) { // Yes, start the timer task. if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( "EFileWatcher: starting timer task."); } mTimerTask = sTimerFeed.request( TimerRequest.builder() .timerName(mKey.toString()) .delay(mWatchRate) .period(mWatchRate) .build()); } // No, the feed is being stopped. else if (mTimerTask != null) { // Stop the timer task. if (sLogger.isLoggable(Level.FINER)) { sLogger.finer( "EFileWatcher: stopping timer task."); } mTimerTask.close(); mTimerTask = null; } f.updateFeedState(fs); } // end of publishStatus(EFeedState, IEPublishFeed) // // end of EPublisher Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // ERequestor Interface Implementation. // /** * Logs the {@link ETimer} feed state. * @param feedState the new eBus timer feed state. * @param feed the eBus timer request feed. */ @Override public void feedStatus(final EFeedState feedState, final IERequestFeed feed) { if (sLogger.isLoggable(Level.FINE)) { sLogger.log(Level.FINE, "{0} is {1}.", new Object[] { ETimer.TIMER_KEY, feedState }); } } // end of feedStatus(EFeedState, ERequestFeed) /** * Checks if any of the subscribed files have changed since * the last timeout. * @param remaining number of replies remaining in this * request. Should be infinite. * @param reply the timer reply message. * @param request the timer request. */ @Override public void reply(final int remaining, final EReplyMessage reply, final ERequest request) { final boolean existsFlag = mFile.exists(); final long modifyTime= mFile.lastModified(); final long fileSize = mFile.length(); EFileNotification.EventType eventType = null; if (sLogger.isLoggable(Level.FINEST)) { sLogger.finest( String.format( "%s: checking for changes.", mFile.getPath())); } // Is this watcher still running? if (mFeed == null) { // No, so do nothing and leave eventType null. } // Has the file been deleted? else if (mExistsFlag && !existsFlag) { // Yes, the file is now gone. eventType = EFileNotification.EventType.DELETE; } // No, not deleted. // Has the file been created? else if (!mExistsFlag && existsFlag) { // Yes, the file now exists. eventType = EFileNotification.EventType.CREATE; } // No, the file was and still is in existance. // Has the file been modified? // Note: while the file size may be > or < // the previous size, the modify time may only be // > previous time. else if (modifyTime > mModifyTime || fileSize != mFileSize) { // Yes, the file was modified. eventType = EFileNotification.EventType.MODIFY; } // Did the file change in any way? // Is the feed still up? if (eventType != null && mFeed.isFeedUp()) { // Yes, update the data members and tell the // subscribers. mExistsFlag = true; mModifyTime = modifyTime; mFileSize = fileSize; mFeed.publish( EFileNotification.builder() .subject((mFeed.key()).subject()) .file(mFile) .eventType(eventType) .timestamp(mModifyTime) .length(fileSize) .build()); } } // end of reply(int, EReplyMessage, ERequest) // // end of ERequestor Interface Implementation. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * Returns {@code true} if the file watcher service is * running for the given subject and {@code false} otherwise. * @param file check if this file or directory has a watcher. * @return {@code true} if the file watcher service is * running. */ public static boolean isFileWatcherRunning( final File file) { boolean retcode; sLock.lock(); try { retcode = sWatchers.containsKey(file); } finally { sLock.unlock(); } return (retcode); } // end of isFileWatcherRunning(File) // // end of Get Methods. //----------------------------------------------------------- /** * Creates the eBus file watcher service using the * {@link #DEFAULT_WATCH_RATE default watch rate}. * @param file watch this given file or directory. * @throws IllegalStateException * if the file watcher service is already running. */ public static void startFileWatcher(final File file) { startFileWatcher(file, DEFAULT_WATCH_RATE); } // end of startFileWatcher(File) /** * Creates an eBus file watcher service for the specified * file or directory and checking at a given watch rate. * Returns the {@link EMessageKey message key} for this * watcher. Use the returned key to * {@link net.sf.eBus.client.ESubscribeFeed#open(net.sf.eBus.client.ESubscriber, net.sf.eBus.messages.EMessageKey, net.sf.eBus.client.EFeed.FeedScope, net.sf.eBus.client.ECondition) open} * a subscription to the newly created file watcher. * @param file watch this given file or directory. * @param rate check for file updates at this millisecond * rate. * @return the file watcher message key used to subscribe to * this file watcher. * @throws IllegalArgumentException * If {@code file} is {@code null} or if {@code rate} is < * {@link #MIN_WATCH_RATE} or > {@link #MAX_WATCH_RATE}. * @throws IllegalStateException * if the file watcher service is already running but at a * different watch rate. */ public static EMessageKey startFileWatcher(final File file, final long rate) { EMessageKey retval = null; if (file == null) { throw (new IllegalArgumentException("file is null")); } else if (rate < MIN_WATCH_RATE || rate > MAX_WATCH_RATE) { throw ( new IllegalArgumentException( String.format( "invalid watchRate %,d", rate))); } sLock.lock(); try { EFileWatcher watcher = sWatchers.get(file); // Is this watcher already in existence? if (watcher != null) { // Yes. At the same rate? if (watcher.mWatchRate != rate) { // No. throw ( new IllegalStateException( "file watcher already started")); } // Ignore this redundant file watcher. else { retval = watcher.mKey; } } else { if (sLogger.isLoggable(Level.INFO)) { sLogger.info( String.format( "EFileWatcher: starting with %,d millisecond watch rate.", rate)); } // Create the singleton and connect it to the // timer. try { retval = new EMessageKey(EFileNotification.class, file.getAbsolutePath()); watcher = new EFileWatcher(file, retval, rate); sWatchers.put(file, watcher); EFeed.register(watcher); EFeed.startup(watcher); } catch (IllegalArgumentException jex) { // If the start up fails, then re-throws the // exception inside an illegal state. throw ( new IllegalStateException( "file watcher startup failed", jex)); } } } finally { sLock.unlock(); } return (retval); } // end of startFileWatcher(File, long) /** * Stops the watcher thread. This retracts the * {@link EFileNotification} advertisement and discards all * subscribed file watchers. * @param file the file or directory. Must be the * same as passed to {@link #startFileWatcher(File)} * * @see #startFileWatcher(File) * @see #startFileWatcher(File, long) */ public static void stopFileWatcher(final File file) { sLock.lock(); try { final EFileWatcher watcher = sWatchers.remove(file); if (watcher != null) { if (sLogger.isLoggable(Level.INFO)) { sLogger.info("EFileWatcher: stopping."); } watcher.shutdown(); } } finally { sLock.unlock(); } } // end of stopFileWatcher(File) /** * Returns the {@code EFileWatcher} message key for the given * file or directory. * @param file generate the message key for this file. * @return file watcher message key for the given file. */ public static EMessageKey key(final File file) { if (file == null) { throw (new IllegalArgumentException("file is null")); } return (new EMessageKey(EFileNotification.class, file.getAbsolutePath())); } // end of key(File) } // end of class EFileWatcher





© 2015 - 2024 Weber Informatics LLC | Privacy Policy