001: /*
002: * ConfigurationManager.java
003: *
004: * Version: $Revision: 2695 $
005: *
006: * Date: $Date: 2008-02-20 11:41:17 -0600 (Wed, 20 Feb 2008) $
007: *
008: * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts
009: * Institute of Technology. All rights reserved.
010: *
011: * Redistribution and use in source and binary forms, with or without
012: * modification, are permitted provided that the following conditions are
013: * met:
014: *
015: * - Redistributions of source code must retain the above copyright
016: * notice, this list of conditions and the following disclaimer.
017: *
018: * - Redistributions in binary form must reproduce the above copyright
019: * notice, this list of conditions and the following disclaimer in the
020: * documentation and/or other materials provided with the distribution.
021: *
022: * - Neither the name of the Hewlett-Packard Company nor the name of the
023: * Massachusetts Institute of Technology nor the names of their
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
030: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
032: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
033: * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
034: * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
035: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
036: * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
037: * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
038: * DAMAGE.
039: */
040: package org.dspace.core;
041:
042: import java.io.BufferedReader;
043: import java.io.File;
044: import java.io.FileInputStream;
045: import java.io.FileOutputStream;
046: import java.io.FileReader;
047: import java.io.IOException;
048: import java.io.InputStream;
049: import java.io.InputStreamReader;
050: import java.io.OutputStreamWriter;
051: import java.io.PrintWriter;
052: import java.net.MalformedURLException;
053: import java.net.URL;
054: import java.util.Enumeration;
055: import java.util.Properties;
056:
057: import javax.servlet.ServletContextEvent;
058: import javax.servlet.ServletContextListener;
059:
060: import org.apache.log4j.Category;
061: import org.apache.log4j.Logger;
062: import org.apache.log4j.helpers.OptionConverter;
063:
064: /**
065: * Class for reading the DSpace system configuration. The main configuration is
066: * read in as properties from a standard properties file. Email templates and
067: * configuration files for other tools are also be accessed via this class.
068: * <P>
069: * The main configuration is by default read from the <em>resource</em>
070: * <code>/dspace.cfg</code>.
071: * To specify a different configuration, the system property
072: * <code>dspace.configuration</code> should be set to the <em>filename</em>
073: * of the configuration file.
074: * <P>
075: * Other configuration files are read from the <code>config</code> directory
076: * of the DSpace installation directory (specified as the property
077: * <code>dspace.dir</code> in the main configuration file.)
078: *
079: *
080: * @author Robert Tansley
081: * @author Larry Stone - Interpolated values.
082: * @version $Revision: 2695 $
083: */
084: public class ConfigurationManager {
085: /** log4j category */
086: private static Logger log = Logger
087: .getLogger(ConfigurationManager.class);
088:
089: /** The configuration properties */
090: private static Properties properties = null;
091:
092: /** The default license */
093: private static String license;
094:
095: // limit of recursive depth of property variable interpolation in
096: // configuration; anything greater than this is very likely to be a loop.
097: private final static int RECURSION_LIMIT = 9;
098:
099: /**
100: *
101: */
102: public static Properties getProperties() {
103: if (properties == null) {
104: loadConfig(null);
105: }
106:
107: return (Properties) properties.clone();
108: }
109:
110: /**
111: * Get a configuration property
112: *
113: * @param property
114: * the name of the property
115: *
116: * @return the value of the property, or <code>null</code> if the property
117: * does not exist.
118: */
119: public static String getProperty(String property) {
120: if (properties == null) {
121: loadConfig(null);
122: }
123:
124: return properties.getProperty(property);
125: }
126:
127: /**
128: * Get a configuration property as an integer
129: *
130: * @param property
131: * the name of the property
132: *
133: * @return the value of the property. <code>0</code> is returned if the
134: * property does not exist. To differentiate between this case and
135: * when the property actually is zero, use <code>getProperty</code>.
136: */
137: public static int getIntProperty(String property) {
138: if (properties == null) {
139: loadConfig(null);
140: }
141:
142: String stringValue = properties.getProperty(property);
143: int intValue = 0;
144:
145: if (stringValue != null) {
146: try {
147: intValue = Integer.parseInt(stringValue.trim());
148: } catch (NumberFormatException e) {
149: warn("Warning: Number format error in property: "
150: + property);
151: }
152: }
153:
154: return intValue;
155: }
156:
157: /**
158: * Get the License
159: *
160: * @param
161: * license file name
162: *
163: * @return
164: * license text
165: *
166: */
167: public static String getLicenseText(String licenseFile) {
168: // Load in default license
169:
170: try {
171: BufferedReader br = new BufferedReader(new FileReader(
172: licenseFile));
173: String lineIn;
174: license = "";
175: while ((lineIn = br.readLine()) != null) {
176: license = license + lineIn + '\n';
177: }
178: } catch (IOException e) {
179: fatal("Can't load configuration", e);
180:
181: // FIXME: Maybe something more graceful here, but with the
182: // configuration we can't do anything
183: System.exit(1);
184: }
185: return license;
186: }
187:
188: /**
189: * Get a configuration property as a boolean. True is indicated if the value
190: * of the property is <code>TRUE</code> or <code>YES</code> (case
191: * insensitive.)
192: *
193: * @param property
194: * the name of the property
195: *
196: * @return the value of the property. <code>false</code> is returned if
197: * the property does not exist. To differentiate between this case
198: * and when the property actually is false, use
199: * <code>getProperty</code>.
200: */
201: public static boolean getBooleanProperty(String property) {
202: return getBooleanProperty(property, false);
203: }
204:
205: /**
206: * Get a configuration property as a boolean, with default.
207: * True is indicated if the value
208: * of the property is <code>TRUE</code> or <code>YES</code> (case
209: * insensitive.)
210: *
211: * @param property
212: * the name of the property
213: *
214: * @param defaultValue
215: * value to return if property is not found.
216: *
217: * @return the value of the property. <code>default</code> is returned if
218: * the property does not exist. To differentiate between this case
219: * and when the property actually is false, use
220: * <code>getProperty</code>.
221: */
222: public static boolean getBooleanProperty(String property,
223: boolean defaultValue) {
224: if (properties == null) {
225: loadConfig(null);
226: }
227:
228: String stringValue = properties.getProperty(property);
229:
230: if (stringValue != null) {
231: stringValue = stringValue.trim();
232: return stringValue.equalsIgnoreCase("true")
233: || stringValue.equalsIgnoreCase("yes");
234: } else {
235: return defaultValue;
236: }
237: }
238:
239: /**
240: * Returns an enumeration of all the keys in the DSpace configuration
241: *
242: * @return an enumeration of all the keys in the DSpace configuration
243: */
244: public static Enumeration propertyNames() {
245: if (properties == null)
246: loadConfig(null);
247:
248: return properties.propertyNames();
249: }
250:
251: /**
252: * Get the template for an email message. The message is suitable for
253: * inserting values using <code>java.text.MessageFormat</code>.
254: *
255: * @param emailFile
256: * full name for the email template, for example "/dspace/config/emails/register".
257: *
258: * @return the email object, with the content and subject filled out from
259: * the template
260: *
261: * @throws IOException
262: * if the template couldn't be found, or there was some other
263: * error reading the template
264: */
265: public static Email getEmail(String emailFile) throws IOException {
266: String charset = null;
267: String subject = "";
268: StringBuffer contentBuffer = new StringBuffer();
269:
270: // Read in template
271: BufferedReader reader = null;
272: try {
273: reader = new BufferedReader(new FileReader(emailFile));
274:
275: boolean more = true;
276:
277: while (more) {
278: String line = reader.readLine();
279:
280: if (line == null) {
281: more = false;
282: } else if (line.toLowerCase().startsWith("subject:")) {
283: // Extract the first subject line - everything to the right
284: // of the colon, trimmed of whitespace
285: subject = line.substring(8).trim();
286: } else if (line.toLowerCase().startsWith("charset:")) {
287: // Extract the character set from the email
288: charset = line.substring(8).trim();
289: } else if (!line.startsWith("#")) {
290: // Add non-comment lines to the content
291: contentBuffer.append(line);
292: contentBuffer.append("\n");
293: }
294: }
295: } finally {
296: if (reader != null) {
297: reader.close();
298: }
299: }
300: // Create an email
301: Email email = new Email();
302: email.setSubject(subject);
303: email.setContent(contentBuffer.toString());
304:
305: if (charset != null)
306: email.setCharset(charset);
307:
308: return email;
309: }
310:
311: /**
312: * Get the site-wide default license that submitters need to grant
313: *
314: * @return the default license
315: */
316: public static String getDefaultSubmissionLicense() {
317: if (properties == null) {
318: loadConfig(null);
319: }
320:
321: return license;
322: }
323:
324: /**
325: * Get the path for the news files.
326: *
327: */
328: public static String getNewsFilePath() {
329: String filePath = ConfigurationManager
330: .getProperty("dspace.dir")
331: + File.separator + "config" + File.separator;
332:
333: return filePath;
334: }
335:
336: /**
337: * Reads news from a text file.
338: *
339: * @param position
340: * a constant indicating which file (top or side) should be read
341: * in.
342: */
343: public static String readNewsFile(String newsFile) {
344: String fileName = getNewsFilePath();
345:
346: fileName += newsFile;
347:
348: String text = "";
349:
350: try {
351: // retrieve existing news from file
352: FileInputStream fir = new FileInputStream(fileName);
353: InputStreamReader ir = new InputStreamReader(fir, "UTF-8");
354: BufferedReader br = new BufferedReader(ir);
355:
356: String lineIn;
357:
358: while ((lineIn = br.readLine()) != null) {
359: text += lineIn;
360: }
361:
362: br.close();
363: } catch (IOException e) {
364: warn("news_read: " + e.getLocalizedMessage());
365: }
366:
367: return text;
368: }
369:
370: /**
371: * Writes news to a text file.
372: *
373: * @param position
374: * a constant indicating which file (top or side) should be
375: * written to.
376: *
377: * @param news
378: * the text to be written to the file.
379: */
380: public static String writeNewsFile(String newsFile, String news) {
381: String fileName = getNewsFilePath();
382:
383: fileName += newsFile;
384:
385: try {
386: // write the news out to the appropriate file
387: FileOutputStream fos = new FileOutputStream(fileName);
388: OutputStreamWriter osr = new OutputStreamWriter(fos,
389: "UTF-8");
390: PrintWriter out = new PrintWriter(osr);
391: out.print(news);
392: out.close();
393: } catch (IOException e) {
394: warn("news_write: " + e.getLocalizedMessage());
395: }
396:
397: return news;
398: }
399:
400: /**
401: * Writes license to a text file.
402: *
403: * @param news
404: * the text to be written to the file.
405: */
406: public static void writeLicenseFile(String newLicense) {
407: String licenseFile = getProperty("dspace.dir") + File.separator
408: + "config" + File.separator + "default.license";
409:
410: try {
411: // write the news out to the appropriate file
412: FileOutputStream fos = new FileOutputStream(licenseFile);
413: OutputStreamWriter osr = new OutputStreamWriter(fos,
414: "UTF-8");
415: PrintWriter out = new PrintWriter(osr);
416: out.print(newLicense);
417: out.close();
418: } catch (IOException e) {
419: warn("license_write: " + e.getLocalizedMessage());
420: }
421:
422: license = newLicense;
423: }
424:
425: private static File loadedFile = null;
426:
427: /**
428: * Return the file that configuration was actually loaded from. Only returns
429: * a valid File after configuration has been loaded.
430: *
431: * @deprecated Please remove all direct usage of the configuration file.
432: * @return File naming configuration data file, or null if not loaded yet.
433: */
434: protected static File getConfigurationFile() {
435: // in case it hasn't been done yet.
436: loadConfig(null);
437:
438: return loadedFile;
439: }
440:
441: /**
442: * Load the DSpace configuration properties. Only does anything if
443: * properties are not already loaded. Properties are loaded in from the
444: * specified file, or default locations.
445: *
446: * @param configFile
447: * The <code>dspace.cfg</code> configuration file to use, or
448: * <code>null</code> to try default locations
449: */
450: public static void loadConfig(String configFile) {
451:
452: if (properties != null) {
453: return;
454: }
455:
456: URL url = null;
457:
458: try {
459: String configProperty = System
460: .getProperty("dspace.configuration");
461:
462: if (configFile != null) {
463: info("Loading provided config file: " + configFile);
464:
465: loadedFile = new File(configFile);
466: url = loadedFile.toURL();
467:
468: }
469: // Has the default configuration location been overridden?
470: else if (configProperty != null) {
471: info("Loading system provided config property (-Ddspace.configuration): "
472: + configProperty);
473:
474: // Load the overriding configuration
475: loadedFile = new File(configProperty);
476: url = loadedFile.toURL();
477: }
478: // Load configuration from default location
479: else {
480: url = ConfigurationManager.class
481: .getResource("/dspace.cfg");
482: if (url != null) {
483: info("Loading from classloader: " + url);
484:
485: loadedFile = new File(url.getPath());
486: }
487: }
488:
489: if (url == null) {
490: fatal("Cannot find dspace.cfg");
491: throw new RuntimeException("Cannot find dspace.cfg");
492: } else {
493: properties = new Properties();
494: properties.load(url.openStream());
495:
496: // walk values, interpolating any embedded references.
497: for (Enumeration pe = properties.propertyNames(); pe
498: .hasMoreElements();) {
499: String key = (String) pe.nextElement();
500: String value = interpolate(key, 1);
501: if (value != null)
502: properties.setProperty(key, value);
503: }
504: }
505:
506: } catch (IOException e) {
507: fatal("Can't load configuration: " + url, e);
508:
509: // FIXME: Maybe something more graceful here, but with the
510: // configuration we can't do anything
511: throw new RuntimeException("Cannot load configuration: "
512: + url, e);
513: }
514:
515: // Load in default license
516: File licenseFile = new File(getProperty("dspace.dir")
517: + File.separator + "config" + File.separator
518: + "default.license");
519: try {
520:
521: FileInputStream fir = new FileInputStream(licenseFile);
522: InputStreamReader ir = new InputStreamReader(fir, "UTF-8");
523: BufferedReader br = new BufferedReader(ir);
524: String lineIn;
525: license = "";
526:
527: while ((lineIn = br.readLine()) != null) {
528: license = license + lineIn + '\n';
529: }
530:
531: br.close();
532:
533: } catch (IOException e) {
534: fatal("Can't load license: " + licenseFile.toString(), e);
535:
536: // FIXME: Maybe something more graceful here, but with the
537: // configuration we can't do anything
538: throw new RuntimeException("Cannot load license: "
539: + licenseFile.toString(), e);
540: }
541:
542: try {
543: /*
544: * Initialize Logging once ConfigurationManager is initialized.
545: *
546: * This is selection from a property in dspace.cfg, if the property
547: * is absent then nothing will be configured and the application
548: * will use the defaults provided by log4j.
549: *
550: * Property format is:
551: *
552: * log.init.config = ${dspace.dir}/config/log4j.properties
553: * or
554: * log.init.config = ${dspace.dir}/config/log4j.xml
555: *
556: * See default log4j initialization documentation here:
557: * http://logging.apache.org/log4j/docs/manual.html
558: *
559: * If there is a problem with the file referred to in
560: * "log.configuration" it needs to be sent to System.err
561: * so do not instantiate another Logging configuration.
562: *
563: */
564: String dsLogConfiguration = ConfigurationManager
565: .getProperty("log.init.config");
566:
567: if (dsLogConfiguration == null
568: || System.getProperty("dspace.log.init.disable") != null) {
569: /*
570: * Do nothing if log config not set in dspace.cfg or "dspace.log.init.disable"
571: * system property set. Leave it upto log4j to properly init its logging
572: * via classpath or system properties.
573: */
574: info("Using default log4j provided log configuration,"
575: + "if uninitended, check your dspace.cfg for (log.init.config)");
576: } else {
577: info("Using dspace provided log configuration (log.init.config)");
578:
579: File logConfigFile = new File(dsLogConfiguration);
580:
581: if (logConfigFile.exists()) {
582: info("Loading: " + dsLogConfiguration);
583:
584: OptionConverter.selectAndConfigure(logConfigFile
585: .toURL(), null, org.apache.log4j.LogManager
586: .getLoggerRepository());
587: } else {
588: info("File does not exist: " + dsLogConfiguration);
589: }
590: }
591:
592: } catch (MalformedURLException e) {
593: fatal("Can't load dspace provided log4j configuration", e);
594: throw new RuntimeException(
595: "Cannot load dspace provided log4j configuration",
596: e);
597: }
598:
599: }
600:
601: /**
602: * Recursively interpolate variable references in value of
603: * property named "key".
604: * @return new value if it contains interpolations, or null
605: * if it had no variable references.
606: */
607: private static String interpolate(String key, int level) {
608: if (level > RECURSION_LIMIT)
609: throw new IllegalArgumentException(
610: "ConfigurationManager: Too many levels of recursion in configuration property variable interpolation, property="
611: + key);
612: String value = (String) properties.get(key);
613: int from = 0;
614: StringBuffer result = null;
615: while (from < value.length()) {
616: int start = value.indexOf("${", from);
617: if (start >= 0) {
618: int end = value.indexOf("}", start);
619: if (end < 0)
620: break;
621: String var = value.substring(start + 2, end);
622: if (result == null)
623: result = new StringBuffer(value.substring(from,
624: start));
625: else
626: result.append(value.substring(from, start));
627: if (properties.containsKey(var)) {
628: String ivalue = interpolate(var, level + 1);
629: if (ivalue != null) {
630: result.append(ivalue);
631: properties.setProperty(var, ivalue);
632: } else
633: result.append((String) properties
634: .getProperty(var));
635: } else {
636: log
637: .warn("Interpolation failed in value of property \""
638: + key
639: + "\", there is no property named \""
640: + var + "\"");
641: }
642: from = end + 1;
643: } else
644: break;
645: }
646: if (result != null && from < value.length())
647: result.append(value.substring(from));
648: return (result == null) ? null : result.toString();
649: }
650:
651: /**
652: * Command-line interface for running configuration tasks. Possible
653: * arguments:
654: * <ul>
655: * <li><code>-property name</code> prints the value of the property
656: * <code>name</code> from <code>dspace.cfg</code> to the standard
657: * output. If the property does not exist, nothing is written.</li>
658: * </ul>
659: *
660: * @param argv
661: * command-line arguments
662: */
663: public static void main(String[] argv) {
664: if ((argv.length == 2) && argv[0].equals("-property")) {
665: String val = getProperty(argv[1]);
666:
667: if (val != null) {
668: System.out.println(val);
669: } else {
670: System.out.println("");
671: }
672:
673: System.exit(0);
674: } else {
675: System.err
676: .println("Usage: ConfigurationManager OPTION\n -property prop.name get value of prop.name from dspace.cfg");
677: }
678:
679: System.exit(1);
680: }
681:
682: private static void info(String string) {
683: if (!isConfigured()) {
684: System.out.println("INFO: " + string);
685: } else {
686: log.info(string);
687: }
688: }
689:
690: private static void warn(String string) {
691: if (!isConfigured()) {
692: System.out.println("WARN: " + string);
693: } else {
694: log.warn(string);
695: }
696: }
697:
698: private static void fatal(String string, Exception e) {
699: if (!isConfigured()) {
700: System.out.println("FATAL: " + string);
701: e.printStackTrace();
702: } else {
703: log.fatal(string, e);
704: }
705: }
706:
707: private static void fatal(String string) {
708: if (!isConfigured()) {
709: System.out.println("FATAL: " + string);
710: } else {
711: log.fatal(string);
712: }
713: }
714:
715: /*
716: * Only current solution available to detect
717: * if log4j is truly configured.
718: */
719: private static boolean isConfigured() {
720: Enumeration en = org.apache.log4j.LogManager.getRootLogger()
721: .getAllAppenders();
722:
723: if (!(en instanceof org.apache.log4j.helpers.NullEnumeration)) {
724: return true;
725: } else {
726: Enumeration cats = Category.getCurrentCategories();
727: while (cats.hasMoreElements()) {
728: Category c = (Category) cats.nextElement();
729: if (!(c.getAllAppenders() instanceof org.apache.log4j.helpers.NullEnumeration))
730: return true;
731: }
732: }
733: return false;
734: }
735:
736: }
|