1 /* 2 * Copyright 2002-2009 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.mock.jndi; 18 19 import java.util.Hashtable; 20 21 import javax.naming.Context; 22 import javax.naming.NamingException; 23 import javax.naming.spi.InitialContextFactory; 24 import javax.naming.spi.InitialContextFactoryBuilder; 25 import javax.naming.spi.NamingManager; 26 27 import org.apache.commons.logging.Log; 28 import org.apache.commons.logging.LogFactory; 29 30 import org.springframework.util.ClassUtils; 31 32 /** 33 * Simple implementation of a JNDI naming context builder. 34 * 35 * <p>Mainly targeted at test environments, where each test case can 36 * configure JNDI appropriately, so that <code>new InitialContext()</code> 37 * will expose the required objects. Also usable for standalone applications, 38 * e.g. for binding a JDBC DataSource to a well-known JNDI location, to be 39 * able to use traditional J2EE data access code outside of a J2EE container. 40 * 41 * <p>There are various choices for DataSource implementations: 42 * <ul> 43 * <li>SingleConnectionDataSource (using the same Connection for all getConnection calls); 44 * <li>DriverManagerDataSource (creating a new Connection on each getConnection call); 45 * <li>Apache's Jakarta Commons DBCP offers BasicDataSource (a real pool). 46 * </ul> 47 * 48 * <p>Typical usage in bootstrap code: 49 * 50 * <pre class="code"> 51 * SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); 52 * DataSource ds = new DriverManagerDataSource(...); 53 * builder.bind("java:comp/env/jdbc/myds", ds); 54 * builder.activate();</pre> 55 * 56 * Note that it's impossible to activate multiple builders within the same JVM, 57 * due to JNDI restrictions. Thus to configure a fresh builder repeatedly, use 58 * the following code to get a reference to either an already activated builder 59 * or a newly activated one: 60 * 61 * <pre class="code"> 62 * SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder(); 63 * DataSource ds = new DriverManagerDataSource(...); 64 * builder.bind("java:comp/env/jdbc/myds", ds);</pre> 65 * 66 * Note that you <i>should not</i> call <code>activate()</code> on a builder from 67 * this factory method, as there will already be an activated one in any case. 68 * 69 * <p>An instance of this class is only necessary at setup time. 70 * An application does not need to keep a reference to it after activation. 71 * 72 * @author Juergen Hoeller 73 * @author Rod Johnson 74 * @see #emptyActivatedContextBuilder() 75 * @see #bind(String, Object) 76 * @see #activate() 77 * @see org.springframework.mock.jndi.SimpleNamingContext 78 * @see org.springframework.jdbc.datasource.SingleConnectionDataSource 79 * @see org.springframework.jdbc.datasource.DriverManagerDataSource 80 * @see org.apache.commons.dbcp.BasicDataSource 81 */ 82 public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder { 83 84 /** An instance of this class bound to JNDI */ 85 private static volatile SimpleNamingContextBuilder activated; 86 87 private static boolean initialized = false; 88 89 private static final Object initializationLock = new Object(); 90 91 92 /** 93 * Checks if a SimpleNamingContextBuilder is active. 94 * @return the current SimpleNamingContextBuilder instance, 95 * or <code>null</code> if none 96 */ 97 public static SimpleNamingContextBuilder getCurrentContextBuilder() { 98 return activated; 99 } 100 101 /** 102 * If no SimpleNamingContextBuilder is already configuring JNDI, 103 * create and activate one. Otherwise take the existing activate 104 * SimpleNamingContextBuilder, clear it and return it. 105 * <p>This is mainly intended for test suites that want to 106 * reinitialize JNDI bindings from scratch repeatedly. 107 * @return an empty SimpleNamingContextBuilder that can be used 108 * to control JNDI bindings 109 */ 110 public static SimpleNamingContextBuilder emptyActivatedContextBuilder() throws NamingException { 111 if (activated != null) { 112 // Clear already activated context builder. 113 activated.clear(); 114 } 115 else { 116 // Create and activate new context builder. 117 SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); 118 // The activate() call will cause an assignment to the activated variable. 119 builder.activate(); 120 } 121 return activated; 122 } 123 124 125 private final Log logger = LogFactory.getLog(getClass()); 126 127 private final Hashtable<String,Object> boundObjects = new Hashtable<String,Object>(); 128 129 130 /** 131 * Register the context builder by registering it with the JNDI NamingManager. 132 * Note that once this has been done, <code>new InitialContext()</code> will always 133 * return a context from this factory. Use the <code>emptyActivatedContextBuilder()</code> 134 * static method to get an empty context (for example, in test methods). 135 * @throws IllegalStateException if there's already a naming context builder 136 * registered with the JNDI NamingManager 137 */ 138 public void activate() throws IllegalStateException, NamingException { 139 logger.info("Activating simple JNDI environment"); 140 synchronized (initializationLock) { 141 if (!initialized) { 142 if (NamingManager.hasInitialContextFactoryBuilder()) { 143 throw new IllegalStateException( 144 "Cannot activate SimpleNamingContextBuilder: there is already a JNDI provider registered. " + 145 "Note that JNDI is a JVM-wide service, shared at the JVM system class loader level, " + 146 "with no reset option. As a consequence, a JNDI provider must only be registered once per JVM."); 147 } 148 NamingManager.setInitialContextFactoryBuilder(this); 149 initialized = true; 150 } 151 } 152 activated = this; 153 } 154 155 /** 156 * Temporarily deactivate this context builder. It will remain registered with 157 * the JNDI NamingManager but will delegate to the standard JNDI InitialContextFactory 158 * (if configured) instead of exposing its own bound objects. 159 * <p>Call <code>activate()</code> again in order to expose this context builder's own 160 * bound objects again. Such activate/deactivate sequences can be applied any number 161 * of times (e.g. within a larger integration test suite running in the same VM). 162 * @see #activate() 163 */ 164 public void deactivate() { 165 logger.info("Deactivating simple JNDI environment"); 166 activated = null; 167 } 168 169 /** 170 * Clear all bindings in this context builder, while keeping it active. 171 */ 172 public void clear() { 173 this.boundObjects.clear(); 174 } 175 176 /** 177 * Bind the given object under the given name, for all naming contexts 178 * that this context builder will generate. 179 * @param name the JNDI name of the object (e.g. "java:comp/env/jdbc/myds") 180 * @param obj the object to bind (e.g. a DataSource implementation) 181 */ 182 public void bind(String name, Object obj) { 183 if (logger.isInfoEnabled()) { 184 logger.info("Static JNDI binding: [" + name + "] = [" + obj + "]"); 185 } 186 this.boundObjects.put(name, obj); 187 } 188 189 190 /** 191 * Simple InitialContextFactoryBuilder implementation, 192 * creating a new SimpleNamingContext instance. 193 * @see SimpleNamingContext 194 */ 195 public InitialContextFactory createInitialContextFactory(Hashtable<?,?> environment) { 196 if (activated == null && environment != null) { 197 Object icf = environment.get(Context.INITIAL_CONTEXT_FACTORY); 198 if (icf != null) { 199 Class<?> icfClass = null; 200 if (icf instanceof Class) { 201 icfClass = (Class<?>) icf; 202 } 203 else if (icf instanceof String) { 204 icfClass = ClassUtils.resolveClassName((String) icf, getClass().getClassLoader()); 205 } 206 else { 207 throw new IllegalArgumentException("Invalid value type for environment key [" + 208 Context.INITIAL_CONTEXT_FACTORY + "]: " + icf.getClass().getName()); 209 } 210 if (!InitialContextFactory.class.isAssignableFrom(icfClass)) { 211 throw new IllegalArgumentException( 212 "Specified class does not implement [" + InitialContextFactory.class.getName() + "]: " + icf); 213 } 214 try { 215 return (InitialContextFactory) icfClass.newInstance(); 216 } 217 catch (Throwable ex) { 218 IllegalStateException ise = 219 new IllegalStateException("Cannot instantiate specified InitialContextFactory: " + icf); 220 ise.initCause(ex); 221 throw ise; 222 } 223 } 224 } 225 226 // Default case... 227 return new InitialContextFactory() { 228 @SuppressWarnings("unchecked") 229 public Context getInitialContext(Hashtable<?,?> environment) { 230 return new SimpleNamingContext("", boundObjects, (Hashtable<String, Object>) environment); 231 } 232 }; 233 } 234 235 }