001/* 002 * Copyright 2002-2019 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.Hashtable; 020 021import javax.naming.Context; 022import javax.naming.NamingException; 023import javax.naming.spi.InitialContextFactory; 024import javax.naming.spi.InitialContextFactoryBuilder; 025import javax.naming.spi.NamingManager; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.lang.Nullable; 031import org.springframework.util.Assert; 032import org.springframework.util.ClassUtils; 033import org.springframework.util.ReflectionUtils; 034 035/** 036 * Simple implementation of a JNDI naming context builder. 037 * 038 * <p>Mainly targeted at test environments, where each test case can 039 * configure JNDI appropriately, so that {@code new InitialContext()} 040 * will expose the required objects. Also usable for standalone applications, 041 * e.g. for binding a JDBC DataSource to a well-known JNDI location, to be 042 * able to use traditional Java EE data access code outside of a Java EE 043 * container. 044 * 045 * <p>There are various choices for DataSource implementations: 046 * <ul> 047 * <li>{@code SingleConnectionDataSource} (using the same Connection for all getConnection calls) 048 * <li>{@code DriverManagerDataSource} (creating a new Connection on each getConnection call) 049 * <li>Apache's Commons DBCP offers {@code org.apache.commons.dbcp.BasicDataSource} (a real pool) 050 * </ul> 051 * 052 * <p>Typical usage in bootstrap code: 053 * 054 * <pre class="code"> 055 * SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); 056 * DataSource ds = new DriverManagerDataSource(...); 057 * builder.bind("java:comp/env/jdbc/myds", ds); 058 * builder.activate();</pre> 059 * 060 * Note that it's impossible to activate multiple builders within the same JVM, 061 * due to JNDI restrictions. Thus to configure a fresh builder repeatedly, use 062 * the following code to get a reference to either an already activated builder 063 * or a newly activated one: 064 * 065 * <pre class="code"> 066 * SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder(); 067 * DataSource ds = new DriverManagerDataSource(...); 068 * builder.bind("java:comp/env/jdbc/myds", ds);</pre> 069 * 070 * Note that you <i>should not</i> call {@code activate()} on a builder from 071 * this factory method, as there will already be an activated one in any case. 072 * 073 * <p>An instance of this class is only necessary at setup time. 074 * An application does not need to keep a reference to it after activation. 075 * 076 * @author Juergen Hoeller 077 * @author Rod Johnson 078 * @see #emptyActivatedContextBuilder() 079 * @see #bind(String, Object) 080 * @see #activate() 081 * @see SimpleNamingContext 082 * @see org.springframework.jdbc.datasource.SingleConnectionDataSource 083 * @see org.springframework.jdbc.datasource.DriverManagerDataSource 084 * @deprecated Deprecated as of Spring Framework 5.2 in favor of complete solutions from 085 * third parties such as <a href="https://github.com/h-thurow/Simple-JNDI">Simple-JNDI</a> 086 */ 087@Deprecated 088public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder { 089 090 /** An instance of this class bound to JNDI. */ 091 @Nullable 092 private static volatile SimpleNamingContextBuilder activated; 093 094 private static boolean initialized = false; 095 096 private static final Object initializationLock = new Object(); 097 098 099 /** 100 * Checks if a SimpleNamingContextBuilder is active. 101 * @return the current SimpleNamingContextBuilder instance, 102 * or {@code null} if none 103 */ 104 @Nullable 105 public static SimpleNamingContextBuilder getCurrentContextBuilder() { 106 return activated; 107 } 108 109 /** 110 * If no SimpleNamingContextBuilder is already configuring JNDI, 111 * create and activate one. Otherwise take the existing activated 112 * SimpleNamingContextBuilder, clear it and return it. 113 * <p>This is mainly intended for test suites that want to 114 * reinitialize JNDI bindings from scratch repeatedly. 115 * @return an empty SimpleNamingContextBuilder that can be used 116 * to control JNDI bindings 117 */ 118 public static SimpleNamingContextBuilder emptyActivatedContextBuilder() throws NamingException { 119 SimpleNamingContextBuilder builder = activated; 120 if (builder != null) { 121 // Clear already activated context builder. 122 builder.clear(); 123 } 124 else { 125 // Create and activate new context builder. 126 builder = new SimpleNamingContextBuilder(); 127 // The activate() call will cause an assignment to the activated variable. 128 builder.activate(); 129 } 130 return builder; 131 } 132 133 134 private final Log logger = LogFactory.getLog(getClass()); 135 136 private final Hashtable<String,Object> boundObjects = new Hashtable<>(); 137 138 139 /** 140 * Register the context builder by registering it with the JNDI NamingManager. 141 * Note that once this has been done, {@code new InitialContext()} will always 142 * return a context from this factory. Use the {@code emptyActivatedContextBuilder()} 143 * static method to get an empty context (for example, in test methods). 144 * @throws IllegalStateException if there's already a naming context builder 145 * registered with the JNDI NamingManager 146 */ 147 public void activate() throws IllegalStateException, NamingException { 148 logger.info("Activating simple JNDI environment"); 149 synchronized (initializationLock) { 150 if (!initialized) { 151 Assert.state(!NamingManager.hasInitialContextFactoryBuilder(), 152 "Cannot activate SimpleNamingContextBuilder: there is already a JNDI provider registered. " + 153 "Note that JNDI is a JVM-wide service, shared at the JVM system class loader level, " + 154 "with no reset option. As a consequence, a JNDI provider must only be registered once per JVM."); 155 NamingManager.setInitialContextFactoryBuilder(this); 156 initialized = true; 157 } 158 } 159 activated = this; 160 } 161 162 /** 163 * Temporarily deactivate this context builder. It will remain registered with 164 * the JNDI NamingManager but will delegate to the standard JNDI InitialContextFactory 165 * (if configured) instead of exposing its own bound objects. 166 * <p>Call {@code activate()} again in order to expose this context builder's own 167 * bound objects again. Such activate/deactivate sequences can be applied any number 168 * of times (e.g. within a larger integration test suite running in the same VM). 169 * @see #activate() 170 */ 171 public void deactivate() { 172 logger.info("Deactivating simple JNDI environment"); 173 activated = null; 174 } 175 176 /** 177 * Clear all bindings in this context builder, while keeping it active. 178 */ 179 public void clear() { 180 this.boundObjects.clear(); 181 } 182 183 /** 184 * Bind the given object under the given name, for all naming contexts 185 * that this context builder will generate. 186 * @param name the JNDI name of the object (e.g. "java:comp/env/jdbc/myds") 187 * @param obj the object to bind (e.g. a DataSource implementation) 188 */ 189 public void bind(String name, Object obj) { 190 if (logger.isInfoEnabled()) { 191 logger.info("Static JNDI binding: [" + name + "] = [" + obj + "]"); 192 } 193 this.boundObjects.put(name, obj); 194 } 195 196 197 /** 198 * Simple InitialContextFactoryBuilder implementation, 199 * creating a new SimpleNamingContext instance. 200 * @see SimpleNamingContext 201 */ 202 @Override 203 @SuppressWarnings("unchecked") 204 public InitialContextFactory createInitialContextFactory(@Nullable Hashtable<?,?> environment) { 205 if (activated == null && environment != null) { 206 Object icf = environment.get(Context.INITIAL_CONTEXT_FACTORY); 207 if (icf != null) { 208 Class<?> icfClass; 209 if (icf instanceof Class) { 210 icfClass = (Class<?>) icf; 211 } 212 else if (icf instanceof String) { 213 icfClass = ClassUtils.resolveClassName((String) icf, getClass().getClassLoader()); 214 } 215 else { 216 throw new IllegalArgumentException("Invalid value type for environment key [" + 217 Context.INITIAL_CONTEXT_FACTORY + "]: " + icf.getClass().getName()); 218 } 219 if (!InitialContextFactory.class.isAssignableFrom(icfClass)) { 220 throw new IllegalArgumentException( 221 "Specified class does not implement [" + InitialContextFactory.class.getName() + "]: " + icf); 222 } 223 try { 224 return (InitialContextFactory) ReflectionUtils.accessibleConstructor(icfClass).newInstance(); 225 } 226 catch (Throwable ex) { 227 throw new IllegalStateException("Unable to instantiate specified InitialContextFactory: " + icf, ex); 228 } 229 } 230 } 231 232 // Default case... 233 return env -> new SimpleNamingContext("", this.boundObjects, (Hashtable<String, Object>) env); 234 } 235 236}