1
2
3
4
5
6
7
8
9
10
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
76 loadDefaultValidators();
77
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
163
164 userCanSave = false;
165 }
166
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
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
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
218 String ret = null;
219 synchronized(userProperties) {
220 if (!userProperties.containsKey(key)) {
221
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
344
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
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 }