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 }