blob: 122ace666498e0702ea1b38809a3d05bbdf91758 [file] [log] [blame]
/**
*
* Copyright 2006 The Apache Software Foundation
*
* 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.apache.xbean.naming.context;
import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.NotContextException;
import javax.naming.LinkRef;
import javax.naming.NameNotFoundException;
import javax.naming.InitialContext;
import javax.naming.OperationNotSupportedException;
import java.io.Serializable;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
public abstract class AbstractContext implements Context, NestedContextFactory, Serializable {
private static final long serialVersionUID = 6481918425692261483L;
private final String nameInNamespace;
private final Name parsedNameInNamespace;
private final ContextAccess contextAccess;
private final boolean modifiable;
protected AbstractContext(String nameInNamespace) {
this(nameInNamespace, ContextAccess.MODIFIABLE);
}
public AbstractContext(String nameInNamespace, ContextAccess contextAccess) {
this.nameInNamespace = nameInNamespace;
try {
this.parsedNameInNamespace = getNameParser().parse(nameInNamespace);
} catch (NamingException e) {
throw new RuntimeException(e);
}
this.contextAccess = contextAccess;
this.modifiable = contextAccess.isModifiable(getParsedNameInNamespace());
}
public void close() throws NamingException {
//Ignore. Explicitly do not close the context
}
protected ContextAccess getContextAccess() {
return contextAccess;
}
//
// Lookup Binding
//
/**
* Gets the object bound to the name. The name may contain slashes.
* @param name the name
* @return the object bound to the name, or null if not found
*/
protected Object getDeepBinding(String name) {
return null;
}
/**
* Gets the object bound to the name. The name will not contain slashes.
* @param name the name
* @return the object bound to the name, or null if not found
*/
protected Object getBinding(String name) throws NamingException {
Map bindings = getBindings();
return bindings.get(name);
}
/**
* Finds the specified entry. Normally there is no need to override this method; instead you should
* simply implement the getDeepBindings(String) and getBindings(String) method.
*
* This method will follow links except for the final element which is always just returned without
* inspection. This means this method can be used to implement lookupLink.
*
* @param stringName the string version of the name; maybe null
* @param parsedName the parsed name; may be null
* @return the value bound to the name
* @throws NamingException if no value is bound to that name or if a problem occurs during the lookup
*/
protected Object lookup(String stringName, Name parsedName) throws NamingException {
if (stringName == null && parsedName == null) {
throw new IllegalArgumentException("Both stringName and parsedName are null");
}
if (stringName == null) stringName = parsedName.toString();
// try to look up the name directly (this is the fastest path)
Object directLookup = getDeepBinding(stringName);
if (directLookup != null) {
return ContextUtil.resolve(stringName, directLookup);
}
// if the parsed name has no parts, they are asking for the current context
if (parsedName == null) parsedName = getNameParser().parse(stringName);
if (parsedName.isEmpty()) {
return this;
}
// we didn't find an entry, pop the first element off the parsed name and attempt to
// get a context from the bindings and delegate to that context
Object localValue;
String firstNameElement = parsedName.get(0);
if (firstNameElement.length() == 0) {
// the element is null... this is normally caused by looking up with a trailing '/' character
localValue = this;
} else {
localValue = getBinding(firstNameElement);
}
if (localValue != null) {
// if the name only had one part, we've looked up everything
if (parsedName.size() == 1) {
localValue = ContextUtil.resolve(stringName, localValue);
return localValue;
}
// if we have a link ref, follow it
if (localValue instanceof LinkRef) {
LinkRef linkRef = (LinkRef) localValue;
localValue = lookup(linkRef.getLinkName());
}
// we have more to lookup so we better have a context object
if (!(localValue instanceof Context)) {
throw new NameNotFoundException(stringName);
}
// delegate to the sub-context
return ((Context) localValue).lookup(parsedName.getSuffix(1));
}
// if we didn't find an entry, it may be an absolute name
Object value = faultLookup(stringName, parsedName);
if (value != null) {
return value;
}
if (parsedName.size() > 1) {
throw new NotContextException(stringName);
} else {
throw new NameNotFoundException(stringName);
}
}
/**
* When a value can not be found within this context, this method is called as a last ditch effort befrore
* thowing a null pointer exception.
* @param stringName the string version of the name; will not be null
* @param parsedName the parsed name; will not be null
* @return the value or null if no fault value could be found
*/
protected Object faultLookup(String stringName, Name parsedName) {
if (stringName.indexOf(':') > 0) {
try {
Context ctx = new InitialContext();
return ctx.lookup(parsedName);
} catch (NamingException ignored) {
// thrown below
}
}
return null;
}
protected Context lookupFinalContext(Name name) throws NamingException {
Object value = null;
try {
value = lookup(name.getPrefix(name.size() - 1));
} catch (NamingException e) {
throw new NotContextException("The intermediate context " + name.get(name.size() - 1) + " does not exist");
}
if (value == null) {
throw new NotContextException("The intermediate context " + name.get(name.size() - 1) + " does not exist");
} else if (!(value instanceof Context)) {
throw new NotContextException("The intermediate context " + name.get(name.size() - 1) + " does is not a context");
} else {
return (Context) value;
}
}
//
// List Bindings
//
/**
* Gets a map of the bindings for the current node (i.e., no names with slashes).
* This method must not return null.
*
* @return a Map from binding name to binding value
* @throws NamingException if a problem occurs while getting the bindigns
*/
protected abstract Map getBindings() throws NamingException;
//
// Add Binding
//
protected void addDeepBinding(Name name, Object value, boolean rebind, boolean createIntermediateContexts) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
if (value == null) throw new NullPointerException("value is null");
if (name.isEmpty()) {
throw new InvalidNameException("Name is empty");
}
if (name.size() == 1) {
addBinding(name.get(0), value, rebind);
return;
}
if (!createIntermediateContexts) {
Context context = lookupFinalContext(name);
String lastSegment = name.get(name.size() - 1);
addBinding(context, lastSegment, value, rebind);
} else {
Context currentContext = this;
for (int i = 0; i < name.size(); i++) {
String part = name.get(i);
// empty path parts are not allowed
if (part.length() == 0) {
// this could be supported but it would be tricky
throw new InvalidNameException("Name part " + i + " is empty: " + name);
}
// Is this the last element in the name?
if (i == name.size() - 1) {
// we're at the end... (re)bind the value into the parent context
addBinding(currentContext, part, value, rebind);
// all done... this is redundant but makes the code more readable
break;
} else {
Object currentValue = getBinding(currentContext, part);
if (currentValue == null) {
// the next step in the tree is not present, so create everything down
// and add it to the current bindings
Context subcontext = createSubcontextTree(name.getPrefix(i).toString(), name.getSuffix(i), value);
addBinding(currentContext, part, subcontext, rebind);
// all done
break;
} else {
// the current value must be a nested subcontext
if (!(currentContext instanceof Context)) {
throw new NotContextException("Expected an instance of context to be bound at " +
part + " but found an instance of " + currentValue.getClass().getName());
}
currentContext = (Context) currentValue;
// now we recurse into the current context
}
}
}
}
}
/**
* Gets the value bound to the specified name within the specified context. If the specified context is an
* AbstractContext this method will call the faster getBinding method, otherwise it will call lookup.
*
* @param context the context to get the binding from
* @param name the binding name
* @return the bound value or null if no value was bound
*/
private static Object getBinding(Context context, String name) {
try {
if (context instanceof AbstractContext) {
AbstractContext abstractContext = (AbstractContext) context;
Object value = abstractContext.getBinding(name);
return value;
} else {
Object value = context.lookup(name);
return value;
}
} catch (NamingException e) {
return null;
}
}
/**
* Binds the specified value to the specified name within the specified context. If the specified context is an
* AbstractContext and is a nested subcontext, this method will call the direct addBinding method, otherwise it
* will call public (re)bind method.
*
* @param context the context to add the binding to
* @param name the binding name
* @param value the value to bind
* @param rebind if true, this method will replace any exsiting binding, otherwise a NamingException will be thrown
* @throws NamingException if a problem occurs while (re)binding
*/
protected void addBinding(Context context, String name, Object value, boolean rebind) throws NamingException {
if (context == this || (context instanceof AbstractContext && isNestedSubcontext(context))) {
AbstractContext abstractContext = (AbstractContext) context;
abstractContext.addBinding(name, value, rebind);
} else {
if (rebind) {
context.rebind(name, value);
} else {
context.bind(name, value);
}
}
}
protected abstract boolean addBinding(String name, Object value, boolean rebind) throws NamingException;
/**
* Creates a context tree which will be rooted at the specified path and contain a single entry located down
* a path specified by the name. All necessary intermediate contexts will be created using the createContext method.
* @param path the path to the context that will contains this context
* @param name the name under which the value should be bound
* @param value the vale
* @return a context with the value bound at the specified name
* @throws NamingException if a problem occurs while creating the subcontext tree
*/
protected Context createSubcontextTree(String path, Name name, Object value) throws NamingException {
if (path == null) throw new NullPointerException("path is null");
if (name == null) throw new NullPointerException("name is null");
if (name.size() < 2) throw new InvalidNameException("name must have at least 2 parts " + name);
if (!path.endsWith("/")) path += "/";
for (int i = name.size() - 2; i >= 0; i--) {
String fullPath = path + name.getSuffix(i);
String key = name.get(i + 1);
value = createNestedSubcontext(fullPath, Collections.singletonMap(key, value));
}
return (Context) value;
}
//
// Remove Binding
//
/**
* Removes the binding from the context. The name will not contain a path and the value will not
* be a nested context although it may be a foreign context.
* @param name name under which the value should be bound
* @param removeNotEmptyContext
* @throws NamingException if a problem occurs during the bind such as a value already being bound
*/
protected abstract boolean removeBinding(String name, boolean removeNotEmptyContext) throws NamingException;
protected void removeDeepBinding(Name name, boolean pruneEmptyContexts) throws NamingException {
removeDeepBinding(name, pruneEmptyContexts, false);
}
protected void removeDeepBinding(Name name, boolean pruneEmptyContexts, boolean removeNotEmptyContext) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
if (name.isEmpty()) {
throw new InvalidNameException("Name is empty");
}
if (name.size() == 1) {
removeBinding(name.get(0), removeNotEmptyContext);
return;
}
if (!pruneEmptyContexts) {
Context context = lookupFinalContext(name);
context.unbind(name.getSuffix(name.size() - 1));
} else {
// we serch the tree for a target context and name to remove
// this is normally the last context in the tree and the final name part, but
// it may be farther up the path if the intervening nodes are empty
Context targetContext = this;
String targetName = name.get(0);
Context currentContext = this;
for (int i = 0; i < name.size(); i++) {
String part = name.get(i);
// empty path parts are not allowed
if (part.length() == 0) {
throw new InvalidNameException("Name part " + i + " is empty: " + name);
}
// update targets
if (getSize(currentContext) > 1) {
targetContext = currentContext;
targetName = part;
}
// Is this the last element in the name?
if (i == name.size() - 1) {
// we're at the end... unbind value
unbind(targetContext, targetName, true);
// all done... this is redundant but makes the code more readable
break;
} else {
Object currentValue = getBinding(currentContext, part);
if (currentValue == null) {
// path not found we are done, but first prune the empty contexts
if (targetContext != currentContext) {
unbind(targetContext, targetName, false);
}
break;
} else {
// the current value must be a context
if (!(currentValue instanceof Context)) {
throw new NotContextException("Expected an instance of context to be bound at " +
part + " but found an instance of " + currentValue.getClass().getName());
}
currentContext = (Context) currentValue;
// now we recurse into the current context
}
}
}
}
}
protected static boolean isEmpty(Context context) throws NamingException {
if (context instanceof AbstractContext) {
AbstractContext abstractContext = (AbstractContext) context;
Map currentBindings = abstractContext.getBindings();
return currentBindings.isEmpty();
} else {
NamingEnumeration namingEnumeration = context.list("");
return namingEnumeration.hasMore();
}
}
protected static int getSize(Context context) throws NamingException {
if (context instanceof AbstractContext) {
AbstractContext abstractContext = (AbstractContext) context;
Map currentBindings = abstractContext.getBindings();
return currentBindings.size();
} else {
NamingEnumeration namingEnumeration = context.list("");
int size = 0;
while (namingEnumeration.hasMore()) size++;
return size;
}
}
/**
* Unbinds any value bound to the specified name within the specified context. If the specified context is an
* AbstractContext and is a nested context, this method will call the direct removeBinding method, otherwise it
* will call public unbind.
*
* @param context the context to remove the binding from
* @param name the binding name
* @param removeNotEmptyContext
* @throws NamingException if a problem occurs while unbinding
*/
private void unbind(Context context, String name, boolean removeNotEmptyContext) throws NamingException {
if (context == this || (context instanceof AbstractContext && isNestedSubcontext(context))) {
AbstractContext abstractContext = (AbstractContext) context;
abstractContext.removeBinding(name, removeNotEmptyContext);
} else {
context.unbind(name);
}
}
//
// Environment
//
/**
* Always returns a new (empty) Hashtable.
* @return a new (empty) Hashtable
*/
public Hashtable getEnvironment() {
return new Hashtable();
}
public Object addToEnvironment(String propName, Object propVal) throws NamingException {
if (propName == null) throw new NullPointerException("propName is null");
if (propVal == null) throw new NullPointerException("propVal is null");
Map env = getEnvironment();
return env.put(propName, propVal);
}
public Object removeFromEnvironment(String propName) throws NamingException {
if (propName == null) throw new NullPointerException("propName is null");
Map env = getEnvironment();
return env.remove(propName);
}
//
// Name handling
//
/**
* Gets the name of this context withing the global namespace. This method may return null
* if the location of the node in the global namespace is not known
* @return the name of this context within the global namespace or null if unknown.
*/
public String getNameInNamespace() {
return nameInNamespace;
}
/**
* Gets the name of this context withing the global namespace. This method may return null
* if the location of the node in the global namespace is not known
* @return the name of this context within the global namespace or null if unknown.
*/
protected Name getParsedNameInNamespace() {
return parsedNameInNamespace;
}
/**
* Gets the name of a path withing the global namespace context.
*/
protected String getNameInNamespace(String path) {
String nameInNamespace = getNameInNamespace();
if (nameInNamespace == null || nameInNamespace.length() == 0) {
return path;
} else {
return nameInNamespace + "/" + path;
}
}
/**
* Gets the name of a path withing the global namespace context.
*/
protected Name getNameInNamespace(Name path) throws NamingException {
Name nameInNamespace = getParsedNameInNamespace();
if (nameInNamespace == null || nameInNamespace.size() == 0) {
return path;
} else {
return composeName(nameInNamespace, path);
}
}
/**
* A parser that can turn Strings into javax.naming.Name objects.
* @return ContextUtil.NAME_PARSER
*/
protected NameParser getNameParser() {
return ContextUtil.NAME_PARSER;
}
public NameParser getNameParser(Name name) {
return getNameParser();
}
public NameParser getNameParser(String name) {
return getNameParser();
}
public Name composeName(Name name, Name prefix) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
if (prefix == null) throw new NullPointerException("prefix is null");
Name result = (Name) prefix.clone();
result.addAll(name);
return result;
}
public String composeName(String name, String prefix) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
if (prefix == null) throw new NullPointerException("prefix is null");
CompositeName result = new CompositeName(prefix);
result.addAll(new CompositeName(name));
return result.toString();
}
//
// Lookup
//
public Object lookup(String name) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
Object value = lookup(name, null);
// if we got a link back we need to resolve it
if (value instanceof LinkRef) {
LinkRef linkRef = (LinkRef) value;
value = lookup(linkRef.getLinkName());
}
return value;
}
public Object lookup(Name name) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
Object value = lookup(null, name);
// if we got a link back we need to resolve it
if (value instanceof LinkRef) {
LinkRef linkRef = (LinkRef) value;
value = lookup(linkRef.getLinkName());
}
return value;
}
public Object lookupLink(String name) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
return lookup(name, null);
}
public Object lookupLink(Name name) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
return lookup(null, name);
}
//
// Bind, rebind, rename and unbind
//
public void bind(String name, Object obj) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (name == null) throw new NullPointerException("name is null");
if (name.length() == 0) {
throw new NameAlreadyBoundException("Cannot bind to an empty name (this context)");
}
bind(new CompositeName(name), obj);
}
public void bind(Name name, Object obj) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (name == null) throw new NullPointerException("name is null");
if (name.isEmpty()) {
throw new NameAlreadyBoundException("Cannot bind to an empty name (this context)");
}
addDeepBinding(name, obj, false, false);
}
public void rebind(String name, Object obj) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (name == null) throw new NullPointerException("name is null");
rebind(new CompositeName(name), obj);
}
public void rebind(Name name, Object obj) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (name == null) throw new NullPointerException("name is null");
if (name.isEmpty()) {
throw new NameAlreadyBoundException("Cannot rebind an empty name (this context)");
}
addDeepBinding(name, obj, true, false);
}
public void rename(String oldName, String newName) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (oldName == null) throw new NullPointerException("oldName is null");
if (newName == null) throw new NullPointerException("newName is null");
rename(new CompositeName(oldName), new CompositeName(newName));
}
public void rename(Name oldName, Name newName) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (oldName == null || newName == null) {
throw new NullPointerException("name is null");
} else if (oldName.isEmpty() || newName.isEmpty()) {
throw new NameAlreadyBoundException("Name cannot be empty");
}
this.bind(newName, this.lookup(oldName));
this.unbind(oldName);
}
public void unbind(String name) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (name == null) throw new NullPointerException("name is null");
unbind(new CompositeName(name));
}
public void unbind(Name name) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (name == null) throw new NullPointerException("name is null");
if (name.isEmpty()) {
throw new InvalidNameException("Cannot unbind empty name");
}
removeDeepBinding(name, false);
}
//
// List
//
protected NamingEnumeration list() throws NamingException {
Map bindings = getBindings();
return new ContextUtil.ListEnumeration(bindings);
}
protected NamingEnumeration listBindings() throws NamingException {
Map bindings = getBindings();
return new ContextUtil.ListBindingEnumeration(bindings);
}
public NamingEnumeration list(String name) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
// if the name is empty, list the current context
if (name.length() == 0) {
return list();
}
// lookup the target context
Object target = null;
try {
target = lookup(name);
} catch (NamingException e) {
throw new NotContextException(name);
}
if (target == this) {
return list();
} else if (target instanceof Context) {
return ((Context) target).list("");
} else {
throw new NotContextException("The name " + name + " cannot be listed");
}
}
public NamingEnumeration list(Name name) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
// if the name is empty, list the current context
if (name.isEmpty()) {
return list();
}
// lookup the target context
Object target = null;
try {
target = lookup(name);
} catch (NamingException e) {
throw new NotContextException(name.toString());
}
if (target == this) {
return list();
} else if (target instanceof Context) {
return ((Context) target).list("");
} else {
throw new NotContextException("The name " + name + " cannot be listed");
}
}
public NamingEnumeration listBindings(String name) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
// if the name is empty, list the current context
if (name.length() == 0) {
return listBindings();
}
// lookup the target context
Object target = null;
try {
target = lookup(name);
} catch (NamingException e) {
throw new NotContextException(name.toString());
}
if (target == this) {
return listBindings();
} else if (target instanceof Context) {
return ((Context) target).listBindings("");
} else {
throw new NotContextException("The name " + name + " cannot be listed");
}
}
public NamingEnumeration listBindings(Name name) throws NamingException {
if (name == null) throw new NullPointerException("name is null");
// if the name is empty, list the current context
if (name.isEmpty()) {
return listBindings();
}
// lookup the target context
Object target = null;
try {
target = lookup(name);
} catch (NamingException e) {
throw new NotContextException(name.toString());
}
if (target == this) {
return listBindings();
} else if (target instanceof Context) {
return ((Context) target).listBindings("");
} else {
throw new NotContextException("The name " + name + " cannot be listed");
}
}
//
// Subcontexts
//
public Context createSubcontext(String name) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (name == null) throw new NullPointerException("name is null");
return createSubcontext(new CompositeName(name));
}
public Context createSubcontext(Name name) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (name == null) throw new NullPointerException("name is null");
if (name.isEmpty()) {
throw new NameAlreadyBoundException("Cannot create a subcontext if the name is empty");
}
Context abstractContext = createNestedSubcontext(name.toString(), Collections.EMPTY_MAP);
addDeepBinding(name, abstractContext, false, false);
return abstractContext;
}
public void destroySubcontext(String name) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (name == null) throw new NullPointerException("name is null");
destroySubcontext(new CompositeName(name));
}
public void destroySubcontext(Name name) throws NamingException {
if (!modifiable) throw new OperationNotSupportedException("Context is read only");
if (name == null) throw new NullPointerException("name is null");
if (name.isEmpty()) {
throw new InvalidNameException("Cannot destroy subcontext with empty name");
}
unbind(name);
}
}