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