MockContext.java |
package org.mockejb.jndi; import java.util.*; import javax.naming.*; import org.apache.commons.logging.*; import org.mockejb.MethodNotImplementedException; /** * Provides implementation of <code>javax.naming.Context</code> interface for * hierarchical in memory single-namespace naming system. * A name in the <code>MockContext</code> namespace is a sequence of one or more * atomic names, relative to a root initial context. * When a name consist of more than one atomic names it is a <code>CompoundName</code> * where atomic names are separated with separator character - '/' or '.'. * It is possible to use both separator characters in the same name. In such cases * any occurrences of '.' are replaced with '/' before parsing. * <p> * Leading and terminal components of a <code>CompoundName</code> can not be empty - * for example "name1/name2/name3" is a valid name, while the following names are * not valid - "/name1/name2/name3", "name1/name2/name3/", "/name1/name2/name3/". * If such name is passed, all empty leading/terminal components will be removed * before the name is actually used (this will not affect the original value) - * from the above three examples the actual name will be "name1/name2/name3". * If a name contains intermediate empty components (for example "a//b") then * <code>InvalidNameException</code> will be thrown. * <p> * Composite names (instances of <code>CompositeName</code>) must contain zero or one * component from the <code>MockContext</code> namespace. * <p> * The namespace of <code>MockContext</code> can be represented as a tree of atomic names. * Each name is bound to an instance of MockContext (subcontext) or to an arbitrary object. * Each subcontext has collection of names bound to other subcontexts or arbitrary objects. * <p> * When instance of <code>Name</code> is used as parameter to any of the * MockContext methods, if the object is not <code>CompositeName</code> then * it is assumed that it is <code>CompoundName</code> * <p> * Example: * <pre><code> * myContext = initialContext.lookup("foo"); * myObject = myContext.lookup("bar"); * </code> * is equivalent to * <code>myObject = initialContext.lookup("foo/bar");</code> * </pre> * <p> * Instances of <code>MockContext</code> are created only through * <code>MockContextFactory</code>, when <code>InitialContext</code> is instantiated. * <p> * If a remote context is provided, this class will search in that remote context if the * object is not found locally. * <p> * For overloaded methods that accept name as <code>String</code> or * <code>Name</code> only the version for <code>Name</code> is documented. * The <code>String</code> version creates <code>CompoundName</code>, from * the string name passed as parameter, and calls the <code>Name</code> version of * the same method. * * @author Alexander Ananiev * @author Dimitar Gospodinov */ public class MockContext implements Context { private static final String ROOT_CONTEXT_NAME = "ROOT"; // MockContext supports single naming scheme and all instances use the same parser. private static final NameParser nameParser = new MockContextNameParser(); // logger for this class private static Log logger = LogFactory.getLog(MockContext.class.getName()); /** * Map of objects registered for this context representing the local context */ private final Map objects = Collections.synchronizedMap(new HashMap()); // Parent Context of this Context private MockContext parent; // Atomic name of this Context private String contextName; /** * Container context used for delegated lookups */ private Context remoteContext; // Shows if this context has been destroyed private boolean isDestroyed; /** * Creates a new instance of the context. * This class can only be instantiated by its factory. * @param remoteContext remote context that MockContext will * delegate to if it fails to lookup an object locally */ protected MockContext( Context remoteContext ) { this(remoteContext, null, ROOT_CONTEXT_NAME); } /** * Creates new instance of <code>MockContext</code>. * @param remoteContext remote context that MockContext will * delegate to if it fails to lookup an object locally * @param parent parent context of this context. <code>null</code> if * this is the root context. * @param name atomic name for this context */ private MockContext(Context remoteContext, MockContext parent, String name) { this.remoteContext = remoteContext; this.parent = parent; this.contextName = name; this.isDestroyed = false; } /** * Not implemented * @see javax.naming.Context#addToEnvironment(java.lang.String, java.lang.Object) */ public Object addToEnvironment(String arg0, Object arg1) throws NamingException { throwMethodNotImplemented("addToEnvironment( String, Object)"); return null; } /** * Binds object <code>obj</code> to name <code>name</code> in this context. * Intermediate contexts that do not exist will be created. * * @param name name of the object to bind * @param obj object to bind. Can be <code>null</code>. * * @throws NoPermissionException if this context has been destroyed * @throws InvalidNameException if <code>name</code> is empty or is * <code>CompositeName</code> that spans more than one naming system * @throws NotContextException if <code>name</code> has more than one * atomic name and intermediate atomic name is bound to object that is * not context. * * @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object) */ public void bind(Name name, Object obj) throws NamingException { checkIsDestroyed(); // Do not check for already bound name. Simply replace the existing value. rebind(name, obj); } /** * Binds object <code>obj</code> to name <code>name</code> in this context. * @param name name of the object to add * @param obj object to bind * @throws NamingException if naming error occurs * @see #bind(Name, Object) */ public void bind(String name, Object obj) throws NamingException { bind(nameParser.parse(name), obj); } /** * Does nothing. */ public void close() throws NamingException { } /** * Returns composition of <code>prefix</code> and <code>name</code>. * @param name name relative to this context * @param prefix name of this context * @see javax.naming.Context#composeName(javax.naming.Name, javax.naming.Name) */ public Name composeName( Name name, Name prefix ) throws NamingException { checkIsDestroyed(); /* * We do not want to modify any of the parameters (JNDI requirement). * Clone <code>prefix</code> to satisfy the requirement. */ Name parsedPrefix = getParsedName((Name)prefix.clone()); Name parsedName = getParsedName(name); return parsedPrefix.addAll(parsedName); } /** * Composes the name of this context with a name relative to this context. * Given a name (name) relative to this context, and the name (prefix) * of this context relative to one of its ancestors, this method returns * the composition of the two names using the syntax appropriate for * the naming system(s) involved. * Example: * composeName("a","b") b/a * composeName("a","") a * @param name name relative to this context * @param prefix name of this context * @see javax.naming.Context#composeName(java.lang.String, java.lang.String) */ public String composeName( String name, String prefix ) throws NamingException { checkIsDestroyed(); return composeName(nameParser.parse(name), nameParser.parse(prefix)).toString(); } /** * Creates subcontext with name <code>name</code>, relative to this Context. * @param name subcontext name. * @return new subcontext named <code>name</code> relative to this context * @throws NoPermissionException if this context has been destroyed * @throws InvalidNameException if <code>name</code> is empty or is * <code>CompositeName</code> that spans more than one naming system * @throws NameAlreadyBoundException if <code>name</code> is already bound in this Context * @throws NotContextException if any intermediate name from <code>name</code> * is not bound to instance of <code>javax.naming.Context</code> * @see javax.naming.Context#createSubcontext(javax.naming.Name) */ public Context createSubcontext(Name name) throws NamingException { checkIsDestroyed(); Name parsedName = getParsedName(name); if (parsedName.size() == 0 || parsedName.get(0).length() == 0) { throw new InvalidNameException("Name can not be empty!"); } String subContextName = parsedName.get(0); Object boundObject = objects.get(parsedName.get(0)); if (parsedName.size() == 1) { // Check if <code>name</code> is already in use if (boundObject == null) { Context subContext = new MockContext(remoteContext, this, subContextName); objects.put(subContextName, subContext); return subContext; } else { throw new NameAlreadyBoundException( "Name " + subContextName + " is already bound!"); } } else { if (boundObject instanceof Context) { // Let the subcontext create new subcontext return ((Context) boundObject).createSubcontext( parsedName.getSuffix(1)); } else { throw new NotContextException( "Expected Context but found " + boundObject); } } } /** * Creates subcontext with name <code>name</code>, relative to this Context. * @param name subcontext name * @return new subcontext named <code>name</code> relative to this context * @throws NamingException if naming error occurs * @see #createSubcontext(javax.naming.Name) */ public Context createSubcontext(String name) throws NamingException { return createSubcontext(nameParser.parse(name)); } /** * Destroys subcontext with name <code>name</code> * The subcontext must be empty otherwise <code>ContextNotEmptyException</code> * is thrown. * <p> * Once a context is destroyed, the instance should not be used. * @param name subcontext to destroy * @throws NoPermissionException if this context has been destroyed * @throws InvalidNameException if <code>name</code> is empty or is * <code>CompositeName</code> that spans more than one naming system * @throws ContextNotEmptyException if Context <code>name</code> is not empty * @throws NameNotFoundException if subcontext with name <code>name</code> can not be found * @throws NotContextException if <code>name</code> is not bound to instance of * <code>MockContext</code> * @see javax.naming.Context#destroySubcontext(javax.naming.Name) */ public void destroySubcontext(Name name) throws NamingException { checkIsDestroyed(); Name parsedName = getParsedName(name); if (parsedName.size() == 0 || parsedName.get(0).length() == 0) { throw new InvalidNameException("Name can not be empty!"); } String subContextName = parsedName.get(0); Object boundObject = objects.get(subContextName); if (boundObject == null) { throw new NameNotFoundException( "Name " + subContextName + "not found in the context!"); } if (!(boundObject instanceof MockContext)) { throw new NotContextException(); } MockContext contextToDestroy = (MockContext) boundObject; if (parsedName.size() == 1) { /* * Check if the Context to be destroyed is empty. * Can not destroy non-empty Context. */ if (contextToDestroy.objects.size() == 0) { objects.remove(subContextName); contextToDestroy.destroyInternal(); } else { throw new ContextNotEmptyException("Can not destroy non-empty Context!"); } } else { // Let the subcontext destroy the context ((MockContext) boundObject).destroySubcontext( parsedName.getSuffix(1)); } } /** * Destroys subcontext with name <code>name</code> * @param name name of subcontext to destroy * @throws NamingException if naming error occurs * @see #destroySubcontext(javax.naming.Name) */ public void destroySubcontext(String name) throws NamingException { destroySubcontext(nameParser.parse(name)); } /** * Not implemented * @see javax.naming.Context#getEnvironment() */ public Hashtable getEnvironment() throws NamingException { throwMethodNotImplemented("getEnvironment()"); return null; } /** * Not implemented * @see javax.naming.Context#getNameInNamespace() */ public String getNameInNamespace() throws NamingException { throwMethodNotImplemented("getNameInNamespace()"); return null; } /** * Retrieves name parser used to parse context with name <code>name</code>. * @param name context name * @return <code>NameParser</code> * @throws NoPermissionException if this context has been destroyed * @throws NamingException if any other naming error occurs * @see javax.naming.Context#getNameParser(javax.naming.Name) */ public NameParser getNameParser(Name name) throws NamingException { checkIsDestroyed(); return nameParser; } /** * Retrieves name parser used to parse context with name <code>name</code>. * @param name context name * @return <code>NameParser</code> * @throws NamingException if naming error occurs * @see #getNameParser(javax.naming.Name) */ public NameParser getNameParser(String name) throws NamingException { checkIsDestroyed(); return nameParser; } /** * The same as <code>listBindings(String)</code> * @param name name of Context, relative to this Context * @return <code>NamingEnumeration</code> of all name-class pairs. Each * element from the enumeration is instance of <code>NameClassPair</code> * @throws NamingException if naming error occurs * @see #listBindings(javax.naming.Name) */ public NamingEnumeration list(Name name) throws NamingException { return listBindings(name); } /** * The same as <code>listBindings(String)</code> * @param name name of Context, relative to this Context * @return <code>NamingEnumeration</code> of all name-class pairs. Each * element from the enumeration is instance of <code>NameClassPair</code> * @throws NamingException if naming error occurs * @see #listBindings(java.lang.String) */ public NamingEnumeration list(String name) throws NamingException { return list(nameParser.parse(name)); } /** * Lists all bindings for Context with name <code>name</code>. * If <code>name</code> is empty then this Context is assumed. * @param name name of Context, relative to this Context * @return <code>NamingEnumeration</code> of all name-object pairs. Each * element from the enumeration is instance of <code>Binding</code> * @throws NoPermissionException if this context has been destroyed * @throws InvalidNameException if <code>name</code> is <code>CompositeName</code> * that spans more than one naming system * @throws NameNotFoundException if <code>name</code> can not be found * @throws NotContextException component of <code>name</code> is not bound to instance of * <code>MockContext</code>, when <code>name</code> is not an atomic name * @throws NamingException if any other naming error occurs * @see javax.naming.Context#listBindings(javax.naming.Name) */ public NamingEnumeration listBindings(Name name) throws NamingException { checkIsDestroyed(); Name parsedName = getParsedName(name); if (parsedName.size() == 0) { Vector bindings = new Vector(); Iterator iterat = objects.keySet().iterator(); while (iterat.hasNext()) { String bindingName = (String) iterat.next(); bindings.addElement( new Binding(bindingName, objects.get(bindingName))); } return new NamingEnumerationImpl(bindings); } else { Object subContext = objects.get(parsedName.get(0)); if (subContext instanceof Context) { return ((Context) subContext).list(parsedName.getSuffix(1)); } if (subContext == null && !objects.containsKey(parsedName.get(0))) { throw new NameNotFoundException("Name " + name + " not found"); } else { throw new NotContextException( "Expected Context but found " + subContext); } } } /** * Lists all bindings for Context with name <code>name</code>. * If <code>name</code> is empty then this Context is assumed. * @param name name of Context, relative to this Context * @return <code>NamingEnumeration</code> of all name-object pairs. Each * element from the enumeration is instance of <code>Binding</code> * @throws NamingException if naming error occurs * @see #listBindings(javax.naming.Name) */ public NamingEnumeration listBindings(String name) throws NamingException { return listBindings(nameParser.parse(name)); } /** * Looks up object with name <code>name</code> in this context. If the object is not * found and the remote context was provided, calls the remote * context to lookup the object. * @param name name to look up * @return object reference bound to name <code>name</code> * @throws NoPermissionException if this context has been destroyed * @throws InvalidNameException if <code>name</code> is <code>CompositeName</code> * that spans more than one naming system * @throws NameNotFoundException if <code>name</code> can not be found * @throws NotContextException component of <code>name</code> is not bound to instance of * <code>MockContext</code>, when <code>name</code> is not atomic name. * @throws NamingException if any other naming error occurs * @see javax.naming.Context#lookup(javax.naming.Name) */ public Object lookup(Name name) throws NamingException { checkIsDestroyed(); try { return lookupInternal(name); } catch (NameNotFoundException ex) { // Shall we delegate? if ( remoteContext != null ) { logger.debug( "Was not able to find name " + name + " in the local context. Will try remote context."); return remoteContext.lookup(name); } else { throw new NameNotFoundException("Name " + name + " not found. "); } } } private Object lookupInternal(Name name) throws NamingException { Name parsedName = getParsedName(name); String nameComponent = parsedName.get(0); Object res = objects.get( nameComponent ); // if not found if ( ! objects.containsKey( nameComponent ) ) { throw new NameNotFoundException("Name " + name + " not found."); } // if this is a compound name else if ( parsedName.size() > 1 ){ if ( res instanceof MockContext) { res = ((MockContext) res).lookupInternal(parsedName.getSuffix(1)); } else { throw new NotContextException( "Expected MockContext but found " + res); } } return res; } /** * Looks up the object in this context. If the object is not * found and the remote context was provided, calls the remote * context to lookup the object. * @param name object to search * @return object reference bound to name <code>name</code> * @throws NamingException if naming error occurs * @see #lookup(javax.naming.Name) */ public Object lookup(String name) throws NamingException { checkIsDestroyed(); try { return lookupInternal(name); } catch (NameNotFoundException ex) { // Shall we delegate? if ( remoteContext != null ) { logger.debug( "Was not able to find name " + name + " in the local context. Will try remote context."); return remoteContext.lookup(name); } else { throw new NameNotFoundException("Name " + name + " not found. "); } } } private Object lookupInternal(String name) throws NamingException { return lookupInternal(nameParser.parse(name)); } /** * Not implemented * @see javax.naming.Context#lookupLink(javax.naming.Name) */ public Object lookupLink(Name arg0) throws NamingException { throwMethodNotImplemented("lookupLink(Name)"); return null; } /** * Not implemented * @see javax.naming.Context#lookupLink(java.lang.String) */ public Object lookupLink(String arg0) throws NamingException { throwMethodNotImplemented("lookupLink(String)"); return null; } /** * Rebinds object <code>obj</code> to name <code>name</code>. * If there is existing binding it will be overwritten. * @param name name of the object to rebind * @param obj object to add. Can be <code>null</code> * @throws NoPermissionException if this context has been destroyed * @throws InvalidNameException if <code>name</code> is empty or is * <code>CompositeName</code> that spans more than one naming system * @throws NotContextException if <code>name</code> has more than one * atomic name and intermediate context is not found * @throws NamingException if any other naming error occurs * @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object) */ public void rebind(Name name, Object obj) throws NamingException { checkIsDestroyed(); Name parsedName = getParsedName(name); if (parsedName.size() == 0 || parsedName.get(0).length() == 0) { throw new InvalidNameException("Name can not be empty!"); } String nameToBind = parsedName.get(0); if (parsedName.size() == 1) { objects.put(nameToBind, obj); logger.debug("Bound object " + obj + " to the name " + nameToBind); } else { Object boundObject = objects.get(nameToBind); if (boundObject instanceof Context) { /* * Let the subcontext bind the object. */ ((Context)boundObject).bind(parsedName.getSuffix(1), obj); } else { if (boundObject == null) { // Create new subcontext and let it do the binding Context sub = createSubcontext(nameToBind); sub.bind(parsedName.getSuffix(1), obj); } else { throw new NotContextException("Expected Context but found " + boundObject); } } } } /** * Same as bind except that if <code>name</code> is already bound in * the context, it will be re-bound to object <code>obj</code> * @param name name of the object to rebind * @param obj object to add. Can be <code>null</code> * @throws NamingException if naming error occurs * @see #rebind(javax.naming.Name, Object) */ public void rebind(String name, Object obj) throws NamingException { rebind(nameParser.parse(name), obj); } /** * Not implemented * @see javax.naming.Context#removeFromEnvironment(java.lang.String) */ public Object removeFromEnvironment(String arg0) throws NamingException { throwMethodNotImplemented("removeFromEnvironment(String)"); return null; } /** * Not implemented * @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name) */ public void rename(Name arg0, Name arg1) throws NamingException { throwMethodNotImplemented("rename(Name arg0, Name arg1)"); } /** * Not implemented * @see javax.naming.Context#rename(java.lang.String, java.lang.String) */ public void rename(String arg0, String arg1) throws NamingException { throwMethodNotImplemented("rename(String arg0, String arg1)"); } /** * Removes <code>name</code> and its associated object from the context. * @param name name to remove * @throws NoPermissionException if this context has been destroyed * @throws InvalidNameException if <code>name</code> is empty or is * <code>CompositeName</code> that spans more than one naming system * @throws NameNotFoundException if intermediate context can not be found * @throws NotContextException if <code>name</code> has more than one * atomic name and intermediate context is not found * @throws NamingException if any other naming exception occurs * @see javax.naming.Context#unbind(javax.naming.Name) */ public void unbind(Name name) throws NamingException { checkIsDestroyed(); Name parsedName = getParsedName(name); if (parsedName.size() == 0 || parsedName.get(0).length() == 0) { throw new InvalidNameException("Name can not be empty!"); } String nameToRemove = parsedName.get(0); if (parsedName.size() == 1) { objects.remove(nameToRemove); logger.debug("Unbound name " + name); } else { Object boundObject = objects.get(nameToRemove); if (boundObject instanceof Context) { /* * Let the subcontext do the unbind */ ((Context) boundObject).unbind(parsedName.getSuffix(1)); } else { if (!objects.containsKey(nameToRemove)) { throw new NameNotFoundException("Can not find " + name); } throw new NotContextException( "Expected Context but found " + boundObject); } } } /** * Removes object from the object map * @param name object to remove * @throws NamingException if naming error occurs * @see #unbind(javax.naming.Name) */ public void unbind(String name) throws NamingException { unbind(nameParser.parse(name)); } // ** Non-standard methods /** * Checks if this context has been destroyed. * <code>isDestroyed</code> is set to <code>true</code> when a context is * destroyed by calling <code>destroySubcontext</code> method. * @throws NoPermissionException if this context has been destroyed */ private void checkIsDestroyed() throws NamingException { if (isDestroyed) { throw new NoPermissionException("Can not invoke operations on destroyed context!"); } } /** * Marks this context as destroyed. * Method called only by <code>destroySubcontext</code>. */ private void destroyInternal() { isDestroyed = true; } /** * Parses <code>name</code> which is <code>CompositeName</code> or <code>CompoundName</code>. * If <code>name</code> is not <code>CompositeName</code> then it is * assumed to be <code>CompoundName</code>. * <p> * If the name contains leading and/or terminal empty components, they will not * be included in the result. * @param name <code>Name</code> to parse * @return parsed name as instance of <code>CompoundName</code> * @throws InvalidNameException if <code>name</code> is * <code>CompositeName</code> and spans more than one name space * @throws NamingException if any other naming exception occurs */ private Name getParsedName(Name name) throws NamingException { Name result = null; if (name instanceof CompositeName) { if (name.size() == 0) { // Return empty CompositeName result = nameParser.parse(""); } else if (name.size() > 1) { throw new InvalidNameException("Multiple name systems are not supported!"); } result = nameParser.parse(name.get(0)); } else { result = (Name)name.clone(); } while (!result.isEmpty()) { if (result.get(0).length() == 0) { result.remove(0); continue; } if (result.get(result.size()-1).length() == 0) { result.remove(result.size()-1); continue; } break; } // Validate that there are not intermediate empty components. // Skip first and last element, they are valid for (int i = 1; i < result.size() - 1; i++) { if (result.get(i).length() == 0) { throw new InvalidNameException("Empty intermediate components are not supported!"); } } return result; } /** * Returns the compound string name of this context. * Suppose a/b/c/d is the full name and this context is "c". * It's compound string name is a/b/c * * @return compound string name of the context */ String getCompoundStringName() throws NamingException { //StringBuffer compositeName = new StringBuffer(); String compositeName=""; MockContext curCtx = this; while( ! curCtx.isRootContext() ) { compositeName = composeName( compositeName, curCtx.getAtomicName() ); curCtx = curCtx.getParentContext(); } return compositeName; } /** * Returns parent context of this context */ MockContext getParentContext() { return parent; } /** * Returns the "atomic" (as opposed to "composite") name of the context. * @return name of the context */ String getAtomicName(){ return contextName; } /** * Returns true if this context is the root context * @return true if the context is the root context */ boolean isRootContext(){ return getParentContext() == null; } private void throwMethodNotImplemented(String methodName) { throw new MethodNotImplementedException( methodName, this.getClass().getName()); } private class NamingEnumerationImpl implements NamingEnumeration { private Vector elements; private int currentElement; NamingEnumerationImpl(Vector elements) { this.elements = elements; this.currentElement = 0; } public void close() { currentElement = 0; elements.clear(); } public boolean hasMore() { return hasMoreElements(); } public boolean hasMoreElements() { if (currentElement < elements.size()) { return true; } close(); return false; } public Object next() { return nextElement(); } public Object nextElement() { if (hasMoreElements()) { return elements.get(currentElement++); } throw new NoSuchElementException(); } } }