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 }