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