org.eclipse.jetty.jndi.NamingContext Maven / Gradle / Ivy
Show all versions of jetty-jndi Show documentation
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.jndi;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.naming.Binding;
import javax.naming.CompoundName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.LinkRef;
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.NotContextException;
import javax.naming.OperationNotSupportedException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.spi.NamingManager;
import org.eclipse.jetty.util.component.Dumpable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* NamingContext
*
* Implementation of Context interface.
*
* Notes:
* All Names are expected to be Compound, not Composite.
*/
@SuppressWarnings("unchecked")
public class NamingContext implements Context, Dumpable
{
private static final Logger LOG = LoggerFactory.getLogger(NamingContext.class);
private static final List __empty = Collections.emptyList();
public static final String DEEP_BINDING = "org.eclipse.jetty.jndi.deepBinding";
public static final String LOCK_PROPERTY = "org.eclipse.jetty.jndi.lock";
public static final String UNLOCK_PROPERTY = "org.eclipse.jetty.jndi.unlock";
/*
* The env is stored as a Hashtable to be compatible with the API.
* There is no need for concurrent protection (see Concurrent Access section
* of the {@link Context} javadoc), so multiple threads acting on the same
* Context env need to self - mutually exclude.
*/
protected final Hashtable _env = new Hashtable<>();
/*
* This contexts bindings are stored as a ConcurrentHashMap.
* There is generally no need for concurrent protection (see Concurrent Access section
* of the {@link Context} javadoc), However, the NamingContext is used for root contexts and
* we do not mutually exclude when initializing, so instead we do make the bindings
* thread safe (unlike the env where we expect users to respect the Concurrent Access requirements).
*/
protected final ConcurrentMap _bindings;
protected NamingContext _parent = null;
protected String _name = null;
protected NameParser _parser = null;
private Collection _listeners;
private Object _lock;
/**
* Naming Context Listener.
*/
public interface Listener
{
/**
* Called by {@link NamingContext#addBinding(Name, Object)} when adding
* a binding.
*
* @param ctx The context to add to.
* @param binding The binding to add.
* @return The binding to bind, or null if the binding should be ignored.
*/
Binding bind(NamingContext ctx, Binding binding);
/**
* @param ctx The context to unbind from
* @param binding The binding that was unbound.
*/
void unbind(NamingContext ctx, Binding binding);
}
/**
* Constructor
*
* @param env environment properties which are copied into this Context's environment
* @param name relative name of this context
* @param parent immediate ancestor Context (can be null)
* @param parser NameParser for this Context
*/
public NamingContext(Hashtable env,
String name,
NamingContext parent,
NameParser parser)
{
this(env, name, parent, parser, null);
}
protected NamingContext(Hashtable env,
String name,
NamingContext parent,
NameParser parser,
ConcurrentMap bindings)
{
if (env != null)
_env.putAll(env);
_name = name;
_parent = parent;
_parser = parser;
_bindings = bindings == null ? new ConcurrentHashMap<>() : bindings;
if (LOG.isDebugEnabled())
LOG.debug("new {}", this);
}
/**
* @return A shallow copy of the Context with the same bindings, but with the passed environment
*/
public Context shallowCopy(Hashtable env)
{
return new NamingContext(env, _name, _parent, _parser, _bindings);
}
public boolean isDeepBindingSupported()
{
// look for deep binding support in _env
Object support = _env.get(DEEP_BINDING);
if (support != null)
return Boolean.parseBoolean(String.valueOf(support));
if (_parent != null)
return _parent.isDeepBindingSupported();
// probably a root context
return Boolean.parseBoolean(System.getProperty(DEEP_BINDING, "false"));
}
/**
* Getter for _name
*
* @return name of this Context (relative, not absolute)
*/
public String getName()
{
return _name;
}
/**
* Getter for _parent
*
* @return parent Context
*/
public Context getParent()
{
return _parent;
}
public void setNameParser(NameParser parser)
{
_parser = parser;
}
public final void setEnv(Hashtable env)
{
Object lock = _env.get(LOCK_PROPERTY);
try
{
_env.clear();
if (env == null)
return;
_env.putAll(env);
}
finally
{
if (lock != null)
_env.put(LOCK_PROPERTY, lock);
}
}
private Object dereference(Object ctx, String firstComponent) throws NamingException
{
if (ctx instanceof Reference)
{
//deference the object
try
{
return NamingManager.getObjectInstance(ctx, _parser.parse(firstComponent), this, _env);
}
catch (NamingException e)
{
throw e;
}
catch (Exception e)
{
LOG.warn("", e);
throw new NamingException(e.getMessage());
}
}
return ctx;
}
private Context getContext(Name cname) throws NamingException
{
String firstComponent = cname.get(0);
if (firstComponent.equals(""))
return this;
Binding binding = getBinding(firstComponent);
if (binding == null)
{
NameNotFoundException nnfe = new NameNotFoundException(firstComponent + " is not bound");
nnfe.setRemainingName(cname);
throw nnfe;
}
Object ctx = dereference(binding.getObject(), firstComponent);
if (!(ctx instanceof Context))
throw new NotContextException(firstComponent + " not a context in " + this.getNameInNamespace());
return (Context)ctx;
}
/**
* Bind a name to an object
*
* @param name Name of the object
* @param obj object to bind
* @throws NamingException if an error occurs
*/
@Override
public void bind(Name name, Object obj)
throws NamingException
{
if (isLocked())
throw new NamingException("This context is immutable");
Name cname = toCanonicalName(name);
if (cname == null)
throw new NamingException("Name is null");
if (cname.size() == 0)
throw new NamingException("Name is empty");
//if no subcontexts, just bind it
if (cname.size() == 1)
{
//get the object to be bound
Object objToBind = NamingManager.getStateToBind(obj, name, this, _env);
// Check for Referenceable
if (objToBind instanceof Referenceable)
{
objToBind = ((Referenceable)objToBind).getReference();
}
//anything else we should be able to bind directly
addBinding(cname, objToBind);
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Checking for existing binding for name={} for first element of name={}", cname, cname.get(0));
//walk down the subcontext hierarchy
//need to ignore trailing empty "" name components
String firstComponent = cname.get(0);
Object ctx = null;
if (firstComponent.equals(""))
ctx = this;
else
{
Binding binding = getBinding(firstComponent);
if (binding == null)
{
if (isDeepBindingSupported())
{
Name subname = _parser.parse(firstComponent);
Context subctx = new NamingContext(_env, firstComponent, this, _parser, null);
addBinding(subname, subctx);
binding = getBinding(subname);
}
else
{
throw new NameNotFoundException(firstComponent + " is not bound");
}
}
ctx = dereference(binding.getObject(), firstComponent);
}
if (ctx instanceof Context)
{
((Context)ctx).bind(cname.getSuffix(1), obj);
}
else
throw new NotContextException("Object bound at " + firstComponent + " is not a Context");
}
}
/**
* Bind a name (as a String) to an object
*
* @param name a String
value
* @param obj an Object
value
* @throws NamingException if an error occurs
*/
@Override
public void bind(String name, Object obj)
throws NamingException
{
bind(_parser.parse(name), obj);
}
/**
* Create a context as a child of this one
*
* @param name a Name
value
* @return a Context
value
* @throws NamingException if an error occurs
*/
@Override
public Context createSubcontext(Name name)
throws NamingException
{
if (isLocked())
{
NamingException ne = new NamingException("This context is immutable");
ne.setRemainingName(name);
throw ne;
}
Name cname = toCanonicalName(name);
if (cname == null)
throw new NamingException("Name is null");
if (cname.size() == 0)
throw new NamingException("Name is empty");
if (cname.size() == 1)
{
//not permitted to bind if something already bound at that name
Binding binding = getBinding(cname);
if (binding != null)
throw new NameAlreadyBoundException(cname.toString());
Context ctx = new NamingContext(_env, cname.get(0), this, _parser);
addBinding(cname, ctx);
return ctx;
}
return getContext(cname).createSubcontext(cname.getSuffix(1));
}
/**
* Create a Context as a child of this one
*
* @param name a String
value
* @return a Context
value
* @throws NamingException if an error occurs
*/
@Override
public Context createSubcontext(String name)
throws NamingException
{
return createSubcontext(_parser.parse(name));
}
/**
* @param name name of subcontext to remove
* @throws NamingException if an error occurs
*/
@Override
public void destroySubcontext(String name)
throws NamingException
{
removeBinding(_parser.parse(name));
}
/**
* @param name name of subcontext to remove
* @throws NamingException if an error occurs
*/
@Override
public void destroySubcontext(Name name)
throws NamingException
{
removeBinding(name);
}
/**
* Lookup a binding by name
*
* @param name name of bound object
* @throws NamingException if an error occurs
*/
@Override
public Object lookup(Name name)
throws NamingException
{
if (LOG.isDebugEnabled())
LOG.debug("Looking up name=\"{}\"", name);
Name cname = toCanonicalName(name);
if ((cname == null) || (cname.size() == 0))
{
if (LOG.isDebugEnabled())
LOG.debug("Null or empty name, returning shallowCopy of this context");
return shallowCopy(_env);
}
if (cname.size() == 1)
{
Binding binding = getBinding(cname);
if (binding == null)
{
NameNotFoundException nnfe = new NameNotFoundException();
nnfe.setRemainingName(cname);
throw nnfe;
}
Object o = binding.getObject();
//handle links by looking up the link
if (o instanceof LinkRef)
{
//if link name starts with ./ it is relative to current context
String linkName = ((LinkRef)o).getLinkName();
if (linkName.startsWith("./"))
return this.lookup(linkName.substring(2));
else
{
//link name is absolute
InitialContext ictx = new InitialContext();
return ictx.lookup(linkName);
}
}
else if (o instanceof Reference)
{
// TODO use deference ??
try
{
return NamingManager.getObjectInstance(o, cname, this, _env);
}
catch (NamingException e)
{
throw e;
}
catch (Exception e)
{
LOG.warn("", e);
throw new NamingException(e.getMessage());
}
}
else
return o;
}
return getContext(cname).lookup(cname.getSuffix(1));
}
/**
* Lookup binding of an object by name
*
* @param name name of bound object
* @return object bound to name
* @throws NamingException if an error occurs
*/
@Override
public Object lookup(String name)
throws NamingException
{
return lookup(_parser.parse(name));
}
/**
* Lookup link bound to name
*
* @param name name of link binding
* @return LinkRef or plain object bound at name
* @throws NamingException if an error occurs
*/
@Override
public Object lookupLink(Name name)
throws NamingException
{
Name cname = toCanonicalName(name);
if (cname == null || name.isEmpty())
{
return shallowCopy(_env);
}
if (cname.size() == 0)
throw new NamingException("Name is empty");
if (cname.size() == 1)
{
Binding binding = getBinding(cname);
if (binding == null)
throw new NameNotFoundException();
return dereference(binding.getObject(), cname.getPrefix(1).toString());
}
return getContext(cname).lookup(cname.getSuffix(1));
}
/**
* Lookup link bound to name
*
* @param name name of link binding
* @return LinkRef or plain object bound at name
* @throws NamingException if an error occurs
*/
@Override
public Object lookupLink(String name)
throws NamingException
{
return lookupLink(_parser.parse(name));
}
/**
* List all names bound at Context named by Name
*
* @param name a Name
value
* @return a NamingEnumeration
value
* @throws NamingException if an error occurs
*/
@Override
public NamingEnumeration list(Name name)
throws NamingException
{
if (LOG.isDebugEnabled())
LOG.debug("list() on Context={} for name={}", getName(), name);
Name cname = toCanonicalName(name);
if (cname == null)
{
return new NameEnumeration(__empty.iterator());
}
if (cname.size() == 0)
{
return new NameEnumeration(_bindings.values().iterator());
}
//multipart name
return getContext(cname).list(cname.getSuffix(1));
}
/**
* List all names bound at Context named by Name
*
* @param name a Name
value
* @return a NamingEnumeration
value
* @throws NamingException if an error occurs
*/
@Override
public NamingEnumeration list(String name)
throws NamingException
{
return list(_parser.parse(name));
}
/**
* List all Bindings present at Context named by Name
*
* @param name a Name
value
* @return a NamingEnumeration
value
* @throws NamingException if an error occurs
*/
@Override
public NamingEnumeration listBindings(Name name)
throws NamingException
{
Name cname = toCanonicalName(name);
if (cname == null)
{
return new BindingEnumeration(__empty.iterator());
}
if (cname.size() == 0)
{
return new BindingEnumeration(_bindings.values().iterator());
}
return getContext(cname).listBindings(cname.getSuffix(1));
}
/**
* List all Bindings at Name
*
* @param name a String
value
* @return a NamingEnumeration
value
* @throws NamingException if an error occurs
*/
@Override
public NamingEnumeration listBindings(String name)
throws NamingException
{
return listBindings(_parser.parse(name));
}
/**
* Overwrite or create a binding
*
* @param name a Name
value
* @param obj an Object
value
* @throws NamingException if an error occurs
*/
@Override
public void rebind(Name name,
Object obj)
throws NamingException
{
if (isLocked())
throw new NamingException("This context is immutable");
Name cname = toCanonicalName(name);
if (cname == null)
throw new NamingException("Name is null");
if (cname.size() == 0)
throw new NamingException("Name is empty");
//if no subcontexts, just bind it
if (cname.size() == 1)
{
//check if it is a Referenceable
Object objToBind = NamingManager.getStateToBind(obj, name, this, _env);
if (objToBind instanceof Referenceable)
{
objToBind = ((Referenceable)objToBind).getReference();
}
removeBinding(cname);
addBinding(cname, objToBind);
}
else
{
getContext(cname).rebind(cname.getSuffix(1), obj);
}
}
/**
* Overwrite or create a binding from Name to Object
*
* @param name a String
value
* @param obj an Object
value
* @throws NamingException if an error occurs
*/
@Override
public void rebind(String name,
Object obj)
throws NamingException
{
rebind(_parser.parse(name), obj);
}
/**
* Not supported.
*
* @param name a String
value
* @throws NamingException if an error occurs
*/
@Override
public void unbind(String name)
throws NamingException
{
unbind(_parser.parse(name));
}
/**
* Not supported.
*
* @param name a String
value
* @throws NamingException if an error occurs
*/
@Override
public void unbind(Name name)
throws NamingException
{
if (name.size() == 0)
return;
if (isLocked())
throw new NamingException("This context is immutable");
Name cname = toCanonicalName(name);
if (cname == null)
throw new NamingException("Name is null");
if (cname.size() == 0)
throw new NamingException("Name is empty");
//if no subcontexts, just unbind it
if (cname.size() == 1)
{
removeBinding(cname);
}
else
{
getContext(cname).unbind(cname.getSuffix(1));
}
}
/**
* Not supported
*
* @param oldName a Name
value
* @param newName a Name
value
* @throws NamingException if an error occurs
*/
@Override
public void rename(Name oldName,
Name newName)
throws NamingException
{
throw new OperationNotSupportedException();
}
/**
* Not supported
*
* @param oldName a Name
value
* @param newName a Name
value
* @throws NamingException if an error occurs
*/
@Override
public void rename(String oldName,
String newName)
throws NamingException
{
throw new OperationNotSupportedException();
}
/**
* Join two names together. These are treated as
* CompoundNames.
*
* @param name a Name
value
* @param prefix a Name
value
* @return a Name
value
* @throws NamingException if an error occurs
*/
@Override
public Name composeName(Name name,
Name prefix)
throws NamingException
{
if (name == null)
throw new NamingException("Name cannot be null");
if (prefix == null)
throw new NamingException("Prefix cannot be null");
Name compoundName = (CompoundName)prefix.clone();
compoundName.addAll(name);
return compoundName;
}
/**
* Join two names together. These are treated as
* CompoundNames.
*
* @param name a Name
value
* @param prefix a Name
value
* @return a Name
value
* @throws NamingException if an error occurs
*/
@Override
public String composeName(String name,
String prefix)
throws NamingException
{
if (name == null)
throw new NamingException("Name cannot be null");
if (prefix == null)
throw new NamingException("Prefix cannot be null");
Name compoundName = _parser.parse(prefix);
compoundName.add(name);
return compoundName.toString();
}
/**
* Do nothing
*
* @throws NamingException if an error occurs
*/
@Override
public void close()
throws NamingException
{
}
/**
* Return a NameParser for this Context.
*
* @param name a Name
value
* @return a NameParser
value
*/
@Override
public NameParser getNameParser(Name name)
{
return _parser;
}
/**
* Return a NameParser for this Context.
*
* @param name a Name
value
* @return a NameParser
value
*/
@Override
public NameParser getNameParser(String name)
{
return _parser;
}
/**
* Get the full name of this Context node
* by visiting it's ancestors back to root.
*
* NOTE: if this Context has a URL namespace then
* the URL prefix will be missing
*
* @return the full name of this Context
* @throws NamingException if an error occurs
*/
@Override
public String getNameInNamespace()
throws NamingException
{
Name name = _parser.parse("");
NamingContext c = this;
while (c != null)
{
String str = c.getName();
if (str != null)
name.add(0, str);
c = (NamingContext)c.getParent();
}
return name.toString();
}
/**
* Add an environment setting to this Context
*
* @param propName name of the property to add
* @param propVal value of the property to add
* @return propVal or previous value of the property
* @throws NamingException if an error occurs
*/
@Override
public Object addToEnvironment(String propName,
Object propVal)
throws NamingException
{
switch (propName)
{
case LOCK_PROPERTY:
if (_lock == null)
_lock = propVal;
return null;
case UNLOCK_PROPERTY:
if (propVal != null && propVal.equals(_lock))
_lock = null;
return null;
default:
if (isLocked())
throw new NamingException("This context is immutable");
return _env.put(propName, propVal);
}
}
/**
* Remove a property from this Context's environment.
*
* @param propName name of property to remove
* @return value of property or null if it didn't exist
* @throws NamingException if an error occurs
*/
@Override
public Object removeFromEnvironment(String propName)
throws NamingException
{
if (isLocked())
throw new NamingException("This context is immutable");
return _env.remove(propName);
}
/**
* Get the environment of this Context.
*
* @return a copy of the environment of this Context.
*/
@Override
public Hashtable getEnvironment()
{
return _env;
}
/**
* Add a name to object binding to this Context.
*
* @param name a Name
value
* @param obj an Object
value
* @throws NameAlreadyBoundException if name already bound
*/
public void addBinding(Name name, Object obj) throws NameAlreadyBoundException
{
String key = name.toString();
Binding binding = new Binding(key, obj);
Collection list = findListeners();
for (Listener listener : list)
{
binding = listener.bind(this, binding);
if (binding == null)
break;
}
if (LOG.isDebugEnabled())
LOG.debug("Adding binding with key={} obj={} for context={} as {}", key, obj, _name, binding);
if (binding != null)
{
if (_bindings.putIfAbsent(key, binding) != null)
{
if (isDeepBindingSupported())
{
// quietly return (no exception)
// this is jndi spec breaking, but is added to support broken
// jndi users like openejb.
return;
}
throw new NameAlreadyBoundException(name.toString());
}
}
}
/**
* Get a name to object binding from this Context
*
* @param name a Name
value
* @return a Binding
value
*/
public Binding getBinding(Name name)
{
return _bindings.get(name.toString());
}
/**
* Get a name to object binding from this Context
*
* @param name as a String
* @return null or the Binding
*/
public Binding getBinding(String name)
{
return _bindings.get(name);
}
public void removeBinding(Name name)
{
String key = name.toString();
if (LOG.isDebugEnabled())
LOG.debug("Removing binding with key={}", key);
Binding binding = _bindings.remove(key);
if (binding != null)
{
Collection list = findListeners();
for (Listener listener : list)
{
listener.unbind(this, binding);
}
}
}
/**
* Remove leading or trailing empty components from
* name. Eg "/comp/env/" -> "comp/env"
*
* @param name the name to normalize
* @return normalized name
*/
public Name toCanonicalName(Name name)
{
Name canonicalName = name;
if (name != null)
{
if (canonicalName.size() > 1)
{
if (canonicalName.get(0).equals(""))
canonicalName = canonicalName.getSuffix(1);
if (canonicalName.get(canonicalName.size() - 1).equals(""))
canonicalName = canonicalName.getPrefix(canonicalName.size() - 1);
}
}
return canonicalName;
}
public boolean isLocked()
{
//TODO lock whole hierarchy?
return _lock != null;
}
@Override
public String dump()
{
return Dumpable.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
Map bindings = new HashMap<>();
for (Map.Entry binding : _bindings.entrySet())
{
bindings.put(binding.getKey(), binding.getValue().getObject());
}
Dumpable.dumpObject(out, this);
Dumpable.dumpMapEntries(out, indent, bindings, _env.isEmpty());
if (!_env.isEmpty())
{
out.append(indent).append("+> environment\n");
Dumpable.dumpMapEntries(out, indent + " ", _env, true);
}
}
private Collection findListeners()
{
Collection list = new ArrayList();
NamingContext ctx = this;
while (ctx != null)
{
if (ctx._listeners != null)
list.addAll(ctx._listeners);
ctx = (NamingContext)ctx.getParent();
}
return list;
}
public void addListener(Listener listener)
{
if (_listeners == null)
_listeners = new ArrayList();
_listeners.add(listener);
}
public boolean removeListener(Listener listener)
{
return _listeners.remove(listener);
}
@Override
public String toString()
{
StringBuilder buf = new StringBuilder();
buf.append(this.getClass().getName()).append('@').append(Integer.toHexString(this.hashCode()));
buf.append("[name=").append(this._name);
buf.append(",parent=");
if (this._parent != null)
{
buf.append(this._parent.getClass().getName()).append('@').append(Integer.toHexString(this._parent.hashCode()));
}
buf.append(",bindings");
if (this._bindings == null)
{
buf.append("=");
}
else
{
buf.append(".size=").append(this._bindings.size());
}
buf.append(']');
return buf.toString();
}
}