001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.configuration;
019:
020: import java.util.ArrayList;
021: import java.util.HashSet;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Set;
025:
026: import javax.naming.Context;
027: import javax.naming.InitialContext;
028: import javax.naming.NameClassPair;
029: import javax.naming.NameNotFoundException;
030: import javax.naming.NamingEnumeration;
031: import javax.naming.NamingException;
032: import javax.naming.NotContextException;
033:
034: import org.apache.commons.lang.StringUtils;
035: import org.apache.commons.logging.LogFactory;
036:
037: /**
038: * This Configuration class allows you to interface with a JNDI datasource.
039: * A JNDIConfiguration is read-only, write operations will throw an
040: * UnsupportedOperationException. The clear operations are supported but the
041: * underlying JNDI data source is not changed.
042: *
043: * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
044: * @version $Id: JNDIConfiguration.java 497181 2007-01-17 21:35:28Z oheger $
045: */
046: public class JNDIConfiguration extends AbstractConfiguration {
047: /** The prefix of the context. */
048: private String prefix;
049:
050: /** The initial JNDI context. */
051: private Context context;
052:
053: /** The base JNDI context. */
054: private Context baseContext;
055:
056: /** The Set of keys that have been virtually cleared. */
057: private Set clearedProperties = new HashSet();
058:
059: /**
060: * Creates a JNDIConfiguration using the default initial context as the
061: * root of the properties.
062: *
063: * @throws NamingException thrown if an error occurs when initializing the default context
064: */
065: public JNDIConfiguration() throws NamingException {
066: this ((String) null);
067: }
068:
069: /**
070: * Creates a JNDIConfiguration using the default initial context, shifted
071: * with the specified prefix, as the root of the properties.
072: *
073: * @param prefix the prefix
074: *
075: * @throws NamingException thrown if an error occurs when initializing the default context
076: */
077: public JNDIConfiguration(String prefix) throws NamingException {
078: this (new InitialContext(), prefix);
079: }
080:
081: /**
082: * Creates a JNDIConfiguration using the specified initial context as the
083: * root of the properties.
084: *
085: * @param context the initial context
086: */
087: public JNDIConfiguration(Context context) {
088: this (context, null);
089: }
090:
091: /**
092: * Creates a JNDIConfiguration using the specified initial context shifted
093: * by the specified prefix as the root of the properties.
094: *
095: * @param context the initial context
096: * @param prefix the prefix
097: */
098: public JNDIConfiguration(Context context, String prefix) {
099: this .context = context;
100: this .prefix = prefix;
101: setLogger(LogFactory.getLog(getClass()));
102: addErrorLogListener();
103: }
104:
105: /**
106: * This method recursive traverse the JNDI tree, looking for Context objects.
107: * When it finds them, it traverses them as well. Otherwise it just adds the
108: * values to the list of keys found.
109: *
110: * @param keys All the keys that have been found.
111: * @param context The parent context
112: * @param prefix What prefix we are building on.
113: * @throws NamingException If JNDI has an issue.
114: */
115: private void recursiveGetKeys(Set keys, Context context,
116: String prefix) throws NamingException {
117: NamingEnumeration elements = null;
118:
119: try {
120: elements = context.list("");
121:
122: // iterates through the context's elements
123: while (elements.hasMore()) {
124: NameClassPair nameClassPair = (NameClassPair) elements
125: .next();
126: String name = nameClassPair.getName();
127: Object object = context.lookup(name);
128:
129: // build the key
130: StringBuffer key = new StringBuffer();
131: key.append(prefix);
132: if (key.length() > 0) {
133: key.append(".");
134: }
135: key.append(name);
136:
137: if (object instanceof Context) {
138: // add the keys of the sub context
139: Context subcontext = (Context) object;
140: recursiveGetKeys(keys, subcontext, key.toString());
141: } else {
142: // add the key
143: keys.add(key.toString());
144: }
145: }
146: } finally {
147: // close the enumeration
148: if (elements != null) {
149: elements.close();
150: }
151: }
152: }
153:
154: /**
155: * Returns an iterator with all property keys stored in this configuration.
156: *
157: * @return an iterator with all keys
158: */
159: public Iterator getKeys() {
160: return getKeys("");
161: }
162:
163: /**
164: * Returns an iterator with all property keys starting with the given
165: * prefix.
166: *
167: * @param prefix the prefix
168: * @return an iterator with the selected keys
169: */
170: public Iterator getKeys(String prefix) {
171: // build the path
172: String[] splitPath = StringUtils.split(prefix, ".");
173:
174: List path = new ArrayList();
175:
176: for (int i = 0; i < splitPath.length; i++) {
177: path.add(splitPath[i]);
178: }
179:
180: try {
181: // find the context matching the specified path
182: Context context = getContext(path, getBaseContext());
183:
184: // return all the keys under the context found
185: Set keys = new HashSet();
186: if (context != null) {
187: recursiveGetKeys(keys, context, prefix);
188: } else if (containsKey(prefix)) {
189: // add the prefix if it matches exactly a property key
190: keys.add(prefix);
191: }
192:
193: return keys.iterator();
194: } catch (NamingException e) {
195: fireError(EVENT_READ_PROPERTY, null, null, e);
196: return new ArrayList().iterator();
197: }
198: }
199:
200: /**
201: * Because JNDI is based on a tree configuration, we need to filter down the
202: * tree, till we find the Context specified by the key to start from.
203: * Otherwise return null.
204: *
205: * @param path the path of keys to traverse in order to find the context
206: * @param context the context to start from
207: * @return The context at that key's location in the JNDI tree, or null if not found
208: * @throws NamingException if JNDI has an issue
209: */
210: private Context getContext(List path, Context context)
211: throws NamingException {
212: // return the current context if the path is empty
213: if (path == null || path.isEmpty()) {
214: return context;
215: }
216:
217: String key = (String) path.get(0);
218:
219: // search a context matching the key in the context's elements
220: NamingEnumeration elements = null;
221:
222: try {
223: elements = context.list("");
224: while (elements.hasMore()) {
225: NameClassPair nameClassPair = (NameClassPair) elements
226: .next();
227: String name = nameClassPair.getName();
228: Object object = context.lookup(name);
229:
230: if (object instanceof Context && name.equals(key)) {
231: Context subcontext = (Context) object;
232:
233: // recursive search in the sub context
234: return getContext(path.subList(1, path.size()),
235: subcontext);
236: }
237: }
238: } finally {
239: if (elements != null) {
240: elements.close();
241: }
242: }
243:
244: return null;
245: }
246:
247: /**
248: * Returns a flag whether this configuration is empty.
249: *
250: * @return the empty flag
251: */
252: public boolean isEmpty() {
253: try {
254: NamingEnumeration enumeration = null;
255:
256: try {
257: enumeration = getBaseContext().list("");
258: return !enumeration.hasMore();
259: } finally {
260: // close the enumeration
261: if (enumeration != null) {
262: enumeration.close();
263: }
264: }
265: } catch (NamingException e) {
266: fireError(EVENT_READ_PROPERTY, null, null, e);
267: return true;
268: }
269: }
270:
271: /**
272: * <p><strong>This operation is not supported and will throw an
273: * UnsupportedOperationException.</strong></p>
274: *
275: * @param key the key
276: * @param value the value
277: * @throws UnsupportedOperationException
278: */
279: public void setProperty(String key, Object value) {
280: throw new UnsupportedOperationException(
281: "This operation is not supported");
282: }
283:
284: /**
285: * Removes the specified property.
286: *
287: * @param key the key of the property to remove
288: */
289: public void clearProperty(String key) {
290: clearedProperties.add(key);
291: }
292:
293: /**
294: * Checks whether the specified key is contained in this configuration.
295: *
296: * @param key the key to check
297: * @return a flag whether this key is stored in this configuration
298: */
299: public boolean containsKey(String key) {
300: if (clearedProperties.contains(key)) {
301: return false;
302: }
303: key = StringUtils.replace(key, ".", "/");
304: try {
305: // throws a NamingException if JNDI doesn't contain the key.
306: getBaseContext().lookup(key);
307: return true;
308: } catch (NameNotFoundException e) {
309: // expected exception, no need to log it
310: return false;
311: } catch (NamingException e) {
312: fireError(EVENT_READ_PROPERTY, key, null, e);
313: return false;
314: }
315: }
316:
317: /**
318: * Returns the prefix.
319: * @return the prefix
320: */
321: public String getPrefix() {
322: return prefix;
323: }
324:
325: /**
326: * Sets the prefix.
327: *
328: * @param prefix The prefix to set
329: */
330: public void setPrefix(String prefix) {
331: this .prefix = prefix;
332:
333: // clear the previous baseContext
334: baseContext = null;
335: }
336:
337: /**
338: * Returns the value of the specified property.
339: *
340: * @param key the key of the property
341: * @return the value of this property
342: */
343: public Object getProperty(String key) {
344: if (clearedProperties.contains(key)) {
345: return null;
346: }
347:
348: try {
349: key = StringUtils.replace(key, ".", "/");
350: return getBaseContext().lookup(key);
351: } catch (NameNotFoundException e) {
352: // expected exception, no need to log it
353: return null;
354: } catch (NotContextException nctxex) {
355: // expected exception, no need to log it
356: return null;
357: } catch (NamingException e) {
358: fireError(EVENT_READ_PROPERTY, key, null, e);
359: return null;
360: }
361: }
362:
363: /**
364: * <p><strong>This operation is not supported and will throw an
365: * UnsupportedOperationException.</strong></p>
366: *
367: * @param key the key
368: * @param obj the value
369: * @throws UnsupportedOperationException
370: */
371: protected void addPropertyDirect(String key, Object obj) {
372: throw new UnsupportedOperationException(
373: "This operation is not supported");
374: }
375:
376: /**
377: * Return the base context with the prefix applied.
378: *
379: * @return the base context
380: * @throws NamingException if an error occurs
381: */
382: public Context getBaseContext() throws NamingException {
383: if (baseContext == null) {
384: baseContext = (Context) getContext().lookup(
385: prefix == null ? "" : prefix);
386: }
387:
388: return baseContext;
389: }
390:
391: /**
392: * Return the initial context used by this configuration. This context is
393: * independent of the prefix specified.
394: *
395: * @return the initial context
396: */
397: public Context getContext() {
398: return context;
399: }
400:
401: /**
402: * Set the initial context of the configuration.
403: *
404: * @param context the context
405: */
406: public void setContext(Context context) {
407: // forget the removed properties
408: clearedProperties.clear();
409:
410: // change the context
411: this.context = context;
412: }
413: }
|