View Javadoc

1   /*
2    * Copyright 2001-2004 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.Properties;
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.Log;
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,v 1.19 2004/10/04 20:06:09 ebourg Exp $
46   */
47  public class JNDIConfiguration extends AbstractConfiguration
48  {
49      /*** Logger. */
50      private static Log log = LogFactory.getLog(JNDIConfiguration.class);
51  
52      /*** The prefix of the context. */
53      private String prefix;
54  
55      /*** The initial JNDI context. */
56      private Context context;
57  
58      /*** The base JNDI context. */
59      private Context baseContext;
60  
61      /*** The Set of keys that have been virtually cleared. */
62      private Set clearedProperties = new HashSet();
63  
64      /***
65       * Creates a JNDIConfiguration using the default initial context as the
66       * root of the properties.
67       *
68       * @throws NamingException thrown if an error occurs when initializing the default context
69       */
70      public JNDIConfiguration() throws NamingException
71      {
72          this((String) null);
73      }
74  
75      /***
76       * Creates a JNDIConfiguration using the default initial context, shifted
77       * with the specified prefix, as the root of the properties.
78       *
79       * @param prefix
80       *
81       * @throws NamingException thrown if an error occurs when initializing the default context
82       */
83      public JNDIConfiguration(String prefix) throws NamingException
84      {
85          this(new InitialContext(), prefix);
86      }
87  
88      /***
89       * Creates a JNDIConfiguration using the specified initial context as the
90       * root of the properties.
91       *
92       * @param context the initial context
93       */
94      public JNDIConfiguration(Context context)
95      {
96          this(context, null);
97      }
98  
99      /***
100      * Creates a JNDIConfiguration using the specified initial context shifted
101      * by the specified prefix as the root of the properties.
102      *
103      * @param context the initial context
104      * @param prefix
105      */
106     public JNDIConfiguration(Context context, String prefix)
107     {
108         this.context = context;
109         this.prefix = prefix;
110     }
111 
112     /***
113      * JNDIConfigurations can not be added to.
114      *
115      * @param key The Key to add the property to.
116      * @param token The Value to add.
117      */
118     public void addProperty(String key, Object token)
119     {
120         throw new UnsupportedOperationException("This operation is not supported");
121     }
122 
123     /***
124      * This method recursive traverse the JNDI tree, looking for Context objects.
125      * When it finds them, it traverses them as well.  Otherwise it just adds the
126      * values to the list of keys found.
127      *
128      * @param keys All the keys that have been found.
129      * @param context The parent context
130      * @param prefix What prefix we are building on.
131      * @throws NamingException If JNDI has an issue.
132      */
133     private void recursiveGetKeys(Set keys, Context context, String prefix) throws NamingException
134     {
135         NamingEnumeration elements = null;
136 
137         try
138         {
139             elements = context.list("");
140 
141             // iterates through the context's elements
142             while (elements.hasMore())
143             {
144                 NameClassPair nameClassPair = (NameClassPair) elements.next();
145                 String name = nameClassPair.getName();
146                 Object object = context.lookup(name);
147 
148                 // build the key
149                 StringBuffer key = new StringBuffer();
150                 key.append(prefix);
151                 if (key.length() > 0)
152                 {
153                     key.append(".");
154                 }
155                 key.append(name);
156 
157                 if (object instanceof Context)
158                 {
159                     // add the keys of the sub context
160                     Context subcontext = (Context) object;
161                     recursiveGetKeys(keys, subcontext, key.toString());
162                 }
163                 else
164                 {
165                     // add the key
166                     keys.add(key.toString());
167                 }
168             }
169         }
170         finally
171         {
172             // close the enumeration
173             if (elements != null)
174             {
175                 elements.close();
176             }
177         }
178     }
179 
180     /***
181      * {@inheritDoc}
182      */
183     public Iterator getKeys()
184     {
185         return getKeys("");
186     }
187 
188     /***
189      * {@inheritDoc}
190      */
191     public Iterator getKeys(String prefix)
192     {
193         // build the path
194         String[] splitPath = StringUtils.split(prefix, ".");
195 
196         List path = new ArrayList();
197 
198         for (int i = 0; i < splitPath.length; i++)
199         {
200             path.add(splitPath[i]);
201         }
202 
203         try
204         {
205             // find the context matching the specified path
206             Context context = getContext(path, getBaseContext());
207 
208             // return all the keys under the context found
209             Set keys = new HashSet();
210             if (context != null)
211             {
212                 recursiveGetKeys(keys, context, prefix);
213             }
214             else if (containsKey(prefix))
215             {
216                 // add the prefix if it matches exactly a property key
217                 keys.add(prefix);
218             }
219 
220             return keys.iterator();
221         }
222         catch (NamingException e)
223         {
224             throw new ConfigurationRuntimeException(e.getMessage(), e);
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      * {@inheritDoc}
282      *
283      * <b>This operation is not supported</b>
284      */
285     public Properties getProperties(String key)
286     {
287         throw new UnsupportedOperationException("This operation is not supported");
288     }
289 
290     /***
291      * {@inheritDoc}
292      */
293     public boolean isEmpty()
294     {
295         try
296         {
297             NamingEnumeration enumeration = null;
298 
299             try
300             {
301                 enumeration = getBaseContext().list("");
302                 return !enumeration.hasMore();
303             }
304             finally
305             {
306                 // close the enumeration
307                 if (enumeration != null)
308                 {
309                     enumeration.close();
310                 }
311             }
312         }
313         catch (NamingException ne)
314         {
315             log.warn(ne);
316             return true;
317         }
318     }
319 
320     /***
321      * {@inheritDoc}
322      */
323     public Object getProperty(String key)
324     {
325         return getPropertyDirect(key);
326     }
327 
328     /***
329      * {@inheritDoc}
330      */
331     public void setProperty(String key, Object value)
332     {
333         throw new UnsupportedOperationException("This operation is not supported");
334     }
335 
336     /***
337      * {@inheritDoc}
338      */
339     public void clearProperty(String key)
340     {
341         clearedProperties.add(key);
342     }
343 
344     /***
345      * {@inheritDoc}
346      */
347     public boolean containsKey(String key)
348     {
349         if (clearedProperties.contains(key))
350         {
351             return false;
352         }
353         key = StringUtils.replace(key, ".", "/");
354         try
355         {
356             // throws a NamingException if JNDI doesn't contain the key.
357             getBaseContext().lookup(key);
358             return true;
359         }
360         catch (NamingException ne)
361         {
362             return false;
363         }
364     }
365 
366     /***
367      * @return String
368      */
369     public String getPrefix()
370     {
371         return prefix;
372     }
373 
374     /***
375      * Sets the prefix.
376      *
377      * @param prefix The prefix to set
378      */
379     public void setPrefix(String prefix)
380     {
381         this.prefix = prefix;
382 
383         // clear the previous baseContext
384         baseContext = null;
385     }
386 
387     /***
388      * {@inheritDoc}
389      */
390     protected Object getPropertyDirect(String key)
391     {
392         if (clearedProperties.contains(key))
393         {
394             return null;
395         }
396 
397         try
398         {
399             key = StringUtils.replace(key, ".", "/");
400             return getBaseContext().lookup(key);
401         }
402         catch (NameNotFoundException e)
403         {
404             return null;
405         }
406         catch (NotContextException e)
407         {
408             return null;
409         }
410         catch (NamingException e)
411         {
412             e.printStackTrace();
413             return null;
414         }
415     }
416 
417     /***
418      * {@inheritDoc}
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     public Context getBaseContext() throws NamingException
429     {
430         if (baseContext == null)
431         {
432             baseContext = (Context) getContext().lookup(prefix == null ? "" : prefix);
433         }
434 
435         return baseContext;
436     }
437 
438     /***
439      * Return the initial context used by this configuration. This context is
440      * independent of the prefix specified.
441      */
442     public Context getContext()
443     {
444         return context;
445     }
446 
447     /***
448      * Set the initial context of the configuration.
449      */
450     public void setContext(Context context)
451     {
452         // forget the removed properties
453         clearedProperties.clear();
454 
455         // change the context
456         this.context = context;
457     }
458 }