View Javadoc

1   /*
2    * $Id: FailbackConfig.java,v 1.32 2004/11/08 10:04:10 mch Exp $
3    *
4    * Copyright 2003 AstroGrid. All rights reserved.
5    *
6    * This software is published under the terms of the AstroGrid Software License,
7    * a copy of which has been included with this distribution in the LICENSE.txt file.
8    */
9   
10  package org.astrogrid.config;
11  
12  import java.util.*;
13  import javax.naming.*;
14  
15  import java.io.File;
16  import java.io.FileNotFoundException;
17  import java.io.IOException;
18  import java.io.PrintWriter;
19  import java.io.Writer;
20  import java.net.MalformedURLException;
21  import java.net.URL;
22  import java.security.AccessControlException;
23  
24  /***
25   * All Things To All Men Configuration.
26   * <p>
27   * A comprehensive facade singleton provding fallback access to Jndi and
28   * standard text configuration files.
29   * <p>
30   * The fallback works like this:
31   * <ul>
32   * <li> Look in local cache (so calling 'setProperty' will override file properties)
33   * <li> Look in jndi for the property.
34   * <li> If that fails, then look in the configuration file (see below for how this is located)
35   * <li> If that fails, then look in the system environments.
36   * <li> If that fails, throw an exception unless a default has been supplied.
37   * </ul>
38   * <p>
39   * The configuration file locator works like this:
40   * <ul>
41   * <li> look in jndi for the key "org.astrogrid.config.url" which gives the url to the file
42   * <li> look in jndi for the key "org.astrogrid.config" which gives the property filename
43   * <li> if that fails, look in the system environment vars for the same key
44   * <li> if that fails, look for the file "astrogrid.properties" on the classpath (not yet implemented)
45   * <li> if that fials, look for the file "astrogrid.properties" in the working directory
46   * </ul>
47   * <p>
48   * The configuration file lookup stops at the first find, so we don't get *too* confused
49   * with lots of configuration files around, and properties being found in one
50   * but not others.  If at any point it is referred to (ie JNDI key for the config filename exists) but
51   * there is a problem loading the file, it fails.  This means you can be sure that
52   * if you've *tried* to configure it, you will know if it hasn't worked.
53   * <p>
54   * Initialisation is 'lazy' - particularly as we may not want to go looking for
55   * configuration files if everything is in Jndi. However given the dangers of
56   * double-checked locking, the initialisation routines are synchronised and
57   * checked *within* for the initialisation flag
58   * <p>
59   * Failures are *all* reported as exceptions, unless a default is given.  So
60   * if you think a value might be missing and you don't want your app to fallover,
61   * supply a default.
62   * <p>
63   * We could think about adding resource bundles instead of property files, but
64   * I think JNDI provides all the complexity we need if we need that much...?
65   * <p>
66   * @author mch
67   */
68  
69  
70  public class FailbackConfig extends Config {
71  
72     /*** Cache - only used for setProperty at the moment, so that Jndi can
73      * reload on-the-fly as necessary */
74     private Hashtable cache = new Hashtable();
75  
76     /*** initialised flags */
77     private boolean jndiInitialised = false;
78     private boolean fileInitialised = false;
79     
80     /*** Jndi context */
81     private InitialContext jndiContext = null;
82     
83     /*** Properties file context */
84     private Properties properties = null;
85     
86     /*** JNDI key to url that locates the properties file */
87     private static String propertyUrlKey = "org.astrogrid.config.url";
88     
89     /*** JNDI key to properties file  */
90     private static String propertyKey = "org.astrogrid.config.filename";
91  
92     /*** prefix to keys when accessing JNDI services.  No idea why this is required... */
93     private static String jndiPrefix = "java:comp/env/";
94  
95     /*** Usual filename for properties file in classpath, etc */
96     private static String configFilename = "astrogrid.properties";
97     
98     /*** filename for default properties file that come with the distribution */
99     private static String defaultFilename = "default.properties";
100 
101    /***
102     * Protected constructor because we shouldn't be able to make one of these.
103     * At the moment the given context is ignored
104     */
105    protected FailbackConfig(Object context) {
106    }
107    
108    /***
109     * Call this before the first getProperty if you want to use a different
110     * property file (eg for testing).  Throws an exception if properties
111     * already loaded, as it's too late then
112     */
113    public void setConfigFilename(String newName)
114    {
115       if (fileInitialised) {
116          throw new ConfigException("Configuration already initialised - too late to set config filename",null);
117       }
118       configFilename = newName;
119    }
120       
121    
122    /***
123     * Initialise Jndi access.  Note that the context may be null (if there is
124     * no Jndi service) even after initialisation.  Synchronized for thread safety
125     */
126    private synchronized void initialiseJndi()  {
127       
128       if (!jndiInitialised) {
129          
130          jndiInitialised = true;
131          
132          try {
133             jndiContext = new InitialContext();
134             
135             try {
136                jndiContext.lookup("java:comp/env"); //this should be present in a Servlet Container
137                
138                addLoadedFrom("JNDI");
139             }
140             catch (NameNotFoundException nnfe) { /*ignore if we can't find 'Dummy' */ }
141             
142             log.info("Config access to JNDI initialised ("+jndiContext+")");
143          }
144          catch (ServiceUnavailableException sue) {
145             //not a problem, but note
146             log.debug("No JNDI service found ("+sue+") so will not use JNDI for config...");
147             jndiContext = null;
148          }
149          catch (NoInitialContextException nice)  {
150             //not a problem
151             log.debug("No JNDI service found ("+nice+") so will not use JNDI for config...");
152             jndiContext = null;
153          }
154          catch (NamingException ne) {
155             
156             throw new ConfigException("Initialising Jndi Access", ne);
157          }
158       }
159    }
160    
161    /***
162     * Initialise access to properties file.  Looks first in jndi for url to file,
163     * then system environment, then classpath, finally working directory.
164     */
165    private synchronized void initialiseFile()  {
166       
167       if (!fileInitialised) {
168          
169          fileInitialised = true;
170 
171          URL fileUrl = null;
172          
173          String urlValue = null; //so we can use in reporting exception
174          String filenameValue = null;
175 
176          //if jndi server is running, look up url in that
177          if (jndiContext != null) {
178             String jndiUrlKey = jndiPrefix+propertyUrlKey;
179             String jndiFileKey = jndiPrefix+propertyKey;
180             String keyUsed = jndiUrlKey;
181             
182             try {
183                try {
184                   urlValue = jndiContext.lookup(jndiUrlKey).toString().trim();//so we can report in exception
185                   fileUrl = new URL(urlValue);
186                } catch (NameNotFoundException nnfe) { } //ignore carry on
187                log.debug("Config: JNDI key "+jndiUrlKey+" => "+fileUrl);
188                try {
189                   filenameValue = jndiContext.lookup(jndiFileKey).toString().trim();
190                } catch (NameNotFoundException nnfe) { } //ignore carry on
191                log.debug("Config: JNDI key "+jndiFileKey+" => "+filenameValue);
192                
193                //if they are both defined, throw an exception - sysadmin should only
194                //define one, then we know where we are
195                if ((fileUrl != null) && (filenameValue != null)) {
196                   throw new ConfigException("Both "+jndiUrlKey+" and "+jndiFileKey+" defined in JNDI; specify only one");
197                }
198 
199                //if filename given, locate
200                if (filenameValue != null) {
201                   File propertyFile = new File(filenameValue);
202                   //if it's relative, locate from the classpath/working directory
203                   if (!propertyFile.isAbsolute()) {
204                      if (lookForConfigFile(filenameValue) == true)
205                      {
206                         //success!
207                         return;
208                      }
209                      throw new FileNotFoundException(filenameValue);
210                   }
211 
212                   //otherwise turn it into a url and carry on
213                   fileUrl = propertyFile.toURL();
214                   keyUsed = jndiFileKey;
215                }
216 
217                if (fileUrl != null) {
218                   loadFromUrl(fileUrl);
219 
220                   log.info("Configuration file loaded from '"+fileUrl.toString()+"' (from JNDI Key="+keyUsed+")");
221                   
222                   return;
223                }
224             }
225             catch (MalformedURLException mue) {
226                throw new ConfigException("Configuration file url ("+urlValue+") given in JNDI (key="+jndiUrlKey+") is malformed",mue);
227             }
228             catch (FileNotFoundException fnfe) {
229                throw new ConfigException("Configuration file ("+filenameValue+") given in JNDI (key="+jndiFileKey+") cannot be found",fnfe);
230             }
231             catch (NamingException ne) {
232                throw new ConfigException("Using key '"+jndiUrlKey+"' or '"+jndiFileKey+"' in JNDI gave: ", ne);
233             }
234             catch (IOException ioe) {
235                throw new ConfigException(ioe+" loading property file at '"+fileUrl+
236                                             "' (returned by JNDI key "+keyUsed+")", ioe);
237             }
238          }
239 
240          //look up url in system environment if nothing given in JNDI
241          if ((filenameValue == null) && (urlValue == null)) {
242             String sysEnvKey = propertyUrlKey;
243             String sysEnvUrl = System.getProperty(sysEnvKey);
244             log.debug("Config: Sys Env key "+sysEnvKey+" => "+sysEnvUrl);
245             if (sysEnvUrl != null) {
246                try {
247                   fileUrl = new URL(sysEnvUrl);
248    
249                   loadFromUrl(fileUrl);
250                   
251                   log.debug("Configuration file loaded from '"+fileUrl.toString()+"' (from SYS ENV="+sysEnvKey+")");
252                   
253                   return;
254                }
255                catch (MalformedURLException mue) {
256                   throw new ConfigException("Configuration file url given in system environment variable '"+
257                                                sysEnvKey+"' is malformed",mue);
258                }
259                catch (IOException ioe) {
260                   throw new ConfigException(ioe+" loading property file at '"+fileUrl+
261                                                "' (returned by system environment variable '"+sysEnvKey+"')", ioe);
262                }
263             }
264          }
265 
266          //Nothing in JNDI, nothing in sys env, so look in class path for general properties file
267          log.info("Config: No key to config file found in JNDI/SysEnv, so falling back to "+configFilename);
268 
269          try {
270             if (lookForConfigFile(configFilename)) {
271                return;
272             }
273          }
274          catch (AccessControlException ace) {
275             //this exception is thrown when a file is looked for, even if it doesn't exist,
276             //if there is no FilePermission set for it.  This can be a pain for the fallback
277             //config as it looks in many places for the configuration.  So we just log it - as an
278             //error - but we don't crash
279             log.error("No Permission to access "+configFilename, ace);
280          }
281             
282 
283          //last resort - look for default.properties that might be part of the distribution
284          try {
285             if (lookForConfigFile(defaultFilename)) {
286                return;
287             }
288          }
289          catch (AccessControlException ace) {
290             //this exception is thrown when a file is looked for, even if it doesn't exist,
291             //if there is no FilePermission set for it.  This can be a pain for the fallback
292             //config as it looks in many places for the configuration.  So we just log it - as an
293             //error - but we don't crash
294             log.error("No Permission to access "+defaultFilename, ace);
295          }
296          
297          
298          //well we haven't found one anywhere - this may not be an error (as none may be desired) but
299          //it should be reported...
300          log.warn("No configuration file found; if you need one, "+
301                      "make sure "+configFilename+" or "+defaultFilename+" is in your classpath, "+
302                      "or set the JNDI key "+propertyUrlKey+" to its URL, "+
303                      "or set the JNDI key "+propertyKey+" to its file location");
304       }
305    }
306 
307    /***
308     * Looks for given config filename absolutely or in classpath and working directory, and loads
309     * it if found.  Returns false if not found.
310     * This could probably make use of Config.resolveFile()
311     */
312    private boolean lookForConfigFile(String givenFilename)  {
313 
314       //replace ${stuff} with sys.env values for stuff
315       String filename = resolveEnvironmentVariables(givenFilename);
316       
317       //for debugging
318       if (!filename.equals(givenFilename)) {
319          givenFilename = givenFilename + " => "+filename;
320       }
321 
322       log.debug("Looking for "+givenFilename);
323      
324          //if it's absolute, look absolutely
325          File f = new File(filename);
326          if (f.isAbsolute()) {
327             if (f.exists()) {
328                loadFromFile(f);
329                return true;
330             }
331             return false;
332          }
333          
334          //look for file in classpath.
335          //see http://www.javaworld.com/javaworld/javaqa/2003-08/01-qa-0808-property.html
336          //NB this works via URL as we don't expect to get config files from inside jars
337          log.debug("Looking for "+givenFilename+" on classpath");
338    //      URL configUrl = ClassLoader.getSystemResource(filename);
339          URL configUrl = this.getClass().getClassLoader().getResource(filename);
340          if (configUrl != null) {
341             try {
342                loadFromUrl(configUrl);
343                
344                log.info("Configuration file loaded from '"+configUrl+"' (found in classpath)");
345                
346                return true;
347                
348             } catch (IOException ioe) {
349                throw new ConfigException(ioe+" loading property file at '"+configUrl+"' (from classpath)", ioe);
350             }
351          }
352          
353          
354          //look for it in the working directory
355          log.debug("Looking for "+givenFilename+" in working directory");
356          if (f.exists()) {
357             loadFromFile(f);
358             return true;
359          }
360          
361       return false;
362    }
363    
364    private void loadFromFile(File f) {
365       try {
366          loadFromUrl(f.toURL());
367          log.info("Configuration file loaded from '"+f.getAbsoluteFile()+"'");
368          return;
369       }
370       catch (IOException ioe) {
371          throw new ConfigException(ioe+" loading property file at '"+f.getAbsoluteFile(), ioe);
372       }
373    }
374    
375   
376    
377    /***
378     * Loads the properties from the given stream.  While this should only be
379     * called once during part of the normal initialisation process, we also
380     * allow public access so that test harnesses etc can load their own
381     * properties differently.
382     */
383    public synchronized void loadFromUrl(URL url) throws IOException   {
384       if (properties == null) {
385          properties = new Properties();
386       }
387 
388       //we load them into a local variable properties instance first, so that
389       //we can override the included config settings with these ones.
390       Properties localProperties = new Properties();
391       localProperties.load(url.openStream());
392 
393       addLoadedFrom(url.toString()); //add to string indicating what's happening
394       
395       //look for chain; if this file contains the property 'include.config.filename'
396       //then load that into the global properties
397       String includeFile = localProperties.getProperty("include.config.filename") ;
398       if (includeFile != null) {
399          boolean found = lookForConfigFile(includeFile);
400          if (!found) {
401             throw new ConfigException("include config file '"+includeFile+"' not found");
402          }
403       }
404       
405       //override any existing set propertis with the local ones loaded above
406       properties.putAll(localProperties);
407       
408    }
409    
410    /***
411     * Keys in the current JDK Property implementation must not contain whitespace,
412     * colons or equals
413     */
414    public static void assertKeyValid(String key)
415    {
416       assert key.indexOf(":") == -1 : "Key '"+key+"' contains an illegal character - a colon";
417       assert key.indexOf(" ") == -1 : "Key '"+key+"' contains an illegal character - a space";
418       assert key.indexOf("=") == -1 : "Key '"+key+"' contains an illegal character - an equals sign";
419    }
420 
421    /***
422     * General get property.  Throws exception if property not found in Jndi,
423     * then configuration file, then system environment.
424     */
425    public Object getProperty(String key)  {
426 
427       assertKeyValid(key);
428 
429       String lookedIn = "Cache";
430       
431       //first look in cache
432       if (cache.containsKey(key)) {
433          return cache.get(key);
434       }
435          
436       //first look in jndi
437       try {
438          if (!jndiInitialised) { initialiseJndi(); }
439       
440          if (jndiContext != null) {
441             lookedIn = lookedIn + ", JNDI";
442             return jndiContext.lookup(jndiPrefix+key);
443          }
444          else {
445             lookedIn = lookedIn + ", (No JNDI)";
446          }
447       }
448       catch (NameNotFoundException nnfe) { } //ignore, we'll look somewhere else
449       catch (NamingException ne) {
450          throw new ConfigException(ne+" locating key="+key+" in JNDI", ne);
451       }
452 
453       //try the properties file
454       if (!fileInitialised) { initialiseFile(); }
455 
456       if (properties == null) {
457          lookedIn = lookedIn +", (no config file)";
458       }
459       else {
460          String value = properties.getProperty(key);
461          lookedIn = lookedIn +", config file(s) ("+loadedFrom()+")";
462       
463          if (value != null) {
464             return value;
465          }
466       }
467       
468       //try the system environment
469       lookedIn = lookedIn +", sysenv";
470       String value = System.getProperty(key);
471       if (value != null) {
472          return value;
473       }
474       
475       throw new PropertyNotFoundException("Could not find '"+key+"' in: "+lookedIn);
476    }
477 
478 
479    /***
480     * Set property.  Stores in cache so it overrides all other properties
481     * with the same key.
482     */
483    public void setProperty(String key, Object value) {
484       //note that we cannot store in the 'Properties' instance as that can
485       //only handle strings
486       if (value == null) {
487          if (cache.containsKey(key)) {
488             cache.remove(key);
489          }
490       } else {
491          cache.put(key, value);
492       }
493    }
494 
495    /***
496     * Returns array of values for the given key. Throws exception if property not found in Jndi,
497     * then configuration file, then system environment.
498     */
499    public Object[] getProperties(String key)  {
500 
501       assertKeyValid(key);
502 
503       String lookedIn = "Cache";
504       
505       //first look in cache
506       if (cache.containsKey(key)) {
507          Object o = cache.get(key);
508          if (o instanceof Object[]) {
509             return (Object[]) o;
510          }
511          else {
512             return new Object[] { o };
513          }
514       }
515          
516       //first look in jndi
517       try {
518          if (!jndiInitialised) { initialiseJndi(); }
519 
520          if (jndiContext != null) {
521             lookedIn = lookedIn + ", JNDI";
522             Context javacontext = (Context)jndiContext.lookup(jndiPrefix);
523             NamingEnumeration en = jndiContext.list(jndiPrefix+key);
524             Vector values = new Vector();
525             while (en.hasMoreElements()) {
526                NameClassPair pair = (NameClassPair) en.next();
527                String value = null;
528                //not sure how all this works, so for now will ignore naming exceptions
529                try {
530                   value = javacontext.lookup(pair.getName()).toString();
531                } catch (NamingException ne) {
532                   value="??Failed lookup: "+ne;
533                } //ignore - value=??failed
534                values.add(value);
535             }
536             return values.toArray();
537          }
538          else {
539             lookedIn = lookedIn + ", (No JNDI)";
540          }
541       }
542       catch (NameNotFoundException nnfe) { } //ignore, we'll look somewhere else
543       catch (NamingException ne) {
544          throw new ConfigException(ne+" locating key="+key+" in JNDI", ne);
545       }
546 
547       //try the properties file. It can only hold one value per key, so we
548       //look for the key (and return a single element array if found) and/or
549       //key.1, key.2, key.3 until a key returns null.
550       if (!fileInitialised) { initialiseFile(); }
551 
552       if (properties == null) {
553          lookedIn = lookedIn +", (no config file)";
554       }
555       else {
556          lookedIn = lookedIn +", config file(s) ("+loadedFrom()+")";
557 
558          String value = properties.getProperty(key);
559          String value1 = properties.getProperty(key+".1");
560       
561          //check that there aren't both settings without number and settings with
562          if ((value != null) && (value1 != null)) {
563             throw new ConfigException("Both single value and sets of values defined for key "+key+" in property file");
564          }
565 
566          //only one value set
567          if (value != null) {
568             return new Object[] { value };
569          }
570          
571          if (value1 != null) {
572             Vector values = new Vector();
573             int v = 2;
574             while (value1 != null) {
575                values.add(value1);
576                value1 = properties.getProperty(key+"."+v);
577                v++;
578             }
579             return values.toArray();
580          }
581          
582       }
583       
584       //try the system environment
585       lookedIn = lookedIn +", sysenv";
586       String value = System.getProperty(key);
587       if (value != null) {
588          return new Object[] { value };
589       }
590       
591       throw new PropertyNotFoundException("Could not find '"+key+"' in: "+lookedIn);
592    }
593 
594    /***
595     * Set property to array.  Stores in cache so it overrides all other properties
596     * with the same key.
597     */
598    public void setProperties(String key, Object[] values) {
599       if (values == null) {
600          if (cache.containsKey(key)) {
601             cache.remove(key);
602          }
603       } else {
604          cache.put(key, values);
605       }
606    }
607 
608    /***
609     * Returns a list of keys.  This list is made up of the values in the
610     * cache, JNDI, properties file and system environment keys; note that duplicate
611     * keys will be hidden.
612     */
613    public Set keySet() {
614 
615       if (!jndiInitialised) { initialiseJndi(); }
616       if (!fileInitialised) { initialiseFile(); }
617       
618       Set allKeys = new HashSet();
619       
620       //cache
621       allKeys.addAll(cache.keySet());
622 
623       //jndi
624       if (jndiContext != null) {
625          try {
626             Hashtable jndi = jndiContext.getEnvironment();
627             allKeys.addAll(jndi.keySet());
628          }
629          catch (NamingException ne) {
630             throw new ConfigException("Getting Environment from "+jndiContext,ne);
631          }
632       }
633       
634       //property files
635       if (properties != null) {
636          allKeys.addAll(properties.keySet());
637       }
638 
639       //sys env
640       allKeys.addAll(System.getProperties().keySet());
641 
642       return allKeys;
643    }
644    
645    /***
646     * Dumps config contents
647     */
648    public void dumpConfig(Writer writer)  {
649       
650       PrintWriter out = new PrintWriter(writer);
651       
652       out.println("Configuration loaded from: "+loadedFrom());
653       out.println();
654 
655       //-- cache --
656       if (cache.isEmpty()) {
657          out.println("(Cache is empty)");
658       }
659       else {
660          out.println("Cache:");
661          Enumeration c = cache.keys();
662          while (c.hasMoreElements()) {
663             Object key = c.nextElement();
664             out.println(formKeyValue(key, cache.get(key)));
665          }
666       }
667       
668       //-- JNDI --
669       out.println();
670       if (jndiContext != null) {
671          out.println("JNDI:");
672          try {
673             out.println("JNDI Environment:");
674             Hashtable env = jndiContext.getEnvironment();
675             Enumeration j = env.keys();
676             while (j.hasMoreElements()) {
677                Object key = j.nextElement();
678                out.println(formKeyValue(key, env.get(key)));
679             }
680             out.println("JNDI Names:");
681             Context javacontext = (Context)jndiContext.lookup(jndiPrefix);
682             NamingEnumeration n = jndiContext.list(jndiPrefix);
683 
684             while (n.hasMoreElements()) {
685                NameClassPair key = (NameClassPair)n.next();
686                String value = "??Failed lookup";
687                //not sure how all this works, so for now will ignore naming exceptions
688                try {
689                   value = javacontext.lookup(key.getName()).toString();
690                } catch (NamingException ne) { } //ignore - print fail value
691                out.println(formKeyValue(key, value));
692             }
693             
694          }
695          catch (NamingException ne) {
696             ne.printStackTrace(out);
697          }
698 
699       }
700       else {
701          out.println("(No JNDI)");
702       }
703       out.println();
704       
705       //--- Property Files ---
706       if (properties != null) {
707          out.println("Properties from file(s):");
708          Enumeration p = properties.keys();
709          while (p.hasMoreElements()) {
710             Object key = p.nextElement();
711             out.println(formKeyValue(key, properties.getProperty(key.toString())));
712          }
713       }
714       else {
715          out.println("(No Config File)");
716       }
717       out.println();
718 
719       //-- System environment variables ----
720       out.println("System Environment Variables:");
721       try {
722          Enumeration s = System.getProperties().keys();
723          while (s.hasMoreElements()) {
724             Object key = s.nextElement();
725             out.println(formKeyValue(key, System.getProperty(key.toString())));
726          }
727       }
728       catch (AccessControlException ace) {
729          //might not be allowed blanket access to system enviornment variables...
730          out.println("No blanket access permitted: "+ace);
731       }
732 
733       out.flush();
734    }
735    
736    /***
737     * Formats a key/value pair for printing.  Used by dumpConfig.  Does noddy
738     * check for 'password' in the key string and hides value if present
739     */
740    public String formKeyValue(Object key, Object value) {
741       if (key.toString().toLowerCase().indexOf("password") > -1) {
742          return "  "+key+" = <hidden>";
743       } else {
744          return "  "+key+" = "+value;
745       }
746    }
747    
748    /***
749     * Main method dumps config contents to console - useful for debugging
750     */
751    public static void main(String[] args) {
752       FailbackConfig config = new FailbackConfig(null);
753 
754       //force lazy load
755       config.getProperty("Nuffink", null);
756 
757       config.dumpConfig(new PrintWriter(System.out));
758    }
759 }
760 /*
761 $Log: FailbackConfig.java,v $
762 Revision 1.32  2004/11/08 10:04:10  mch
763 Throw error if include file not found
764 
765 Revision 1.31  2004/11/07 16:48:30  mch
766 Added include so we can maintain all the ivorn shortcuts
767 
768 Revision 1.30  2004/11/07 16:44:25  mch
769 fix to multi-value getProperties for JNDI
770 
771 Revision 1.29  2004/10/08 17:12:26  mch
772 Fix to getting sets of property when that property has been set to a set thpththh
773 
774 Revision 1.28  2004/10/08 16:33:57  mch
775 Added setProperties() and attempt to make getProperties() work with property files
776 
777 Revision 1.27  2004/10/05 19:32:43  mch
778 Added getProperties
779 
780 Revision 1.26  2004/08/25 00:34:15  mch
781 Updated documentaiton
782 
783 Revision 1.25  2004/08/04 12:11:59  mch
784 Fixed access control exception so it gets thrown when a config file is given
785 
786 Revision 1.24  2004/08/03 11:13:29  mch
787 Added trap for AccessControlException
788 
789 Revision 1.23  2004/07/16 16:03:04  mch
790 Added trim for config filename from JNDI, better error reporting and checking
791 
792 Revision 1.22  2004/07/14 14:43:00  pah
793 get the jndi values to print out properly in the dump
794 
795 Revision 1.21  2004/04/23 19:01:22  pah
796 changed the test for jndi presence slightly
797 
798 Revision 1.20  2004/04/07 11:41:04  jdt
799 Modified by the A.A.A.A.
800 
801 Revision 1.19  2004/03/31 11:00:14  mch
802 Added keySet()
803 
804 Revision 1.18  2004/03/12 13:22:34  mch
805 Fix for null setProperty value
806 
807 Revision 1.17  2004/03/09 16:34:51  mch
808 Added sysenv resolver & better error reporting
809 
810 Revision 1.16  2004/03/09 16:32:27  mch
811 Added sysenv resolver
812 
813 Revision 1.15  2004/03/06 22:22:08  mch
814 Added resolveFile
815 
816 Revision 1.14  2004/03/06 14:58:17  mch
817 Added more failback config files, incl default.properties
818 
819 Revision 1.13  2004/03/06 14:54:26  mch
820 Better file lookup
821 
822 Revision 1.12  2004/03/05 12:32:29  mch
823 Hides values for keys that include the word 'password'
824 
825 Revision 1.11  2004/03/05 00:33:02  mch
826 Added exception name to errors
827 
828 Revision 1.10  2004/03/04 20:25:02  mch
829 Added dumpConfig to Config, and changed argument to general Writer
830 
831 Revision 1.9  2004/03/03 17:29:00  mch
832 Added debug statements
833 
834 Revision 1.8  2004/03/03 17:11:07  mch
835 Added command line dump and tested
836 
837 Revision 1.7  2004/03/03 16:56:46  mch
838 minor comment change
839 
840 Revision 1.6  2004/03/03 16:51:10  mch
841 Added dumpConfig
842 
843 Revision 1.5  2004/03/03 16:21:59  mch
844 Added better reporting if no config file found
845 
846 Revision 1.4  2004/03/01 14:06:39  mch
847 Added filename failback and better error reporting
848 
849 Revision 1.3  2004/02/27 14:23:12  mch
850 Changed loadUrl to loadFromUrl
851 
852 Revision 1.2  2004/02/26 17:31:30  mch
853 Fixed org.astrogrid.config.url from org.astrogrid.properties.url
854 
855 Revision 1.1  2004/02/24 15:35:12  mch
856 Refactoring to include FailbackConfig
857 
858 Revision 1.5  2004/02/17 14:47:15  mch
859 Increased test coverage
860 
861 Revision 1.4  2004/02/17 14:31:49  mch
862 Minor changes to please checkstyle
863 
864 Revision 1.3  2004/02/17 12:36:16  mch
865 Fixed self import
866 
867 Revision 1.2  2004/02/17 03:54:35  mch
868 Nughtily large number of fixes for demo
869 
870  */
871 
872 
873 
874 
875 
876 
877 
878 
879