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 }