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.configuration.tree.ExpressionEngine;
037 import org.apache.commons.lang.StringUtils;
038 import org.apache.commons.lang.SystemUtils;
039 import org.apache.commons.logging.Log;
040 import org.apache.commons.logging.LogFactory;
041
042 /**
043 * Miscellaneous utility methods for configurations.
044 *
045 * @see ConfigurationConverter Utility methods to convert configurations.
046 *
047 * @author <a href="mailto:herve.quiroz@esil.univ-mrs.fr">Herve Quiroz</a>
048 * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
049 * @author Emmanuel Bourg
050 * @version $Revision: 720600 $, $Date: 2008-11-25 22:20:01 +0100 (Di, 25 Nov 2008) $
051 */
052 public final class ConfigurationUtils
053 {
054 /** Constant for the file URL protocol.*/
055 static final String PROTOCOL_FILE = "file";
056
057 /** Constant for the resource path separator.*/
058 static final String RESOURCE_PATH_SEPARATOR = "/";
059
060 /** Constant for the name of the clone() method.*/
061 private static final String METHOD_CLONE = "clone";
062
063 /** Constant for Java version 1.4.*/
064 private static final float JAVA_1_4 = 1.4f;
065
066 /** The logger.*/
067 private static Log log = LogFactory.getLog(ConfigurationUtils.class);
068
069 /**
070 * Private constructor. Prevents instances from being created.
071 */
072 private ConfigurationUtils()
073 {
074 // to prevent instantiation...
075 }
076
077 /**
078 * Dump the configuration key/value mappings to some ouput stream.
079 *
080 * @param configuration the configuration
081 * @param out the output stream to dump the configuration to
082 */
083 public static void dump(Configuration configuration, PrintStream out)
084 {
085 dump(configuration, new PrintWriter(out));
086 }
087
088 /**
089 * Dump the configuration key/value mappings to some writer.
090 *
091 * @param configuration the configuration
092 * @param out the writer to dump the configuration to
093 */
094 public static void dump(Configuration configuration, PrintWriter out)
095 {
096 Iterator keys = configuration.getKeys();
097 while (keys.hasNext())
098 {
099 String key = (String) keys.next();
100 Object value = configuration.getProperty(key);
101 out.print(key);
102 out.print("=");
103 out.print(value);
104
105 if (keys.hasNext())
106 {
107 out.println();
108 }
109 }
110
111 out.flush();
112 }
113
114 /**
115 * Get a string representation of the key/value mappings of a
116 * configuration.
117 *
118 * @param configuration the configuration
119 * @return a string representation of the configuration
120 */
121 public static String toString(Configuration configuration)
122 {
123 StringWriter writer = new StringWriter();
124 dump(configuration, new PrintWriter(writer));
125 return writer.toString();
126 }
127
128 /**
129 * <p>Copy all properties from the source configuration to the target
130 * configuration. Properties in the target configuration are replaced with
131 * the properties with the same key in the source configuration.</p>
132 * <p><em>Note:</em> This method is not able to handle some specifics of
133 * configurations derived from <code>AbstractConfiguration</code> (e.g.
134 * list delimiters). For a full support of all of these features the
135 * <code>copy()</code> method of <code>AbstractConfiguration</code> should
136 * be used. In a future release this method might become deprecated.</p>
137 *
138 * @param source the source configuration
139 * @param target the target configuration
140 * @since 1.1
141 */
142 public static void copy(Configuration source, Configuration target)
143 {
144 Iterator keys = source.getKeys();
145 while (keys.hasNext())
146 {
147 String key = (String) keys.next();
148 target.setProperty(key, source.getProperty(key));
149 }
150 }
151
152 /**
153 * <p>Append all properties from the source configuration to the target
154 * configuration. Properties in the source configuration are appended to
155 * the properties with the same key in the target configuration.</p>
156 * <p><em>Note:</em> This method is not able to handle some specifics of
157 * configurations derived from <code>AbstractConfiguration</code> (e.g.
158 * list delimiters). For a full support of all of these features the
159 * <code>copy()</code> method of <code>AbstractConfiguration</code> should
160 * be used. In a future release this method might become deprecated.</p>
161 *
162 * @param source the source configuration
163 * @param target the target configuration
164 * @since 1.1
165 */
166 public static void append(Configuration source, Configuration target)
167 {
168 Iterator keys = source.getKeys();
169 while (keys.hasNext())
170 {
171 String key = (String) keys.next();
172 target.addProperty(key, source.getProperty(key));
173 }
174 }
175
176 /**
177 * Converts the passed in configuration to a hierarchical one. If the
178 * configuration is already hierarchical, it is directly returned. Otherwise
179 * all properties are copied into a new hierarchical configuration.
180 *
181 * @param conf the configuration to convert
182 * @return the new hierarchical configuration (the result is <b>null</b> if
183 * and only if the passed in configuration is <b>null</b>)
184 * @since 1.3
185 */
186 public static HierarchicalConfiguration convertToHierarchical(
187 Configuration conf)
188 {
189 return convertToHierarchical(conf, null);
190 }
191
192 /**
193 * Converts the passed in <code>Configuration</code> object to a
194 * hierarchical one using the specified <code>ExpressionEngine</code>. This
195 * conversion works by adding the keys found in the configuration to a newly
196 * created hierarchical configuration. When adding new keys to a
197 * hierarchical configuration the keys are interpreted by its
198 * <code>ExpressionEngine</code>. If they contain special characters (e.g.
199 * brackets) that are treated in a special way by the default expression
200 * engine, it may be necessary using a specific engine that can deal with
201 * such characters. Otherwise <b>null</b> can be passed in for the
202 * <code>ExpressionEngine</code>; then the default expression engine is
203 * used. If the passed in configuration is already hierarchical, it is
204 * directly returned. (However, the <code>ExpressionEngine</code> is set if
205 * it is not <b>null</b>.) Otherwise all properties are copied into a new
206 * hierarchical configuration.
207 *
208 * @param conf the configuration to convert
209 * @param engine the <code>ExpressionEngine</code> for the hierarchical
210 * configuration or <b>null</b> for the default
211 * @return the new hierarchical configuration (the result is <b>null</b> if
212 * and only if the passed in configuration is <b>null</b>)
213 * @since 1.6
214 */
215 public static HierarchicalConfiguration convertToHierarchical(
216 Configuration conf, ExpressionEngine engine)
217 {
218 if (conf == null)
219 {
220 return null;
221 }
222
223 if (conf instanceof HierarchicalConfiguration)
224 {
225 HierarchicalConfiguration hc = (HierarchicalConfiguration) conf;
226 if (engine != null)
227 {
228 hc.setExpressionEngine(engine);
229 }
230
231 return hc;
232 }
233 else
234 {
235 HierarchicalConfiguration hc = new HierarchicalConfiguration();
236 if (engine != null)
237 {
238 hc.setExpressionEngine(engine);
239 }
240
241 // Workaround for problem with copy()
242 boolean delimiterParsingStatus = hc.isDelimiterParsingDisabled();
243 hc.setDelimiterParsingDisabled(true);
244 hc.append(conf);
245 hc.setDelimiterParsingDisabled(delimiterParsingStatus);
246 return hc;
247 }
248 }
249
250 /**
251 * Clones the given configuration object if this is possible. If the passed
252 * in configuration object implements the <code>Cloneable</code>
253 * interface, its <code>clone()</code> method will be invoked. Otherwise
254 * an exception will be thrown.
255 *
256 * @param config the configuration object to be cloned (can be <b>null</b>)
257 * @return the cloned configuration (<b>null</b> if the argument was
258 * <b>null</b>, too)
259 * @throws ConfigurationRuntimeException if cloning is not supported for
260 * this object
261 * @since 1.3
262 */
263 public static Configuration cloneConfiguration(Configuration config)
264 throws ConfigurationRuntimeException
265 {
266 if (config == null)
267 {
268 return null;
269 }
270 else
271 {
272 try
273 {
274 return (Configuration) clone(config);
275 }
276 catch (CloneNotSupportedException cnex)
277 {
278 throw new ConfigurationRuntimeException(cnex);
279 }
280 }
281 }
282
283 /**
284 * An internally used helper method for cloning objects. This implementation
285 * is not very sophisticated nor efficient. Maybe it can be replaced by an
286 * implementation from Commons Lang later. The method checks whether the
287 * passed in object implements the <code>Cloneable</code> interface. If
288 * this is the case, the <code>clone()</code> method is invoked by
289 * reflection. Errors that occur during the cloning process are re-thrown as
290 * runtime exceptions.
291 *
292 * @param obj the object to be cloned
293 * @return the cloned object
294 * @throws CloneNotSupportedException if the object cannot be cloned
295 */
296 static Object clone(Object obj) throws CloneNotSupportedException
297 {
298 if (obj instanceof Cloneable)
299 {
300 try
301 {
302 Method m = obj.getClass().getMethod(METHOD_CLONE, null);
303 return m.invoke(obj, null);
304 }
305 catch (NoSuchMethodException nmex)
306 {
307 throw new CloneNotSupportedException(
308 "No clone() method found for class"
309 + obj.getClass().getName());
310 }
311 catch (IllegalAccessException iaex)
312 {
313 throw new ConfigurationRuntimeException(iaex);
314 }
315 catch (InvocationTargetException itex)
316 {
317 throw new ConfigurationRuntimeException(itex);
318 }
319 }
320 else
321 {
322 throw new CloneNotSupportedException(obj.getClass().getName()
323 + " does not implement Cloneable");
324 }
325 }
326
327 /**
328 * Constructs a URL from a base path and a file name. The file name can
329 * be absolute, relative or a full URL. If necessary the base path URL is
330 * applied.
331 *
332 * @param basePath the base path URL (can be <b>null</b>)
333 * @param file the file name
334 * @return the resulting URL
335 * @throws MalformedURLException if URLs are invalid
336 */
337 public static URL getURL(String basePath, String file) throws MalformedURLException
338 {
339 File f = new File(file);
340 if (f.isAbsolute()) // already absolute?
341 {
342 return toURL(f);
343 }
344
345 try
346 {
347 if (basePath == null)
348 {
349 return new URL(file);
350 }
351 else
352 {
353 URL base = new URL(basePath);
354 return new URL(base, file);
355 }
356 }
357 catch (MalformedURLException uex)
358 {
359 return toURL(constructFile(basePath, file));
360 }
361 }
362
363 /**
364 * Helper method for constructing a file object from a base path and a
365 * file name. This method is called if the base path passed to
366 * <code>getURL()</code> does not seem to be a valid URL.
367 *
368 * @param basePath the base path
369 * @param fileName the file name
370 * @return the resulting file
371 */
372 static File constructFile(String basePath, String fileName)
373 {
374 File file = null;
375
376 File absolute = null;
377 if (fileName != null)
378 {
379 absolute = new File(fileName);
380 }
381
382 if (StringUtils.isEmpty(basePath) || (absolute != null && absolute.isAbsolute()))
383 {
384 file = new File(fileName);
385 }
386 else
387 {
388 StringBuffer fName = new StringBuffer();
389 fName.append(basePath);
390
391 // My best friend. Paranoia.
392 if (!basePath.endsWith(File.separator))
393 {
394 fName.append(File.separator);
395 }
396
397 //
398 // We have a relative path, and we have
399 // two possible forms here. If we have the
400 // "./" form then just strip that off first
401 // before continuing.
402 //
403 if (fileName.startsWith("." + File.separator))
404 {
405 fName.append(fileName.substring(2));
406 }
407 else
408 {
409 fName.append(fileName);
410 }
411
412 file = new File(fName.toString());
413 }
414
415 return file;
416 }
417
418 /**
419 * Return the location of the specified resource by searching the user home
420 * directory, the current classpath and the system classpath.
421 *
422 * @param name the name of the resource
423 *
424 * @return the location of the resource
425 */
426 public static URL locate(String name)
427 {
428 return locate(null, name);
429 }
430
431 /**
432 * Return the location of the specified resource by searching the user home
433 * directory, the current classpath and the system classpath.
434 *
435 * @param base the base path of the resource
436 * @param name the name of the resource
437 *
438 * @return the location of the resource
439 */
440 public static URL locate(String base, String name)
441 {
442 if (log.isDebugEnabled())
443 {
444 StringBuffer buf = new StringBuffer();
445 buf.append("ConfigurationUtils.locate(): base is ").append(base);
446 buf.append(", name is ").append(name);
447 log.debug(buf.toString());
448 }
449
450 if (name == null)
451 {
452 // undefined, always return null
453 return null;
454 }
455
456 URL url = null;
457
458 // attempt to create an URL directly
459 try
460 {
461 if (base == null)
462 {
463 url = new URL(name);
464 }
465 else
466 {
467 URL baseURL = new URL(base);
468 url = new URL(baseURL, name);
469
470 // check if the file exists
471 InputStream in = null;
472 try
473 {
474 in = url.openStream();
475 }
476 finally
477 {
478 if (in != null)
479 {
480 in.close();
481 }
482 }
483 }
484
485 log.debug("Loading configuration from the URL " + url);
486 }
487 catch (IOException e)
488 {
489 url = null;
490 }
491
492 // attempt to load from an absolute path
493 if (url == null)
494 {
495 File file = new File(name);
496 if (file.isAbsolute() && file.exists()) // already absolute?
497 {
498 try
499 {
500 url = toURL(file);
501 log.debug("Loading configuration from the absolute path " + name);
502 }
503 catch (MalformedURLException e)
504 {
505 log.warn("Could not obtain URL from file", e);
506 }
507 }
508 }
509
510 // attempt to load from the base directory
511 if (url == null)
512 {
513 try
514 {
515 File file = constructFile(base, name);
516 if (file != null && file.exists())
517 {
518 url = toURL(file);
519 }
520
521 if (url != null)
522 {
523 log.debug("Loading configuration from the path " + file);
524 }
525 }
526 catch (MalformedURLException e)
527 {
528 log.warn("Could not obtain URL from file", e);
529 }
530 }
531
532 // attempt to load from the user home directory
533 if (url == null)
534 {
535 try
536 {
537 File file = constructFile(System.getProperty("user.home"), name);
538 if (file != null && file.exists())
539 {
540 url = toURL(file);
541 }
542
543 if (url != null)
544 {
545 log.debug("Loading configuration from the home path " + file);
546 }
547
548 }
549 catch (MalformedURLException e)
550 {
551 log.warn("Could not obtain URL from file", e);
552 }
553 }
554
555 // attempt to load from classpath
556 if (url == null)
557 {
558 url = locateFromClasspath(name);
559 }
560 return url;
561 }
562
563 /**
564 * Tries to find a resource with the given name in the classpath.
565 * @param resourceName the name of the resource
566 * @return the URL to the found resource or <b>null</b> if the resource
567 * cannot be found
568 */
569 static URL locateFromClasspath(String resourceName)
570 {
571 URL url = null;
572 // attempt to load from the context classpath
573 ClassLoader loader = Thread.currentThread().getContextClassLoader();
574 if (loader != null)
575 {
576 url = loader.getResource(resourceName);
577
578 if (url != null)
579 {
580 log.debug("Loading configuration from the context classpath (" + resourceName + ")");
581 }
582 }
583
584 // attempt to load from the system classpath
585 if (url == null)
586 {
587 url = ClassLoader.getSystemResource(resourceName);
588
589 if (url != null)
590 {
591 log.debug("Loading configuration from the system classpath (" + resourceName + ")");
592 }
593 }
594 return url;
595 }
596
597 /**
598 * Return the path without the file name, for example http://xyz.net/foo/bar.xml
599 * results in http://xyz.net/foo/
600 *
601 * @param url the URL from which to extract the path
602 * @return the path component of the passed in URL
603 */
604 static String getBasePath(URL url)
605 {
606 if (url == null)
607 {
608 return null;
609 }
610
611 String s = url.toString();
612
613 if (s.endsWith("/") || StringUtils.isEmpty(url.getPath()))
614 {
615 return s;
616 }
617 else
618 {
619 return s.substring(0, s.lastIndexOf("/") + 1);
620 }
621 }
622
623 /**
624 * Extract the file name from the specified URL.
625 *
626 * @param url the URL from which to extract the file name
627 * @return the extracted file name
628 */
629 static String getFileName(URL url)
630 {
631 if (url == null)
632 {
633 return null;
634 }
635
636 String path = url.getPath();
637
638 if (path.endsWith("/") || StringUtils.isEmpty(path))
639 {
640 return null;
641 }
642 else
643 {
644 return path.substring(path.lastIndexOf("/") + 1);
645 }
646 }
647
648 /**
649 * Tries to convert the specified base path and file name into a file object.
650 * This method is called e.g. by the save() methods of file based
651 * configurations. The parameter strings can be relative files, absolute
652 * files and URLs as well. This implementation checks first whether the passed in
653 * file name is absolute. If this is the case, it is returned. Otherwise
654 * further checks are performed whether the base path and file name can be
655 * combined to a valid URL or a valid file name. <em>Note:</em> The test
656 * if the passed in file name is absolute is performed using
657 * <code>java.io.File.isAbsolute()</code>. If the file name starts with a
658 * slash, this method will return <b>true</b> on Unix, but <b>false</b> on
659 * Windows. So to ensure correct behavior for relative file names on all
660 * platforms you should never let relative paths start with a slash. E.g.
661 * in a configuration definition file do not use something like that:
662 * <pre>
663 * <properties fileName="/subdir/my.properties"/>
664 * </pre>
665 * Under Windows this path would be resolved relative to the configuration
666 * definition file. Under Unix this would be treated as an absolute path
667 * name.
668 *
669 * @param basePath the base path
670 * @param fileName the file name
671 * @return the file object (<b>null</b> if no file can be obtained)
672 */
673 public static File getFile(String basePath, String fileName)
674 {
675 // Check if the file name is absolute
676 File f = new File(fileName);
677 if (f.isAbsolute())
678 {
679 return f;
680 }
681
682 // Check if URLs are involved
683 URL url;
684 try
685 {
686 url = new URL(new URL(basePath), fileName);
687 }
688 catch (MalformedURLException mex1)
689 {
690 try
691 {
692 url = new URL(fileName);
693 }
694 catch (MalformedURLException mex2)
695 {
696 url = null;
697 }
698 }
699
700 if (url != null)
701 {
702 return fileFromURL(url);
703 }
704
705 return constructFile(basePath, fileName);
706 }
707
708 /**
709 * Tries to convert the specified URL to a file object. If this fails,
710 * <b>null</b> is returned.
711 *
712 * @param url the URL
713 * @return the resulting file object
714 */
715 public static File fileFromURL(URL url)
716 {
717 if (PROTOCOL_FILE.equals(url.getProtocol()))
718 {
719 return new File(URLDecoder.decode(url.getPath()));
720 }
721 else
722 {
723 return null;
724 }
725 }
726
727 /**
728 * Convert the specified file into an URL. This method is equivalent
729 * to file.toURI().toURL() on Java 1.4 and above, and equivalent to
730 * file.toURL() on Java 1.3. This is to work around a bug in the JDK
731 * preventing the transformation of a file into an URL if the file name
732 * contains a '#' character. See the issue CONFIGURATION-300 for
733 * more details.
734 *
735 * @param file the file to be converted into an URL
736 */
737 static URL toURL(File file) throws MalformedURLException
738 {
739 if (SystemUtils.isJavaVersionAtLeast(JAVA_1_4))
740 {
741 try
742 {
743 Method toURI = file.getClass().getMethod("toURI", (Class[]) null);
744 Object uri = toURI.invoke(file, (Class[]) null);
745 Method toURL = uri.getClass().getMethod("toURL", (Class[]) null);
746 URL url = (URL) toURL.invoke(uri, (Class[]) null);
747
748 return url;
749 }
750 catch (Exception e)
751 {
752 throw new MalformedURLException(e.getMessage());
753 }
754 }
755 else
756 {
757 return file.toURL();
758 }
759 }
760
761 /**
762 * Enables runtime exceptions for the specified configuration object. This
763 * method can be used for configuration implementations that may face errors
764 * on normal property access, e.g. <code>DatabaseConfiguration</code> or
765 * <code>JNDIConfiguration</code>. Per default such errors are simply
766 * logged and then ignored. This implementation will register a special
767 * <code>{@link ConfigurationErrorListener}</code> that throws a runtime
768 * exception (namely a <code>ConfigurationRuntimeException</code>) on
769 * each received error event.
770 *
771 * @param src the configuration, for which runtime exceptions are to be
772 * enabled; this configuration must be derived from
773 * <code>{@link EventSource}</code>
774 */
775 public static void enableRuntimeExceptions(Configuration src)
776 {
777 if (!(src instanceof EventSource))
778 {
779 throw new IllegalArgumentException(
780 "Configuration must be derived from EventSource!");
781 }
782 ((EventSource) src).addErrorListener(new ConfigurationErrorListener()
783 {
784 public void configurationError(ConfigurationErrorEvent event)
785 {
786 // Throw a runtime exception
787 throw new ConfigurationRuntimeException(event.getCause());
788 }
789 });
790 }
791 }