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