View Javadoc

1   /*
2   Copyright (C) 2000 - 2007 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  
18  package com.gridsystems.utils;
19  
20  import java.io.IOException;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.FileOutputStream;
24  import java.util.Enumeration;
25  import java.util.Properties;
26  import java.util.concurrent.atomic.AtomicBoolean;
27  import java.util.concurrent.atomic.AtomicInteger;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  
32  /**
33   * This class implements a Properties object linked to a File object.
34   * Changes in the file will be automatically updated, and changes in the
35   * object will produce changes in the file.
36   *
37   * @author Rodrigo Ruiz
38   * @version 1.0
39   */
40  public class FileProperties extends Properties {
41  
42    /**
43     * Default Serial UID.
44     */
45    private static final long serialVersionUID = 1234523452345L;
46  
47    /**
48     * Class logger.
49     */
50    private static final Log LOG = LogFactory.getLog(FileProperties.class);
51  
52    /**
53     * This value controls a timeout for updating the properties from file.
54     */
55    private static final long UPDATE_INTERVAL = 400;
56  
57    /**
58     * This value controls the access synchronised to the security files.
59     */
60    public static Boolean synchronize = Boolean.TRUE;
61  
62    /**
63     * The source file for the property data.
64     */
65    protected File f = null;
66  
67    /**
68     * The last known modification time of the file.
69     */
70    protected long lastModified = 0L;
71  
72    /**
73     * Last update time.
74     */
75    protected long ts = 0;
76  
77    /**
78     * The header to set when writing the file.
79     */
80    protected String header = "";
81  
82    /**
83     * A flag indicating if a setProperty should invoke commit.
84     */
85    protected AtomicInteger lockWrite = new AtomicInteger();
86  
87    /**
88     * A call to commit after some property modification triggers the execution of the
89     * SecurityManager on file access. If the file is ig.properties, the Security Manager
90     * can attempt itself to write on the already opened file, producing undesired
91     * side-effects.
92     * This flag is used to prevent the Security Manager from commiting ig.properties when
93     * the main thread is already trying to do it.
94     */
95    private volatile AtomicBoolean accessing = new AtomicBoolean(false);
96  
97    /**
98     * Creates a file linked Properties object, with the specified default
99     * values.
100    *
101    * @param f The file to link to
102    * @param defaults The default values
103    */
104   public FileProperties(File f, Properties defaults) {
105     super(defaults);
106     this.f = f;
107     sync();
108   }
109 
110   /**
111    * Create a file linked Properties object.
112    *
113    * @param f The file to link to
114    */
115   public FileProperties(File f) {
116     this(f, null);
117   }
118 
119   /**
120    * Sets the text of the header that will be written to the file.
121    *
122    * @param header The header text to use
123    */
124   public void setHeader(String header) {
125     this.header = header;
126   }
127 
128   /**
129    * Sets the file associated to this instance.
130    *
131    * @param f The file
132    */
133   public synchronized void setFile(File f) {
134     if (this.f != f) {
135       this.f = f;
136       this.ts = 0;
137       this.lastModified = 0L;
138       sync();
139     }
140   }
141 
142   /**
143    * It locks commits. Used when you make multiple setProperties.
144    */
145   public void lockWrites() {
146     this.lockWrite.incrementAndGet();
147   }
148 
149   /**
150    * It unlocks commits and commits the last changes.
151    *
152    */
153   public void unlockWrites() {
154     if (this.lockWrite.decrementAndGet() == 0) {
155       commit();
156     }
157   }
158 
159   /**
160    * Searches for the property with the specified key in this property list.
161    * If the key is not found in this property list, the default property list,
162    * and its defaults, recursively, are then checked. The method returns the
163    * default value argument if the property is not found.
164    *
165    * @param   key            the key.
166    * @param   defaultValue   a default value.
167    *
168    * @return  the value in this property list with the specified key value.
169    */
170   @Override public String getProperty(String key, String defaultValue) {
171     sync();
172     return super.getProperty(key, defaultValue);
173   }
174 
175   /**
176    * Searches for the property with the specified key in this property list.
177    * If the key is not found in this property list, the default property list,
178    * and its defaults, recursively, are then checked. The method returns
179    * <code>null</code> if the property is not found.
180    *
181    * @param   key   the property key.
182    * @return  the value in this property list with the specified key value.
183    */
184   @Override public String getProperty(String key) {
185     sync();
186     return super.getProperty(key);
187   }
188 
189   /**
190    * Generic property access method for integer properties.
191    *
192    * @param key The name of the property
193    * @param defaultValue The default value to return in case of error
194    * @return the integer value of the property
195    */
196   public int getIntProperty(String key, int defaultValue) {
197     try {
198       String s = this.getProperty(key);
199       return Integer.parseInt(s);
200     } catch (Exception e) {
201       return defaultValue;
202     }
203   }
204 
205   /**
206    * Gets a property as an int value.
207    *
208    * @param key The property key
209    * @return The property value as an int
210    */
211   public int getIntProperty(String key) {
212     return Integer.parseInt(this.getProperty(key));
213   }
214 
215   /**
216    * Gets a property as a boolean value.
217    *
218    * @param key The key of the property
219    * @return the boolean value of the property
220    */
221   public boolean getBoolProperty(String key) {
222     String s = this.getProperty(key);
223     return Boolean.valueOf(s).booleanValue();
224   }
225 
226   /**
227    * Gets a property as a long value.
228    *
229    * @param key  The key of the property
230    * @return the long value of the property
231    */
232   public long getLongProperty(String key) {
233     return getLongProperty(key, 0L);
234   }
235 
236   /**
237    * Gets a property as a long value.
238    *
239    * @param key  The key of the property
240    * @param def  The default value in case of error
241    * @return The long value of the property
242    */
243   public long getLongProperty(String key, long def) {
244     try {
245       return Long.parseLong(this.getProperty(key));
246     } catch (Exception e) {
247       return def;
248     }
249   }
250 
251   /**
252    * Calls the hashtable method <code>put</code>. Provided for
253    * parallelism with the <tt>getProperty</tt> method. Enforces use of
254    * strings for property keys and values.
255    *
256    * @param key the key to be placed into this property list.
257    * @param value the value corresponding to <tt>key</tt>.
258    * @return the previous value
259    */
260   @Override public Object setProperty(String key, String value) {
261     Object ret = null;
262     synchronized (this) {
263       sync();
264       ret = super.setProperty(key, value);
265       commit();
266     }
267 
268     return ret;
269   }
270 
271   /**
272    * Returns an enumeration of all the keys in this property list, including
273    * the keys in the default property list.
274    *
275    * @return  an enumeration of all the keys in this property list, including
276    *          the keys in the default property list.
277    * @see     java.util.Enumeration
278    */
279   @Override
280   public Enumeration< ? > propertyNames() {
281     sync();
282     return super.propertyNames();
283   }
284 
285   /**
286    * If associated file is older than current version, updates the properties
287    * from the file.<br>
288    * <br>
289    * If the file does not exists, the current values are cleared.
290    */
291   protected synchronized void sync() {
292     if (accessing.getAndSet(true)) {
293       return;
294     }
295 
296     try {
297       long now = System.currentTimeMillis();
298       if (now - ts < UPDATE_INTERVAL) {
299         return;
300       }
301       ts = now;
302 
303       // If there is no file, clear current values and exit
304       if (f == null || !f.isFile()) {
305         super.clear();
306         return;
307       }
308 
309       // If it is older than ours, do nothing
310       long lm = f.lastModified();
311       if (lm <= this.lastModified) {
312         return;
313       }
314 
315       // At this point we know the file exists and is newer than last
316       // time we read it
317       super.clear();
318       FileInputStream fis = null;
319       try {
320         fis = new FileInputStream(f);
321         this.load(fis);
322 
323         // LastModified is only updated if no exception is raised
324         this.lastModified = lm;
325       } catch (Throwable e) {
326         LOG.warn("Could not sync FileProperties changes", e);
327       } finally {
328         // Free resources
329         FileUtils.close(fis);
330         fis = null;
331       }
332     } finally {
333       accessing.set(false);
334     }
335   }
336 
337   /**
338    * Propagates changes in memory to the file associated with this object.
339    */
340   public synchronized void commit() {
341     if (f == null || accessing.getAndSet(true)) {
342       return;
343     }
344 
345     try {
346       if (lockWrite.get() > 0) {
347         return;
348       }
349 
350       FileOutputStream fos = null;
351       try {
352         fos = new FileOutputStream(f);
353         this.store(fos, header);
354       } catch (IOException e) {
355         LOG.warn("Could not commit FileProperties changes", e);
356       } finally {
357         FileUtils.close(fos);
358         this.lastModified = f.lastModified();
359       }
360     } finally {
361       accessing.set(false);
362     }
363   }
364 
365 }