Mega Code Archive

 
Categories / Java / Development Class
 

A java util Properties class that will check a file or URL for changes periodically

/*  * RefreshingProperties.java  *  * Created on November 11, 2005, 10:15 PM  *  * This library is free software; you can redistribute it and/or  * modify it under the terms of the GNU Lesser General Public  * License as published by the Free Software Foundation; either  * version 2.1 of the License, or (at your option) any later version.  *  * This library is distributed in the hope that it will be useful,  * but WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  * Lesser General Public License for more details.  *  * You should have received a copy of the GNU Lesser General Public  * License along with this library; if not, write to the Free Software  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA  */ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.Properties; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; /**  * This is a java.util.Properties class that will check a file or URL for changes  * periodically. It has a threaded and non-threaded mode, and will reload a URL  * every recheck time, or inspect the last modified date on a file on check.  * @author <a href="mailto:cooper@screaming-penguin.com">Robert "kebernet" Cooper</a>  * @version $Revision: 1.4 $  */ public class RefreshingProperties extends Properties {     /**      * DOCUMENT ME!      */     private static final Logger LOG = Logger.getLogger( RefreshingProperties.class.getCanonicalName() );               /**      * DOCUMENT ME!      */     private ArrayList<RefreshingProperties.RefreshListener> listeners = new ArrayList<RefreshingProperties.RefreshListener>();          /**      * DOCUMENT ME!      */     private Thread updater;          /**      * DOCUMENT ME!      */     private URL url;          /**      * DOCUMENT ME!      */     private long lastCheck;          /**      * DOCUMENT ME!      */     private long recheckTime = 5 * 60 * 1000;          private boolean loading = false;          private ArrayList<RefreshingProperties> augmentProps = new ArrayList<RefreshingProperties>();     private ArrayList<RefreshingProperties> overrideProps = new ArrayList<RefreshingProperties>();          private boolean noImportMode = false;          /**      * Creates a new RefreshingProperties object.      * This constructor will use the default settings of threaded mode and recheck at 5 minutes.      * @param url URL to read from.      * @throws IOException Thrown on read errors.      */     public RefreshingProperties(URL url) throws IOException {         init(url,recheckTime,true);     }          /**      * Creates a new RefreshingProperties object.      * This will use the default recheck at 5 minutes.      * @param url URL to read from      * @param useThread Indicates whether the check should run in threaded or non-threaded road.      * @throws IOException Thrown on read errors.      */     public RefreshingProperties(URL url,boolean useThread) throws IOException {         init(url,recheckTime,useThread);     }          /**      * Creates a new RefreshingProperties object.      * Uses the default threaded mode.      * @param recheckTime number of milliseconds between rechecks      * @param url URL to load from      * @throws IOException Thrown on read errors.      */     public RefreshingProperties(URL url,long recheckTime) throws IOException {         init(url,recheckTime,true);     }          /**      * Creates a new RefreshingProperties object.      * @param url URL to read from      * @param recheckTime recheck time in milliseconds      * @param useThread Whether the recheck should be threaded or unthreaded.      * @throws IOException Thrown on read errors.      */     public RefreshingProperties(URL url,long recheckTime,boolean useThread) throws IOException {         init(url,recheckTime,useThread);     }          /**      * Calls the <tt>Hashtable</tt> method <code>put</code>. Provided for      * parallelism with the <tt>getProperty</tt> method. Enforces use of      * strings for property keys and values. The value returned is the      * result of the <tt>Hashtable</tt> call to <code>put</code>.      *      * @param key the key to be placed into this property list.      * @param value the value corresponding to <tt>key</tt>.      * @return     the previous value of the specified key in this property      *             list, or <code>null</code> if it did not have one.      * @see #getProperty      * @since    1.2      */     public Object setProperty(String key,String value) {         Object retValue;         threadCheck();         retValue = super.setProperty(key,value);                  return retValue;     }          /**      * Searches for the property with the specified key in this property list.      * If the key is not found in this property list, the default property list,      * and its defaults, recursively, are then checked. The method returns      * <code>null</code> if the property is not found.      *      * @param   key   the property key.      * @return  the value in this property list with the specified key value.      * @see     #setProperty      * @see     #defaults      */     public String getProperty(String key) {         threadCheck();                  String retValue;         retValue = super.getProperty(key);                  return retValue;     }          /**      * Searches for the property with the specified key in this property list.      * If the key is not found in this property list, the default property list,      * and its defaults, recursively, are then checked. The method returns the      * default value argument if the property is not found.      *      * @param   key            the hashtable key.      * @param   defaultValue   a default value.      *      * @return  the value in this property list with the specified key value.      * @see     #setProperty      * @see     #defaults      */     public String getProperty(String key,String defaultValue) {         String retValue;         threadCheck();         retValue = super.getProperty(key,defaultValue);                  return retValue;     }          /**      * DOCUMENT ME!      *      * @param listener DOCUMENT ME!      */     public void addRefreshListener(RefreshingProperties.RefreshListener listener) {         this.listeners.add(listener);     }          /**      * Creates a shallow copy of this hashtable. All the structure of the      * hashtable itself is copied, but the keys and values are not cloned.      * This is a relatively expensive operation.      *      * @return  a clone of the hashtable.      */     public Object clone() {         Object retValue;         threadCheck();         retValue = super.clone();                  return retValue;     }          /**      * Tests if some key maps into the specified value in this hashtable.      * This operation is more expensive than the <code>containsKey</code>      * method.<p>      *      * Note that this method is identical in functionality to containsValue,      * (which is part of the Map interface in the collections framework).      *      * @return <code>true</code> if and only if some key maps to the      *             <code>value</code> argument in this hashtable as      *             determined by the <tt>equals</tt> method;      *             <code>false</code> otherwise.      * @see #containsKey(Object)      * @see #containsValue(Object)      * @see Map      * @param value a value to search for.      */     public boolean contains(Object value) {         threadCheck();                  boolean retValue;                  retValue = super.contains(value);                  return retValue;     }          /**      * Tests if the specified object is a key in this hashtable.      *      * @return <code>true</code> if and only if the specified object      *          is a key in this hashtable, as determined by the      *          <tt>equals</tt> method; <code>false</code> otherwise.      * @see #contains(Object)      * @param key possible key.      */     public boolean containsKey(Object key) {         boolean retValue;         threadCheck();         retValue = super.containsKey(key);                  return retValue;     }          /**      * Returns true if this Hashtable maps one or more keys to this value.<p>      *      * Note that this method is identical in functionality to contains      * (which predates the Map interface).      *      * @return <tt>true</tt> if this map maps one or more keys to the      *         specified value.      * @see Map      * @since 1.2      * @param value value whose presence in this Hashtable is to be tested.      */     public boolean containsValue(Object value) {         boolean retValue;         threadCheck();         retValue = super.containsValue(value);                  return retValue;     }          /**      * Returns an enumeration of the values in this hashtable.      * Use the Enumeration methods on the returned object to fetch the elements      * sequentially.      *      * @return  an enumeration of the values in this hashtable.      * @see     java.util.Enumeration      * @see     #keys()      * @see        #values()      * @see        Map      */     public java.util.Enumeration<Object> elements() {         java.util.Enumeration retValue;         threadCheck();         retValue = super.elements();                  return retValue;     }          /**      * Returns a Set view of the entries contained in this Hashtable.      * Each element in this collection is a Map.Entry.  The Set is      * backed by the Hashtable, so changes to the Hashtable are reflected in      * the Set, and vice-versa.  The Set supports element removal      * (which removes the corresponding entry from the Hashtable),      * but not element addition.      *      * @return a set view of the mappings contained in this map.      * @see   Map.Entry      * @since 1.2      */     public java.util.Set<java.util.Map.Entry<Object,Object>> entrySet() {         java.util.Set retValue;         threadCheck();         retValue = super.entrySet();                  return retValue;     }          /**      * Returns the value to which the specified key is mapped in this hashtable.      *      * @return the value to which the key is mapped in this hashtable;      *          <code>null</code> if the key is not mapped to any value in      *          this hashtable.      * @see #put(Object, Object)      * @param key a key in the hashtable.      */     public Object get(Object key) {         threadCheck();                  Object retValue;         for( RefreshingProperties over : this.overrideProps ){             Object overValue = over.get(key);             if( overValue != null){                 return overValue;             }         }         retValue = super.get(key);         if( retValue == null ){             for( RefreshingProperties aug : this.augmentProps ){                 Object augValue = aug.get( key );                 if( augValue != null ){                     retValue = augValue;                     break;                 }             }         }         return retValue;     }          /**      * Returns a Set view of the keys contained in this Hashtable.  The Set      * is backed by the Hashtable, so changes to the Hashtable are reflected      * in the Set, and vice-versa.  The Set supports element removal      * (which removes the corresponding entry from the Hashtable), but not      * element addition.      *      * @return a set view of the keys contained in this map.      * @since 1.2      */     public java.util.Set<Object> keySet() {         java.util.Set retValue;         threadCheck();         retValue = super.keySet();         for( RefreshingProperties props : this.augmentProps ){             retValue.addAll( props.keySet() );         }         for( RefreshingProperties props : this.overrideProps ){             retValue.addAll( props.keySet() );         }         return retValue;     }          /**      * Returns an enumeration of the keys in this hashtable.      *      * @return  an enumeration of the keys in this hashtable.      * @see     Enumeration      * @see     #elements()      * @see        #keySet()      * @see        Map      */     public java.util.Enumeration<Object> keys() {         java.util.Enumeration retValue;         threadCheck();         retValue = (new Vector( this.keySet() )).elements();                 return retValue;     }          /**      * Returns an enumeration of all the keys in this property list,      * including distinct keys in the default property list if a key      * of the same name has not already been found from the main      * properties list.      *      * @return  an enumeration of all the keys in this property list, including      *          the keys in the default property list.      * @see     java.util.Enumeration      * @see     java.util.Properties#defaults      */     public java.util.Enumeration<Object> propertyNames() {         java.util.Enumeration retValue;         threadCheck();         retValue = super.propertyNames();                  return retValue;     }          /**      * Maps the specified <code>key</code> to the specified      * <code>value</code> in this hashtable. Neither the key nor the      * value can be <code>null</code>. <p>      *      * The value can be retrieved by calling the <code>get</code> method      * with a key that is equal to the original key.      *      * @return the previous value of the specified key in this hashtable,      *             or <code>null</code> if it did not have one.      * @see Object#equals(Object)      * @see #get(Object)      * @param key the hashtable key.      * @param value the value.      */     public Object put(Object key,Object value) {         threadCheck();         if( !this.noImportMode && key instanceof String && ((String) key ).startsWith("@import.") ){             String keyString = ((String) key );             String importType = keyString.substring( 8, keyString.lastIndexOf(".") );             ImportRefreshListener irl = null;                          if( importType.equals("override") ){                 irl = new ImportRefreshListener( this, true );             } else if( importType.equals("augment") ) {                 irl = new ImportRefreshListener( this, false );             } else {                 throw new RuntimeException("Import type: "+importType+" unknown.");             }             try{                 boolean useThread = (this.updater != null );                 RefreshingProperties importedProp = new RefreshingProperties( new URL( this.url, (String) value ), this.recheckTime, useThread );                 if( irl.clobber ){                     this.overrideProps.add( importedProp );                 } else {                     this.augmentProps.add( importedProp );                 }                 importedProp.addRefreshListener( irl );                 this.importLoad( importedProp, irl.clobber );             } catch(Exception e){                 throw new RuntimeException("Exception creaing child properties", e);             }         }         return super.put(key,value);     }          /**      * Copies all of the mappings from the specified Map to this Hashtable      * These mappings will replace any mappings that this Hashtable had for any      * of the keys currently in the specified Map.      *      * @since 1.2      * @param t Mappings to be stored in this map.      */     public void putAll(java.util.Map t) {         threadCheck();         super.putAll(t);     }          /**      * Removes the key (and its corresponding value) from this      * hashtable. This method does nothing if the key is not in the hashtable.      *      * @return the value to which the key had been mapped in this hashtable,      *          or <code>null</code> if the key did not have a mapping.      * @param key the key that needs to be removed.      */     public Object remove(Object key) {         threadCheck();                  Object retValue;                  retValue = super.remove(key);                  return retValue;     }          /**      * DOCUMENT ME!      *      * @param listener DOCUMENT ME!      */     public void removeRefreshListener(RefreshingProperties.RefreshListener listener) {         this.listeners.remove(listener);     }          /**      * Returns a Collection view of the values contained in this Hashtable.      * The Collection is backed by the Hashtable, so changes to the Hashtable      * are reflected in the Collection, and vice-versa.  The Collection      * supports element removal (which removes the corresponding entry from      * the Hashtable), but not element addition.      *      * @return a collection view of the values contained in this map.      * @since 1.2      */     public java.util.Collection<Object> values() {         java.util.Collection retValue;         threadCheck();         ArrayList values = new ArrayList();         for( Object key : this.keySet() ){             values.add( this.get(key ) );         }                  return values;     }          /**      * DOCUMENT ME!      */     private void check() {         try {             if(this.url.getProtocol().equals("file") ) {                 File f = new File(this.url.getFile());                 if( f.lastModified() > this.lastCheck ){                     this.load();                 }             } else if( !this.url.getProtocol().equals("file") && System.currentTimeMillis() - this.lastCheck > this.recheckTime ){                 this.load();             }             this.lastCheck = System.currentTimeMillis();         } catch(IOException e) {             RefreshingProperties.LOG.log(Level.WARNING,"Exception reloading properies.",e);         }              }          private void importLoad( RefreshingProperties source, boolean clobber ){         Enumeration keys = source.keys();         while( keys.hasMoreElements() ){             String key = (String) keys.nextElement();             if( clobber || this.getProperty( key ) == null )                 this.put( key, source.getProperty( key ) );         }         this.fireEvents();              }               /**      * DOCUMENT ME!      *      * @param url DOCUMENT ME!      * @param recheckTime DOCUMENT ME!      * @param useThread DOCUMENT ME!      *      * @throws IOException DOCUMENT ME!      */     private void init(URL url,long recheckTime,boolean useThread) throws IOException {         this.url = url;         this.recheckTime = recheckTime;         if(useThread) {             this.updater = new UpdateThread(this);             this.updater.start();         }                  this.check();     }          /**      * DOCUMENT ME!      *      * @throws IOException DOCUMENT ME!      */     private void load() throws IOException {         this.loading = true;         InputStream is = null;         super.clear();         is = this.url.openStream();         super.load(is);         is.close();         RefreshingProperties.LOG.log(Level.FINEST,"Loading of " + this.url + " at " + new Date());                  this.fireEvents();                  this.loading = false;     }          private void fireEvents(){         RefreshingProperties.ReloadEvent event = new ReloadEvent(this, this.url,System.currentTimeMillis());                  for(RefreshingProperties.RefreshListener listener : this.listeners) {             listener.propertiesRefreshNotify(event);         }     }          /**      * DOCUMENT ME!      */     private void threadCheck() {         if(!this.loading && this.updater == null) {             check();         }     }          /**      * DOCUMENT ME!      *      * @author $author$      * @version $Revision: 1.4 $      */     public static interface RefreshListener {         /**          * DOCUMENT ME!          *          * @param event DOCUMENT ME!          */         public void propertiesRefreshNotify(RefreshingProperties.ReloadEvent event);     }          /**      * DOCUMENT ME!      *      * @author $author$      * @version $Revision: 1.4 $      */     public static class ReloadEvent {         /**          * DOCUMENT ME!          */         private URL url;                  /**          * DOCUMENT ME!          */         private long time;                  private RefreshingProperties source;         /**          * Creates a new ReloadEvent object.          *          * @param url DOCUMENT ME!          * @param time DOCUMENT ME!          */         public ReloadEvent(RefreshingProperties source, URL url,long time) {             this.setSource(source);             this.url = url;             this.time = time;         }                  /**          * DOCUMENT ME!          *          * @param time DOCUMENT ME!          */         public void setTime(long time) {             this.time = time;         }                  /**          * DOCUMENT ME!          *          * @return DOCUMENT ME!          */         public long getTime() {             return time;         }                  /**          * DOCUMENT ME!          *          * @param url DOCUMENT ME!          */         public void setUrl(URL url) {             this.url = url;         }                  /**          * DOCUMENT ME!          *          * @return DOCUMENT ME!          */         public URL getUrl() {             return url;         }                  public RefreshingProperties getSource() {             return source;         }                  public void setSource(RefreshingProperties source) {             this.source = source;         }     }          /**      * DOCUMENT ME!      *      * @author $author$      * @version $Revision: 1.4 $      */     private class UpdateThread extends Thread {         /**          * DOCUMENT ME!          */         private RefreshingProperties props;                  /**          * Creates a new UpdateThread object.          *          * @param props DOCUMENT ME!          */         UpdateThread(RefreshingProperties props) {             this.setDaemon(true);             this.props = props;         }                  /**          * DOCUMENT ME!          */         public void run() {             boolean running = true;                          while(running) {                 props.LOG.log(Level.FINEST,"RefeshingProperties thread check of " + props.url + " at " + new Date());                                  try {                     Thread.sleep(props.recheckTime);                 } catch(InterruptedException e) {                     RefreshingProperties.LOG.log(Level.WARNING,"Interrupted.",e);                 }                                  props.check();             }         }     }          private class ImportRefreshListener implements RefreshListener {         private RefreshingProperties target;         private boolean clobber;         ImportRefreshListener( RefreshingProperties target, boolean clobber ){             this.target = target;             this.clobber = clobber;         }                  public void propertiesRefreshNotify(ReloadEvent event) {             target.fireEvents();         }     } }