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