View Javadoc

1   /*
2    * Copyright 2001-2005 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.configuration;
18  
19  import java.util.ArrayList;
20  import java.util.HashSet;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Set;
24  
25  import javax.naming.Context;
26  import javax.naming.InitialContext;
27  import javax.naming.NameClassPair;
28  import javax.naming.NameNotFoundException;
29  import javax.naming.NamingEnumeration;
30  import javax.naming.NamingException;
31  
32  import org.apache.commons.lang.StringUtils;
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  
36  /***
37   * This Configuration class allows you to interface with a JNDI datasource.
38   * A JNDIConfiguration is read-only, write operations will throw an
39   * UnsupportedOperationException. The clear operations are supported but the
40   * underlying JNDI data source is not changed.
41   *
42   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
43   * @version $Id: JNDIConfiguration.java 295090 2005-10-05 19:36:15Z oheger $
44   */
45  public class JNDIConfiguration extends AbstractConfiguration
46  {
47      /*** Logger. */
48      private static Log log = LogFactory.getLog(JNDIConfiguration.class);
49  
50      /*** The prefix of the context. */
51      private String prefix;
52  
53      /*** The initial JNDI context. */
54      private Context context;
55  
56      /*** The base JNDI context. */
57      private Context baseContext;
58  
59      /*** The Set of keys that have been virtually cleared. */
60      private Set clearedProperties = new HashSet();
61  
62      /***
63       * Creates a JNDIConfiguration using the default initial context as the
64       * root of the properties.
65       *
66       * @throws NamingException thrown if an error occurs when initializing the default context
67       */
68      public JNDIConfiguration() throws NamingException
69      {
70          this((String) null);
71      }
72  
73      /***
74       * Creates a JNDIConfiguration using the default initial context, shifted
75       * with the specified prefix, as the root of the properties.
76       *
77       * @param prefix the prefix
78       *
79       * @throws NamingException thrown if an error occurs when initializing the default context
80       */
81      public JNDIConfiguration(String prefix) throws NamingException
82      {
83          this(new InitialContext(), prefix);
84      }
85  
86      /***
87       * Creates a JNDIConfiguration using the specified initial context as the
88       * root of the properties.
89       *
90       * @param context the initial context
91       */
92      public JNDIConfiguration(Context context)
93      {
94          this(context, null);
95      }
96  
97      /***
98       * Creates a JNDIConfiguration using the specified initial context shifted
99       * by the specified prefix as the root of the properties.
100      *
101      * @param context the initial context
102      * @param prefix the prefix
103      */
104     public JNDIConfiguration(Context context, String prefix)
105     {
106         this.context = context;
107         this.prefix = prefix;
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             log.error(e.getMessage(), 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             log.error(e.getMessage(), 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             log.error(e.getMessage(), 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 (NamingException e)
406         {
407             log.error(e.getMessage(), e);
408             return null;
409         }
410     }
411 
412     /***
413      * <p><strong>This operation is not supported and will throw an
414      * UnsupportedOperationException.</strong></p>
415      *
416      * @param key the key
417      * @param obj the value
418      * @throws UnsupportedOperationException
419      */
420     protected void addPropertyDirect(String key, Object obj)
421     {
422         throw new UnsupportedOperationException("This operation is not supported");
423     }
424 
425     /***
426      * Return the base context with the prefix applied.
427      *
428      * @return the base context
429      * @throws NamingException if an error occurs
430      */
431     public Context getBaseContext() throws NamingException
432     {
433         if (baseContext == null)
434         {
435             baseContext = (Context) getContext().lookup(prefix == null ? "" : prefix);
436         }
437 
438         return baseContext;
439     }
440 
441     /***
442      * Return the initial context used by this configuration. This context is
443      * independent of the prefix specified.
444      *
445      * @return the initial context
446      */
447     public Context getContext()
448     {
449         return context;
450     }
451 
452     /***
453      * Set the initial context of the configuration.
454      *
455      * @param context the context
456      */
457     public void setContext(Context context)
458     {
459         // forget the removed properties
460         clearedProperties.clear();
461 
462         // change the context
463         this.context = context;
464     }
465 }