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 497181 2007-01-17 21:35:28Z 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      * @throws NamingException If JNDI has an issue.
119      */
120     private void recursiveGetKeys(Set keys, Context context, String prefix) throws NamingException
121     {
122         NamingEnumeration elements = null;
123 
124         try
125         {
126             elements = context.list("");
127 
128             // iterates through the context's elements
129             while (elements.hasMore())
130             {
131                 NameClassPair nameClassPair = (NameClassPair) elements.next();
132                 String name = nameClassPair.getName();
133                 Object object = context.lookup(name);
134 
135                 // build the key
136                 StringBuffer key = new StringBuffer();
137                 key.append(prefix);
138                 if (key.length() > 0)
139                 {
140                     key.append(".");
141                 }
142                 key.append(name);
143 
144                 if (object instanceof Context)
145                 {
146                     // add the keys of the sub context
147                     Context subcontext = (Context) object;
148                     recursiveGetKeys(keys, subcontext, key.toString());
149                 }
150                 else
151                 {
152                     // add the key
153                     keys.add(key.toString());
154                 }
155             }
156         }
157         finally
158         {
159             // close the enumeration
160             if (elements != null)
161             {
162                 elements.close();
163             }
164         }
165     }
166 
167     /***
168      * Returns an iterator with all property keys stored in this configuration.
169      *
170      * @return an iterator with all keys
171      */
172     public Iterator getKeys()
173     {
174         return getKeys("");
175     }
176 
177     /***
178      * Returns an iterator with all property keys starting with the given
179      * prefix.
180      *
181      * @param prefix the prefix
182      * @return an iterator with the selected keys
183      */
184     public Iterator getKeys(String prefix)
185     {
186         // build the path
187         String[] splitPath = StringUtils.split(prefix, ".");
188 
189         List path = new ArrayList();
190 
191         for (int i = 0; i < splitPath.length; i++)
192         {
193             path.add(splitPath[i]);
194         }
195 
196         try
197         {
198             // find the context matching the specified path
199             Context context = getContext(path, getBaseContext());
200 
201             // return all the keys under the context found
202             Set keys = new HashSet();
203             if (context != null)
204             {
205                 recursiveGetKeys(keys, context, prefix);
206             }
207             else if (containsKey(prefix))
208             {
209                 // add the prefix if it matches exactly a property key
210                 keys.add(prefix);
211             }
212 
213             return keys.iterator();
214         }
215         catch (NamingException e)
216         {
217             fireError(EVENT_READ_PROPERTY, null, null, e);
218             return new ArrayList().iterator();
219         }
220     }
221 
222     /***
223      * Because JNDI is based on a tree configuration, we need to filter down the
224      * tree, till we find the Context specified by the key to start from.
225      * Otherwise return null.
226      *
227      * @param path     the path of keys to traverse in order to find the context
228      * @param context  the context to start from
229      * @return The context at that key's location in the JNDI tree, or null if not found
230      * @throws NamingException if JNDI has an issue
231      */
232     private Context getContext(List path, Context context) throws NamingException
233     {
234         // return the current context if the path is empty
235         if (path == null || path.isEmpty())
236         {
237             return context;
238         }
239 
240         String key = (String) path.get(0);
241 
242         // search a context matching the key in the context's elements
243         NamingEnumeration elements = null;
244 
245         try
246         {
247             elements = context.list("");
248             while (elements.hasMore())
249             {
250                 NameClassPair nameClassPair = (NameClassPair) elements.next();
251                 String name = nameClassPair.getName();
252                 Object object = context.lookup(name);
253 
254                 if (object instanceof Context && name.equals(key))
255                 {
256                     Context subcontext = (Context) object;
257 
258                     // recursive search in the sub context
259                     return getContext(path.subList(1, path.size()), subcontext);
260                 }
261             }
262         }
263         finally
264         {
265             if (elements != null)
266             {
267                 elements.close();
268             }
269         }
270 
271         return null;
272     }
273 
274     /***
275      * Returns a flag whether this configuration is empty.
276      *
277      * @return the empty flag
278      */
279     public boolean isEmpty()
280     {
281         try
282         {
283             NamingEnumeration enumeration = null;
284 
285             try
286             {
287                 enumeration = getBaseContext().list("");
288                 return !enumeration.hasMore();
289             }
290             finally
291             {
292                 // close the enumeration
293                 if (enumeration != null)
294                 {
295                     enumeration.close();
296                 }
297             }
298         }
299         catch (NamingException e)
300         {
301             fireError(EVENT_READ_PROPERTY, null, null, e);
302             return true;
303         }
304     }
305 
306     /***
307      * <p><strong>This operation is not supported and will throw an
308      * UnsupportedOperationException.</strong></p>
309      *
310      * @param key the key
311      * @param value the value
312      * @throws UnsupportedOperationException
313      */
314     public void setProperty(String key, Object value)
315     {
316         throw new UnsupportedOperationException("This operation is not supported");
317     }
318 
319     /***
320      * Removes the specified property.
321      *
322      * @param key the key of the property to remove
323      */
324     public void clearProperty(String key)
325     {
326         clearedProperties.add(key);
327     }
328 
329     /***
330      * Checks whether the specified key is contained in this configuration.
331      *
332      * @param key the key to check
333      * @return a flag whether this key is stored in this configuration
334      */
335     public boolean containsKey(String key)
336     {
337         if (clearedProperties.contains(key))
338         {
339             return false;
340         }
341         key = StringUtils.replace(key, ".", "/");
342         try
343         {
344             // throws a NamingException if JNDI doesn't contain the key.
345             getBaseContext().lookup(key);
346             return true;
347         }
348         catch (NameNotFoundException e)
349         {
350             // expected exception, no need to log it
351             return false;
352         }
353         catch (NamingException e)
354         {
355             fireError(EVENT_READ_PROPERTY, key, null, e);
356             return false;
357         }
358     }
359 
360     /***
361      * Returns the prefix.
362      * @return the prefix
363      */
364     public String getPrefix()
365     {
366         return prefix;
367     }
368 
369     /***
370      * Sets the prefix.
371      *
372      * @param prefix The prefix to set
373      */
374     public void setPrefix(String prefix)
375     {
376         this.prefix = prefix;
377 
378         // clear the previous baseContext
379         baseContext = null;
380     }
381 
382     /***
383      * Returns the value of the specified property.
384      *
385      * @param key the key of the property
386      * @return the value of this property
387      */
388     public Object getProperty(String key)
389     {
390         if (clearedProperties.contains(key))
391         {
392             return null;
393         }
394 
395         try
396         {
397             key = StringUtils.replace(key, ".", "/");
398             return getBaseContext().lookup(key);
399         }
400         catch (NameNotFoundException e)
401         {
402             // expected exception, no need to log it
403             return null;
404         }
405         catch (NotContextException nctxex)
406         {
407             // expected exception, no need to log it
408             return null;
409         }
410         catch (NamingException e)
411         {
412             fireError(EVENT_READ_PROPERTY, key, null, e);
413             return null;
414         }
415     }
416 
417     /***
418      * <p><strong>This operation is not supported and will throw an
419      * UnsupportedOperationException.</strong></p>
420      *
421      * @param key the key
422      * @param obj the value
423      * @throws UnsupportedOperationException
424      */
425     protected void addPropertyDirect(String key, Object obj)
426     {
427         throw new UnsupportedOperationException("This operation is not supported");
428     }
429 
430     /***
431      * Return the base context with the prefix applied.
432      *
433      * @return the base context
434      * @throws NamingException if an error occurs
435      */
436     public Context getBaseContext() throws NamingException
437     {
438         if (baseContext == null)
439         {
440             baseContext = (Context) getContext().lookup(prefix == null ? "" : prefix);
441         }
442 
443         return baseContext;
444     }
445 
446     /***
447      * Return the initial context used by this configuration. This context is
448      * independent of the prefix specified.
449      *
450      * @return the initial context
451      */
452     public Context getContext()
453     {
454         return context;
455     }
456 
457     /***
458      * Set the initial context of the configuration.
459      *
460      * @param context the context
461      */
462     public void setContext(Context context)
463     {
464         // forget the removed properties
465         clearedProperties.clear();
466 
467         // change the context
468         this.context = context;
469     }
470 }