001: /*
002: * Copyright 2002-2005 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: * http://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:
017: package org.springframework.mock.jndi;
018:
019: import java.util.Hashtable;
020:
021: import javax.naming.Context;
022: import javax.naming.NamingException;
023: import javax.naming.spi.InitialContextFactory;
024: import javax.naming.spi.InitialContextFactoryBuilder;
025: import javax.naming.spi.NamingManager;
026:
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029:
030: /**
031: * Simple implementation of a JNDI naming context builder.
032: *
033: * <p>Mainly targeted at test environments, where each test case can
034: * configure JNDI appropriately, so that <code>new InitialContext()</code>
035: * will expose the required objects. Also usable for standalone applications,
036: * e.g. for binding a JDBC DataSource to a well-known JNDI location, to be
037: * able to use traditional J2EE data access code outside of a J2EE container.
038: *
039: * <p>There are various choices for DataSource implementations:
040: * <ul>
041: * <li>SingleConnectionDataSource (using the same Connection for all getConnection calls);
042: * <li>DriverManagerDataSource (creating a new Connection on each getConnection call);
043: * <li>Apache's Jakarta Commons DBCP offers BasicDataSource (a real pool).
044: * </ul>
045: *
046: * <p>Typical usage in bootstrap code:
047: *
048: * <pre>
049: * SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
050: * DataSource ds = new DriverManagerDataSource(...);
051: * builder.bind("java:comp/env/jdbc/myds", ds);
052: * builder.activate();</pre>
053: *
054: * Note that it's impossible to activate multiple builders within the same JVM,
055: * due to JNDI restrictions. Thus to configure a fresh builder repeatedly, use
056: * the following code to get a reference to either an already activated builder
057: * or a newly activated one:
058: *
059: * <pre>
060: * SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
061: * DataSource ds = new DriverManagerDataSource(...);
062: * builder.bind("java:comp/env/jdbc/myds", ds);</pre>
063: *
064: * Note that you <i>should not</i> call activate() on a builder from this
065: * factory method, as there will already be an activated one in any case.
066: *
067: * <p>An instance of this class is only necessary at setup time.
068: * An application does not need to keep a reference to it after activation.
069: *
070: * @author Juergen Hoeller
071: * @author Rod Johnson
072: * @see #emptyActivatedContextBuilder()
073: * @see #bind(String, Object)
074: * @see #activate()
075: * @see org.springframework.mock.jndi.SimpleNamingContext
076: * @see org.springframework.jdbc.datasource.SingleConnectionDataSource
077: * @see org.springframework.jdbc.datasource.DriverManagerDataSource
078: * @see org.apache.commons.dbcp.BasicDataSource
079: */
080: public class SimpleNamingContextBuilder implements
081: InitialContextFactoryBuilder {
082:
083: /** An instance of this class bound to JNDI */
084: private static SimpleNamingContextBuilder activated;
085:
086: /**
087: * Checks if a SimpleNamingContextBuilder is active.
088: * @return the current SimpleNamingContextBuilder instance,
089: * or <code>null</code> if none
090: */
091: public static SimpleNamingContextBuilder getCurrentContextBuilder() {
092: return activated;
093: }
094:
095: /**
096: * If no SimpleNamingContextBuilder is already configuring JNDI,
097: * create and activate one. Otherwise take the existing activate
098: * SimpleNamingContextBuilder, clear it and return it.
099: * <p>This is mainly intended for test suites that want to
100: * reinitialize JNDI bindings from scratch repeatedly.
101: * @return an empty SimpleNamingContextBuilder that can be used
102: * to control JNDI bindings
103: */
104: public static SimpleNamingContextBuilder emptyActivatedContextBuilder()
105: throws NamingException {
106: if (activated != null) {
107: // Clear already activated context builder.
108: activated.clear();
109: } else {
110: // Create and activate new context builder.
111: SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
112: // The activate() call will cause an assigment to the activated variable.
113: builder.activate();
114: }
115: return activated;
116: }
117:
118: private final Log logger = LogFactory.getLog(getClass());
119:
120: private final Hashtable boundObjects = new Hashtable();
121:
122: /**
123: * Register the context builder by registering it with the JNDI NamingManager.
124: * Note that once this has been done, <code>new InitialContext()</code> will always
125: * return a context from this factory. Use the <code>emptyActivatedContextBuilder()</code>
126: * static method to get an empty context (for example, in test methods).
127: * @throws IllegalStateException if there's already a naming context builder
128: * registered with the JNDI NamingManager
129: */
130: public void activate() throws IllegalStateException,
131: NamingException {
132: logger.info("Activating simple JNDI environment");
133: if (NamingManager.hasInitialContextFactoryBuilder()) {
134: throw new IllegalStateException(
135: "Cannot activate SimpleNamingContextBuilder: there is already a JNDI provider registered. "
136: + "Note that JNDI is a JVM-wide service, shared at the JVM system class loader level, "
137: + "with no reset option. As a consequence, a JNDI provider must only be registered once per JVM.");
138: }
139: NamingManager.setInitialContextFactoryBuilder(this );
140: activated = this ;
141: }
142:
143: /**
144: * Clear all bindings in this context builder.
145: */
146: public void clear() {
147: this .boundObjects.clear();
148: }
149:
150: /**
151: * Bind the given object under the given name, for all naming contexts
152: * that this context builder will generate.
153: * @param name the JNDI name of the object (e.g. "java:comp/env/jdbc/myds")
154: * @param obj the object to bind (e.g. a DataSource implementation)
155: */
156: public void bind(String name, Object obj) {
157: if (logger.isInfoEnabled()) {
158: logger.info("Static JNDI binding: [" + name + "] = [" + obj
159: + "]");
160: }
161: this .boundObjects.put(name, obj);
162: }
163:
164: /**
165: * Simple InitialContextFactoryBuilder implementation,
166: * creating a new SimpleNamingContext instance.
167: * @see SimpleNamingContext
168: */
169: public InitialContextFactory createInitialContextFactory(
170: Hashtable environment) {
171: return new InitialContextFactory() {
172: public Context getInitialContext(Hashtable environment) {
173: return new SimpleNamingContext("", boundObjects,
174: environment);
175: }
176: };
177: }
178:
179: }
|