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.util.ArrayList;
    21  import java.util.HashSet;
    22  import java.util.Iterator;
    23  import java.util.List;
    24  import java.util.Set;
    25
    26  import javax.naming.Context;
    27  import javax.naming.InitialContext;
    28  import javax.naming.NameClassPair;
    29  import javax.naming.NameNotFoundException;
    30  import javax.naming.NamingEnumeration;
    31  import javax.naming.NamingException;
    32
    33  import org.apache.commons.lang.StringUtils;
    34  import org.apache.commons.logging.Log;
    35  import org.apache.commons.logging.LogFactory;
    36
    37  /***
    38   * This Configuration class allows you to interface with a JNDI datasource.
    39   * A JNDIConfiguration is read-only, write operations will throw an
    40   * UnsupportedOperationException. The clear operations are supported but the
    41   * underlying JNDI data source is not changed.
    42   *
    43   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
    44   * @version $Id: JNDIConfiguration.java 439648 2006-09-02 20:42:10Z oheger $
    45   */
    46  public class JNDIConfiguration extends AbstractConfiguration
    47  {
    48      /*** Logger. */
    49      private static Log log = LogFactory.getLog(JNDIConfiguration.class);
    50
    51      /*** The prefix of the context. */
    52      private String prefix;
    53
    54      /*** The initial JNDI context. */
    55      private Context context;
    56
    57      /*** The base JNDI context. */
    58      private Context baseContext;
    59
    60      /*** The Set of keys that have been virtually cleared. */
    61      private Set clearedProperties = new HashSet();
    62
    63      /***
    64       * Creates a JNDIConfiguration using the default initial context as the
    65       * root of the properties.
    66       *
    67       * @throws NamingException thrown if an error occurs when initializing the default context
    68       */
    69      public JNDIConfiguration() throws NamingException
    70      {
    71          this((String) null);
    72      }
    73
    74      /***
    75       * Creates a JNDIConfiguration using the default initial context, shifted
    76       * with the specified prefix, as the root of the properties.
    77       *
    78       * @param prefix the prefix
    79       *
    80       * @throws NamingException thrown if an error occurs when initializing the default context
    81       */
    82      public JNDIConfiguration(String prefix) throws NamingException
    83      {
    84          this(new InitialContext(), prefix);
    85      }
    86
    87      /***
    88       * Creates a JNDIConfiguration using the specified initial context as the
    89       * root of the properties.
    90       *
    91       * @param context the initial context
    92       */
    93      public JNDIConfiguration(Context context)
    94      {
    95          this(context, null);
    96      }
    97
    98      /***
    99       * Creates a JNDIConfiguration using the specified initial context shifted
    100      * by the specified prefix as the root of the properties.
    101      *
    102      * @param context the initial context
    103      * @param prefix the prefix
    104      */
    105     public JNDIConfiguration(Context context, String prefix)
    106     {
    107         this.context = context;
    108         this.prefix = prefix;
    109     }
    110
    111     /***
    112      * This method recursive traverse the JNDI tree, looking for Context objects.
    113      * When it finds them, it traverses them as well.  Otherwise it just adds the
    114      * values to the list of keys found.
    115      *
    116      * @param keys All the keys that have been found.
    117      * @param context The parent context
    118      * @param prefix What prefix we are building on.
    119      * @throws NamingException If JNDI has an issue.
    120      */
    121     private void recursiveGetKeys(Set keys, Context context, String prefix) throws NamingException
    122     {
    123         NamingEnumeration elements = null;
    124
    125         try
    126         {
    127             elements = context.list("");
    128
    129             // iterates through the context's elements
    130             while (elements.hasMore())
    131             {
    132                 NameClassPair nameClassPair = (NameClassPair) elements.next();
    133                 String name = nameClassPair.getName();
    134                 Object object = context.lookup(name);
    135
    136                 // build the key
    137                 StringBuffer key = new StringBuffer();
    138                 key.append(prefix);
    139                 if (key.length() > 0)
    140                 {
    141                     key.append(".");
    142                 }
    143                 key.append(name);
    144
    145                 if (object instanceof Context)
    146                 {
    147                     // add the keys of the sub context
    148                     Context subcontext = (Context) object;
    149                     recursiveGetKeys(keys, subcontext, key.toString());
    150                 }
    151                 else
    152                 {
    153                     // add the key
    154                     keys.add(key.toString());
    155                 }
    156             }
    157         }
    158         finally
    159         {
    160             // close the enumeration
    161             if (elements != null)
    162             {
    163                 elements.close();
    164             }
    165         }
    166     }
    167
    168     /***
    169      * Returns an iterator with all property keys stored in this configuration.
    170      *
    171      * @return an iterator with all keys
    172      */
    173     public Iterator getKeys()
    174     {
    175         return getKeys("");
    176     }
    177
    178     /***
    179      * Returns an iterator with all property keys starting with the given
    180      * prefix.
    181      *
    182      * @param prefix the prefix
    183      * @return an iterator with the selected keys
    184      */
    185     public Iterator getKeys(String prefix)
    186     {
    187         // build the path
    188         String[] splitPath = StringUtils.split(prefix, ".");
    189
    190         List path = new ArrayList();
    191
    192         for (int i = 0; i < splitPath.length; i++)
    193         {
    194             path.add(splitPath[i]);
    195         }
    196
    197         try
    198         {
    199             // find the context matching the specified path
    200             Context context = getContext(path, getBaseContext());
    201
    202             // return all the keys under the context found
    203             Set keys = new HashSet();
    204             if (context != null)
    205             {
    206                 recursiveGetKeys(keys, context, prefix);
    207             }
    208             else if (containsKey(prefix))
    209             {
    210                 // add the prefix if it matches exactly a property key
    211                 keys.add(prefix);
    212             }
    213
    214             return keys.iterator();
    215         }
    216         catch (NamingException e)
    217         {
    218             log.error(e.getMessage(), e);
    219             return new ArrayList().iterator();
    220         }
    221     }
    222
    223     /***
    224      * Because JNDI is based on a tree configuration, we need to filter down the
    225      * tree, till we find the Context specified by the key to start from.
    226      * Otherwise return null.
    227      *
    228      * @param path     the path of keys to traverse in order to find the context
    229      * @param context  the context to start from
    230      * @return The context at that key's location in the JNDI tree, or null if not found
    231      * @throws NamingException if JNDI has an issue
    232      */
    233     private Context getContext(List path, Context context) throws NamingException
    234     {
    235         // return the current context if the path is empty
    236         if (path == null || path.isEmpty())
    237         {
    238             return context;
    239         }
    240
    241         String key = (String) path.get(0);
    242
    243         // search a context matching the key in the context's elements
    244         NamingEnumeration elements = null;
    245
    246         try
    247         {
    248             elements = context.list("");
    249             while (elements.hasMore())
    250             {
    251                 NameClassPair nameClassPair = (NameClassPair) elements.next();
    252                 String name = nameClassPair.getName();
    253                 Object object = context.lookup(name);
    254
    255                 if (object instanceof Context && name.equals(key))
    256                 {
    257                     Context subcontext = (Context) object;
    258
    259                     // recursive search in the sub context
    260                     return getContext(path.subList(1, path.size()), subcontext);
    261                 }
    262             }
    263         }
    264         finally
    265         {
    266             if (elements != null)
    267             {
    268                 elements.close();
    269             }
    270         }
    271
    272         return null;
    273     }
    274
    275     /***
    276      * Returns a flag whether this configuration is empty.
    277      *
    278      * @return the empty flag
    279      */
    280     public boolean isEmpty()
    281     {
    282         try
    283         {
    284             NamingEnumeration enumeration = null;
    285
    286             try
    287             {
    288                 enumeration = getBaseContext().list("");
    289                 return !enumeration.hasMore();
    290             }
    291             finally
    292             {
    293                 // close the enumeration
    294                 if (enumeration != null)
    295                 {
    296                     enumeration.close();
    297                 }
    298             }
    299         }
    300         catch (NamingException e)
    301         {
    302             log.error(e.getMessage(), e);
    303             return true;
    304         }
    305     }
    306
    307     /***
    308      * <p><strong>This operation is not supported and will throw an
    309      * UnsupportedOperationException.</strong></p>
    310      *
    311      * @param key the key
    312      * @param value the value
    313      * @throws UnsupportedOperationException
    314      */
    315     public void setProperty(String key, Object value)
    316     {
    317         throw new UnsupportedOperationException("This operation is not supported");
    318     }
    319
    320     /***
    321      * Removes the specified property.
    322      *
    323      * @param key the key of the property to remove
    324      */
    325     public void clearProperty(String key)
    326     {
    327         clearedProperties.add(key);
    328     }
    329
    330     /***
    331      * Checks whether the specified key is contained in this configuration.
    332      *
    333      * @param key the key to check
    334      * @return a flag whether this key is stored in this configuration
    335      */
    336     public boolean containsKey(String key)
    337     {
    338         if (clearedProperties.contains(key))
    339         {
    340             return false;
    341         }
    342         key = StringUtils.replace(key, ".", "/");
    343         try
    344         {
    345             // throws a NamingException if JNDI doesn't contain the key.
    346             getBaseContext().lookup(key);
    347             return true;
    348         }
    349         catch (NameNotFoundException e)
    350         {
    351             // expected exception, no need to log it
    352             return false;
    353         }
    354         catch (NamingException e)
    355         {
    356             log.error(e.getMessage(), e);
    357             return false;
    358         }
    359     }
    360
    361     /***
    362      * Returns the prefix.
    363      * @return the prefix
    364      */
    365     public String getPrefix()
    366     {
    367         return prefix;
    368     }
    369
    370     /***
    371      * Sets the prefix.
    372      *
    373      * @param prefix The prefix to set
    374      */
    375     public void setPrefix(String prefix)
    376     {
    377         this.prefix = prefix;
    378
    379         // clear the previous baseContext
    380         baseContext = null;
    381     }
    382
    383     /***
    384      * Returns the value of the specified property.
    385      *
    386      * @param key the key of the property
    387      * @return the value of this property
    388      */
    389     public Object getProperty(String key)
    390     {
    391         if (clearedProperties.contains(key))
    392         {
    393             return null;
    394         }
    395
    396         try
    397         {
    398             key = StringUtils.replace(key, ".", "/");
    399             return getBaseContext().lookup(key);
    400         }
    401         catch (NameNotFoundException e)
    402         {
    403             // expected exception, no need to log it
    404             return null;
    405         }
    406         catch (NamingException e)
    407         {
    408             log.error(e.getMessage(), e);
    409             return null;
    410         }
    411     }
    412
    413     /***
    414      * <p><strong>This operation is not supported and will throw an
    415      * UnsupportedOperationException.</strong></p>
    416      *
    417      * @param key the key
    418      * @param obj the value
    419      * @throws UnsupportedOperationException
    420      */
    421     protected void addPropertyDirect(String key, Object obj)
    422     {
    423         throw new UnsupportedOperationException("This operation is not supported");
    424     }
    425
    426     /***
    427      * Return the base context with the prefix applied.
    428      *
    429      * @return the base context
    430      * @throws NamingException if an error occurs
    431      */
    432     public Context getBaseContext() throws NamingException
    433     {
    434         if (baseContext == null)
    435         {
    436             baseContext = (Context) getContext().lookup(prefix == null ? "" : prefix);
    437         }
    438
    439         return baseContext;
    440     }
    441
    442     /***
    443      * Return the initial context used by this configuration. This context is
    444      * independent of the prefix specified.
    445      *
    446      * @return the initial context
    447      */
    448     public Context getContext()
    449     {
    450         return context;
    451     }
    452
    453     /***
    454      * Set the initial context of the configuration.
    455      *
    456      * @param context the context
    457      */
    458     public void setContext(Context context)
    459     {
    460         // forget the removed properties
    461         clearedProperties.clear();
    462
    463         // change the context
    464         this.context = context;
    465     }
    466 }