org.springframework.mock.jndi.SimpleNamingContextBuilder Maven / Gradle / Ivy
Show all versions of spring-test Show documentation
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.mock.jndi;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.spi.InitialContextFactory;
import javax.naming.spi.InitialContextFactoryBuilder;
import javax.naming.spi.NamingManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* Simple implementation of a JNDI naming context builder.
*
* Mainly targeted at test environments, where each test case can
* configure JNDI appropriately, so that {@code new InitialContext()}
* will expose the required objects. Also usable for standalone applications,
* e.g. for binding a JDBC DataSource to a well-known JNDI location, to be
* able to use traditional Java EE data access code outside of a Java EE
* container.
*
*
There are various choices for DataSource implementations:
*
* - {@code SingleConnectionDataSource} (using the same Connection for all getConnection calls)
*
- {@code DriverManagerDataSource} (creating a new Connection on each getConnection call)
*
- Apache's Commons DBCP offers {@code org.apache.commons.dbcp.BasicDataSource} (a real pool)
*
*
* Typical usage in bootstrap code:
*
*
* SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
* DataSource ds = new DriverManagerDataSource(...);
* builder.bind("java:comp/env/jdbc/myds", ds);
* builder.activate();
*
* Note that it's impossible to activate multiple builders within the same JVM,
* due to JNDI restrictions. Thus to configure a fresh builder repeatedly, use
* the following code to get a reference to either an already activated builder
* or a newly activated one:
*
*
* SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
* DataSource ds = new DriverManagerDataSource(...);
* builder.bind("java:comp/env/jdbc/myds", ds);
*
* Note that you should not call {@code activate()} on a builder from
* this factory method, as there will already be an activated one in any case.
*
* An instance of this class is only necessary at setup time.
* An application does not need to keep a reference to it after activation.
*
* @author Juergen Hoeller
* @author Rod Johnson
* @see #emptyActivatedContextBuilder()
* @see #bind(String, Object)
* @see #activate()
* @see SimpleNamingContext
* @see org.springframework.jdbc.datasource.SingleConnectionDataSource
* @see org.springframework.jdbc.datasource.DriverManagerDataSource
*/
public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder {
/** An instance of this class bound to JNDI */
@Nullable
private static volatile SimpleNamingContextBuilder activated;
private static boolean initialized = false;
private static final Object initializationLock = new Object();
/**
* Checks if a SimpleNamingContextBuilder is active.
* @return the current SimpleNamingContextBuilder instance,
* or {@code null} if none
*/
@Nullable
public static SimpleNamingContextBuilder getCurrentContextBuilder() {
return activated;
}
/**
* If no SimpleNamingContextBuilder is already configuring JNDI,
* create and activate one. Otherwise take the existing activate
* SimpleNamingContextBuilder, clear it and return it.
*
This is mainly intended for test suites that want to
* reinitialize JNDI bindings from scratch repeatedly.
* @return an empty SimpleNamingContextBuilder that can be used
* to control JNDI bindings
*/
public static SimpleNamingContextBuilder emptyActivatedContextBuilder() throws NamingException {
SimpleNamingContextBuilder builder = activated;
if (builder != null) {
// Clear already activated context builder.
builder.clear();
}
else {
// Create and activate new context builder.
builder = new SimpleNamingContextBuilder();
// The activate() call will cause an assignment to the activated variable.
builder.activate();
}
return builder;
}
private final Log logger = LogFactory.getLog(getClass());
private final Hashtable boundObjects = new Hashtable<>();
/**
* Register the context builder by registering it with the JNDI NamingManager.
* Note that once this has been done, {@code new InitialContext()} will always
* return a context from this factory. Use the {@code emptyActivatedContextBuilder()}
* static method to get an empty context (for example, in test methods).
* @throws IllegalStateException if there's already a naming context builder
* registered with the JNDI NamingManager
*/
public void activate() throws IllegalStateException, NamingException {
logger.info("Activating simple JNDI environment");
synchronized (initializationLock) {
if (!initialized) {
Assert.state(!NamingManager.hasInitialContextFactoryBuilder(),
"Cannot activate SimpleNamingContextBuilder: there is already a JNDI provider registered. " +
"Note that JNDI is a JVM-wide service, shared at the JVM system class loader level, " +
"with no reset option. As a consequence, a JNDI provider must only be registered once per JVM.");
NamingManager.setInitialContextFactoryBuilder(this);
initialized = true;
}
}
activated = this;
}
/**
* Temporarily deactivate this context builder. It will remain registered with
* the JNDI NamingManager but will delegate to the standard JNDI InitialContextFactory
* (if configured) instead of exposing its own bound objects.
* Call {@code activate()} again in order to expose this context builder's own
* bound objects again. Such activate/deactivate sequences can be applied any number
* of times (e.g. within a larger integration test suite running in the same VM).
* @see #activate()
*/
public void deactivate() {
logger.info("Deactivating simple JNDI environment");
activated = null;
}
/**
* Clear all bindings in this context builder, while keeping it active.
*/
public void clear() {
this.boundObjects.clear();
}
/**
* Bind the given object under the given name, for all naming contexts
* that this context builder will generate.
* @param name the JNDI name of the object (e.g. "java:comp/env/jdbc/myds")
* @param obj the object to bind (e.g. a DataSource implementation)
*/
public void bind(String name, Object obj) {
if (logger.isInfoEnabled()) {
logger.info("Static JNDI binding: [" + name + "] = [" + obj + "]");
}
this.boundObjects.put(name, obj);
}
/**
* Simple InitialContextFactoryBuilder implementation,
* creating a new SimpleNamingContext instance.
* @see SimpleNamingContext
*/
@Override
@SuppressWarnings("unchecked")
public InitialContextFactory createInitialContextFactory(@Nullable Hashtable,?> environment) {
if (activated == null && environment != null) {
Object icf = environment.get(Context.INITIAL_CONTEXT_FACTORY);
if (icf != null) {
Class> icfClass;
if (icf instanceof Class) {
icfClass = (Class>) icf;
}
else if (icf instanceof String) {
icfClass = ClassUtils.resolveClassName((String) icf, getClass().getClassLoader());
}
else {
throw new IllegalArgumentException("Invalid value type for environment key [" +
Context.INITIAL_CONTEXT_FACTORY + "]: " + icf.getClass().getName());
}
if (!InitialContextFactory.class.isAssignableFrom(icfClass)) {
throw new IllegalArgumentException(
"Specified class does not implement [" + InitialContextFactory.class.getName() + "]: " + icf);
}
try {
return (InitialContextFactory) ReflectionUtils.accessibleConstructor(icfClass).newInstance();
}
catch (Throwable ex) {
throw new IllegalStateException("Unable to instantiate specified InitialContextFactory: " + icf, ex);
}
}
}
// Default case...
return environment1 -> new SimpleNamingContext("", boundObjects, (Hashtable) environment1);
}
}