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 549591 2007-06-21 19:57:25Z oheger $ 045 */ 046 public class JNDIConfiguration extends AbstractConfiguration 047 { 048 /** The prefix of the context. */ 049 private String prefix; 050 051 /** The initial JNDI context. */ 052 private Context context; 053 054 /** The base JNDI context. */ 055 private Context baseContext; 056 057 /** The Set of keys that have been virtually cleared. */ 058 private Set clearedProperties = new HashSet(); 059 060 /** 061 * Creates a JNDIConfiguration using the default initial context as the 062 * root of the properties. 063 * 064 * @throws NamingException thrown if an error occurs when initializing the default context 065 */ 066 public JNDIConfiguration() throws NamingException 067 { 068 this((String) null); 069 } 070 071 /** 072 * Creates a JNDIConfiguration using the default initial context, shifted 073 * with the specified prefix, as the root of the properties. 074 * 075 * @param prefix the prefix 076 * 077 * @throws NamingException thrown if an error occurs when initializing the default context 078 */ 079 public JNDIConfiguration(String prefix) throws NamingException 080 { 081 this(new InitialContext(), prefix); 082 } 083 084 /** 085 * Creates a JNDIConfiguration using the specified initial context as the 086 * root of the properties. 087 * 088 * @param context the initial context 089 */ 090 public JNDIConfiguration(Context context) 091 { 092 this(context, null); 093 } 094 095 /** 096 * Creates a JNDIConfiguration using the specified initial context shifted 097 * by the specified prefix as the root of the properties. 098 * 099 * @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 // iterates through the context's elements 131 while (elements.hasMore()) 132 { 133 NameClassPair nameClassPair = (NameClassPair) elements.next(); 134 String name = nameClassPair.getName(); 135 Object object = context.lookup(name); 136 137 // build the key 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 // add the keys of the sub context 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 // add the key 159 keys.add(key.toString()); 160 } 161 } 162 } 163 finally 164 { 165 // close the enumeration 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 // build the path 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 // find the context matching the specified path 205 Context context = getContext(path, getBaseContext()); 206 207 // return all the keys under the context found 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 // add the prefix if it matches exactly a property key 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 // 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 * 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 // close the enumeration 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 // throws a NamingException if JNDI doesn't contain the key. 351 getBaseContext().lookup(key); 352 return true; 353 } 354 catch (NameNotFoundException e) 355 { 356 // expected exception, no need to log it 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 // clear the previous baseContext 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 // expected exception, no need to log it 409 return null; 410 } 411 catch (NotContextException nctxex) 412 { 413 // expected exception, no need to log it 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 // forget the removed properties 471 clearedProperties.clear(); 472 473 // change the context 474 this.context = context; 475 } 476 }