/** * junit-rules: JUnit Rules Library * * Copyright (c) 2009-2011 by Alistair A. Israel. * This software is made available under the terms of the MIT License. * * Created Jun 2, 2011 */ package junit.rules.jndi; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Logger; import javax.naming.Binding; import javax.naming.Context; import javax.naming.Name; import javax.naming.NameClassPair; import javax.naming.NameParser; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.spi.InitialContextFactory; import javax.naming.spi.InitialContextFactoryBuilder; import javax.naming.spi.NamingManager; import junit.rules.TestFixture; /** * A 'stub' JNDI Context backed by a simple {@link Map}, useful only for unit testing. This implementation is * <em>not</em> thread-safe, and should <em>not</em> be used in production. Consider using Spring's SimpleNamingContext * for a more robust implementation with more features. * * @author Alistair.Israel */ public class StubJndiContext extends TestFixture { private static final Logger logger = Logger.getLogger(StubJndiContext.class.getCanonicalName()); private final Map<String, Object> boundObjects = new HashMap<String, Object>(); private boolean closed; /** * The {@link InitialContextFactoryBuilder} for our {@link StubContext} * * @author Alistair A. Israel */ private class StubContextFactoryBuilder implements InitialContextFactoryBuilder { /** * {@inheritDoc} * * @see javax.naming.spi.InitialContextFactoryBuilder#createInitialContextFactory(java.util.Hashtable) */ @Override public InitialContextFactory createInitialContextFactory(final Hashtable<?, ?> environment) throws NamingException { return new InitialContextFactory() { @Override public Context getInitialContext(final Hashtable<?, ?> environment) throws NamingException { return new StubContext(environment); } }; } } /** * @param name * the name to bind to * @param obj * the object to bind */ public final void bind(final String name, final Object obj) { boundObjects.put(name, obj); logger.finest("Bound " + obj.getClass().getCanonicalName() + "@" + System.identityHashCode(obj) + " to \"" + name + "\""); } /** * {@inheritDoc} * * @see junit.rules.TestFixture#setUp() */ @Override protected final void setUp() throws Throwable { logger.info("Activating stub JNDI context"); if (!NamingManager.hasInitialContextFactoryBuilder()) { try { NamingManager.setInitialContextFactoryBuilder(new StubContextFactoryBuilder()); } catch (final NamingException e) { throw new RuntimeException(e.getClass().getCanonicalName() + " attempting to activate StubJndiContextBuilder", e); } } } /** * @return if {@link StubContext#close()} was called */ public final boolean wasClosed() { return closed; } /** * Our internal, stub JNDI context. */ private class StubContext implements Context { private final Hashtable<String, Object> environment; /** * @param environment * this context's environment */ public StubContext(final Hashtable<?, ?> environment) { this.environment = new Hashtable<String, Object>(); if (environment != null && !environment.isEmpty()) { for (final Entry<?, ?> entry : environment.entrySet()) { this.environment.put(entry.getKey().toString(), entry.getValue()); } } } /** * {@inheritDoc} * * @see javax.naming.Context#addToEnvironment(java.lang.String, java.lang.Object) */ @Override public Object addToEnvironment(final String propName, final Object propVal) throws NamingException { return environment.put(propName, propVal); } /** * {@inheritDoc} * * @see javax.naming.Context#bind(java.lang.String, java.lang.Object) */ @Override public void bind(final String name, final Object obj) throws NamingException { boundObjects.put(name, obj); logger.finest("Bound \"" + name + "\" to " + obj.getClass().getCanonicalName() + "@" + System.identityHashCode(obj)); } /** * {@inheritDoc} * * @see javax.naming.Context#close() */ @Override public void close() throws NamingException { closed = true; } /** * {@inheritDoc} * * @see javax.naming.Context#composeName(java.lang.String, java.lang.String) */ @Override public String composeName(final String name, final String prefix) throws NamingException { return prefix + name; } /** * {@inheritDoc} * * @see javax.naming.Context#createSubcontext(java.lang.String) */ @Override public Context createSubcontext(final String name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#destroySubcontext(java.lang.String) */ @Override public void destroySubcontext(final String name) throws NamingException { // TODO Auto-generated method stub } /** * {@inheritDoc} * * @see javax.naming.Context#getEnvironment() */ @Override public Hashtable<?, ?> getEnvironment() throws NamingException { return this.environment; } /** * {@inheritDoc} * * @see javax.naming.Context#list(java.lang.String) */ @Override public NamingEnumeration<NameClassPair> list(final String name) throws NamingException { // TODO Auto-generated method stub return null; } /** * {@inheritDoc} * * @see javax.naming.Context#listBindings(java.lang.String) */ @Override public NamingEnumeration<Binding> listBindings(final String name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#lookup(java.lang.String) */ @Override public Object lookup(final String name) throws NamingException { final Object o = boundObjects.get(name); if (o != null) { logger.finest("lookup(\"" + name + "\") returning " + o.getClass().getCanonicalName() + "@" + System.identityHashCode(o)); } else { logger.finest("lookup(\"" + name + "\") returning null"); } return o; } /** * {@inheritDoc} * * @see javax.naming.Context#lookupLink(java.lang.String) */ @Override public Object lookupLink(final String name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#rebind(java.lang.String, java.lang.Object) */ @Override public void rebind(final String name, final Object obj) throws NamingException { boundObjects.put(name, obj); } /** * {@inheritDoc} * * @see javax.naming.Context#removeFromEnvironment(java.lang.String) */ @Override public Object removeFromEnvironment(final String propName) throws NamingException { return environment.remove(propName); } /** * {@inheritDoc} * * @see javax.naming.Context#rename(java.lang.String, java.lang.String) */ @Override public void rename(final String oldName, final String newName) throws NamingException { if (!boundObjects.containsKey(oldName)) { throw new NamingException("StubJndiContext namme \"" + oldName + "\" not bound!"); } if (boundObjects.containsKey(newName)) { throw new NamingException("StubJndiContext name \"" + oldName + "\" already bound to object of type: " + boundObjects.get(newName).getClass().getCanonicalName()); } final Object obj = boundObjects.remove(oldName); boundObjects.put(newName, obj); } /** * {@inheritDoc} * * @see javax.naming.Context#unbind(java.lang.String) */ @Override public void unbind(final String name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object) */ @Override public void bind(final Name name, final Object obj) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#composeName(javax.naming.Name, javax.naming.Name) */ @Override public Name composeName(final Name name, final Name prefix) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#createSubcontext(javax.naming.Name) */ @Override public Context createSubcontext(final Name name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#destroySubcontext(javax.naming.Name) */ @Override public void destroySubcontext(final Name name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#getNameInNamespace() */ @Override public String getNameInNamespace() throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#getNameParser(javax.naming.Name) */ @Override public NameParser getNameParser(final Name name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#getNameParser(java.lang.String) */ @Override public NameParser getNameParser(final String name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#list(javax.naming.Name) */ @Override public NamingEnumeration<NameClassPair> list(final Name name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#listBindings(javax.naming.Name) */ @Override public NamingEnumeration<Binding> listBindings(final Name name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#lookup(javax.naming.Name) */ @Override public Object lookup(final Name name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#lookupLink(javax.naming.Name) */ @Override public Object lookupLink(final Name name) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object) */ @Override public void rebind(final Name name, final Object obj) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name) */ @Override public void rename(final Name oldName, final Name newName) throws NamingException { throw notSupported(); } /** * {@inheritDoc} * * @see javax.naming.Context#unbind(javax.naming.Name) */ @Override public void unbind(final Name name) throws NamingException { throw notSupported(); } } /** * Equivalent to: * * <pre> * new UnsupportedOperationException("ClassName.MethodName is not supported!"); * </pre> * * @return {@link UnsupportedOperationException} */ public static UnsupportedOperationException notSupported() { final StackTraceElement[] unwound = unwindStackTrace(Thread.currentThread().getStackTrace()); final UnsupportedOperationException e = new UnsupportedOperationException("StubJndiContext." + unwound[0].getMethodName() + " is not supported!"); e.setStackTrace(unwound); return e; } /** * @param st * the {@link StackTraceElement}[] * @return the remaining {@link StackTraceElement}[] */ private static StackTraceElement[] unwindStackTrace(final StackTraceElement[] st) { final int len = st.length; int i = 0; // Unwind up to StubJndiContext final String stubJndiContext = StubJndiContext.class.getName(); while (i < len && !st[i].getClassName().equals(stubJndiContext)) { ++i; } // Unwind past unwindStackTrace() and notSupported() i += 2; final int rest = len - i; final StackTraceElement[] unwound = new StackTraceElement[rest]; System.arraycopy(st, i, unwound, 0, rest); return unwound; } }