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 }