View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import java.io.File;
21  import java.io.PrintStream;
22  import java.io.PrintWriter;
23  import java.io.StringWriter;
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.net.MalformedURLException;
27  import java.net.URL;
28  import java.util.Iterator;
29  
30  import org.apache.commons.configuration.event.ConfigurationErrorEvent;
31  import org.apache.commons.configuration.event.ConfigurationErrorListener;
32  import org.apache.commons.configuration.event.EventSource;
33  import org.apache.commons.configuration.reloading.Reloadable;
34  import org.apache.commons.configuration.tree.ExpressionEngine;
35  import org.apache.commons.lang.StringUtils;
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  
39  /**
40   * Miscellaneous utility methods for configurations.
41   *
42   * @see ConfigurationConverter Utility methods to convert configurations.
43   *
44   * @author <a href="mailto:herve.quiroz@esil.univ-mrs.fr">Herve Quiroz</a>
45   * @author Emmanuel Bourg
46   * @version $Id: ConfigurationUtils.java 1208795 2011-11-30 21:18:17Z oheger $
47   */
48  public final class ConfigurationUtils
49  {
50      /** Constant for the file URL protocol.*/
51      static final String PROTOCOL_FILE = "file";
52  
53      /** Constant for the resource path separator.*/
54      static final String RESOURCE_PATH_SEPARATOR = "/";
55  
56      /** Constant for the file URL protocol */
57      private static final String FILE_SCHEME = "file:";
58  
59      /** Constant for the name of the clone() method.*/
60      private static final String METHOD_CLONE = "clone";
61  
62      /** Constant for parsing numbers in hex format. */
63      private static final int HEX = 16;
64  
65      /** The logger.*/
66      private static final Log LOG = LogFactory.getLog(ConfigurationUtils.class);
67  
68      /**
69       * Private constructor. Prevents instances from being created.
70       */
71      private ConfigurationUtils()
72      {
73          // to prevent instantiation...
74      }
75  
76      /**
77       * Dump the configuration key/value mappings to some ouput stream.
78       *
79       * @param configuration the configuration
80       * @param out the output stream to dump the configuration to
81       */
82      public static void dump(Configuration configuration, PrintStream out)
83      {
84          dump(configuration, new PrintWriter(out));
85      }
86  
87      /**
88       * Dump the configuration key/value mappings to some writer.
89       *
90       * @param configuration the configuration
91       * @param out the writer to dump the configuration to
92       */
93      public static void dump(Configuration configuration, PrintWriter out)
94      {
95          for (Iterator<String> keys = configuration.getKeys(); keys.hasNext();)
96          {
97              String key = keys.next();
98              Object value = configuration.getProperty(key);
99              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      * &lt;properties fileName="/subdir/my.properties"/&gt;
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 }