001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.mock.jndi; 018 019import java.util.HashMap; 020import java.util.Hashtable; 021import java.util.Iterator; 022import java.util.Map; 023import javax.naming.Binding; 024import javax.naming.Context; 025import javax.naming.Name; 026import javax.naming.NameClassPair; 027import javax.naming.NameNotFoundException; 028import javax.naming.NameParser; 029import javax.naming.NamingEnumeration; 030import javax.naming.NamingException; 031import javax.naming.OperationNotSupportedException; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035 036import org.springframework.util.StringUtils; 037 038/** 039 * Simple implementation of a JNDI naming context. 040 * Only supports binding plain Objects to String names. 041 * Mainly for test environments, but also usable for standalone applications. 042 * 043 * <p>This class is not intended for direct usage by applications, although it 044 * can be used for example to override JndiTemplate's {@code createInitialContext} 045 * method in unit tests. Typically, SimpleNamingContextBuilder will be used to 046 * set up a JVM-level JNDI environment. 047 * 048 * @author Rod Johnson 049 * @author Juergen Hoeller 050 * @see SimpleNamingContextBuilder 051 * @see org.springframework.jndi.JndiTemplate#createInitialContext 052 */ 053public class SimpleNamingContext implements Context { 054 055 private final Log logger = LogFactory.getLog(getClass()); 056 057 private final String root; 058 059 private final Hashtable<String, Object> boundObjects; 060 061 private final Hashtable<String, Object> environment = new Hashtable<String, Object>(); 062 063 064 /** 065 * Create a new naming context. 066 */ 067 public SimpleNamingContext() { 068 this(""); 069 } 070 071 /** 072 * Create a new naming context with the given naming root. 073 */ 074 public SimpleNamingContext(String root) { 075 this.root = root; 076 this.boundObjects = new Hashtable<String, Object>(); 077 } 078 079 /** 080 * Create a new naming context with the given naming root, 081 * the given name/object map, and the JNDI environment entries. 082 */ 083 public SimpleNamingContext(String root, Hashtable<String, Object> boundObjects, Hashtable<String, Object> env) { 084 this.root = root; 085 this.boundObjects = boundObjects; 086 if (env != null) { 087 this.environment.putAll(env); 088 } 089 } 090 091 092 // Actual implementations of Context methods follow 093 094 @Override 095 public NamingEnumeration<NameClassPair> list(String root) throws NamingException { 096 if (logger.isDebugEnabled()) { 097 logger.debug("Listing name/class pairs under [" + root + "]"); 098 } 099 return new NameClassPairEnumeration(this, root); 100 } 101 102 @Override 103 public NamingEnumeration<Binding> listBindings(String root) throws NamingException { 104 if (logger.isDebugEnabled()) { 105 logger.debug("Listing bindings under [" + root + "]"); 106 } 107 return new BindingEnumeration(this, root); 108 } 109 110 /** 111 * Look up the object with the given name. 112 * <p>Note: Not intended for direct use by applications. 113 * Will be used by any standard InitialContext JNDI lookups. 114 * @throws javax.naming.NameNotFoundException if the object could not be found 115 */ 116 @Override 117 public Object lookup(String lookupName) throws NameNotFoundException { 118 String name = this.root + lookupName; 119 if (logger.isDebugEnabled()) { 120 logger.debug("Static JNDI lookup: [" + name + "]"); 121 } 122 if (name.isEmpty()) { 123 return new SimpleNamingContext(this.root, this.boundObjects, this.environment); 124 } 125 Object found = this.boundObjects.get(name); 126 if (found == null) { 127 if (!name.endsWith("/")) { 128 name = name + "/"; 129 } 130 for (String boundName : this.boundObjects.keySet()) { 131 if (boundName.startsWith(name)) { 132 return new SimpleNamingContext(name, this.boundObjects, this.environment); 133 } 134 } 135 throw new NameNotFoundException( 136 "Name [" + this.root + lookupName + "] not bound; " + this.boundObjects.size() + " bindings: [" + 137 StringUtils.collectionToDelimitedString(this.boundObjects.keySet(), ",") + "]"); 138 } 139 return found; 140 } 141 142 @Override 143 public Object lookupLink(String name) throws NameNotFoundException { 144 return lookup(name); 145 } 146 147 /** 148 * Bind the given object to the given name. 149 * Note: Not intended for direct use by applications 150 * if setting up a JVM-level JNDI environment. 151 * Use SimpleNamingContextBuilder to set up JNDI bindings then. 152 * @see org.springframework.mock.jndi.SimpleNamingContextBuilder#bind 153 */ 154 @Override 155 public void bind(String name, Object obj) { 156 if (logger.isInfoEnabled()) { 157 logger.info("Static JNDI binding: [" + this.root + name + "] = [" + obj + "]"); 158 } 159 this.boundObjects.put(this.root + name, obj); 160 } 161 162 @Override 163 public void unbind(String name) { 164 if (logger.isInfoEnabled()) { 165 logger.info("Static JNDI remove: [" + this.root + name + "]"); 166 } 167 this.boundObjects.remove(this.root + name); 168 } 169 170 @Override 171 public void rebind(String name, Object obj) { 172 bind(name, obj); 173 } 174 175 @Override 176 public void rename(String oldName, String newName) throws NameNotFoundException { 177 Object obj = lookup(oldName); 178 unbind(oldName); 179 bind(newName, obj); 180 } 181 182 @Override 183 public Context createSubcontext(String name) { 184 String subcontextName = this.root + name; 185 if (!subcontextName.endsWith("/")) { 186 subcontextName += "/"; 187 } 188 Context subcontext = new SimpleNamingContext(subcontextName, this.boundObjects, this.environment); 189 bind(name, subcontext); 190 return subcontext; 191 } 192 193 @Override 194 public void destroySubcontext(String name) { 195 unbind(name); 196 } 197 198 @Override 199 public String composeName(String name, String prefix) { 200 return prefix + name; 201 } 202 203 @Override 204 public Hashtable<String, Object> getEnvironment() { 205 return this.environment; 206 } 207 208 @Override 209 public Object addToEnvironment(String propName, Object propVal) { 210 return this.environment.put(propName, propVal); 211 } 212 213 @Override 214 public Object removeFromEnvironment(String propName) { 215 return this.environment.remove(propName); 216 } 217 218 @Override 219 public void close() { 220 } 221 222 223 // Unsupported methods follow: no support for javax.naming.Name 224 225 @Override 226 public NamingEnumeration<NameClassPair> list(Name name) throws NamingException { 227 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 228 } 229 230 @Override 231 public NamingEnumeration<Binding> listBindings(Name name) throws NamingException { 232 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 233 } 234 235 @Override 236 public Object lookup(Name name) throws NamingException { 237 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 238 } 239 240 @Override 241 public Object lookupLink(Name name) throws NamingException { 242 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 243 } 244 245 @Override 246 public void bind(Name name, Object obj) throws NamingException { 247 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 248 } 249 250 @Override 251 public void unbind(Name name) throws NamingException { 252 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 253 } 254 255 @Override 256 public void rebind(Name name, Object obj) throws NamingException { 257 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 258 } 259 260 @Override 261 public void rename(Name oldName, Name newName) throws NamingException { 262 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 263 } 264 265 @Override 266 public Context createSubcontext(Name name) throws NamingException { 267 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 268 } 269 270 @Override 271 public void destroySubcontext(Name name) throws NamingException { 272 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 273 } 274 275 @Override 276 public String getNameInNamespace() throws NamingException { 277 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 278 } 279 280 @Override 281 public NameParser getNameParser(Name name) throws NamingException { 282 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 283 } 284 285 @Override 286 public NameParser getNameParser(String name) throws NamingException { 287 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 288 } 289 290 @Override 291 public Name composeName(Name name, Name prefix) throws NamingException { 292 throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); 293 } 294 295 296 private abstract static class AbstractNamingEnumeration<T> implements NamingEnumeration<T> { 297 298 private final Iterator<T> iterator; 299 300 private AbstractNamingEnumeration(SimpleNamingContext context, String proot) throws NamingException { 301 if (!proot.isEmpty() && !proot.endsWith("/")) { 302 proot = proot + "/"; 303 } 304 String root = context.root + proot; 305 Map<String, T> contents = new HashMap<String, T>(); 306 for (String boundName : context.boundObjects.keySet()) { 307 if (boundName.startsWith(root)) { 308 int startIndex = root.length(); 309 int endIndex = boundName.indexOf('/', startIndex); 310 String strippedName = 311 (endIndex != -1 ? boundName.substring(startIndex, endIndex) : boundName.substring(startIndex)); 312 if (!contents.containsKey(strippedName)) { 313 try { 314 contents.put(strippedName, createObject(strippedName, context.lookup(proot + strippedName))); 315 } 316 catch (NameNotFoundException ex) { 317 // cannot happen 318 } 319 } 320 } 321 } 322 if (contents.size() == 0) { 323 throw new NamingException("Invalid root: [" + context.root + proot + "]"); 324 } 325 this.iterator = contents.values().iterator(); 326 } 327 328 protected abstract T createObject(String strippedName, Object obj); 329 330 @Override 331 public boolean hasMore() { 332 return this.iterator.hasNext(); 333 } 334 335 @Override 336 public T next() { 337 return this.iterator.next(); 338 } 339 340 @Override 341 public boolean hasMoreElements() { 342 return this.iterator.hasNext(); 343 } 344 345 @Override 346 public T nextElement() { 347 return this.iterator.next(); 348 } 349 350 @Override 351 public void close() { 352 } 353 } 354 355 356 private static final class NameClassPairEnumeration extends AbstractNamingEnumeration<NameClassPair> { 357 358 private NameClassPairEnumeration(SimpleNamingContext context, String root) throws NamingException { 359 super(context, root); 360 } 361 362 @Override 363 protected NameClassPair createObject(String strippedName, Object obj) { 364 return new NameClassPair(strippedName, obj.getClass().getName()); 365 } 366 } 367 368 369 private static final class BindingEnumeration extends AbstractNamingEnumeration<Binding> { 370 371 private BindingEnumeration(SimpleNamingContext context, String root) throws NamingException { 372 super(context, root); 373 } 374 375 @Override 376 protected Binding createObject(String strippedName, Object obj) { 377 return new Binding(strippedName, obj); 378 } 379 } 380 381}