1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
131 while (elements.hasMore())
132 {
133 NameClassPair nameClassPair = (NameClassPair) elements.next();
134 String name = nameClassPair.getName();
135 Object object = context.lookup(name);
136
137
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
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
159 keys.add(key.toString());
160 }
161 }
162 }
163 finally
164 {
165
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
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
205 Context context = getContext(path, getBaseContext());
206
207
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
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
241 if (path == null || path.isEmpty())
242 {
243 return context;
244 }
245
246 String key = (String) path.get(0);
247
248
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
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
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
351 getBaseContext().lookup(key);
352 return true;
353 }
354 catch (NameNotFoundException e)
355 {
356
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
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
409 return null;
410 }
411 catch (NotContextException nctxex)
412 {
413
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
471 clearedProperties.clear();
472
473
474 this.context = context;
475 }
476 }