/*
* Copyright 2013 Brian Matthews
*
* 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 com.btmatthews.mockjndi.core;
import com.btmatthews.mockjndi.core.object.ObjectBinding;
import com.btmatthews.mockjndi.core.objectfactory.ObjectFactoryBinding;
import javax.naming.*;
import javax.naming.spi.ObjectFactory;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
* @author <a href="mailto:brian@btmatthews.com">Brian Matthews</a>
* @since 1.0.0
*/
public final class MockContext implements Context {
private final String fullName;
private final String name;
private final NameParser nameParser;
private final Map<String, MockContext> subContexts = new HashMap<String, MockContext>();
private final Map<String, MockBinding> bindings = new HashMap<String, MockBinding>();
private final Hashtable<String, Object> environment = new Hashtable<String, Object>();
MockContext(final String fullName,
final String name,
final NameParser nameParser) {
this.fullName = fullName;
this.name = name;
this.nameParser = nameParser;
}
boolean addMockContext(final String name, final MockContext context) {
if (subContexts.containsKey(name) || bindings.containsKey(name)) {
return false;
} else {
subContexts.put(name, context);
return true;
}
}
boolean addBinding(final MockBinding binding) {
if (subContexts.containsKey(name) || bindings.containsKey(name)) {
return false;
} else {
bindings.put(binding.getName(), binding);
return true;
}
}
@Override
public Object lookup(final Name name)
throws NamingException {
if (name != null) {
if (name.isEmpty()) {
try {
return clone();
} catch (final CloneNotSupportedException e) {
throw new NamingException();
}
} else {
return visitContext(name.getPrefix(name.size() - 1), new MockContextVisitor<Object>() {
@Override
public Object visit(final MockContext context) throws NamingException {
final String key = name.get(name.size() - 1);
if (context.bindings.containsKey(key)) {
return context.bindings.get(key).getBoundObject();
} else if (context.subContexts.containsKey(key)) {
return context.subContexts.get(key);
} else {
throw new NameNotFoundException();
}
}
});
}
} else {
throw new InvalidNameException();
}
}
@Override
public Object lookup(final String name)
throws NamingException {
return lookup(nameParser.parse(name));
}
/**
* Bind the object {@code obj} to the name {@code name}.
*
* @param name The name to bind, may not be empty.
* @param obj The object to be bound, may be {@code null}.
* @throws NamingException
*/
@Override
public void bind(final Name name,
final Object obj) throws NamingException {
if (name != null && name.size() > 0) {
visitContext(name.getPrefix(name.size() - 1), new MockContextVisitor<Object>() {
@Override
public Object visit(final MockContext context) throws NamingException {
final String key = name.get(name.size() - 1);
if (context.subContexts.containsKey(key) || context.bindings.containsKey(key)) {
throw new NameAlreadyBoundException();
}
if (obj instanceof ObjectFactory) {
bindings.put(key, new ObjectFactoryBinding(key, (ObjectFactory) obj));
} else {
bindings.put(key, new ObjectBinding(key, obj));
}
return null;
}
});
} else {
throw new InvalidNameException();
}
}
@Override
public void bind(final String name,
final Object obj)
throws NamingException {
bind(nameParser.parse(name), obj);
}
@Override
public void rebind(final Name name,
final Object obj)
throws NamingException {
visitContext(name.getPrefix(name.size() - 1), new MockContextVisitor<Object>() {
@Override
public Object visit(final MockContext context) throws NamingException {
final String key = name.get(name.size() - 1);
if (context.subContexts.containsKey(key)) {
context.subContexts.remove(key).close();
} else if (!context.bindings.containsKey(key)) {
throw new NameNotFoundException();
}
if (obj instanceof ObjectFactory) {
context.bindings.put(key, new ObjectFactoryBinding(key, (ObjectFactory) obj));
} else {
context.bindings.put(key, new ObjectBinding(key, obj));
}
return null;
}
});
}
@Override
public void rebind(final String name,
final Object obj)
throws NamingException {
rebind(nameParser.parse(name), obj);
}
@Override
public void unbind(final Name name)
throws NamingException {
if (name != null && !name.isEmpty()) {
visitContext(name.getPrefix(name.size() - 1), new MockContextVisitor<Object>() {
@Override
public Object visit(final MockContext context) throws NamingException {
final String key = name.get(name.size() - 1);
if (context.bindings.containsKey(key)) {
context.bindings.remove(key);
} else {
throw new NameNotFoundException();
}
return null;
}
});
} else {
throw new InvalidNameException();
}
}
@Override
public void unbind(String name)
throws NamingException {
unbind(nameParser.parse(name));
}
@Override
public void rename(final Name oldName,
final Name newName)
throws NamingException {
if (oldName != null && oldName != null && newName != null && !newName.isEmpty()) {
if (!oldName.equals(newName)) {
final Name oldRoot = oldName.getPrefix(oldName.size() - 1);
final String oldKey = oldName.get(oldName.size() - 1);
final Name newRoot = newName.getPrefix(newName.size() - 1);
final String newKey = newName.get(newName.size() - 1);
visitContext(newRoot, new MockContextVisitor<Object>() {
@Override
public Object visit(final MockContext newContext) throws NamingException {
if (!newContext.bindings.containsKey(newKey) && !newContext.subContexts.containsKey(newKey)) {
visitContext(oldRoot, new MockContextVisitor<Object>() {
@Override
public Object visit(final MockContext oldContext) throws NamingException {
if (oldContext.bindings.containsKey(oldKey)) {
final MockBinding obj = oldContext.bindings.remove(oldKey);
newContext.bindings.put(newKey, obj);
} else if (oldContext.subContexts.containsKey(oldKey)) {
final MockContext obj = oldContext.subContexts.remove(oldKey);
newContext.subContexts.put(newKey, obj);
} else {
throw new NameNotFoundException();
}
return null;
}
});
} else {
throw new NameAlreadyBoundException();
}
return null;
}
});
}
} else {
throw new InvalidNameException();
}
}
@Override
public void rename(final String oldName,
final String newName)
throws NamingException {
rename(nameParser.parse(oldName), nameParser.parse(newName));
}
@Override
public NamingEnumeration<NameClassPair> list(final Name name)
throws NamingException {
throw new UnsupportedOperationException("list(Name) is not currently supported by MockJNDI");
}
@Override
public NamingEnumeration<NameClassPair> list(final String name)
throws NamingException {
throw new UnsupportedOperationException("list(String) is not currently supported by MockJNDI");
}
@Override
public NamingEnumeration<Binding> listBindings(final Name name)
throws NamingException {
throw new UnsupportedOperationException("listBindings(Name) is not currently supported by MockJNDI");
}
@Override
public NamingEnumeration<Binding> listBindings(final String name)
throws NamingException {
throw new UnsupportedOperationException("listBindings(String) is not currently supported by MockJNDI");
}
@Override
public void destroySubcontext(final Name name)
throws NamingException {
if (name != null && !name.isEmpty()) {
visitContext(name.getPrefix(name.size() - 1), new MockContextVisitor<Object>() {
@Override
public Object visit(final MockContext context) throws NamingException {
final String key = name.get(name.size() - 1);
if (context.subContexts.containsKey(key)) {
context.subContexts.remove(key).close();
return null;
} else {
throw new NameNotFoundException();
}
}
});
} else {
throw new InvalidNameException();
}
}
@Override
public void destroySubcontext(final String name)
throws NamingException {
destroySubcontext(nameParser.parse(name));
}
@Override
public Context createSubcontext(final Name name)
throws NamingException {
if (name != null && name.size() > 0) {
return visitContext(name.getPrefix(name.size() - 1), new MockContextVisitor<Context>() {
@Override
public Context visit(MockContext context) throws NamingException {
final String key = name.get(name.size() - 1);
if (context.bindings.containsValue(key) || context.subContexts.containsValue(key)) {
throw new NameAlreadyBoundException();
}
final MockContext newContext = new MockContext(name.toString(), key, nameParser);
context.subContexts.put(key, newContext);
return newContext;
}
});
} else {
throw new InvalidNameException();
}
}
@Override
public Context createSubcontext(final String name)
throws NamingException {
return createSubcontext(nameParser.parse(name));
}
@Override
public Object lookupLink(final Name name)
throws NamingException {
return lookup(name);
}
@Override
public Object lookupLink(final String name)
throws NamingException {
return lookupLink(nameParser.parse(name));
}
@Override
public NameParser getNameParser(final Name name)
throws NamingException {
return visitContext(name, new MockContextVisitor<NameParser>() {
@Override
public NameParser visit(final MockContext context) throws NamingException {
return context.nameParser;
}
});
}
/**
*
* @param name
* @return
* @throws NamingException
*/
@Override
public NameParser getNameParser(final String name)
throws NamingException {
return getNameParser(nameParser.parse(name));
}
/**
* Concatenate two names.
*
* @param name The value to be appended to the prefix.
* @param prefix The value of the name root.
* @return The result of the concatenation.
* @throws NamingException If there was a problem concatenating the names.
*/
@Override
public Name composeName(final Name name,
final Name prefix)
throws NamingException {
return ((Name) prefix.clone()).addAll(name);
}
/**
* Concatenate two names.
*
* @param name The value to be appended to the prefix.
* @param prefix The value of the name root.
* @return The result of the concatenation.
* @throws NamingException If there was a problem concatenating the names.
*/
@Override
public String composeName(final String name,
final String prefix)
throws NamingException {
return composeName(nameParser.parse(name), nameParser.parse(prefix)).toString();
}
/**
* Add an environment setting.
*
* @param propName The environment setting name.
* @param propVal The environment setting value.
* @return The previous value of environment setting or {@code null} if the environment setting is new.
* @throws NamingException If there was a problem adding the environment setting.
*/
@Override
public Object addToEnvironment(final String propName,
final Object propVal)
throws NamingException {
return environment.put(propName, propVal);
}
/**
* Remove an environment setting.
*
* @param propName The environment setting value.
* @return The previous value of the environment setting or {@code null} if the environment setting did not exist.
* @throws NamingException If there was a problem removing the environment setting.
*/
@Override
public Object removeFromEnvironment(final String propName)
throws NamingException {
return environment.remove(propName);
}
/**
* Get the environment settings associated with the naming context.
*
* @return A {@link Hashtable} containing the environment settings.
* @throws NamingException If there was a problem getting the environment settings.
*/
@Override
public Hashtable<?, ?> getEnvironment() throws
NamingException {
return environment;
}
/**
* Close the naming context.
*
* @throws NamingException If there was problem closing the naming context.
*/
@Override
public void close() throws NamingException {
}
/**
* Get the full name of the naming context.
*
* @return The full name of the naming context.
* @throws NamingException If there was a problem getting the full name of the naming context.
*/
@Override
public String getNameInNamespace() throws NamingException {
return fullName;
}
/**
* Recursively descend down through the context namespace to match the context named {@code name} and
* then invoke a callback.
*
* @param name The name to be matched.
* @param visitor The callback invoked by the visitor when the context is matched..
* @param <T> The return type of the visitor callback.
* @return The response from the visitor callback.
* @throws NamingException If there was an exception matching the context or executing the callback.
*/
private <T> T visitContext(final Name name, final MockContextVisitor<T> visitor) throws NamingException {
if (name == null || name.isEmpty()) {
return visitor.visit(this);
}
final String key = name.get(0);
final MockContext subContext = subContexts.get(key);
if (subContext == null) {
if (bindings.containsKey(key)) {
throw new NotContextException();
} else {
throw new NameNotFoundException();
}
} else {
return subContext.visitContext(name.getSuffix(1), visitor);
}
}
}