blob: 61c9be5f369c97ae9453747b73f37c48e2eeff7a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.geode.internal.jndi;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Vector;
import javax.naming.Binding;
import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.ContextNotEmptyException;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameNotFoundException;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.NoPermissionException;
import javax.naming.NotContextException;
import javax.transaction.SystemException;
import org.apache.geode.LogWriter;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.internal.jta.TransactionUtils;
import org.apache.geode.internal.jta.UserTransactionImpl;
/**
* Provides implementation of javax.naming.Context interface. A name in the ContextImpl 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 CompoundName 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. This rule can be
* altered/modified by making changes in NameParserImpl class.
*
*/
public class ContextImpl implements Context {
private static final String ROOT_CONTEXT_NAME = "ROOT";
// Naming scheme for the ContextImpl.
@Immutable
private static final NameParser nameParser = new NameParserImpl();
/*
* Map of objects registered for this context representing the local context
*/
private final Map ctxMaps = Collections.synchronizedMap(new HashMap());
// Name of this Context
private String ctxName;
// Parent Context of this Context
private ContextImpl parentCtx;
// Shows if this context has been destroyed
private boolean isDestroyed;
/*
* Creates new instance of ContextImpl. @param parentCtx parent context of this context. null if
* this is the root context. @param name atomic name for this context
*/
private ContextImpl(ContextImpl parentCtx, String name) {
this.parentCtx = parentCtx;
this.ctxName = name;
this.isDestroyed = false;
}
/**
* Default constructor
*/
public ContextImpl() {
// call the constructor with default setting
this(null, ROOT_CONTEXT_NAME);
}
/**
* Not implemented
*
*/
@Override
public Object addToEnvironment(String key, Object value) throws NamingException {
throw new NamingException(
"addToEnvironment(String key, Object value) is not implemented");
}
/**
* Binds object to a name 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 null.
* @throws NoPermissionException if this context has been destroyed.
* @throws InvalidNameException if name is empty or is CompositeName that spans more than one
* naming system.
* @throws NotContextException if name has more than one atomic name and intermediate atomic name
* is bound to object that is not context.
*/
@Override
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 to name in this context.
*
* @param name * name of the object to add
* @param obj object to bind
* @throws NamingException if naming error occurs
*
*/
@Override
public void bind(String name, Object obj) throws NamingException {
bind(nameParser.parse(name), obj);
}
/**
* Does nothing.
*/
@Override
public void close() throws NamingException {}
/**
* Returns composition of prefix and name .
*
* @param name name relative to this context
* @param prefix name of this context Example: composeName("a","b") : b/a composeName("a","") : a
*
*/
@Override
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);
}
/**
* Returns composition of prefix and name .
*
* @param name name relative to this context
* @param prefix name of this context Example: composeName("a","b") : b/a composeName("a","") : a
*
*/
@Override
public String composeName(String name, String prefix) throws NamingException {
checkIsDestroyed();
return composeName(nameParser.parse(name), nameParser.parse(prefix)).toString();
}
/**
* Creates subcontext with name, relative to this Context.
*
* @param name subcontext name.
* @return new subcontext named name relative to this context.
* @throws NoPermissionException if this context has been destroyed.
* @throws InvalidNameException if name is empty or is CompositeName that spans more than one
* naming system.
* @throws NameAlreadyBoundException if name is already bound in this Context
* @throws NotContextException if any intermediate name from name is not bound to instance of
* javax.naming.Context.
*
*/
@Override
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 = ctxMaps.get(parsedName.get(0));
if (parsedName.size() == 1) {
// Check if name is already in use
if (boundObject == null) {
Context subContext = new ContextImpl(this, subContextName);
ctxMaps.put(subContextName, subContext);
return subContext;
} else {
throw new NameAlreadyBoundException(
String.format("Name %s is already bound!", subContextName));
}
} else {
if (boundObject instanceof Context) {
// Let the subcontext create new subcontext
// lets consider a scenerio a/b/c
// case a/b exists : c will be created
// case a exists : b/c will not be created
// an exception will be thrown in that case.
return ((Context) boundObject).createSubcontext(parsedName.getSuffix(1));
} else {
throw new NotContextException(String.format("Expected Context but found %s",
boundObject));
}
}
}
/**
* Creates subcontext with name, relative to this Context.
*
* @param name subcontext name
* @return new subcontext named name relative to this context.
* @throws NamingException if naming error occurs.
*
*/
@Override
public Context createSubcontext(String name) throws NamingException {
return createSubcontext(nameParser.parse(name));
}
/**
* Destroys subcontext with name name. The subcontext must be empty otherwise
* ContextNotEmptyException is thrown. 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 name is empty or is CompositeName that spans more than one
* naming system.
* @throws ContextNotEmptyException if Context name is not empty.
* @throws NameNotFoundException if subcontext with name name can not be found.
* @throws NotContextException if name is not bound to instance of ContextImpl.
*
*/
@Override
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 = ctxMaps.get(subContextName);
if (boundObject == null) {
throw new NameNotFoundException(String.format("Name %s not found in the context!",
subContextName));
}
if (!(boundObject instanceof ContextImpl)) {
throw new NotContextException();
}
ContextImpl contextToDestroy = (ContextImpl) boundObject;
if (parsedName.size() == 1) {
// Check if the Context to be destroyed is empty. Can not destroy
// non-empty Context.
if (contextToDestroy.ctxMaps.size() == 0) {
ctxMaps.remove(subContextName);
contextToDestroy.destroyInternal();
} else {
throw new ContextNotEmptyException(
"Can not destroy non-empty Context!");
}
} else {
// Let the subcontext destroy the context
((ContextImpl) boundObject).destroySubcontext(parsedName.getSuffix(1));
}
}
/**
* Destroys subcontext with name name.
*
* @param name name of subcontext to destroy.
* @throws NamingException if naming error occurs
*/
@Override
public void destroySubcontext(String name) throws NamingException {
destroySubcontext(nameParser.parse(name));
}
/**
* Not implemented
*
*/
@Override
public Hashtable getEnvironment() throws NamingException {
throw new NamingException(
"getEnvironment() is not implemented");
}
/**
* Not implemented
*
*/
@Override
public String getNameInNamespace() throws NamingException {
throw new NamingException(
"getNameInNamespace() is not implemented");
}
/**
* Retrieves NameParser used to parse context with name name.
*
* @param name context name.
* @return NameParser.
* @throws NoPermissionException if this context has been destroyed.
* @throws NamingException if any other naming error occurs.
*
*/
@Override
public NameParser getNameParser(Name name) throws NamingException {
checkIsDestroyed();
return nameParser;
}
/**
* Retrieves NameParser used to parse context with name name.
*
* @param name context name.
* @return NameParser.
* @throws NoPermissionException if this context has been destroyed.
* @throws NamingException if any other naming error occurs.
*
*/
@Override
public NameParser getNameParser(String name) throws NamingException {
checkIsDestroyed();
return nameParser;
}
/**
* The same as listBindings(String).
*
* @param name name of Context, relative to this Context
* @return NamingEnumeration of all name-class pairs. Each element from the enumeration is
* instance of NameClassPair
* @throws NamingException if naming error occurs
*
*/
@Override
public NamingEnumeration list(Name name) throws NamingException {
return listBindings(name);
}
/**
* The same as listBindings(String).
*
* @param name name of Context, relative to this Context
* @return NamingEnumeration of all name-class pairs. Each element from the enumeration is
* instance of NameClassPair
* @throws NamingException if naming error occurs
*
*/
@Override
public NamingEnumeration list(String name) throws NamingException {
return list(nameParser.parse(name));
}
/**
* Lists all bindings for Context with name name. If name is empty then this Context is assumed.
*
* @param name name of Context, relative to this Context
* @return NamingEnumeration of all name-object pairs. Each element from the enumeration is
* instance of Binding.
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if name is CompositeName that spans more than one naming system
* @throws NameNotFoundException if name can not be found
* @throws NotContextException component of name is not bound to instance of ContextImpl, when
* name is not an atomic name
* @throws NamingException if any other naming error occurs
*
*/
@Override
public NamingEnumeration listBindings(Name name) throws NamingException {
checkIsDestroyed();
Name parsedName = getParsedName(name);
if (parsedName.size() == 0) {
Vector bindings = new Vector();
Iterator iterat = ctxMaps.keySet().iterator();
while (iterat.hasNext()) {
String bindingName = (String) iterat.next();
bindings.addElement(new Binding(bindingName, ctxMaps.get(bindingName)));
}
return new NamingEnumerationImpl(bindings);
} else {
Object subContext = ctxMaps.get(parsedName.get(0));
if (subContext instanceof Context) {
Name nextLayer = nameParser.parse("");
// getSuffix(1) only apply to name with size() > 1
if (parsedName.size() > 1) {
nextLayer = parsedName.getSuffix(1);
}
return ((Context) subContext).list(nextLayer);
}
if (subContext == null && !ctxMaps.containsKey(parsedName.get(0))) {
throw new NameNotFoundException(
String.format("Name %s not found", name));
} else {
throw new NotContextException(String.format("Expected Context but found %s",
subContext));
}
}
}
/**
* Lists all bindings for Context with name name. If name is empty then this Context is assumed.
*
* @param name name of Context, relative to this Context
* @return NamingEnumeration of all name-object pairs. Each element from the enumeration is
* instance of Binding.
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if name is CompositeName that spans more than one naming system
* @throws NameNotFoundException if name can not be found
* @throws NotContextException component of name is not bound to instance of ContextImpl, when
* name is not an atomic name
* @throws NamingException if any other naming error occurs
*
*/
@Override
public NamingEnumeration listBindings(String name) throws NamingException {
return listBindings(nameParser.parse(name));
}
/**
* Looks up object with binding name name in this context.
*
* @param name name to look up
* @return object reference bound to name name.
* @throws NoPermissionException if this context has been destroyed.
* @throws InvalidNameException if name is CompositeName that spans more than one naming system
* @throws NameNotFoundException if name can not be found.
* @throws NotContextException component of name is not bound to instance of ContextImpl, when
* name is not atomic name.
* @throws NamingException if any other naming error occurs
*
*/
@Override
public Object lookup(Name name) throws NamingException {
checkIsDestroyed();
try {
Name parsedName = getParsedName(name);
String nameComponent = parsedName.get(0);
Object res = ctxMaps.get(nameComponent);
if (res instanceof UserTransactionImpl) {
res = new UserTransactionImpl();
}
// if not found
if (!ctxMaps.containsKey(nameComponent)) {
throw new NameNotFoundException(
String.format("Name %s not found", name));
}
// if this is a compound name
else if (parsedName.size() > 1) {
if (res instanceof ContextImpl) {
res = ((ContextImpl) res).lookup(parsedName.getSuffix(1));
} else {
throw new NotContextException(
String.format("Expected ContextImpl but found %s", res));
}
}
return res;
} catch (NameNotFoundException e) {
LogWriter writer = TransactionUtils.getLogWriter();
if (writer.infoEnabled())
writer.info(String.format("ContextImpl::lookup::Error while looking up %s", name),
e);
throw new NameNotFoundException(
String.format("Name %s not found", name));
} catch (SystemException se) {
LogWriter writer = TransactionUtils.getLogWriter();
if (writer.severeEnabled())
writer.info(
"ContextImpl::lookup::Error while creating UserTransaction object",
se);
throw new NameNotFoundException(
"ContextImpl::lookup::Error while creating UserTransaction object");
}
}
/**
* 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 name.
* @throws NamingException if naming error occurs.
*
*/
@Override
public Object lookup(String name) throws NamingException {
checkIsDestroyed();
try {
return lookup(nameParser.parse(name));
} catch (NameNotFoundException e) {
LogWriter writer = TransactionUtils.getLogWriter();
if (writer.infoEnabled())
writer.info(String.format("ContextImpl::lookup::Error while looking up %s", name),
e);
throw new NameNotFoundException(
String.format("Name %s not found", new Object[] {name}));
}
}
/**
* Not implemented
*
*/
@Override
public Object lookupLink(Name name) throws NamingException {
throw new NamingException(
"lookupLink(Name name) is not implemented");
}
/**
* Not implemented
*
*/
@Override
public Object lookupLink(String name) throws NamingException {
throw new NamingException(
"lookupLink(String name) is not implemented");
}
/**
* Rebinds object obj to name name . If there is existing binding it will be overwritten.
*
* @param name name of the object to rebind.
* @param obj object to add. Can be null.
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if name is empty or is CompositeName that spans more than one
* naming system
* @throws NotContextException if name has more than one atomic name and intermediate context is
* not found
* @throws NamingException if any other naming error occurs
*
*/
@Override
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) {
ctxMaps.put(nameToBind, obj);
} else {
Object boundObject = ctxMaps.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(String.format("Expected Context but found %s",
boundObject));
}
}
}
}
/**
* Rebinds object obj to String name. If there is existing binding it will be overwritten.
*
* @param name name of the object to rebind.
* @param obj object to add. Can be null.
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if name is empty or is CompositeName that spans more than one
* naming system
* @throws NotContextException if name has more than one atomic name and intermediate context is
* not found
* @throws NamingException if any other naming error occurs
*
*/
@Override
public void rebind(String name, Object obj) throws NamingException {
rebind(nameParser.parse(name), obj);
}
/**
* Not implemented
*
*/
@Override
public Object removeFromEnvironment(String key) throws NamingException {
throw new NamingException(
"removeFromEnvironment(String key) is not implemented");
}
/**
* Not implemented
*
*/
@Override
public void rename(Name name1, Name name2) throws NamingException {
throw new NamingException(
"rename(Name name1, Name name2) is not implemented");
}
/**
* Not implemented
*
*/
@Override
public void rename(String name1, String name2) throws NamingException {
throw new NamingException(
"rename(String name1, String name2) is not implemented");
}
/**
* Removes name and its associated object from the context.
*
* @param name name to remove
* @throws NoPermissionException if this context has been destroyed.
* @throws InvalidNameException if name is empty or is CompositeName that spans more than one
* naming system
* @throws NameNotFoundException if intermediate context can not be found
* @throws NotContextException if name has more than one atomic name and intermediate context is
* not found.
* @throws NamingException if any other naming exception occurs
*
*/
@Override
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);
// scenerio unbind a
// remove a and its associated object
if (parsedName.size() == 1) {
ctxMaps.remove(nameToRemove);
} else {
// scenerio unbind a/b or a/b/c
// remove b and its associated object
Object boundObject = ctxMaps.get(nameToRemove);
if (boundObject instanceof Context) {
// remove b and its associated object
((Context) boundObject).unbind(parsedName.getSuffix(1));
} else {
// if the name is not found then throw exception
if (!ctxMaps.containsKey(nameToRemove)) {
throw new NameNotFoundException(
String.format("Can not find %s", name));
}
throw new NotContextException(String.format("Expected Context but found %s",
boundObject));
}
}
}
/**
*
* Removes name and its associated object from the context.
*
* @param name name to remove
* @throws NoPermissionException if this context has been destroyed.
* @throws InvalidNameException if name is empty or is CompositeName that spans more than one
* naming system
* @throws NameNotFoundException if intermediate context can not be found
* @throws NotContextException if name has more than one atomic name and intermediate context is
* not found.
* @throws NamingException if any other naming exception occurs
*
*/
@Override
public void unbind(String name) throws NamingException {
unbind(nameParser.parse(name));
}
/**
* Checks if this context has been destroyed. isDestroyed is set to true when a context is
* destroyed by calling destroySubcontext 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 destroySubcontext.
*/
private void destroyInternal() {
isDestroyed = true;
}
/**
* Parses name which is CompositeName or CompoundName . If name is not CompositeName then it is
* assumed to be CompoundName. If the name contains leading and/or terminal empty components, they
* will not be included in the result.
*
* @param name Name to parse.
* @return parsed name as instance of CompoundName.
* @throws InvalidNameException if name is CompositeName 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 = "";
ContextImpl curCtx = this;
while (!curCtx.isRootContext()) {
compositeName = composeName(compositeName, curCtx.getAtomicName());
curCtx = curCtx.getParentContext();
}
return compositeName;
}
/*
* Returns parent context of this context
*/
ContextImpl getParentContext() {
return parentCtx;
}
/*
* Returns the "atomic" (as opposed to "composite") name of the context.
*
* @return name of the context
*/
String getAtomicName() {
return ctxName;
}
/*
* Returns true if this context is the root context @return true if the context is the root
* context
*/
boolean isRootContext() {
return getParentContext() == null;
}
private static class NamingEnumerationImpl implements NamingEnumeration {
private Vector elements;
private int currentElement;
NamingEnumerationImpl(Vector elements) {
this.elements = elements;
this.currentElement = 0;
}
@Override
public void close() {
currentElement = 0;
elements.clear();
}
@Override
public boolean hasMore() {
return hasMoreElements();
}
@Override
public boolean hasMoreElements() {
if (currentElement < elements.size()) {
return true;
}
close();
return false;
}
@Override
public Object next() {
return nextElement();
}
@Override
public Object nextElement() {
if (hasMoreElements()) {
return elements.get(currentElement++);
}
throw new NoSuchElementException();
}
}
}