001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.configuration;
019:
020: import java.io.File;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.io.PrintStream;
024: import java.io.PrintWriter;
025: import java.io.StringWriter;
026: import java.lang.reflect.InvocationTargetException;
027: import java.lang.reflect.Method;
028: import java.net.MalformedURLException;
029: import java.net.URL;
030: import java.net.URLDecoder;
031: import java.util.Iterator;
032:
033: import org.apache.commons.configuration.event.ConfigurationErrorEvent;
034: import org.apache.commons.configuration.event.ConfigurationErrorListener;
035: import org.apache.commons.configuration.event.EventSource;
036: import org.apache.commons.lang.StringUtils;
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039:
040: /**
041: * Miscellaneous utility methods for configurations.
042: *
043: * @see ConfigurationConverter Utility methods to convert configurations.
044: *
045: * @author <a href="mailto:herve.quiroz@esil.univ-mrs.fr">Herve Quiroz</a>
046: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
047: * @author Emmanuel Bourg
048: * @version $Revision: 503227 $, $Date: 2007-02-03 17:19:15 +0100 (Sa, 03 Feb 2007) $
049: */
050: public final class ConfigurationUtils {
051: /** Constant for the file URL protocol.*/
052: static final String PROTOCOL_FILE = "file";
053:
054: /** Constant for the resource path separator.*/
055: static final String RESOURCE_PATH_SEPARATOR = "/";
056:
057: /** Constant for the name of the clone() method.*/
058: private static final String METHOD_CLONE = "clone";
059:
060: /** The logger.*/
061: private static Log log = LogFactory
062: .getLog(ConfigurationUtils.class);
063:
064: /**
065: * Private constructor. Prevents instances from being created.
066: */
067: private ConfigurationUtils() {
068: // to prevent instanciation...
069: }
070:
071: /**
072: * Dump the configuration key/value mappings to some ouput stream.
073: *
074: * @param configuration the configuration
075: * @param out the output stream to dump the configuration to
076: */
077: public static void dump(Configuration configuration, PrintStream out) {
078: dump(configuration, new PrintWriter(out));
079: }
080:
081: /**
082: * Dump the configuration key/value mappings to some writer.
083: *
084: * @param configuration the configuration
085: * @param out the writer to dump the configuration to
086: */
087: public static void dump(Configuration configuration, PrintWriter out) {
088: Iterator keys = configuration.getKeys();
089: while (keys.hasNext()) {
090: String key = (String) keys.next();
091: Object value = configuration.getProperty(key);
092: out.print(key);
093: out.print("=");
094: out.print(value);
095:
096: if (keys.hasNext()) {
097: out.println();
098: }
099: }
100:
101: out.flush();
102: }
103:
104: /**
105: * Get a string representation of the key/value mappings of a
106: * configuration.
107: *
108: * @param configuration the configuration
109: * @return a string representation of the configuration
110: */
111: public static String toString(Configuration configuration) {
112: StringWriter writer = new StringWriter();
113: dump(configuration, new PrintWriter(writer));
114: return writer.toString();
115: }
116:
117: /**
118: * Copy all properties from the source configuration to the target
119: * configuration. Properties in the target configuration are replaced with
120: * the properties with the same key in the source configuration.
121: * <em>Note:</em> This method won't work well on hierarchical configurations
122: * because it is not able to copy information about the properties'
123: * structure. So when dealing with hierarchical configuration objects their
124: * <code>{@link HierarchicalConfiguration#clone() clone()}</code> methods
125: * should be used.
126: *
127: * @param source the source configuration
128: * @param target the target configuration
129: * @since 1.1
130: */
131: public static void copy(Configuration source, Configuration target) {
132: Iterator keys = source.getKeys();
133: while (keys.hasNext()) {
134: String key = (String) keys.next();
135: target.setProperty(key, source.getProperty(key));
136: }
137: }
138:
139: /**
140: * Append all properties from the source configuration to the target
141: * configuration. Properties in the source configuration are appended to
142: * the properties with the same key in the target configuration.
143: *
144: * @param source the source configuration
145: * @param target the target configuration
146: * @since 1.1
147: */
148: public static void append(Configuration source, Configuration target) {
149: Iterator keys = source.getKeys();
150: while (keys.hasNext()) {
151: String key = (String) keys.next();
152: target.addProperty(key, source.getProperty(key));
153: }
154: }
155:
156: /**
157: * Converts the passed in configuration to a hierarchical one. If the
158: * configuration is already hierarchical, it is directly returned. Otherwise
159: * all properties are copied into a new hierarchical configuration.
160: *
161: * @param conf the configuration to convert
162: * @return the new hierarchical configuration (the result is <b>null</b> if
163: * and only if the passed in configuration is <b>null</b>)
164: * @since 1.3
165: */
166: public static HierarchicalConfiguration convertToHierarchical(
167: Configuration conf) {
168: if (conf == null) {
169: return null;
170: }
171:
172: if (conf instanceof HierarchicalConfiguration) {
173: return (HierarchicalConfiguration) conf;
174: } else {
175: HierarchicalConfiguration hc = new HierarchicalConfiguration();
176: ConfigurationUtils.copy(conf, hc);
177: return hc;
178: }
179: }
180:
181: /**
182: * Clones the given configuration object if this is possible. If the passed
183: * in configuration object implements the <code>Cloneable</code>
184: * interface, its <code>clone()</code> method will be invoked. Otherwise
185: * an exception will be thrown.
186: *
187: * @param config the configuration object to be cloned (can be <b>null</b>)
188: * @return the cloned configuration (<b>null</b> if the argument was
189: * <b>null</b>, too)
190: * @throws ConfigurationRuntimeException if cloning is not supported for
191: * this object
192: * @since 1.3
193: */
194: public static Configuration cloneConfiguration(Configuration config)
195: throws ConfigurationRuntimeException {
196: if (config == null) {
197: return null;
198: } else {
199: try {
200: return (Configuration) clone(config);
201: } catch (CloneNotSupportedException cnex) {
202: throw new ConfigurationRuntimeException(cnex);
203: }
204: }
205: }
206:
207: /**
208: * An internally used helper method for cloning objects. This implementation
209: * is not very sophisticated nor efficient. Maybe it can be replaced by an
210: * implementation from Commons Lang later. The method checks whether the
211: * passed in object implements the <code>Cloneable</code> interface. If
212: * this is the case, the <code>clone()</code> method is invoked by
213: * reflection. Errors that occur during the cloning process are re-thrown as
214: * runtime exceptions.
215: *
216: * @param obj the object to be cloned
217: * @return the cloned object
218: * @throws CloneNotSupportedException if the object cannot be cloned
219: */
220: static Object clone(Object obj) throws CloneNotSupportedException {
221: if (obj instanceof Cloneable) {
222: try {
223: Method m = obj.getClass().getMethod(METHOD_CLONE, null);
224: return m.invoke(obj, null);
225: } catch (NoSuchMethodException nmex) {
226: throw new CloneNotSupportedException(
227: "No clone() method found for class"
228: + obj.getClass().getName());
229: } catch (IllegalAccessException iaex) {
230: throw new ConfigurationRuntimeException(iaex);
231: } catch (InvocationTargetException itex) {
232: throw new ConfigurationRuntimeException(itex);
233: }
234: } else {
235: throw new CloneNotSupportedException(obj.getClass()
236: .getName()
237: + " does not implement Cloneable");
238: }
239: }
240:
241: /**
242: * Constructs a URL from a base path and a file name. The file name can
243: * be absolute, relative or a full URL. If necessary the base path URL is
244: * applied.
245: *
246: * @param basePath the base path URL (can be <b>null</b>)
247: * @param file the file name
248: * @return the resulting URL
249: * @throws MalformedURLException if URLs are invalid
250: */
251: public static URL getURL(String basePath, String file)
252: throws MalformedURLException {
253: File f = new File(file);
254: if (f.isAbsolute()) // already absolute?
255: {
256: return f.toURL();
257: }
258:
259: try {
260: if (basePath == null) {
261: return new URL(file);
262: } else {
263: URL base = new URL(basePath);
264: return new URL(base, file);
265: }
266: } catch (MalformedURLException uex) {
267: return constructFile(basePath, file).toURL();
268: }
269: }
270:
271: /**
272: * Helper method for constructing a file object from a base path and a
273: * file name. This method is called if the base path passed to
274: * <code>getURL()</code> does not seem to be a valid URL.
275: *
276: * @param basePath the base path
277: * @param fileName the file name
278: * @return the resulting file
279: */
280: static File constructFile(String basePath, String fileName) {
281: File file = null;
282:
283: File absolute = null;
284: if (fileName != null) {
285: absolute = new File(fileName);
286: }
287:
288: if (StringUtils.isEmpty(basePath)
289: || (absolute != null && absolute.isAbsolute())) {
290: file = new File(fileName);
291: } else {
292: StringBuffer fName = new StringBuffer();
293: fName.append(basePath);
294:
295: // My best friend. Paranoia.
296: if (!basePath.endsWith(File.separator)) {
297: fName.append(File.separator);
298: }
299:
300: //
301: // We have a relative path, and we have
302: // two possible forms here. If we have the
303: // "./" form then just strip that off first
304: // before continuing.
305: //
306: if (fileName.startsWith("." + File.separator)) {
307: fName.append(fileName.substring(2));
308: } else {
309: fName.append(fileName);
310: }
311:
312: file = new File(fName.toString());
313: }
314:
315: return file;
316: }
317:
318: /**
319: * Return the location of the specified resource by searching the user home
320: * directory, the current classpath and the system classpath.
321: *
322: * @param name the name of the resource
323: *
324: * @return the location of the resource
325: */
326: public static URL locate(String name) {
327: return locate(null, name);
328: }
329:
330: /**
331: * Return the location of the specified resource by searching the user home
332: * directory, the current classpath and the system classpath.
333: *
334: * @param base the base path of the resource
335: * @param name the name of the resource
336: *
337: * @return the location of the resource
338: */
339: public static URL locate(String base, String name) {
340: if (log.isDebugEnabled()) {
341: StringBuffer buf = new StringBuffer();
342: buf.append("ConfigurationUtils.locate(): base is ").append(
343: base);
344: buf.append(", name is ").append(name);
345: log.debug(buf.toString());
346: }
347:
348: if (name == null) {
349: // undefined, always return null
350: return null;
351: }
352:
353: URL url = null;
354:
355: // attempt to create an URL directly
356: try {
357: if (base == null) {
358: url = new URL(name);
359: } else {
360: URL baseURL = new URL(base);
361: url = new URL(baseURL, name);
362:
363: // check if the file exists
364: InputStream in = null;
365: try {
366: in = url.openStream();
367: } finally {
368: if (in != null) {
369: in.close();
370: }
371: }
372: }
373:
374: log.debug("Loading configuration from the URL " + url);
375: } catch (IOException e) {
376: url = null;
377: }
378:
379: // attempt to load from an absolute path
380: if (url == null) {
381: File file = new File(name);
382: if (file.isAbsolute() && file.exists()) // already absolute?
383: {
384: try {
385: url = file.toURL();
386: log
387: .debug("Loading configuration from the absolute path "
388: + name);
389: } catch (MalformedURLException e) {
390: log.warn("Could not obtain URL from file", e);
391: }
392: }
393: }
394:
395: // attempt to load from the base directory
396: if (url == null) {
397: try {
398: File file = constructFile(base, name);
399: if (file != null && file.exists()) {
400: url = file.toURL();
401: }
402:
403: if (url != null) {
404: log.debug("Loading configuration from the path "
405: + file);
406: }
407: } catch (MalformedURLException e) {
408: log.warn("Could not obtain URL from file", e);
409: }
410: }
411:
412: // attempt to load from the user home directory
413: if (url == null) {
414: try {
415: File file = constructFile(System
416: .getProperty("user.home"), name);
417: if (file != null && file.exists()) {
418: url = file.toURL();
419: }
420:
421: if (url != null) {
422: log
423: .debug("Loading configuration from the home path "
424: + file);
425: }
426:
427: } catch (MalformedURLException e) {
428: log.warn("Could not obtain URL from file", e);
429: }
430: }
431:
432: // attempt to load from classpath
433: if (url == null) {
434: url = locateFromClasspath(name);
435: }
436: return url;
437: }
438:
439: /**
440: * Tries to find a resource with the given name in the classpath.
441: * @param resourceName the name of the resource
442: * @return the URL to the found resource or <b>null</b> if the resource
443: * cannot be found
444: */
445: static URL locateFromClasspath(String resourceName) {
446: URL url = null;
447: // attempt to load from the context classpath
448: ClassLoader loader = Thread.currentThread()
449: .getContextClassLoader();
450: if (loader != null) {
451: url = loader.getResource(resourceName);
452:
453: if (url != null) {
454: log
455: .debug("Loading configuration from the context classpath ("
456: + resourceName + ")");
457: }
458: }
459:
460: // attempt to load from the system classpath
461: if (url == null) {
462: url = ClassLoader.getSystemResource(resourceName);
463:
464: if (url != null) {
465: log
466: .debug("Loading configuration from the system classpath ("
467: + resourceName + ")");
468: }
469: }
470: return url;
471: }
472:
473: /**
474: * Return the path without the file name, for example http://xyz.net/foo/bar.xml
475: * results in http://xyz.net/foo/
476: *
477: * @param url the URL from which to extract the path
478: * @return the path component of the passed in URL
479: */
480: static String getBasePath(URL url) {
481: if (url == null) {
482: return null;
483: }
484:
485: String s = url.toString();
486:
487: if (s.endsWith("/") || StringUtils.isEmpty(url.getPath())) {
488: return s;
489: } else {
490: return s.substring(0, s.lastIndexOf("/") + 1);
491: }
492: }
493:
494: /**
495: * Extract the file name from the specified URL.
496: *
497: * @param url the URL from which to extract the file name
498: * @return the extracted file name
499: */
500: static String getFileName(URL url) {
501: if (url == null) {
502: return null;
503: }
504:
505: String path = url.getPath();
506:
507: if (path.endsWith("/") || StringUtils.isEmpty(path)) {
508: return null;
509: } else {
510: return path.substring(path.lastIndexOf("/") + 1);
511: }
512: }
513:
514: /**
515: * Tries to convert the specified base path and file name into a file object.
516: * This method is called e.g. by the save() methods of file based
517: * configurations. The parameter strings can be relative files, absolute
518: * files and URLs as well. This implementation checks first whether the passed in
519: * file name is absolute. If this is the case, it is returned. Otherwise
520: * further checks are performed whether the base path and file name can be
521: * combined to a valid URL or a valid file name. <em>Note:</em> The test
522: * if the passed in file name is absolute is performed using
523: * <code>java.io.File.isAbsolute()</code>. If the file name starts with a
524: * slash, this method will return <b>true</b> on Unix, but <b>false</b> on
525: * Windows. So to ensure correct behavior for relative file names on all
526: * platforms you should never let relative paths start with a slash. E.g.
527: * in a configuration definition file do not use something like that:
528: * <pre>
529: * <properties fileName="/subdir/my.properties"/>
530: * </pre>
531: * Under Windows this path would be resolved relative to the configuration
532: * definition file. Under Unix this would be treated as an absolute path
533: * name.
534: *
535: * @param basePath the base path
536: * @param fileName the file name
537: * @return the file object (<b>null</b> if no file can be obtained)
538: */
539: public static File getFile(String basePath, String fileName) {
540: // Check if the file name is absolute
541: File f = new File(fileName);
542: if (f.isAbsolute()) {
543: return f;
544: }
545:
546: // Check if URLs are involved
547: URL url;
548: try {
549: url = new URL(new URL(basePath), fileName);
550: } catch (MalformedURLException mex1) {
551: try {
552: url = new URL(fileName);
553: } catch (MalformedURLException mex2) {
554: url = null;
555: }
556: }
557:
558: if (url != null) {
559: return fileFromURL(url);
560: }
561:
562: return constructFile(basePath, fileName);
563: }
564:
565: /**
566: * Tries to convert the specified URL to a file object. If this fails,
567: * <b>null</b> is returned.
568: *
569: * @param url the URL
570: * @return the resulting file object
571: */
572: public static File fileFromURL(URL url) {
573: if (PROTOCOL_FILE.equals(url.getProtocol())) {
574: return new File(URLDecoder.decode(url.getPath()));
575: } else {
576: return null;
577: }
578: }
579:
580: /**
581: * Enables runtime exceptions for the specified configuration object. This
582: * method can be used for configuration implementations that may face errors
583: * on normal property access, e.g. <code>DatabaseConfiguration</code> or
584: * <code>JNDIConfiguration</code>. Per default such errors are simply
585: * logged and then ignored. This implementation will register a special
586: * <code>{@link ConfigurationErrorListener}</code> that throws a runtime
587: * exception (namely a <code>ConfigurationRuntimeException</code>) on
588: * each received error event.
589: *
590: * @param src the configuration, for which runtime exceptions are to be
591: * enabled; this configuration must be derived from
592: * <code>{@link EventSource}</code>
593: */
594: public static void enableRuntimeExceptions(Configuration src) {
595: if (!(src instanceof EventSource)) {
596: throw new IllegalArgumentException(
597: "Configuration must be derived from EventSource!");
598: }
599: ((EventSource) src)
600: .addErrorListener(new ConfigurationErrorListener() {
601: public void configurationError(
602: ConfigurationErrorEvent event) {
603: // Throw a runtime exception
604: throw new ConfigurationRuntimeException(event
605: .getCause());
606: }
607: });
608: }
609: }
|