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.Arrays; 022 import java.util.HashSet; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.Set; 026 027 import javax.naming.Context; 028 import javax.naming.InitialContext; 029 import javax.naming.NameClassPair; 030 import javax.naming.NameNotFoundException; 031 import javax.naming.NamingEnumeration; 032 import javax.naming.NamingException; 033 import javax.naming.NotContextException; 034 035 import org.apache.commons.lang.StringUtils; 036 import org.apache.commons.logging.LogFactory; 037 038 /** 039 * This Configuration class allows you to interface with a JNDI datasource. 040 * A JNDIConfiguration is read-only, write operations will throw an 041 * UnsupportedOperationException. The clear operations are supported but the 042 * underlying JNDI data source is not changed. 043 * 044 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a> 045 * @version $Id: JNDIConfiguration.java 1234985 2012-01-23 21:09:09Z oheger $ 046 */ 047 public class JNDIConfiguration extends AbstractConfiguration 048 { 049 /** The prefix of the context. */ 050 private String prefix; 051 052 /** The initial JNDI context. */ 053 private Context context; 054 055 /** The base JNDI context. */ 056 private Context baseContext; 057 058 /** The Set of keys that have been virtually cleared. */ 059 private Set<String> clearedProperties = new HashSet<String>(); 060 061 /** 062 * Creates a JNDIConfiguration using the default initial context as the 063 * root of the properties. 064 * 065 * @throws NamingException thrown if an error occurs when initializing the default context 066 */ 067 public JNDIConfiguration() throws NamingException 068 { 069 this((String) null); 070 } 071 072 /** 073 * Creates a JNDIConfiguration using the default initial context, shifted 074 * with the specified prefix, as the root of the properties. 075 * 076 * @param prefix the prefix 077 * 078 * @throws NamingException thrown if an error occurs when initializing the default context 079 */ 080 public JNDIConfiguration(String prefix) throws NamingException 081 { 082 this(new InitialContext(), prefix); 083 } 084 085 /** 086 * Creates a JNDIConfiguration using the specified initial context as the 087 * root of the properties. 088 * 089 * @param context the initial context 090 */ 091 public JNDIConfiguration(Context context) 092 { 093 this(context, null); 094 } 095 096 /** 097 * Creates a JNDIConfiguration using the specified initial context shifted 098 * by the specified prefix as the root of the properties. 099 * 100 * @param context the initial context 101 * @param prefix the prefix 102 */ 103 public JNDIConfiguration(Context context, String prefix) 104 { 105 this.context = context; 106 this.prefix = prefix; 107 setLogger(LogFactory.getLog(getClass())); 108 addErrorLogListener(); 109 } 110 111 /** 112 * This method recursive traverse the JNDI tree, looking for Context objects. 113 * When it finds them, it traverses them as well. Otherwise it just adds the 114 * values to the list of keys found. 115 * 116 * @param keys All the keys that have been found. 117 * @param context The parent context 118 * @param prefix What prefix we are building on. 119 * @param processedCtx a set with the so far processed objects 120 * @throws NamingException If JNDI has an issue. 121 */ 122 private void recursiveGetKeys(Set<String> keys, Context context, String prefix, 123 Set<Context> processedCtx) throws NamingException 124 { 125 processedCtx.add(context); 126 NamingEnumeration<NameClassPair> elements = null; 127 128 try 129 { 130 elements = context.list(""); 131 132 // iterates through the context's elements 133 while (elements.hasMore()) 134 { 135 NameClassPair nameClassPair = elements.next(); 136 String name = nameClassPair.getName(); 137 Object object = context.lookup(name); 138 139 // build the key 140 StringBuilder key = new StringBuilder(); 141 key.append(prefix); 142 if (key.length() > 0) 143 { 144 key.append("."); 145 } 146 key.append(name); 147 148 if (object instanceof Context) 149 { 150 // add the keys of the sub context 151 Context subcontext = (Context) object; 152 if (!processedCtx.contains(subcontext)) 153 { 154 recursiveGetKeys(keys, subcontext, key.toString(), 155 processedCtx); 156 } 157 } 158 else 159 { 160 // add the key 161 keys.add(key.toString()); 162 } 163 } 164 } 165 finally 166 { 167 // close the enumeration 168 if (elements != null) 169 { 170 elements.close(); 171 } 172 } 173 } 174 175 /** 176 * Returns an iterator with all property keys stored in this configuration. 177 * 178 * @return an iterator with all keys 179 */ 180 public Iterator<String> getKeys() 181 { 182 return getKeys(""); 183 } 184 185 /** 186 * Returns an iterator with all property keys starting with the given 187 * prefix. 188 * 189 * @param prefix the prefix 190 * @return an iterator with the selected keys 191 */ 192 @Override 193 public Iterator<String> getKeys(String prefix) 194 { 195 // build the path 196 String[] splitPath = StringUtils.split(prefix, "."); 197 198 List<String> path = Arrays.asList(splitPath); 199 200 try 201 { 202 // find the context matching the specified path 203 Context context = getContext(path, getBaseContext()); 204 205 // return all the keys under the context found 206 Set<String> keys = new HashSet<String>(); 207 if (context != null) 208 { 209 recursiveGetKeys(keys, context, prefix, new HashSet<Context>()); 210 } 211 else if (containsKey(prefix)) 212 { 213 // add the prefix if it matches exactly a property key 214 keys.add(prefix); 215 } 216 217 return keys.iterator(); 218 } 219 catch (NameNotFoundException e) 220 { 221 // expected exception, no need to log it 222 return new ArrayList<String>().iterator(); 223 } 224 catch (NamingException e) 225 { 226 fireError(EVENT_READ_PROPERTY, null, null, e); 227 return new ArrayList<String>().iterator(); 228 } 229 } 230 231 /** 232 * Because JNDI is based on a tree configuration, we need to filter down the 233 * tree, till we find the Context specified by the key to start from. 234 * Otherwise return null. 235 * 236 * @param path the path of keys to traverse in order to find the context 237 * @param context the context to start from 238 * @return The context at that key's location in the JNDI tree, or null if not found 239 * @throws NamingException if JNDI has an issue 240 */ 241 private Context getContext(List<String> path, Context context) throws NamingException 242 { 243 // return the current context if the path is empty 244 if (path == null || path.isEmpty()) 245 { 246 return context; 247 } 248 249 String key = path.get(0); 250 251 // search a context matching the key in the context's elements 252 NamingEnumeration<NameClassPair> elements = null; 253 254 try 255 { 256 elements = context.list(""); 257 while (elements.hasMore()) 258 { 259 NameClassPair nameClassPair = elements.next(); 260 String name = nameClassPair.getName(); 261 Object object = context.lookup(name); 262 263 if (object instanceof Context && name.equals(key)) 264 { 265 Context subcontext = (Context) object; 266 267 // recursive search in the sub context 268 return getContext(path.subList(1, path.size()), subcontext); 269 } 270 } 271 } 272 finally 273 { 274 if (elements != null) 275 { 276 elements.close(); 277 } 278 } 279 280 return null; 281 } 282 283 /** 284 * Returns a flag whether this configuration is empty. 285 * 286 * @return the empty flag 287 */ 288 public boolean isEmpty() 289 { 290 try 291 { 292 NamingEnumeration<NameClassPair> enumeration = null; 293 294 try 295 { 296 enumeration = getBaseContext().list(""); 297 return !enumeration.hasMore(); 298 } 299 finally 300 { 301 // close the enumeration 302 if (enumeration != null) 303 { 304 enumeration.close(); 305 } 306 } 307 } 308 catch (NamingException e) 309 { 310 fireError(EVENT_READ_PROPERTY, null, null, e); 311 return true; 312 } 313 } 314 315 /** 316 * <p><strong>This operation is not supported and will throw an 317 * UnsupportedOperationException.</strong></p> 318 * 319 * @param key the key 320 * @param value the value 321 * @throws UnsupportedOperationException 322 */ 323 @Override 324 public void setProperty(String key, Object value) 325 { 326 throw new UnsupportedOperationException("This operation is not supported"); 327 } 328 329 /** 330 * Removes the specified property. 331 * 332 * @param key the key of the property to remove 333 */ 334 @Override 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 = key.replaceAll("\\.", "/"); 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 = key.replaceAll("\\.", "/"); 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 @Override 437 protected void addPropertyDirect(String key, Object obj) 438 { 439 throw new UnsupportedOperationException("This operation is not supported"); 440 } 441 442 /** 443 * Return the base context with the prefix applied. 444 * 445 * @return the base context 446 * @throws NamingException if an error occurs 447 */ 448 public Context getBaseContext() throws NamingException 449 { 450 if (baseContext == null) 451 { 452 baseContext = (Context) getContext().lookup(prefix == null ? "" : prefix); 453 } 454 455 return baseContext; 456 } 457 458 /** 459 * Return the initial context used by this configuration. This context is 460 * independent of the prefix specified. 461 * 462 * @return the initial context 463 */ 464 public Context getContext() 465 { 466 return context; 467 } 468 469 /** 470 * Set the initial context of the configuration. 471 * 472 * @param context the context 473 */ 474 public void setContext(Context context) 475 { 476 // forget the removed properties 477 clearedProperties.clear(); 478 479 // change the context 480 this.context = context; 481 } 482 }