001    /*
002     * 
003     * $Revision: 15619 $ $Date: 2009-07-25 08:28:03 +0200 (Sat, 25 Jul 2009) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.common;
025    
026    import java.io.File;
027    import java.io.FileInputStream;
028    import java.io.FileNotFoundException;
029    import java.io.FileOutputStream;
030    import java.io.IOException;
031    import java.io.InputStream;
032    import java.io.OutputStream;
033    import java.io.PrintStream;
034    import java.io.PrintWriter;
035    import java.lang.reflect.Method;
036    import java.util.Enumeration;
037    import java.util.Hashtable;
038    import java.util.Properties;
039    import java.util.StringTokenizer;
040    
041    import org.apache.log4j.Logger;
042    import org.apache.log4j.PropertyConfigurator;
043    
044    import org.mycore.services.plugins.FilterPluginInstantiationException;
045    
046    /**
047     * Provides methods to manage and read all configuration properties from the
048     * MyCoRe configuration files. The class is implemented using the singleton
049     * pattern. Using this class is very easy, here is an example:
050     * 
051     * <PRE>
052     * // Get a configuration property as a String: 
053     * String driver = MCRConfiguration.instance().getString( "MCR.JDBC.Driver" ); 
054     * // Get a configuration property as an int, use 500 as default if not set: 
055     * int max = MCRConfiguration.instance().getInt( "MCR.Cache.Size", 500 );
056     * </PRE>
057     * 
058     * As you see, the class provides methods to get configuration properties as
059     * different data types and allows you to specify defaults. All MyCoRe
060     * configuration properties should start with "<CODE>MCR.</CODE>" When
061     * <CODE>instance()</CODE> is called the first time, the file <B><CODE>
062     * mycore.properties</CODE> </B> is read. It can be located somewhere in the
063     * <CODE>CLASSPATH</CODE>, even in a jar or zip file. The properties file may
064     * have a property called <B><CODE>MCR.Configuration.Include</CODE> </B> that
065     * contains a comma-separated list of other configuration files to read
066     * subsequently. The class also reads any Java <B>system properties</B> that
067     * start with "<CODE>MCR.</CODE>" and that are set when the application
068     * starts. System properties will override properties read from the
069     * configuration files. Furthermore, the name of the main configuration file can
070     * be altered by specifying the system property <B><CODE>
071     * MCR.Configuration.File</CODE> </B>. Here is an example:
072     * 
073     * <PRE>
074     * java -DMCR.Configuration.File=some_other.properties -DMCR.foo=bar MyCoReSample
075     * </PRE>
076     * 
077     * Property values may include the values of other properties, recursively, by
078     * referencing the other property. Example:
079     * 
080     * <PRE>
081     * MCR.Foo1=FooValue
082     * MCR.Foo2=Some %MCR.Foo1% more information
083     * </PRE>
084     * 
085     * The class also provides methods for <B>listing or saving </B> all properties
086     * to an <CODE>OutputStream</CODE> and for <B>reloading </B> all configuration
087     * properties at runtime. This allows servlets that run a long time to re-read
088     * the configuration files when client code tells them to do so, for example.
089     * Using the <CODE>set</CODE> methods allows client code to set new
090     * configuration properties or overwrite existing ones with new values. Finally,
091     * applications could also <B>subclass <CODE>MCRConfiguration</CODE> </B> to
092     * change or add behavior and use an instance of the subclass instead of this
093     * class. This is transparent for client code, they would still use <CODE>
094     * MCRConfiguration.instance()</CODE> to get the subclass instance. To use a
095     * subclass instead of <CODE>MCRConfiguration</CODE> itself, specify the
096     * system property <CODE>MCR.Configuration.Class</CODE>, e. g.
097     * 
098     * <PRE>
099     * java -DMCR.Configuration.Class=MCRConfigurationSubclass MyCoReSample
100     * </PRE>
101     * 
102     * @see #loadFromFile
103     * @see #reload
104     * @see #list(PrintStream)
105     * @see #store
106     * 
107     * @author Frank Lützenkirchen
108     * @version $Revision: 15619 $ $Date: 2009-07-25 08:28:03 +0200 (Sat, 25 Jul 2009) $
109     */
110    public class MCRConfiguration {
111        /**
112         * The single instance of this class that will be used at runtime
113         */
114        protected static MCRConfiguration singleton;
115    
116        private static Hashtable instanceHolder;
117    
118        static File lastModifiedFile;
119    
120        /**
121         * Returns the single instance of this class that can be used to read and
122         * manage the configuration properties.
123         * 
124         * @return the single instance of <CODE>MCRConfiguration</CODE> to be used
125         */
126        public static synchronized MCRConfiguration instance() {
127            if (singleton == null) {
128                createSingleton();
129            }
130    
131            return singleton;
132        }
133    
134        /**
135         * Instantiates the singleton by calling the protected constructor. If the
136         * system property <CODE>MCR.Configuration.Class</CODE> is set when the
137         * system starts, the class specified in that property will be instantiated
138         * instead. This allows for subclassing <CODE>MCRConfiguration</CODE> to
139         * change behaviour and use the subclass instead of <CODE>MCRConfiguration
140         * </CODE>.
141         */
142        protected static void createSingleton() {
143            String name = System.getProperty("MCR.Configuration.Class");
144    
145            if (name != null) {
146                try {
147                    singleton = (MCRConfiguration) (Class.forName(name).newInstance());
148                } catch (Exception exc) {
149                    throw new MCRConfigurationException("Could not create MCR.Configuration.Class singleton \"" + name + "\"", exc);
150                }
151            } else {
152                singleton = new MCRConfiguration();
153            }
154            singleton.systemModified();
155        }
156    
157        /**
158         * returns the last point in time when the MyCoRe system was last modified.
159         * 
160         * This method can help you to validate caches not under your controll, e.g.
161         * client caches.
162         * 
163         * @see System#currentTimeMillis()
164         */
165        public final long getSystemLastModified() {
166            return lastModifiedFile.lastModified();
167        }
168    
169        /**
170         * signalize that the system state has changed.
171         * 
172         * Call this method when ever you changed the persistency layer.
173         * 
174         */
175        public final void systemModified() {
176            lastModifiedFile.setLastModified(System.currentTimeMillis());
177        }
178    
179        /**
180         * The properties instance that stores the values that have been read from
181         * every configuration file
182         */
183        protected Properties properties;
184    
185        /**
186         * List of deprecated properties with their new name
187         */
188        protected Properties depr;
189    
190        /**
191         * Protected constructor to create the singleton instance
192         */
193        protected MCRConfiguration() {
194            properties = new Properties();
195            depr = new Properties();
196            reload(true);
197            final String dataDirKey = "MCR.datadir";
198            if (properties.containsKey(dataDirKey)) {
199                lastModifiedFile = new File(getString(dataDirKey), ".systemTime");
200            } else {
201                try {
202                    lastModifiedFile = File.createTempFile("MyCoRe", ".systemTime");
203                } catch (IOException e) {
204                    throw new MCRException("Could not create temporary file, please set property MCR.datadir");
205                }
206            }
207            if (!lastModifiedFile.exists()) {
208                try {
209                    FileOutputStream fout = new FileOutputStream(lastModifiedFile);
210                    fout.write(new byte[0]);
211                } catch (Exception e) {
212                    throw new MCRException("Error while creating file: " + lastModifiedFile, e);
213                }
214    
215            }
216        }
217    
218        /**
219         * Reloads all properties from the configuration files. If the system
220         * property <CODE>MCR.Configuration.File</CODE> is set, the file specified
221         * in this property will be used as main configuration file, otherwise the
222         * default file <CODE>mycore.properties</CODE> will be read. If the
223         * parameter <CODE>clear</CODE> is <CODE>true</CODE>, all properties
224         * currently set will be deleted first, otherwise the properties read from
225         * the configuration files will be added to the properties currently set and
226         * they will overwrite existing properties with the same name.
227         * 
228         * @param clear
229         *            if true, properties currently set will be deleted first
230         * @throws MCRConfigurationException
231         *             if the config files can not be loaded
232         */
233        public void reload(boolean clear) {
234            if (clear) {
235                properties.clear();
236                depr.clear();
237            }
238    
239            String fn = System.getProperty("MCR.Configuration.File", "mycore.properties");
240            loadFromFile(fn);
241    
242            Enumeration names = System.getProperties().propertyNames();
243            while (names.hasMoreElements()) {
244                String name = (String) (names.nextElement());
245                if (name.startsWith("MCR.")) {
246                    String value = System.getProperty(name);
247                    if (value != null) {
248                        set(name, value);
249                    }
250                }
251            }
252    
253            substituteReferences();
254            substituteDeprecatedProperties();
255    
256            if (clear) {
257                configureLogging();
258            }
259        }
260    
261        /**
262         * Substitute any %reference% in any property value with the value of the
263         * referenced property, recursively.
264         */
265        private void substituteReferences() {
266            boolean found;
267            do {
268                found = false;
269                Enumeration keys = properties.keys();
270                while (keys.hasMoreElements()) {
271                    String key = (String) (keys.nextElement());
272                    String value = properties.getProperty(key, "");
273                    int pos1 = value.indexOf("%");
274                    if (pos1 >= 0) {
275                        int pos2 = value.indexOf("%", pos1 + 1);
276                        if (pos2 == -1)
277                            continue;
278    
279                        String ref = value.substring(pos1 + 1, pos2);
280                        String refValue = properties.getProperty(ref, null);
281                        if (refValue == null)
282                            continue;
283    
284                        found = true;
285                        value = value.substring(0, pos1) + refValue + value.substring(pos2 + 1);
286                        properties.setProperty(key, value);
287                    }
288                }
289            } while (found);
290        }
291    
292        /**
293         * Loads file deprecated.properties that can be used to rename old properties.
294         * The file contains a list of renamed properties: OldPropertyName=NewPropertyName.
295         * The old property is automatically replaced with the new name, so that
296         * existing mycore.properties files must not be migrated immediately. Users get a
297         * warning when their configuration still contains deprecated properties. 
298         */
299        private void substituteDeprecatedProperties() {
300            InputStream in = this.getClass().getResourceAsStream("/deprecated.properties");
301            if (in == null)
302                return;
303            try {
304                depr.load(in);
305                in.close();
306            } catch (Exception exc) {
307                throw new MCRConfigurationException("Could not load configuration file deprecated.properties", exc);
308            }
309    
310            Enumeration names = depr.keys();
311            while (names.hasMoreElements()) {
312                String deprecatedName = (String) (names.nextElement());
313                if (properties.containsKey(deprecatedName)) {
314                    String newName = depr.getProperty(deprecatedName);
315                    String msg = "DEPRECATED: User should rename property " + deprecatedName + " to " + newName;
316                    Logger.getLogger(this.getClass()).warn(msg);
317                    if (!properties.containsKey(newName))
318                        properties.put(newName, properties.get(deprecatedName));
319                }
320            }
321        }
322    
323        /**
324         * Loads configuration properties from a specified properties file and adds
325         * them to the properties currently set. This method scans the <CODE>
326         * CLASSPATH</CODE> for the properties file, it may be a plain file, but
327         * may also be located in a zip or jar file. If the properties file contains
328         * a property called <CODE>MCR.Configuration.Include</CODE>, the files
329         * specified in that property will also be read. Multiple include files have
330         * to be separated by spaces or colons.
331         * 
332         * @param filename
333         *            the properties file to be loaded
334         * @throws MCRConfigurationException
335         *             if the file can not be loaded
336         */
337        private void loadFromFile(String filename) {
338            File mycoreProperties = new File(filename);
339            InputStream in;
340            if (mycoreProperties.canRead()) {
341                try {
342                    in = new FileInputStream(mycoreProperties);
343                } catch (FileNotFoundException e) {
344                    // should never happend, because we verified it allready with canRead() above
345                    String msg = "Could not find configuration file " + filename;
346                    throw new MCRConfigurationException(msg, e);
347                }
348            } else {
349                in = this.getClass().getResourceAsStream("/" + filename);
350            }
351            if (in == null) {
352                String msg = "Could not find configuration file " + filename + " in CLASSPATH";
353                throw new MCRConfigurationException(msg);
354            }
355    
356            try {
357                properties.load(in);
358                in.close();
359            } catch (Exception exc) {
360                throw new MCRConfigurationException("Could not load configuration file " + filename, exc);
361            }
362    
363            String include = getString("MCR.Configuration.Include", null);
364    
365            if (include != null) {
366                StringTokenizer st = new StringTokenizer(include, ", ");
367                set("MCR.Configuration.Include", null);
368    
369                while (st.hasMoreTokens())
370                    loadFromFile(st.nextToken());
371            }
372        }
373    
374        /**
375         * Returns all the properties
376         * 
377         * @return the list of properties
378         */
379        public Properties getProperties() {
380            return properties;
381        }
382    
383        /**
384         * Returns all the properties beginning with the specified string
385         * 
386         * @param startsWith
387         *            the string all the returned properties start with
388         * @return the list of properties
389         */
390        public Properties getProperties(String startsWith) {
391            Properties properties = new Properties();
392    
393            Enumeration names = this.properties.propertyNames();
394    
395            while (names.hasMoreElements()) {
396                String name = (String) (names.nextElement());
397    
398                if (name.startsWith(startsWith)) {
399                    String value = this.properties.getProperty(name);
400                    properties.setProperty(name, value);
401                }
402            }
403    
404            return properties;
405        }
406    
407        /**
408         * Configures Log4J based on the log4j properties
409         */
410        public synchronized void configureLogging() {
411            Properties prop = new Properties();
412            Enumeration names = this.properties.propertyNames();
413            boolean reconfigure = false;
414            java.util.List<String> warn = new java.util.ArrayList<String>();
415    
416            while (names.hasMoreElements()) {
417                String name = (String) (names.nextElement());
418                if (!name.contains("log4j"))
419                    continue;
420                String value = this.properties.getProperty(name);
421                if (name.startsWith("MCR.log4j")) {
422                    warn.add(name);
423                    name = name.substring(4);
424                }
425                if (name.startsWith("log4j")) {
426                    prop.setProperty(name, value);
427                    reconfigure = true;
428                }
429            }
430    
431            if (reconfigure) {
432                System.out.println("MCRConfiguration reconfiguring Log4J logging...");
433                org.apache.log4j.LogManager.resetConfiguration();
434                PropertyConfigurator.configure(prop);
435                for (String name : warn) {
436                    Logger logger = Logger.getLogger(this.getClass());
437                    logger.warn("DEPRECATED: User should rename property " + name + " to " + name.substring(4));
438                }
439            }
440        }
441    
442        /**
443         * Returns a new instance of the class specified in the configuration
444         * property with the given name.
445         * 
446         * @param name
447         *            the non-null and non-empty name of the configuration property
448         * @return the value of the configuration property as a String, or null
449         * @throws MCRConfigurationException
450         *             if the property is not set or the class can not be loaded or
451         *             instantiated
452         */
453        public Object getInstanceOf(String name, String defaultname) throws MCRConfigurationException {
454            String classname = getString(name, defaultname);
455            if (classname == null) {
456                throw new MCRConfigurationException("Configuration property missing: " + name);
457            }
458            
459            Logger.getLogger(this.getClass()).debug("Loading Class: " + classname);
460    
461            Class cl;
462            try {
463                cl = Class.forName(classname);
464            } catch (Exception ex) {
465                throw new MCRConfigurationException("Could not load class " + classname, ex);
466            }
467    
468            Object o = null;
469    
470            try {
471                try {
472                    o = cl.newInstance();
473                } catch (Exception e) {
474                    if (e instanceof FilterPluginInstantiationException)
475                        Logger.getLogger(this.getClass()).info(e.toString());
476                    // check for singleton
477                    Method[] querymethods = cl.getMethods();
478    
479                    for (int i = 0; i < querymethods.length; i++) {
480                        if (querymethods[i].getName().toLowerCase().equals("instance")
481                                || querymethods[i].getName().toLowerCase().equals("getinstance")) {
482                            Object[] ob = new Object[0];
483                            o = querymethods[i].invoke(cl, ob);
484    
485                            break;
486                        }
487                    }
488                }
489            } catch (Throwable t) {
490                String msg = "Could not instantiate class " + classname;
491    
492                if (t instanceof ExceptionInInitializerError) {
493                    Throwable t2 = ((ExceptionInInitializerError) t).getException();
494    
495                    if (t2 instanceof Exception) {
496                        throw new MCRConfigurationException(msg, (Exception) t2);
497                    }
498    
499                    throw new MCRConfigurationException(msg + ": " + t2.getClass().getName() + " - " + t2.getMessage());
500                } else if (t instanceof Exception) {
501                    t.printStackTrace();
502                    throw new MCRConfigurationException(msg, (Exception) t);
503                } else {
504                    msg += (" because of: " + t.getMessage());
505                    msg += ("\n" + MCRException.getStackTraceAsString(t));
506                    throw new MCRConfigurationException(msg);
507                }
508            }
509    
510            return o;
511        }
512    
513        /**
514         * Returns a new instance of the class specified in the configuration
515         * property with the given name.
516         * 
517         * @param name
518         *            the non-null and non-empty name of the configuration property
519         * @return the value of the configuration property as a String, or null
520         * @throws MCRConfigurationException
521         *             if the property is not set or the class can not be loaded or
522         *             instantiated
523         */
524        public Object getInstanceOf(String name) throws MCRConfigurationException {
525            return getInstanceOf(name, null);
526        }
527    
528        /**
529         * Returns a instance of the class specified in the configuration property
530         * with the given name. If the class was prevously instantiated by this
531         * method this instance is returned.
532         * 
533         * @param name
534         *            the non-null and non-empty name of the configuration property
535         * @return the instance of the class named by the value of the configuration
536         *         propertyl
537         * @throws MCRConfigurationException
538         *             if the property is not set or the class can not be loaded or
539         *             instantiated
540         */
541        public Object getSingleInstanceOf(String name, String defaultname) throws MCRConfigurationException {
542            if (instanceHolder == null) {
543                instanceHolder = new Hashtable(); // initialize the hashtable if it's not yet
544            } else if (instanceHolder.containsKey(name)) {
545                return instanceHolder.get(name); // we have an instance allready, return it
546            }
547    
548            Object inst = getInstanceOf(name, defaultname); // we need a new instance, get it
549            instanceHolder.put(name, inst); // save the instance in the hashtable
550    
551            return inst;
552        }
553    
554        /**
555         * Returns a instance of the class specified in the configuration property
556         * with the given name. If the class was prevously instantiated by this
557         * method this instance is returned.
558         * 
559         * @param name
560         *            non-null and non-empty name of the configuration property
561         * @return the instance of the class named by the value of the configuration
562         *         propertyl
563         * @throws MCRConfigurationException
564         *             if the property is not set or the class can not be loaded or
565         *             instantiated
566         */
567        public Object getSingleInstanceOf(String name) {
568            return getSingleInstanceOf(name, null);
569        }
570    
571        /**
572         * Returns the configuration property with the specified name as a String.
573         * 
574         * @param name
575         *            the non-null and non-empty name of the configuration property
576         * @return the value of the configuration property as a String
577         * @throws MCRConfigurationException
578         *             if the property with this name is not set
579         */
580        public String getString(String name) {
581            String value = getString(name, null);
582    
583            if (value == null) {
584                throw new MCRConfigurationException("Configuration property " + name + " is not set");
585            }
586    
587            return value.trim();
588        }
589    
590        /**
591         * Returns the configuration property with the specified name as a String,
592         * or returns a given default value if the property is not set.
593         * 
594         * @param name
595         *            the non-null and non-empty name of the configuration property
596         * @param defaultValue
597         *            the value to return if the configuration property is not set
598         * @return the value of the configuration property as a String
599         */
600        public String getString(String name, String defaultValue) {
601            if (depr.containsKey(name)) {
602                String msg = "DEPRECATED: Developer should rename property " + name + " to " + depr.getProperty(name);
603                Logger.getLogger(this.getClass()).warn(msg);
604            }
605    
606            String value = properties.getProperty(name);
607            if ((value == null) && depr.containsKey(name))
608                value = properties.getProperty(depr.getProperty(name));
609    
610            return (value == null ? defaultValue : value.trim());
611        }
612    
613        /**
614         * Returns the configuration property with the specified name as an <CODE>
615         * int</CODE> value.
616         * 
617         * @param name
618         *            the non-null and non-empty name of the configuration property
619         * @return the value of the configuration property as an <CODE>int</CODE>
620         *         value
621         * @throws NumberFormatException
622         *             if the configuration property is not an <CODE>int</CODE>
623         *             value
624         * @throws MCRConfigurationException
625         *             if the property with this name is not set
626         */
627        public int getInt(String name) throws NumberFormatException {
628            return Integer.parseInt(getString(name));
629        }
630    
631        /**
632         * Returns the configuration property with the specified name as an <CODE>
633         * int</CODE> value, or returns a given default value if the property is
634         * not set.
635         * 
636         * @param name
637         *            the non-null and non-empty name of the configuration property
638         *            /** Returns the configuration property with the specified name
639         *            as an <CODE>int</CODE> value, or returns a given default
640         *            value if the property is not set.
641         * @param defaultValue
642         *            the value to return if the configuration property is not set
643         * @return the value of the specified property as an <CODE>int</CODE>
644         *         value
645         * @throws NumberFormatException
646         *             if the configuration property is set but is not an <CODE>int
647         *             </CODE> value
648         */
649        public int getInt(String name, int defaultValue) throws NumberFormatException {
650            String value = getString(name, null);
651    
652            return ((value == null) ? defaultValue : Integer.parseInt(value));
653        }
654    
655        /**
656         * Returns the configuration property with the specified name as a <CODE>
657         * long</CODE> value.
658         * 
659         * @param name
660         *            the non-null and non-empty name of the configuration property
661         * @return the value of the configuration property as a <CODE>long</CODE>
662         *         value
663         * @throws NumberFormatException
664         *             if the configuration property is not a <CODE>long</CODE>
665         *             value
666         * @throws MCRConfigurationException
667         *             if the property with this name is not set
668         */
669        public long getLong(String name) throws NumberFormatException {
670            return Long.parseLong(getString(name));
671        }
672    
673        /**
674         * Returns the configuration property with the specified name as a <CODE>
675         * long</CODE> value, or returns a given default value if the property is
676         * not set.
677         * 
678         * @return the value of the specified property as a <CODE>long</CODE>
679         *         value
680         * @param name
681         *            the non-null and non-empty name of the configuration property
682         * @param defaultValue
683         *            the value to return if the configuration property is not set
684         * @throws NumberFormatException
685         *             if the configuration property is set but is not a <CODE>long
686         *             </CODE> value
687         */
688        public long getLong(String name, long defaultValue) throws NumberFormatException {
689            String value = getString(name, null);
690    
691            return ((value == null) ? defaultValue : Long.parseLong(value));
692        }
693    
694        /**
695         * Returns the configuration property with the specified name as a <CODE>
696         * float</CODE> value.
697         * 
698         * @param name
699         *            the non-null and non-empty name of the configuration property
700         * @return the value of the configuration property as a <CODE>float</CODE>
701         *         value
702         * @throws NumberFormatException
703         *             if the configuration property is not a <CODE>float</CODE>
704         *             value
705         * @throws MCRConfigurationException
706         *             if the property with this name is not set
707         */
708        public float getFloat(String name) throws NumberFormatException {
709            return Float.parseFloat(getString(name));
710        }
711    
712        /**
713         * Returns the configuration property with the specified name as a <CODE>
714         * float</CODE> value, or returns a given default value if the property is
715         * not set.
716         * 
717         * @return the value of the specified property as a <CODE>float</CODE>
718         *         value
719         * @param name
720         *            the non-null and non-empty name of the configuration property
721         * @param defaultValue
722         *            the value to return if the configuration property is not set
723         * @throws NumberFormatException
724         *             if the configuration property is set but is not a <CODE>
725         *             float</CODE> value
726         */
727        public float getFloat(String name, float defaultValue) throws NumberFormatException {
728            String value = getString(name, null);
729    
730            return ((value == null) ? defaultValue : Float.parseFloat(value));
731        }
732    
733        /**
734         * Returns the configuration property with the specified name as a <CODE>
735         * double</CODE> value.
736         * 
737         * @param name
738         *            the non-null and non-empty name of the configuration property
739         * @return the value of the configuration property as a <CODE>double
740         *         </CODE> value
741         * @throws NumberFormatException
742         *             if the configuration property is not a <CODE>double</CODE>
743         *             value
744         * @throws MCRConfigurationException
745         *             if the property with this name is not set
746         */
747        public double getDouble(String name) throws NumberFormatException {
748            return Double.parseDouble(getString(name));
749        }
750    
751        /**
752         * Returns the configuration property with the specified name as a <CODE>
753         * double</CODE> value, or returns a given default value if the property is
754         * not set.
755         * 
756         * @return the value of the specified property as a <CODE>double</CODE>
757         *         value
758         * @param name
759         *            the non-null and non-empty name of the configuration property
760         * @param defaultValue
761         *            the value to return if the configuration property is not set
762         * @throws NumberFormatException
763         *             if the configuration property is set but is not a <CODE>
764         *             double</CODE> value
765         */
766        public double getDouble(String name, double defaultValue) throws NumberFormatException {
767            String value = getString(name, null);
768    
769            return ((value == null) ? defaultValue : Double.parseDouble(value));
770        }
771    
772        /**
773         * Returns the configuration property with the specified name as a <CODE>
774         * boolean</CODE> value.
775         * 
776         * @param name
777         *            the non-null and non-empty name of the configuration property
778         * @return <CODE>true</CODE>, if and only if the specified property has
779         *         the value <CODE>true</CODE>
780         * @throws MCRConfigurationException
781         *             if the property with this name is not set
782         */
783        public boolean getBoolean(String name) {
784            String value = getString(name);
785    
786            return "true".equals(value.trim());
787        }
788    
789        /**
790         * Returns the configuration property with the specified name as a <CODE>
791         * boolean</CODE> value, or returns a given default value if the property
792         * is not set. If the property is set and its value is not <CODE>true
793         * </CODE>, then <code>false</code> is returned.
794         * 
795         * @return the value of the specified property as a <CODE>boolean</CODE>
796         *         value
797         * @param name
798         *            the non-null and non-empty name of the configuration property
799         * @param defaultValue
800         *            the value to return if the configuration property is not set
801         */
802        public boolean getBoolean(String name, boolean defaultValue) {
803            String value = getString(name, null);
804    
805            return ((value == null) ? defaultValue : "true".equals(value.trim()));
806        }
807    
808        /**
809         * Sets the configuration property with the specified name to a new <CODE>
810         * String</CODE> value. If the parameter <CODE>value</CODE> is <CODE>
811         * null</CODE>, the property will be deleted.
812         * 
813         * @param name
814         *            the non-null and non-empty name of the configuration property
815         * @param value
816         *            the new value of the configuration property, possibly <CODE>
817         *            null</CODE>
818         */
819        public void set(String name, String value) {
820            if (value == null) {
821                properties.remove(name);
822            } else {
823                properties.setProperty(name, value);
824            }
825        }
826    
827        /**
828         * Sets the configuration property with the specified name to a new <CODE>
829         * int</CODE> value.
830         * 
831         * @param name
832         *            the non-null and non-empty name of the configuration property
833         * @param value
834         *            the new value of the configuration property
835         */
836        public void set(String name, int value) {
837            set(name, String.valueOf(value));
838        }
839    
840        /**
841         * Sets the configuration property with the specified name to a new <CODE>
842         * long</CODE> value.
843         * 
844         * @param name
845         *            the non-null and non-empty name of the configuration property
846         * @param value
847         *            the new value of the configuration property
848         */
849        public void set(String name, long value) {
850            set(name, String.valueOf(value));
851        }
852    
853        /**
854         * Sets the configuration property with the specified name to a new <CODE>
855         * float</CODE> value.
856         * 
857         * @param name
858         *            the non-null and non-empty name of the configuration property
859         * @param value
860         *            the new value of the configuration property
861         */
862        public void set(String name, float value) {
863            set(name, String.valueOf(value));
864        }
865    
866        /**
867         * Sets the configuration property with the specified name to a new <CODE>
868         * double</CODE> value.
869         * 
870         * @param name
871         *            the non-null and non-empty name of the configuration property
872         * @param value
873         *            the new value of the configuration property
874         */
875        public void set(String name, double value) {
876            set(name, String.valueOf(value));
877        }
878    
879        /**
880         * Sets the configuration property with the specified name to a new <CODE>
881         * boolean</CODE> value.
882         * 
883         * @param name
884         *            the non-null and non-empty name of the configuration property
885         * @param value
886         *            the new value of the configuration property
887         */
888        public void set(String name, boolean value) {
889            set(name, String.valueOf(value));
890        }
891    
892        /**
893         * Lists all configuration properties currently set to a PrintStream. Useful
894         * for debugging, e. g. by calling
895         * 
896         * <P>
897         * <CODE>MCRConfiguration.instance().list( System.out );</CODE>
898         * </P>
899         * 
900         * @see java.util.Properties#list( PrintStream )
901         * 
902         * @param out
903         *            the PrintStream to list the configuration properties on
904         */
905        public void list(PrintStream out) {
906            properties.list(out);
907        }
908    
909        /**
910         * Lists all configuration properties currently set to a PrintWriter. Useful
911         * for debugging.
912         * 
913         * @see java.util.Properties#list( PrintWriter )
914         * 
915         * @param out
916         *            the PrintWriter to list the configuration properties on
917         */
918        public void list(PrintWriter out) {
919            properties.list(out);
920        }
921    
922        /**
923         * Stores all configuration properties currently set to an OutputStream.
924         * 
925         * @see java.util.Properties#store
926         * 
927         * @param out
928         *            the OutputStream to write the configuration properties to
929         * @param header
930         *            the header to prepend before writing the list of properties
931         * @throws IOException
932         *             if writing to the OutputStream throws an <CODE>IOException
933         *             </CODE>
934         */
935        public void store(OutputStream out, String header) throws IOException {
936            properties.store(out, header);
937        }
938    
939        /**
940         * Returns a String containing the configuration properties currently set.
941         * Useful for debugging, e. g. by calling
942         * 
943         * <P>
944         * <CODE>System.out.println( MCRConfiguration.instance() );</CODE>
945         * </P>
946         * 
947         * @return a String containing the configuration properties currently set
948         */
949        public String toString() {
950            return properties.toString();
951        }
952    }