/* * 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.ode.utils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.File; import java.util.Map; import java.util.HashMap; import java.util.Collection; import java.util.List; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * This class is based on {@link org.apache.log4j.helpers.FileWatchdog}.<p/> * Modifications have been made to support additional abstract ressource and more events (creation, deletion and updates), and to allow "manual" * invocations of {@link #check()} (i.e wihtout having to use a thread) while preserving time checking.<p/> * Now two use cases coexist: * <ol> * <li>Pass an instance of {@link WatchDog} to a new thread ({@link WatchDog} is a {@link Runnable}). * So that {@link WatchDog# check ()} will be called automatically every {@code delay} milliseconds.</li> * <li>Invoke {@link WatchDog# check ()} only when you feel like it. If the expiration date previously set is lower than NOW then event * callback methods will be invoked accordingly.</li> * </ol> * * @author <a href="mailto:midon@intalio.com">Alexis Midon</a> */ public class WatchDog<T, C extends WatchDog.Observer> implements Runnable { static final public long DEFAULT_DELAY = 30000; final Log log = LogFactory.getLog(WatchDog.class); private long expire; private T lastModif; private long delay = DEFAULT_DELAY; private boolean existedBefore, warnedAlready, interrupted; protected Mutable<T> mutable; protected C observer; public WatchDog() { } /** * @param mutable the object to watch closely * @param delay between two checks */ public WatchDog(Mutable<T> mutable, long delay) { this(mutable); this.delay = delay; } public WatchDog(Mutable<T> mutable, C observer) { this.mutable = mutable; this.observer = observer; } /** * @see #WatchDog(org.apache.ode.utils.WatchDog.Mutable, long) */ public WatchDog(Mutable<T> mutable) { this.mutable = mutable; } public Mutable<T> getMutable() { return mutable; } public C getObserver() { return observer; } public long getDelay() { return delay; } public void setDelay(long delay) { this.delay = delay; } public void run() { try { while (!interrupted) { try { Thread.sleep(delay); } catch (InterruptedException e) { // no interruption expected } check(); } } catch (Exception e) { log.warn("Exception occured. Thread will stop", e); } } public final void check() { long now = System.currentTimeMillis(); if (expire <= now) { /* get a lock on the observer right now It would be overkilled to lock before testing the dates. By locking after the comparaison the worst scenario is that 2 threads get in the "if", thread A gets the lock, thread B waits for it. Once the lock is released, thread B acquires it and do the checks on the mutable. So this scenario is *harmless*. */ observer.getLock().lock(); expire = now + delay; try { if (log.isDebugEnabled()) log.debug("[" + mutable + "]" + " check for changes"); if (mutable.exists()) { existedBefore = true; if (lastModif == null || mutable.hasChangedSince(lastModif)) { lastModif = mutable.lastModified(); observer.onUpdate(); if (log.isInfoEnabled()) log.info("[" + mutable + "]" + " updated"); warnedAlready = false; } else { if (log.isDebugEnabled()) log.debug("[" + mutable + "]" + " has not changed"); } } else if (!observer.isInitialized()) { // no resource and first time observer.init(); if (log.isInfoEnabled()) log.info("[" + mutable + "]" + " initialized"); } else { if (existedBefore) { existedBefore = false; lastModif = null; observer.onDelete(); if (log.isInfoEnabled()) log.info("[" + mutable + "]" + " deleted"); } if (!warnedAlready) { warnedAlready = true; if (log.isInfoEnabled()) log.info("[" + mutable + "]" + " does not exist."); } } }catch(Exception e){ if (log.isDebugEnabled()) log.debug("[" + mutable + "]" + " exception occurred during check.", e); // reset so that the next check retries right away expire = 0; lastModif = null; existedBefore = false; warnedAlready = false; observer.reset(); if (log.isInfoEnabled()) log.info("[" + mutable + "] resetted."); throw new RuntimeException(e); } finally { observer.getLock().unlock(); } } else { if (log.isTraceEnabled()) log.trace("[" + mutable + "]" + " wait period is not over"); } } public static <C extends Observer> WatchDog<Long, C> watchFile(File file, C handler) { return new WatchDog<Long, C>(new FileMutable(file), handler); } public static <C extends Observer> WatchDog<Map<File, Long>, C> watchFiles(List<File> files, C handler) { return new WatchDog<Map<File, Long>, C>(new FileSetMutable(files), handler); } /** * have you said that duck typing would be nice? */ public interface Mutable<T> { boolean exists(); boolean hasChangedSince(T since); T lastModified(); } static public class FileMutable implements WatchDog.Mutable<Long> { File file; public FileMutable(File file) { this.file = file; } public boolean exists() { return file.exists(); } public boolean hasChangedSince(Long since) { // do use 'greater than' to handle file deletion. The timestamp of a non-exising file is 0L. return lastModified().longValue() != since.longValue(); } public Long lastModified() { return Long.valueOf(file.lastModified()); } public String toString() { return file.toString(); } } static public class FileSetMutable implements WatchDog.Mutable<Map<File, Long>> { File[] files; public FileSetMutable(Collection<File> files) { this.files = new File[files.size()]; files.toArray(this.files); } public FileSetMutable(File[] files) { this.files = files; } public boolean exists() { return true; } public boolean hasChangedSince(Map<File, Long> since) { Map<File, Long> snapshot = lastModified(); return !CollectionUtils.equals(snapshot, since); } public Map<File, Long> lastModified() { Map<File, Long> m = new HashMap<File, Long>(files.length * 15 / 10); for (File f : files) m.put(f, Long.valueOf(f.lastModified())); return m; } } public interface Observer<A> { boolean isInitialized(); /** * Called by {@link WatchDog#check()} if the underlying object is not {@link #isInitialized initialized} and the {@link WatchDog.Mutable#exists()} resource does not exist}. * <br/> This method might called to reset the underlying object. * * @throws Exception */ void init(); void reset(); /** * Called only if the resource previously existed and now does not exist. * <br/>The default implementation invokes {@link #init()} . * * @throws Exception */ void onDelete(); /** * Called only if the resource previously existed but the {@link WatchDog.Mutable#lastModified()} timestamp has changed (greater than the previous value). * <br/>The default implementation invokes {@link #init()} . * * @throws Exception */ void onUpdate(); Lock getLock(); A get(); } /** * A default implementation of #ChangeHandler. Delete and Update will both invoke the #init method which satifies most use cases. * So subclasses may simply override the #init method to fit their own needs. */ public static class DefaultObserver<A> implements Observer<A> { protected final ReadWriteLock lock = new ReentrantReadWriteLock(); protected A object; /** * @return true if the wrapped if not null */ public boolean isInitialized() { return object != null; } /** * empty implementation */ public void init() { } public void reset() { object = null; } /** * delegate to #init */ public void onDelete() { init(); } /** * delegate to #init */ public void onUpdate() { init(); } public Lock getLock() { return lock.writeLock(); } public A get() { lock.readLock().lock(); try { return object; } finally { lock.readLock().unlock(); } } } }