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 1081926 2011-03-15 20:20: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 (NameNotFoundException e) 222 { 223 // expected exception, no need to log it 224 return new ArrayList().iterator(); 225 } 226 catch (NamingException e) 227 { 228 fireError(EVENT_READ_PROPERTY, null, null, e); 229 return new ArrayList().iterator(); 230 } 231 } 232 233 /** 234 * Because JNDI is based on a tree configuration, we need to filter down the 235 * tree, till we find the Context specified by the key to start from. 236 * Otherwise return null. 237 * 238 * @param path the path of keys to traverse in order to find the context 239 * @param context the context to start from 240 * @return The context at that key's location in the JNDI tree, or null if not found 241 * @throws NamingException if JNDI has an issue 242 */ 243 private Context getContext(List path, Context context) throws NamingException 244 { 245 // return the current context if the path is empty 246 if (path == null || path.isEmpty()) 247 { 248 return context; 249 } 250 251 String key = (String) path.get(0); 252 253 // search a context matching the key in the context's elements 254 NamingEnumeration elements = null; 255 256 try 257 { 258 elements = context.list(""); 259 while (elements.hasMore()) 260 { 261 NameClassPair nameClassPair = (NameClassPair) elements.next(); 262 String name = nameClassPair.getName(); 263 Object object = context.lookup(name); 264 265 if (object instanceof Context && name.equals(key)) 266 { 267 Context subcontext = (Context) object; 268 269 // recursive search in the sub context 270 return getContext(path.subList(1, path.size()), subcontext); 271 } 272 } 273 } 274 finally 275 { 276 if (elements != null) 277 { 278 elements.close(); 279 } 280 } 281 282 return null; 283 } 284 285 /** 286 * Returns a flag whether this configuration is empty. 287 * 288 * @return the empty flag 289 */ 290 public boolean isEmpty() 291 { 292 try 293 { 294 NamingEnumeration enumeration = null; 295 296 try 297 { 298 enumeration = getBaseContext().list(""); 299 return !enumeration.hasMore(); 300 } 301 finally 302 { 303 // close the enumeration 304 if (enumeration != null) 305 { 306 enumeration.close(); 307 } 308 } 309 } 310 catch (NamingException e) 311 { 312 fireError(EVENT_READ_PROPERTY, null, null, e); 313 return true; 314 } 315 } 316 317 /** 318 * <p><strong>This operation is not supported and will throw an 319 * UnsupportedOperationException.</strong></p> 320 * 321 * @param key the key 322 * @param value the value 323 * @throws UnsupportedOperationException 324 */ 325 public void setProperty(String key, Object value) 326 { 327 throw new UnsupportedOperationException("This operation is not supported"); 328 } 329 330 /** 331 * Removes the specified property. 332 * 333 * @param key the key of the property to remove 334 */ 335 public void clearProperty(String key) 336 { 337 clearedProperties.add(key); 338 } 339 340 /** 341 * Checks whether the specified key is contained in this configuration. 342 * 343 * @param key the key to check 344 * @return a flag whether this key is stored in this configuration 345 */ 346 public boolean containsKey(String key) 347 { 348 if (clearedProperties.contains(key)) 349 { 350 return false; 351 } 352 key = StringUtils.replace(key, ".", "/"); 353 try 354 { 355 // throws a NamingException if JNDI doesn't contain the key. 356 getBaseContext().lookup(key); 357 return true; 358 } 359 catch (NameNotFoundException e) 360 { 361 // expected exception, no need to log it 362 return false; 363 } 364 catch (NamingException e) 365 { 366 fireError(EVENT_READ_PROPERTY, key, null, e); 367 return false; 368 } 369 } 370 371 /** 372 * Returns the prefix. 373 * @return the prefix 374 */ 375 public String getPrefix() 376 { 377 return prefix; 378 } 379 380 /** 381 * Sets the prefix. 382 * 383 * @param prefix The prefix to set 384 */ 385 public void setPrefix(String prefix) 386 { 387 this.prefix = prefix; 388 389 // clear the previous baseContext 390 baseContext = null; 391 } 392 393 /** 394 * Returns the value of the specified property. 395 * 396 * @param key the key of the property 397 * @return the value of this property 398 */ 399 public Object getProperty(String key) 400 { 401 if (clearedProperties.contains(key)) 402 { 403 return null; 404 } 405 406 try 407 { 408 key = StringUtils.replace(key, ".", "/"); 409 return getBaseContext().lookup(key); 410 } 411 catch (NameNotFoundException e) 412 { 413 // expected exception, no need to log it 414 return null; 415 } 416 catch (NotContextException nctxex) 417 { 418 // expected exception, no need to log it 419 return null; 420 } 421 catch (NamingException e) 422 { 423 fireError(EVENT_READ_PROPERTY, key, null, e); 424 return null; 425 } 426 } 427 428 /** 429 * <p><strong>This operation is not supported and will throw an 430 * UnsupportedOperationException.</strong></p> 431 * 432 * @param key the key 433 * @param obj the value 434 * @throws UnsupportedOperationException 435 */ 436 protected void addPropertyDirect(String key, Object obj) 437 { 438 throw new UnsupportedOperationException("This operation is not supported"); 439 } 440 441 /** 442 * Return the base context with the prefix applied. 443 * 444 * @return the base context 445 * @throws NamingException if an error occurs 446 */ 447 public Context getBaseContext() throws NamingException 448 { 449 if (baseContext == null) 450 { 451 baseContext = (Context) getContext().lookup(prefix == null ? "" : prefix); 452 } 453 454 return baseContext; 455 } 456 457 /** 458 * Return the initial context used by this configuration. This context is 459 * independent of the prefix specified. 460 * 461 * @return the initial context 462 */ 463 public Context getContext() 464 { 465 return context; 466 } 467 468 /** 469 * Set the initial context of the configuration. 470 * 471 * @param context the context 472 */ 473 public void setContext(Context context) 474 { 475 // forget the removed properties 476 clearedProperties.clear(); 477 478 // change the context 479 this.context = context; 480 } 481 }