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