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 1208795 2011-11-30 21:18:17Z 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 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 }