001: /*
002: * This file or a portion of this file is licensed under the terms of
003: * the Globus Toolkit Public License, found in file GTPL, or at
004: * http://www.globus.org/toolkit/download/license.html. This notice must
005: * appear in redistributions of this file, with or without modification.
006: *
007: * Redistributions of this Software, with or without modification, must
008: * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
009: * some other similar material which is provided with the Software (if
010: * any).
011: *
012: * Copyright 1999-2004 University of Chicago and The University of
013: * Southern California. All rights reserved.
014: */
015: package org.griphyn.common.util;
016:
017: import java.io.*;
018: import java.util.*;
019: import java.util.regex.*;
020:
021: /**
022: * This class creates a common interface to handle package properties.
023: * The package properties are meant as read-only (so far, until
024: * requirements crop up for write access). The class is implemented
025: * as a Singleton pattern.
026: *
027: * @author Jens-S. Vöckler
028: * @author Yong Zhao
029: * @author Karan Vahi
030: * @version $Revision: 70 $
031: * */
032: public class VDSProperties {
033: /**
034: * implements the singleton access via class variable.
035: */
036: private static VDSProperties m_instance = null;
037:
038: /**
039: * internal set of properties. Direct access is expressly forbidden.
040: */
041: private Properties m_props;
042:
043: /**
044: * internal copy of a system property. There is read-only access.
045: */
046: private String m_home;
047:
048: /**
049: * GNU: read-only architecture-independent data in DIR [PREFIX/share].
050: * The files in this directory have a low change frequency, are
051: * effectively read-only, can be shared via a networked FS, and they
052: * are usually valid for multiple users.
053: */
054: private File m_dataDir;
055:
056: /**
057: * GNU: read-only single-machine data in DIR [PREFIX/etc].
058: * The files in this directory have a low change frequency, are
059: * effectively read-only, they reside on a per-machine basis, and they
060: * are usually valid for a single user.
061: */
062: private File m_sysConfDir;
063:
064: /**
065: * GNU: modifiable architecture-independent data in DIR [PREFIX/com].
066: * The files in this directory have a high change frequency, are
067: * effectively read-write, can be shared via a networked FS, and they
068: * are usually valid for multiple users.
069: */
070: private File m_sharedStateDir;
071:
072: /**
073: * GNU: modifiable single-machine data in DIR [PREFIX/var].
074: * The files in this directory have a high change frequency, are
075: * effectively read-write, they reside on a per-machine basis, and they
076: * are usually valid for a single user.
077: */
078: private File m_localStateDir;
079:
080: /**
081: * Basename of the file to read to obtain system properties
082: */
083: public static final String PROPERTY_FILENAME = "properties";
084:
085: /**
086: * Basename of the (old) file to read for user properties.
087: * Warning, the old name will eventually fall prey to bit rot.
088: */
089: public static final String OLD_USER_PROPERTY_FILENAME = ".chimerarc";
090:
091: /**
092: * Basename of the (new) file to read for user properties.
093: */
094: public static final String USER_PROPERTY_FILENAME = ".pegasusrc";
095:
096: /**
097: * Adds new properties to an existing set of properties while
098: * substituting variables. This function will allow value
099: * substitutions based on other property values. Value substitutions
100: * may not be nested. A value substitution will be ${property.key},
101: * where the dollar-brace and close-brace are being stripped before
102: * looking up the value to replace it with. Note that the ${..}
103: * combination must be escaped from the shell.
104: *
105: * @param a is the initial set of known properties (besides System ones)
106: * @param b is the set of properties to add to a
107: * @return the combined set of properties from a and b.
108: */
109: protected static Properties addProperties(Properties a, Properties b) {
110: // initial
111: Properties result = new Properties(a);
112: Properties sys = System.getProperties();
113: Pattern pattern = Pattern.compile("\\$\\{[-a-zA-Z0-9._]+\\}");
114:
115: for (Enumeration e = b.propertyNames(); e.hasMoreElements();) {
116: String key = (String) e.nextElement();
117: String value = b.getProperty(key);
118:
119: // unparse value ${prop.key} inside braces
120: Matcher matcher = pattern.matcher(value);
121: StringBuffer sb = new StringBuffer();
122: while (matcher.find()) {
123: // extract name of properties from braces
124: String newKey = value.substring(matcher.start() + 2,
125: matcher.end() - 1);
126:
127: // try to find a matching value in result properties
128: String newVal = result.getProperty(newKey);
129:
130: /*
131: * // if not found, try b's properties
132: * if ( newVal == null ) newVal = b.getProperty(newKey);
133: */
134:
135: // if still not found, try system properties
136: if (newVal == null)
137: newVal = sys.getProperty(newKey);
138:
139: // replace braced string with the actual value or empty string
140: matcher.appendReplacement(sb, newVal == null ? ""
141: : newVal);
142: }
143: matcher.appendTail(sb);
144: result.setProperty(key, sb.toString());
145: }
146:
147: // final
148: return result;
149: }
150:
151: /**
152: * Set some defaults, should values be missing in the dataset.
153: *
154: * @return the properties.
155: */
156: private static Properties defaultProps() {
157: // initial
158: Properties result = new Properties();
159:
160: // copy pegasus keys as specified in the system properties to defaults
161: Properties sys = System.getProperties();
162: for (Enumeration e = sys.propertyNames(); e.hasMoreElements();) {
163: String key = (String) e.nextElement();
164: if (key.startsWith("pegasus."))
165: result.setProperty(key, sys.getProperty(key));
166: }
167:
168: // INSERT HERE!
169:
170: // final
171: return addProperties(new Properties(), result);
172: }
173:
174: /**
175: * ctor. This initializes the local instance of properties
176: * from a central file.
177: *
178: * @param propFilename is the basename of the file to read. This file
179: * will be looked for in the $PEGASUS_HOME/etc directory. Usually, the name
180: * will be set from the PROPERTY_FILENAME constant above.
181: * Alternatively, the name will be ignored, if an alternative properties
182: * location is specified via <code>-Dpegasus.properties</code>.
183: * @exception IOException will be thrown if reading the property file
184: * goes awry.
185: * @exception MissingResourceException will be thrown if you forgot
186: * to specify the <code>-Dpegasus.home=$PEGASUS_HOME</code> to the runtime
187: * environment.
188: */
189: protected VDSProperties(String propFilename) throws IOException,
190: MissingResourceException {
191: // create empty new instance
192: this .m_props = new Properties(defaultProps());
193:
194: // We have to write the appropriate shell scripts to translate
195: // between $PEGASUS_HOME and "java -Dpegasus.home=$PEGASUS_HOME <class>..."
196: this .m_home = System.getProperty("pegasus.home");
197:
198: // if this is a valid path, continue
199: if (this .m_home != null && this .m_home.length() > 0) {
200: // check for a file called "$PEGASUS_HOME/etc/<propFilename>"
201: // in a system-independent fashion, allowing for overrides
202: String alternative = System
203: .getProperty("pegasus.home.sysconfdir");
204: File etcDir = (alternative == null ? new File(this .m_home,
205: "etc") : new File(alternative));
206:
207: // check for an alternative property file spec
208: alternative = System.getProperty("pegasus.properties");
209: File props = (alternative == null ? new File(etcDir,
210: propFilename) : new File(alternative));
211:
212: if (props.exists()) {
213: // if this file exists, read the properties (will throw IOException)
214: Properties temp = new Properties();
215: InputStream stream = new BufferedInputStream(
216: new FileInputStream(props));
217: temp.load(stream);
218: stream.close();
219:
220: this .m_props = addProperties(this .m_props, temp);
221: }
222:
223: // add user properties afterwards to have higher precedence
224: String userHome = System.getProperty("user.home", ".");
225: alternative = System.getProperty("pegasus.user.properties");
226: if (alternative == null) {
227: // Prefer $HOME/.pegasusrc over $HOME/.chimerarc
228: props = new File(userHome,
229: VDSProperties.USER_PROPERTY_FILENAME);
230: if (props.exists()) {
231: // new user props file does exist, sanity check for old one
232: File old = new File(userHome,
233: VDSProperties.OLD_USER_PROPERTY_FILENAME);
234: if (old.exists()) {
235: // both user props exist, does the user know what he's doing?
236: System.out
237: .println("INFO: Both user property files "
238: + old.getName()
239: + " and "
240: + props.getName()
241: + " exist, using "
242: + VDSProperties.USER_PROPERTY_FILENAME);
243: }
244: } else {
245: // new user props file did not exist, check for old user props file
246: props = new File(userHome,
247: VDSProperties.OLD_USER_PROPERTY_FILENAME);
248: }
249: } else {
250: // was overwritten -- use user's overwrite
251: props = new File(alternative);
252: }
253:
254: if (props.exists()) {
255: // if this file exists, read the properties (will throw IOException)
256: Properties temp = new Properties();
257: InputStream stream = new BufferedInputStream(
258: new FileInputStream(props));
259: temp.load(stream);
260: stream.close();
261:
262: this .m_props = addProperties(this .m_props, temp);
263: }
264:
265: // now set the paths: set sysconfdir to correct latest value
266: alternative = this .m_props
267: .getProperty("pegasus.home.datadir");
268: this .m_dataDir = (alternative == null ? new File(
269: this .m_home, "share") : new File(alternative));
270: alternative = this .m_props
271: .getProperty("pegasus.home.sysconfdir");
272: this .m_sysConfDir = (alternative == null ? new File(
273: this .m_home, "etc") : new File(alternative));
274: alternative = this .m_props
275: .getProperty("pegasus.home.sharedstatedir");
276: this .m_sharedStateDir = (alternative == null ? new File(
277: this .m_home, "com") : new File(alternative));
278: alternative = this .m_props
279: .getProperty("pegasus.home.localstatedir");
280: this .m_localStateDir = (alternative == null ? new File(
281: this .m_home, "var") : new File(alternative));
282: } else {
283: // die on (forgotten) missing property home
284: throw new MissingResourceException(
285: "The pegasus.home property was not set!",
286: "java.util.Properties", "pegasus.home");
287: }
288: }
289:
290: /**
291: * Singleton threading: Creates the one and only instance of the
292: * properties in the current application.
293: *
294: * @return a reference to the properties.
295: * @exception IOException will be thrown if reading the property file
296: * goes awry.
297: * @exception MissingResourceException will be thrown if you forgot
298: * to specify the <code>-Dpegasus.home=$PEGASUS_HOME</code> to the runtime
299: * environment.
300: * @see #noHassleInstance()
301: */
302: public static VDSProperties instance() throws IOException,
303: MissingResourceException {
304: if (VDSProperties.m_instance == null)
305: VDSProperties.m_instance = new VDSProperties(
306: VDSProperties.PROPERTY_FILENAME);
307: return VDSProperties.m_instance;
308: }
309:
310: /**
311: * Create a temporary property that is not attached to the Singleton.
312: * This may be helpful with portal, which do magic things during the
313: * lifetime of a process.
314: *
315: * @param propFilename is the full path name to the location of the
316: * properties file to read. In case of null, the default location
317: * will be taken
318: * @return a reference to the parsed properties.
319: * @exception IOException will be thrown if reading the property file
320: * goes awry.
321: * @exception MissingResourceException will be thrown if you forgot
322: * to specify the <code>-Dpegasus.home=$PEGASUS_HOME</code> to the runtime
323: * environment.
324: * @see #instance()
325: */
326: public static VDSProperties nonSingletonInstance(String propFilename)
327: throws IOException, MissingResourceException {
328: return new VDSProperties((propFilename == null) ?
329: // pick up the default value
330: VDSProperties.PROPERTY_FILENAME
331: :
332: // pick up the file mentioned
333: propFilename);
334: }
335:
336: /**
337: * Singleton interface: Creates the one and only instance of the
338: * properties in the current application, and does not bother the
339: * programmer with exceptions. Rather, exceptions from the underlying
340: * <code>instance()</code> call are caught, converted to an error
341: * message on stderr, and the program is exited.
342: *
343: * @return a reference to the properties.
344: * @see #instance()
345: */
346: public static VDSProperties noHassleInstance() {
347: VDSProperties result = null;
348: try {
349: result = instance();
350: } catch (IOException e) {
351: System.err.println("While reading property file: "
352: + e.getMessage());
353: System.exit(1);
354: } catch (MissingResourceException mre) {
355: System.err
356: .println("You probably forgot to set the -Dpegasus.home=$PEGASUS_HOME");
357: System.exit(1);
358: }
359: return result;
360: }
361:
362: /**
363: * Accessor: Obtains the root directory of the VDS runtime
364: * system. Kept to make ChimeraProperties.java to compile for time being
365: *
366: * @return the root directory of the Pegasus runtime system, as initially
367: * set from the system properties.
368: */
369: public String getVDSHome() {
370: return this .m_home;
371: }
372:
373: /**
374: * Accessor: Obtains the root directory of the Pegasus runtime
375: * system.
376: *
377: * @return the root directory of the Pegasus runtime system, as initially
378: * set from the system properties.
379: */
380: public String getPegasusHome() {
381: return this .m_home;
382: }
383:
384: /**
385: * Accessor to $PEGASUS_HOME/share. The files in this directory have a low
386: * change frequency, are effectively read-only, can be shared via a
387: * networked FS, and they are valid for multiple users.
388: *
389: * @return the "share" directory of the VDS runtime system.
390: */
391: public File getDataDir() {
392: return this .m_dataDir;
393: }
394:
395: /**
396: * Accessor to $PEGASUS_HOME/etc. The files in this directory have a low
397: * change frequency, are effectively read-only, they reside on a
398: * per-machine basis, and they are valid usually for a single user.
399: *
400: * @return the "etc" directory of the VDS runtime system.
401: */
402: public File getSysConfDir() {
403: return this .m_sysConfDir;
404: }
405:
406: /**
407: * Accessor to $PEGASUS_HOME/com. The files in this directory have a high
408: * change frequency, are effectively read-write, they reside on a
409: * per-machine basis, and they are valid usually for a single user.
410: *
411: * @return the "com" directory of the VDS runtime system.
412: */
413: public File getSharedStateDir() {
414: return this .m_sharedStateDir;
415: }
416:
417: /**
418: * Accessor to $PEGASUS_HOME/var. The files in this directory have a high
419: * change frequency, are effectively read-write, they reside on a
420: * per-machine basis, and they are valid usually for a single user.
421: *
422: * @return the "var" directory of the VDS runtime system.
423: */
424: public File getLocalStateDir() {
425: return this .m_localStateDir;
426: }
427:
428: /**
429: * Accessor: Obtains the number of properties known to the project.
430: *
431: * @return number of properties in the project property space.
432: */
433: public int size() {
434: return this .m_props.size();
435: }
436:
437: /**
438: * Accessor: access to the internal properties as read from file.
439: * An existing system property of the same key will have precedence
440: * over any project property. This method will remove leading and
441: * trailing ASCII control characters and whitespaces.
442: *
443: * @param key is the key to look up
444: * @return the value for the key, or null, if not found.
445: */
446: public String getProperty(String key) {
447: String result = System.getProperty(key, this .m_props
448: .getProperty(key));
449: return (result == null ? result : result.trim());
450: }
451:
452: /**
453: * Accessor: access to the internal properties as read from file
454: * An existing system property of the same key will have precedence
455: * over any project property. This method will remove leading and
456: * trailing ASCII control characters and whitespaces.
457: *
458: * @param key is the key to look up
459: * @param defValue is a default to use, if no value can be found for the key.
460: * @return the value for the key, or the default value, if not found.
461: */
462: public String getProperty(String key, String defValue) {
463: String result = System.getProperty(key, this .m_props
464: .getProperty(key, defValue));
465: return (result == null ? result : result.trim());
466: }
467:
468: /**
469: * Accessor: Overwrite any properties from within the program.
470: *
471: * @param key is the key to look up
472: * @param value is the new property value to place in the system.
473: * @return the old value, or null if it didn't exist before.
474: */
475: public Object setProperty(String key, String value) {
476: return System.setProperty(key, value);
477: }
478:
479: /**
480: * Accessor: enumerate all keys known to this property collection
481: * @return an enumerator for the keys of the properties.
482: */
483: public Enumeration propertyNames() {
484: return this .m_props.propertyNames();
485: }
486:
487: /**
488: * Extracts a specific property key subset from the known properties.
489: * The prefix may be removed from the keys in the resulting dictionary,
490: * or it may be kept. In the latter case, exact matches on the prefix
491: * will also be copied into the resulting dictionary.
492: *
493: * @param prefix is the key prefix to filter the properties by.
494: * @param keepPrefix if true, the key prefix is kept in the resulting
495: * dictionary. As side-effect, a key that matches the prefix exactly
496: * will also be copied. If false, the resulting dictionary's keys are
497: * shortened by the prefix. An exact prefix match will not be copied,
498: * as it would result in an empty string key.
499: * @return a property dictionary matching the filter key. May be
500: * an empty dictionary, if no prefix matches were found.
501: *
502: * @see #getProperty( String ) is used to assemble matches
503: */
504: public Properties matchingSubset(String prefix, boolean keepPrefix) {
505: Properties result = new Properties();
506:
507: // sanity check
508: if (prefix == null || prefix.length() == 0)
509: return result;
510:
511: String prefixMatch; // match prefix strings with this
512: String prefixSelf; // match self with this
513: if (prefix.charAt(prefix.length() - 1) != '.') {
514: // prefix does not end in a dot
515: prefixSelf = prefix;
516: prefixMatch = prefix + '.';
517: } else {
518: // prefix does end in one dot, remove for exact matches
519: prefixSelf = prefix.substring(0, prefix.length() - 1);
520: prefixMatch = prefix;
521: }
522: // POSTCONDITION: prefixMatch and prefixSelf are initialized!
523:
524: // now add all matches into the resulting properties.
525: // Remark 1: #propertyNames() will contain the System properties!
526: // Remark 2: We need to give priority to System properties. This is done
527: // automatically by calling this class's getProperty method.
528: String key;
529: for (Enumeration e = propertyNames(); e.hasMoreElements();) {
530: key = (String) e.nextElement();
531:
532: if (keepPrefix) {
533: // keep full prefix in result, also copy direct matches
534: if (key.startsWith(prefixMatch)
535: || key.equals(prefixSelf))
536: result.setProperty(key, getProperty(key));
537: } else {
538: // remove full prefix in result, dont copy direct matches
539: if (key.startsWith(prefixMatch))
540: result.setProperty(key.substring(prefixMatch
541: .length()), getProperty(key));
542: }
543: }
544:
545: // done
546: return result;
547: }
548:
549: /**
550: * Extracts a specific property key subset from the properties passed.
551: * The prefix may be removed from the keys in the resulting dictionary,
552: * or it may be kept. In the latter case, exact matches on the prefix
553: * will also be copied into the resulting dictionary.
554: *
555: *
556: * @param prefix is the key prefix to filter the properties by.
557: * @param keepPrefix if true, the key prefix is kept in the resulting
558: * dictionary. As side-effect, a key that matches the prefix exactly
559: * will also be copied. If false, the resulting dictionary's keys are
560: * shortened by the prefix. An exact prefix match will not be copied,
561: * as it would result in an empty string key.
562: * @return a property dictionary matching the filter key. May be
563: * an empty dictionary, if no prefix matches were found.
564: *
565: * @see #getProperty( String ) is used to assemble matches
566: */
567: public static Properties matchingSubset(Properties properties,
568: String prefix, boolean keepPrefix) {
569: Properties result = new Properties();
570:
571: // sanity check
572: if (prefix == null || prefix.length() == 0)
573: return result;
574:
575: String prefixMatch; // match prefix strings with this
576: String prefixSelf; // match self with this
577: if (prefix.charAt(prefix.length() - 1) != '.') {
578: // prefix does not end in a dot
579: prefixSelf = prefix;
580: prefixMatch = prefix + '.';
581: } else {
582: // prefix does end in one dot, remove for exact matches
583: prefixSelf = prefix.substring(0, prefix.length() - 1);
584: prefixMatch = prefix;
585: }
586: // POSTCONDITION: prefixMatch and prefixSelf are initialized!
587:
588: // now add all matches into the resulting properties.
589: // Remark 1: #propertyNames() will contain the System properties!
590: // Remark 2: We need to give priority to System properties. This is done
591: // automatically by calling this class's getProperty method.
592: String key;
593: for (Enumeration e = properties.propertyNames(); e
594: .hasMoreElements();) {
595: key = (String) e.nextElement();
596:
597: if (keepPrefix) {
598: // keep full prefix in result, also copy direct matches
599: if (key.startsWith(prefixMatch)
600: || key.equals(prefixSelf))
601: result
602: .setProperty(key, properties
603: .getProperty(key));
604: } else {
605: // remove full prefix in result, dont copy direct matches
606: if (key.startsWith(prefixMatch))
607: result.setProperty(key.substring(prefixMatch
608: .length()), properties.getProperty(key));
609: }
610: }
611:
612: // done
613: return result;
614: }
615:
616: }
|