A java.util.Properties class that will check a file or URL for changes periodically : Properties « Development Class « Java






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();
        }
    }
}

   
    
    
    
    
    
  








Related examples in the same category

1.Read a set of properties from the received input stream, strip off any excess white space that exists in those property values,
2.Copy a set of properties from one Property to another.
3.Sorts property list and print out each key=value pair prepended with specific indentation.
4.Sorts a property list and turns the sorted list into a string.
5.A utility class for replacing properties in strings.
6.Property Loader
7.Property access utility methods
8.Represents a persistent set of properties
9.Converts specified map to java.util.Properties
10.Encapsulates java.util.Properties to add java primitives and some other java classes
11.Load and save properties to files.
12.Adds new properties to an existing set of properties
13.Extracts a specific property key subset from the known properties
14.Observable Properties
15.Merge Properties Into Map
16.XML configuration management
17.JDOM based XML properties
18.XML Properties
19.Converts Unicode into something that can be embedded in a java properties file
20.Create Properties from String array
21.Task to overwrite Properties
22.Gets strong-type-value property from a standard Properties
23.The properties iterator iterates over a set of enumerated properties.
24.An utility class to ease up using property-file resource bundles.
25.Sorted Properties
26.A subclass of the java.util.Properties class that must be initialized from a file on disk
27.This class contains a collection of static utility methods for creating, retrieving, saving and loading properties.
28.Simplify routine job with properties
29.Property Manager