From e6972a8bd89d5cd957eafa26a095f63bbb1928f3 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Thu, 18 Dec 2008 22:30:35 +0000 Subject: [PATCH] re-adding mock.jndi.* artifacts to .orm test codebase eliminated after moving away from svn:externals git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@474 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../mock/jndi/ExpectedLookupTemplate.java | 82 +++++ .../mock/jndi/SimpleNamingContext.java | 345 ++++++++++++++++++ .../mock/jndi/SimpleNamingContextBuilder.java | 234 ++++++++++++ 3 files changed, 661 insertions(+) create mode 100644 org.springframework.orm/src/test/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java create mode 100644 org.springframework.orm/src/test/java/org/springframework/mock/jndi/SimpleNamingContext.java create mode 100644 org.springframework.orm/src/test/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java diff --git a/org.springframework.orm/src/test/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java b/org.springframework.orm/src/test/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java new file mode 100644 index 00000000000..28670fd4383 --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2008 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.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.naming.NamingException; + +import org.springframework.core.CollectionFactory; +import org.springframework.jndi.JndiTemplate; + +/** + * Simple extension of the JndiTemplate class that always returns + * a given object. Very useful for testing. Effectively a mock object. + * + * @author Rod Johnson + * @author Juergen Hoeller + */ +public class ExpectedLookupTemplate extends JndiTemplate { + + private final Map jndiObjects = new ConcurrentHashMap(); + + + /** + * Construct a new JndiTemplate that will always return given objects + * for given names. To be populated through addObject calls. + * @see #addObject(String, Object) + */ + public ExpectedLookupTemplate() { + } + + /** + * Construct a new JndiTemplate that will always return the + * given object, but honour only requests for the given name. + * @param name the name the client is expected to look up + * @param object the object that will be returned + */ + public ExpectedLookupTemplate(String name, Object object) { + addObject(name, object); + } + + + /** + * Add the given object to the list of JNDI objects that this + * template will expose. + * @param name the name the client is expected to look up + * @param object the object that will be returned + */ + public void addObject(String name, Object object) { + this.jndiObjects.put(name, object); + } + + + /** + * If the name is the expected name specified in the constructor, + * return the object provided in the constructor. If the name is + * unexpected, a respective NamingException gets thrown. + */ + public Object lookup(String name) throws NamingException { + Object object = this.jndiObjects.get(name); + if (object == null) { + throw new NamingException("Unexpected JNDI name '" + name + "': expecting " + this.jndiObjects.keySet()); + } + return object; + } + +} diff --git a/org.springframework.orm/src/test/java/org/springframework/mock/jndi/SimpleNamingContext.java b/org.springframework.orm/src/test/java/org/springframework/mock/jndi/SimpleNamingContext.java new file mode 100644 index 00000000000..b6489e42029 --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/mock/jndi/SimpleNamingContext.java @@ -0,0 +1,345 @@ +/* + * Copyright 2002-2008 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.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.OperationNotSupportedException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.StringUtils; + +/** + * Simple implementation of a JNDI naming context. + * Only supports binding plain Objects to String names. + * Mainly for test environments, but also usable for standalone applications. + * + *

This class is not intended for direct usage by applications, although it + * can be used for example to override JndiTemplate's createInitialContext + * method in unit tests. Typically, SimpleNamingContextBuilder will be used to + * set up a JVM-level JNDI environment. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see org.springframework.mock.jndi.SimpleNamingContextBuilder + * @see org.springframework.jndi.JndiTemplate#createInitialContext + */ +public class SimpleNamingContext implements Context { + + private final Log logger = LogFactory.getLog(getClass()); + + private final String root; + + private final Hashtable boundObjects; + + private final Hashtable environment = new Hashtable(); + + + /** + * Create a new naming context. + */ + public SimpleNamingContext() { + this(""); + } + + /** + * Create a new naming context with the given naming root. + */ + public SimpleNamingContext(String root) { + this.root = root; + this.boundObjects = new Hashtable(); + } + + /** + * Create a new naming context with the given naming root, + * the given name/object map, and the JNDI environment entries. + */ + public SimpleNamingContext(String root, Hashtable boundObjects, Hashtable env) { + this.root = root; + this.boundObjects = boundObjects; + if (env != null) { + this.environment.putAll(env); + } + } + + + // Actual implementations of Context methods follow + + public NamingEnumeration list(String root) throws NamingException { + if (logger.isDebugEnabled()) { + logger.debug("Listing name/class pairs under [" + root + "]"); + } + return new NameClassPairEnumeration(this, root); + } + + public NamingEnumeration listBindings(String root) throws NamingException { + if (logger.isDebugEnabled()) { + logger.debug("Listing bindings under [" + root + "]"); + } + return new BindingEnumeration(this, root); + } + + /** + * Look up the object with the given name. + *

Note: Not intended for direct use by applications. + * Will be used by any standard InitialContext JNDI lookups. + * @throws javax.naming.NameNotFoundException if the object could not be found + */ + public Object lookup(String lookupName) throws NameNotFoundException { + String name = this.root + lookupName; + if (logger.isDebugEnabled()) { + logger.debug("Static JNDI lookup: [" + name + "]"); + } + if ("".equals(name)) { + return new SimpleNamingContext(this.root, this.boundObjects, this.environment); + } + Object found = this.boundObjects.get(name); + if (found == null) { + if (!name.endsWith("/")) { + name = name + "/"; + } + for (String boundName : this.boundObjects.keySet()) { + if (boundName.startsWith(name)) { + return new SimpleNamingContext(name, this.boundObjects, this.environment); + } + } + throw new NameNotFoundException( + "Name [" + this.root + lookupName + "] not bound; " + this.boundObjects.size() + " bindings: [" + + StringUtils.collectionToDelimitedString(this.boundObjects.keySet(), ",") + "]"); + } + return found; + } + + public Object lookupLink(String name) throws NameNotFoundException { + return lookup(name); + } + + /** + * Bind the given object to the given name. + * Note: Not intended for direct use by applications + * if setting up a JVM-level JNDI environment. + * Use SimpleNamingContextBuilder to set up JNDI bindings then. + * @see org.springframework.mock.jndi.SimpleNamingContextBuilder#bind + */ + public void bind(String name, Object obj) { + if (logger.isInfoEnabled()) { + logger.info("Static JNDI binding: [" + this.root + name + "] = [" + obj + "]"); + } + this.boundObjects.put(this.root + name, obj); + } + + public void unbind(String name) { + if (logger.isInfoEnabled()) { + logger.info("Static JNDI remove: [" + this.root + name + "]"); + } + this.boundObjects.remove(this.root + name); + } + + public void rebind(String name, Object obj) { + bind(name, obj); + } + + public void rename(String oldName, String newName) throws NameNotFoundException { + Object obj = lookup(oldName); + unbind(oldName); + bind(newName, obj); + } + + public Context createSubcontext(String name) { + String subcontextName = this.root + name; + if (!subcontextName.endsWith("/")) { + subcontextName += "/"; + } + Context subcontext = new SimpleNamingContext(subcontextName, this.boundObjects, this.environment); + bind(name, subcontext); + return subcontext; + } + + public void destroySubcontext(String name) { + unbind(name); + } + + public String composeName(String name, String prefix) { + return prefix + name; + } + + public Hashtable getEnvironment() { + return this.environment; + } + + public Object addToEnvironment(String propName, Object propVal) { + return this.environment.put(propName, propVal); + } + + public Object removeFromEnvironment(String propName) { + return this.environment.remove(propName); + } + + public void close() { + } + + + // Unsupported methods follow: no support for javax.naming.Name + + public NamingEnumeration list(Name name) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public NamingEnumeration listBindings(Name name) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public Object lookup(Name name) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public Object lookupLink(Name name) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public void bind(Name name, Object obj) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public void unbind(Name name) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public void rebind(Name name, Object obj) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public void rename(Name oldName, Name newName) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public Context createSubcontext(Name name) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public void destroySubcontext(Name name) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public String getNameInNamespace() throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public NameParser getNameParser(Name name) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public NameParser getNameParser(String name) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + public Name composeName(Name name, Name prefix) throws NamingException { + throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]"); + } + + + private static abstract class AbstractNamingEnumeration implements NamingEnumeration { + + private Iterator iterator; + + private AbstractNamingEnumeration(SimpleNamingContext context, String proot) throws NamingException { + if (!"".equals(proot) && !proot.endsWith("/")) { + proot = proot + "/"; + } + String root = context.root + proot; + Map contents = new HashMap(); + for (String boundName : context.boundObjects.keySet()) { + if (boundName.startsWith(root)) { + int startIndex = root.length(); + int endIndex = boundName.indexOf('/', startIndex); + String strippedName = + (endIndex != -1 ? boundName.substring(startIndex, endIndex) : boundName.substring(startIndex)); + if (!contents.containsKey(strippedName)) { + try { + contents.put(strippedName, createObject(strippedName, context.lookup(proot + strippedName))); + } + catch (NameNotFoundException ex) { + // cannot happen + } + } + } + } + if (contents.size() == 0) { + throw new NamingException("Invalid root: [" + context.root + proot + "]"); + } + this.iterator = contents.values().iterator(); + } + + protected abstract T createObject(String strippedName, Object obj); + + public boolean hasMore() { + return this.iterator.hasNext(); + } + + public T next() { + return this.iterator.next(); + } + + public boolean hasMoreElements() { + return this.iterator.hasNext(); + } + + public T nextElement() { + return this.iterator.next(); + } + + public void close() { + } + } + + + private static class NameClassPairEnumeration extends AbstractNamingEnumeration { + + private NameClassPairEnumeration(SimpleNamingContext context, String root) throws NamingException { + super(context, root); + } + + protected NameClassPair createObject(String strippedName, Object obj) { + return new NameClassPair(strippedName, obj.getClass().getName()); + } + } + + + private static class BindingEnumeration extends AbstractNamingEnumeration { + + private BindingEnumeration(SimpleNamingContext context, String root) throws NamingException { + super(context, root); + } + + protected Binding createObject(String strippedName, Object obj) { + return new Binding(strippedName, obj); + } + } + +} diff --git a/org.springframework.orm/src/test/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java b/org.springframework.orm/src/test/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java new file mode 100644 index 00000000000..130de79de5e --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java @@ -0,0 +1,234 @@ +/* + * Copyright 2002-2008 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.util.ClassUtils; + +/** + * Simple implementation of a JNDI naming context builder. + * + *

Mainly targeted at test environments, where each test case can + * configure JNDI appropriately, so that 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 J2EE data access code outside of a J2EE container. + * + *

There are various choices for DataSource implementations: + *

    + *
  • SingleConnectionDataSource (using the same Connection for all getConnection calls); + *
  • DriverManagerDataSource (creating a new Connection on each getConnection call); + *
  • Apache's Jakarta Commons DBCP offers 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 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 org.springframework.mock.jndi.SimpleNamingContext + * @see org.springframework.jdbc.datasource.SingleConnectionDataSource + * @see org.springframework.jdbc.datasource.DriverManagerDataSource + * @see org.apache.commons.dbcp.BasicDataSource + */ +public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder { + + /** An instance of this class bound to JNDI */ + 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 null if none + */ + 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 { + if (activated != null) { + // Clear already activated context builder. + activated.clear(); + } + else { + // Create and activate new context builder. + SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder(); + // The activate() call will cause an assigment to the activated variable. + builder.activate(); + } + return activated; + } + + + 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, new InitialContext() will always + * return a context from this factory. Use the 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) { + if (NamingManager.hasInitialContextFactoryBuilder()) { + throw new IllegalStateException( + "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 activate() again in order to expose this contexz 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 + */ + public InitialContextFactory createInitialContextFactory(Hashtable environment) { + if (activated == null && environment != null) { + Object icf = environment.get(Context.INITIAL_CONTEXT_FACTORY); + if (icf != null) { + Class icfClass = null; + 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) icfClass.newInstance(); + } + catch (Throwable ex) { + IllegalStateException ise = + new IllegalStateException("Cannot instantiate specified InitialContextFactory: " + icf); + ise.initCause(ex); + throw ise; + } + } + } + + // Default case... + return new InitialContextFactory() { + public Context getInitialContext(Hashtable environment) { + return new SimpleNamingContext("", boundObjects, environment); + } + }; + } + +}