View Javadoc

1   /*
2    *******************************************************************************
3    * Copyright (c) 2005 Chris Rose and AIMedia
4    * All rights reserved. CRONConfiguration and the accompanying materials
5    * are made available under the terms of the Common Public License v1.0
6    * which accompanies this distribution, and is available at
7    * http://www.eclipse.org/legal/cpl-v10.html
8    * 
9    * Contributors:
10   *     Chris Rose
11   *******************************************************************************/
12  package ca.spaz.cron;
13  
14  import java.io.*;
15  import java.lang.reflect.*;
16  import java.util.*;
17  import java.util.regex.*;
18  
19  import org.apache.log4j.Logger;
20  
21  import ca.spaz.cron.config.*;
22  import ca.spaz.util.ToolBox;
23  
24  public final class CRONConfiguration {
25     /***
26      * Logger for this class
27      */
28     private static final Logger logger = Logger
29           .getLogger(CRONConfiguration.class);
30  
31     private static CRONConfiguration instance = null;
32     
33     private static final Pattern VALIDATOR_PATTERN = Pattern.compile("cronconfig//.validator//.(//d+)//.classname");
34     
35     private static final Pattern ENV_PATTERN = Pattern.compile("//$//(([^)]+)//)");
36     
37     /***
38      * Get the global singleton instance of the configuration manager
39      * @return a unique instance of <code>CRONConfiguration</code>.
40      */
41     public static final CRONConfiguration getInstance() {
42        return getInstance(DEFAULT_CONFIGURATION);
43     }
44     
45     static final CRONConfiguration getInstance(String propertiesURL) {
46        if (null == instance) {
47           instance = new CRONConfiguration(propertiesURL);
48        }
49        return instance;
50     }
51     
52     private Properties systemProperties;
53     private Properties userProperties;
54     
55     private static final String DEFAULT_CONFIGURATION = "/cron.properties";
56     private static final String USER_PROPERTIES_FILE = "cron.properties";
57  
58     private boolean userCanSave;
59  
60     private File userPropertyFile = null;
61  
62     private Set cachedKeys = null;
63  
64     private boolean writeOnChange;
65  
66     private List validatorList = null;
67  
68     private String systemPropertiesURL;
69     
70     private CRONConfiguration(String propertiesURL) {
71        this.systemPropertiesURL = propertiesURL;
72        writeOnChange = true;
73        userCanSave = true;
74  
75        // Load user property validators
76        loadDefaultValidators();
77        // Load user properties
78        try {
79           loadUserProperties();
80        } catch (FileNotFoundException e) {
81           logger.error("CRONConfiguration()", e);
82        } catch (IOException e) {
83           logger.error("CRONConfiguration()", e);
84        }
85     }
86  
87     private static String getClassnameKey(int value) {
88        return "cronconfig.validator." + value + ".classname";
89     }
90     
91     private static String getFactoryMethodNameKey(int value) {
92        return "cronconfig.validator." + value + ".constructor";
93     }
94     
95     private void loadDefaultValidators() {
96        Properties p = getSystemProperties();
97        for (Iterator iter = p.keySet().iterator(); iter.hasNext();) {
98           String key = (String) iter.next();
99           Matcher matcher = VALIDATOR_PATTERN.matcher(key);
100          if (matcher.find()) {
101             int id = Integer.parseInt(matcher.group(1));
102             String classname = p.getProperty(getClassnameKey(id));
103             String methodName = "";
104             try {
105                Class val = Class.forName(classname);
106                methodName = p.getProperty(getFactoryMethodNameKey(id));
107                PropertyValidator validator = null;
108                if (null == methodName) {
109                   validator = (PropertyValidator) val.newInstance();
110                } else {
111                   Method method = val.getMethod(methodName, null);
112                   validator = (PropertyValidator) method.invoke(val, null);
113                }
114                addPropertyValidator(validator);
115                logger.info("Loaded default validator " + classname);
116             } catch (ClassNotFoundException e) {
117                logger.error("Error in " + systemPropertiesURL + ", " + key + "=" + classname + " does not exist.");
118             } catch (SecurityException e) {
119                logger.error("loadDefaultValidators()", e);
120             } catch (InstantiationException e) {
121                logger.error("loadDefaultValidators()", e);
122             } catch (IllegalAccessException e) {
123                logger.error("loadDefaultValidators()", e);
124             } catch (NoSuchMethodException e) {
125                logger.error("Error in " + systemPropertiesURL + ", " + getFactoryMethodNameKey(id) + "constructor=" + methodName + " does not exist.");
126             } catch (IllegalArgumentException e) {
127                logger.error("loadDefaultValidators()", e);
128             } catch (InvocationTargetException e) {
129                logger.error("loadDefaultValidators()", e);
130             }
131          }
132       }
133    }
134 
135    private Properties getSystemProperties() {
136       if (null == systemProperties) {
137          systemProperties = new Properties();
138       
139          InputStream sysProps = getClass().getResourceAsStream(systemPropertiesURL);
140          if (null == sysProps) {
141             logger.error("Unable to load resource for system properties");
142             throw new IllegalStateException("No system property resource");
143          }
144          try {
145             systemProperties.load(sysProps);
146          } catch (IOException e) {
147             logger.error("getSystemProperties()", e);
148             throw new IllegalStateException("Cannot operate without system properties");
149          }
150       }
151       return systemProperties;
152    }
153 
154    /***
155     * @throws IOException
156     * @throws FileNotFoundException
157     */
158    private void loadUserProperties() throws IOException, FileNotFoundException {
159       userProperties = new Properties(getSystemProperties());
160       File userFile = getUserPropertiesFile();
161       if (userFile == null || !userFile.exists()) {
162          // Here, it's critical.  We failed to create the user props file, which is bad.
163          // However, this might just mean we can't save the file.
164          userCanSave = false;
165       }
166       // At this point we're guaranteed that the user properties exist.
167       userProperties.load(new FileInputStream(userFile));
168    }
169    
170    private File getUserPropertiesFile() {
171       if (userPropertyFile == null && userCanSave) {
172          File appDir = ToolBox.getUserAppDirectory("cronometer");
173          if (!appDir.exists()) {
174             appDir.mkdirs();
175             if (!appDir.exists()) {
176                logger.error("Unable to create user app prefs directory " + appDir);
177             }
178          }
179          userPropertyFile = new File(appDir, USER_PROPERTIES_FILE);
180          logger.info("Initializing user property file " + userPropertyFile.getAbsolutePath());
181          if (!userPropertyFile.exists()) {
182             logger.info("Creating user property file " + userPropertyFile.getAbsolutePath());
183             try {
184                if (userPropertyFile.createNewFile()) {
185                   // Nothing.  All is well.
186                } else {
187                   logger.error("Unable to create user property file");
188                   userCanSave = false;
189                }
190             } catch (IOException e) {
191                logger.error("getUserPropertiesFile()", e);
192                userCanSave = false;
193             }
194          } else {
195             // Nothing.  All is well.
196          }
197       }
198       return userPropertyFile;
199    }
200    
201    /***
202     * Sets the value of a property in the user configuration section.  If
203     * <code>writeOnChange</code> is <code>true</code>, this will immediately commit
204     * the change to the user config file.  Otherwise, it will write to the
205     * properties in memory, and the caller must invoke <code>store()</code> to
206     * write the configuration to permanent storage.
207     * 
208     * @param key The name of the property to set.
209     * @param value The new value of the property
210     * @return the value that the property held prior to being set.  <code>null</code>,
211     * if the property had not previously been set.
212     */
213    public String setProperty(String key, String value) {
214       if (!validateKey(key, value)) {
215          return null;
216       }
217       // Set the user property
218       String ret = null;
219       synchronized(userProperties) {
220          if (!userProperties.containsKey(key)) {
221             // Clear the key cache
222             cachedKeys = null;
223          }
224          ret = (String) userProperties.setProperty(key, value);
225          if (writeOnChange) {
226             store();
227          }
228       }
229       return ret;
230    }
231    
232    /***
233     * Add a <code>PropertyValidator</code> to the configuration.  This will be checked
234     * against each time a property is set.
235     * @param validator the validator.
236     */
237    public void addPropertyValidator(PropertyValidator validator) {
238       getValidators().add(validator);
239    }
240    
241    /***
242     * Remove a validator from the configuration.
243     * @param validator The validator to remove.
244     */
245    public void removePropertyValidator(PropertyValidator validator) {
246       getValidators().remove(validator);
247    }
248    
249    private List getValidators() {
250       if (null == validatorList) {
251          validatorList = new ArrayList();
252       }
253       return validatorList;
254    }
255 
256    private boolean validateKey(String key, String value) {
257       for (Iterator iter = getValidators().iterator(); iter.hasNext();) {
258          PropertyValidator val = (PropertyValidator) iter.next();
259          if (!val.isValid(key, value)) {
260             return false;
261          }
262       }
263       return true;
264    }
265    
266    public String getProperty(String property, String defaultValue, boolean expandEnv) {
267       String ret = userProperties.getProperty(property, defaultValue);
268       if (expandEnv) {
269          ret = replaceEnvVars(ret);
270       }
271       return ret;
272    }
273    
274    public String getProperty(String property, boolean expandEnv) {
275       String ret = userProperties.getProperty(property);
276       if (ret != null && expandEnv) {
277          ret = replaceEnvVars(ret);
278       }
279       return ret;
280    }
281 
282    /***
283     * @param value
284     * @return
285     */
286    private String replaceEnvVars(String value) {
287       Matcher mat = ENV_PATTERN.matcher(value);
288       int idx = 0;
289       StringBuffer buf = new StringBuffer();
290       while (mat.find(idx)) {
291          buf.append(value.substring(idx, mat.start()));
292          buf.append(System.getenv().get(mat.group(1)));
293          idx = mat.end();
294       }
295       buf.append(value.substring(idx));
296       
297       value = buf.toString();
298       return value;
299    }
300 
301    /***
302     * Get a property from the configuration set, with a provided default value.
303     * @param property The name of the property to retrieve.
304     * @param defaultValue The default value of the named property.
305     * @return The value of the property in the configuration, or 
306     * <code>defaultValue</code> if none was set.
307     */
308    public String getProperty(String property, String defaultValue) {
309       return getProperty(property, defaultValue, false);
310    }
311    
312    /***
313     * Get a property from the configuration set.
314     * @param property The name of the property to retrieve.
315     * @return The value of the property in the configuration, or 
316     * <code>null</code> if none was set.
317     */
318    public String getProperty(String property) {
319       return getProperty(property, false);
320    }
321    
322    /***
323     * Set this value to determine if the configuration manager should write to disk
324     * on every change to the configuration, or if it should wait until
325     * <code>store()</code> is called to proceed.
326     * 
327     * @param writeOnChange Set to <code>true</code> to enable writes, <code>false</code>
328     * to disable.
329     */
330    public void setWriteOnChange(boolean writeOnChange) {
331       this.writeOnChange = writeOnChange;
332    }
333    
334    /***
335     * Write the configuration to disk.  This will flush the user config into their
336     * configuration file.
337     * @return <code>true</code> if the write succeeded, <code>false</code> if some
338     * exception was thrown.
339     */
340    public boolean store() {
341       boolean success = false;
342       if (userCanSave) {
343          // Store them.  Log any errors, but catch exceptions.
344          // We have a valid property file.
345          try {
346             FileOutputStream fos = new FileOutputStream(getUserPropertiesFile(), false);
347             userProperties.store(fos, "CRONOMETER User Property File.");
348             success = true;
349          } catch (FileNotFoundException e) {
350             logger.error("setProperty(String, String)", e);
351             userCanSave = false;
352          } catch (IOException e) {
353             logger.error("setProperty(String, String)", e);
354             userCanSave = false;
355          }
356       }
357       return success;
358    }
359    
360    /***
361     * Retrieve the set of all keys in both the user and system configurations.
362     * 
363     * @return a <code>Set</code> containing the union of the property keys in both
364     * the user properties and system properties.
365     */
366    public Set keySet() {
367       if (cachedKeys == null) {
368          cachedKeys = new HashSet();
369          cachedKeys.addAll(userProperties.keySet());
370          cachedKeys.addAll(systemProperties.keySet());
371       }
372       return Collections.unmodifiableSet(cachedKeys);
373    }
374 
375    /***
376     * The main method.  This is what is run when the app is executed.
377     * @param args  The command-line arguments for the application.
378     */
379    public static void main(String[] args) {
380       CRONOMETER.configureLogger();
381       // Test -- list all properties
382       CRONConfiguration cfg = getInstance();
383       cfg.setWriteOnChange(false);
384       Properties sysprops = cfg.systemProperties;
385       Properties userprops = cfg.userProperties;
386 
387       cfg.setProperty("datasource.thisshouldnotwork", "BAD!");
388 
389       System.out.println("System properties:");
390       for (Iterator siter = sysprops.keySet().iterator(); siter.hasNext();) {
391          String key = (String) siter.next();
392          System.out.println(key + "=" + sysprops.getProperty(key, "no value"));
393       }
394       
395       System.out.println("User properties:");
396       for (Iterator iter = userprops.keySet().iterator(); iter.hasNext();) {
397          String key = (String) iter.next();
398          System.out.println(key + "=" + userprops.getProperty(key, "no value"));
399       }
400    }
401 
402 }