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 }