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