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