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 }