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