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 }