/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.naming; import com.caucho.util.L10N; import javax.naming.*; import javax.naming.spi.NamingManager; import java.util.Hashtable; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; /** * Resin's implementation of the JNDI <code>Context</code>. The actual storage * of the persistent data is in the <code>AbstractModel</code>. * * <p>The <code>ContextImpl</code> is just a Visitor around * the <code>AbstractModel</code> which also encapsulate * the JNDI environment. * * <p>In JNDI, each <code>Context</code> is a <model, env> pair. * Each client might pass a different environment * to the <code>InitialContext</code> so each <code>ContextImpl</code> * must be unique for each client. (Granted, this is a bit wasteful of * space which is why JNDI values should be cached.) * * <p>Applications which want a different model can still use * <code>ContextImpl</code> and specify the root * object for <code>AbstractModel</code>. <code>ContextImpl</code> will * take care of the JNDI API for the model. */ public class ContextImpl implements Context { protected static final Logger log = Logger.getLogger(ContextImpl.class.getName()); protected static final L10N L = new L10N(ContextImpl.class); protected Hashtable<String,Object> _env; protected AbstractModel _model; protected String _name; /** * Creates a <code>ContextImpl</code>. * * @param model The underlying storage node. * @param env The client's JNDI environment. */ public ContextImpl(AbstractModel model, Hashtable<String,Object> env) { _model = model; _env = env; _name = ""; if (_model == null) throw new NullPointerException(); } /** * Creates a <code>ContextImpl</code>. * * @param name JNDI name, used for error messages, etc. * @param model The underlying storage node. * @param env The client's JNDI environment. */ public ContextImpl(String name, AbstractModel model, Hashtable<String,Object> env) { _model = model; _env = env; _name = name; if (_model == null) throw new NullPointerException(); } /** * Creates a new instance of the <code>ContextImpl</code>. Subclasses will * override this method to return a new instance of the subclass. * * @param name the JNDI name for the new context * @param model the underlying storage node * @param env the client's JNDI environment. * * @return a new instance of the implementing class. */ protected ContextImpl create(String name, AbstractModel model, Hashtable<String,Object> env) { return new ContextImpl(name, model, env); } /** * Creates a new <code>ContextImpl</code> when the name is irrelevant. * * @param model the underlying storage node * @param env the client's JNDI environment. * * @return a new instance of the implementing class. */ final protected ContextImpl create(AbstractModel model, Hashtable<String,Object> env) { return create("", model, env); } /** * Returns the underlying storage node. */ public AbstractModel getModel() { return _model; } /** * Returns the name. */ public String getName() { return _name; } /** * Looks up an object using its full string name. The path is searched * recursively. <code>parseFirst</code> returns the first segment. The * * @param name the JNDI name to lookup for the child. * * @return the retrieved object */ @Override public Object lookup(String name) throws NamingException { Object value = lookupImpl(name); if (value == NullValue.NULL) { // env/0gc9 return null; } else if (value != null) { // server/1509 return value; } else throw new NameNotFoundException(getFullPath(name)); } /** * Looks up an object using its full string name. The path is searched * recursively. <code>parseFirst</code> returns the first segment. The * * @param name the JNDI name to lookup for the child. * * @return the retrieved object */ protected Object lookupImpl(String name) throws NamingException { String tail = name; AbstractModel model = _model; while (tail != null) { String first = parseFirst(tail); String rest = parseRest(tail); if (first == null) return create(getFullPath(name), model, _env); Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; tail = rest; continue; } value = dereference(value, null, model); if (rest == null) return value; else if (value instanceof Context) return ((Context) value).lookup(rest); else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } return create(getFullPath(name), model, _env); } /** * Looks up an object with the given parsed JNDI name. */ @Override public Object lookup(Name name) throws NamingException { Object value = lookupImpl(name); if (value == NullValue.NULL) { // env/0gf9 return null; } else if (value != null) { return value; } else throw new NameNotFoundException(getFullPath(name)); } private Object lookupImpl(Name name) throws NamingException { if (log.isLoggable(Level.FINEST)) log.finest(L.l("JNDI lookup `{0}'", name)); if (name == null) return create(_model, _env); AbstractModel model = _model; for (int i = 0; i < name.size(); i++) { String first = name.get(i); Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; continue; } value = dereference(value, null, model); if (i + 1 == name.size()) { return value; } else if (value instanceof Context) { return ((Context) value).lookup(name.getSuffix(i + 1)); } else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } return create(getFullPath(name), model, _env); } /** * Looks up an object given the name, but doesn't dereference links. */ @Override public Object lookupLink(String name) throws NamingException { String tail = name; AbstractModel model = _model; while (tail != null) { String first = parseFirst(tail); String rest = parseRest(tail); if (first == null) { return create(getFullPath(name), model, _env); } Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; tail = rest; continue; } if (rest == null) { if (value == NullValue.NULL) return null; else if (value != null) return value; else throw new NameNotFoundException(getFullPath(name)); } value = dereference(value, null, model); if (value instanceof Context) return ((Context) value).lookupLink(rest); else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } return create(getFullPath(name), model, _env); } /** * Looks up an object with the given parsed JNDI name, but don't * dereference the final object. */ public Object lookupLink(Name name) throws NamingException { if (name == null) return create(_model, _env); AbstractModel model = _model; for (int i = 0; i < name.size(); i++) { String first = name.get(i); Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; continue; } if (i + 1 == name.size()) { if (value == NullValue.NULL) return null; else if (value != null) return value; else throw new NameNotFoundException(getFullPath(name)); } value = dereference(value, null, model); if (value instanceof Context) return ((Context) value).lookupLink(name.getSuffix(i + 1)); else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } return create(getFullPath(name), model, _env); } /** * Binds an object to the context. */ public void bind(String name, Object obj) throws NamingException { String tail = name; AbstractModel model = _model; if (obj == null) obj = NullValue.NULL; while (true) { String first = parseFirst(tail); String rest = parseRest(tail); if (first == null) throw new NamingException(L.l("can't bind root")); if (rest == null) { Object value = model.lookup(first); if (value != null) throw new NamingException(L.l("`{0}' is already bound to `{1}'", name, value)); model.bind(first, getReference(model, obj)); return; } Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; tail = rest; continue; } value = dereference(value, null, model); if (value instanceof Context) { ((Context) value).bind(rest, obj); return; } else if (value != null) { throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); } else { throw new NameNotFoundException(getFullPath(name)); } } } /** * Binds an object to the context. */ public void bind(Name name, Object obj) throws NamingException { if (log.isLoggable(Level.FINEST)) log.finest(L.l("JNDI bind `{0}'", name)); if (name.size() == 0) throw new NamingException(L.l("can't bind root")); AbstractModel model = _model; int i = 0; for (; i + 1 < name.size(); i++) { String first = name.get(i); Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; continue; } value = dereference(value, null, model); if (value instanceof Context) { ((Context) value).bind(name.getSuffix(i + 1), obj); return; } else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } String first = name.get(i); Object value = model.lookup(first); if (value != null) throw new NamingException(L.l("`{0}' is already bound to `{1}'", name, value)); if (obj == null) obj = NullValue.NULL; model.bind(first, getReference(model, obj)); } /** * Binds an object to the context, overriding any old value. * * @param name the name to bind * @param obj the object to bind */ @Override public void rebind(String name, Object obj) throws NamingException { if (log.isLoggable(Level.FINEST)) log.finest(L.l("JNDI rebind `{0}' value: {1}", name, obj)); String tail = name; AbstractModel model = _model; // env/0gde if (obj == null) obj = NullValue.NULL; while (true) { String first = parseFirst(tail); String rest = parseRest(tail); if (first == null) throw new NamingException(L.l("can't bind root")); if (rest == null) { model.bind(first, getReference(model, obj)); return; } Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; tail = rest; continue; } value = dereference(value, null, model); if (value instanceof Context) { ((Context) value).rebind(rest, obj); return; } else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at '{1}'\n {2}", getFullPath(name), value, Thread.currentThread().getContextClassLoader())); else throw new NameNotFoundException(L.l("{0}: JNDI rebind requires the parent Context '{1}' to be created but it is null.\n {2}',", getFullPath(name), first, Thread.currentThread().getContextClassLoader())); } } public void rebind(Name name, Object obj) throws NamingException { if (name.size() == 0) throw new NamingException(L.l("can't bind root")); AbstractModel model = _model; int i = 0; for (; i + 1 < name.size(); i++) { String first = name.get(i); Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; continue; } value = dereference(value, null, model); if (value instanceof Context) { ((Context) value).bind(name.getSuffix(i + 1), obj); return; } else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } String first = name.get(i); if (obj == null) obj = NullValue.NULL; model.bind(first, getReference(model, obj)); } private Object getReference(AbstractModel model, Object obj) { return obj; } /** * Unbinds an object from the context. * * @param name the name to unbind */ public void unbind(String name) throws NamingException { unbindImpl(name, false); } private void unbindImpl(String name, boolean isRename) throws NamingException { String tail = name; AbstractModel model = _model; while (true) { String first = parseFirst(tail); String rest = parseRest(tail); if (first == null) throw new NamingException(L.l("can't unbind root")); if (rest == null) { if (! isRename && model.lookup(name) instanceof AbstractModel) throw new NamingException(L.l("can't unbind subcontext; use destroySubcontext")); model.unbind(first); return; } Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; tail = rest; continue; } value = dereference(value, null, model); if (value instanceof Context) { ((Context) value).unbind(rest); return; } else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } } private Object dereference(Object value, Name tail, AbstractModel model) throws NamingException { try { if (value instanceof ObjectProxy) return ((ObjectProxy) value).createObject(_env); else if (value instanceof Reference) { Context context = create(model, _env); return NamingManager.getObjectInstance(value, null, context, _env); } else return value; } catch (RuntimeException e) { throw e; } catch (NamingException e) { throw e; } catch (Exception e) { throw new NamingExceptionWrapper(e); } } public void unbind(Name name) throws NamingException { if (name.size() == 0) throw new NamingException(L.l("can't unbind root")); AbstractModel model = _model; int i = 0; for (; i + 1 < name.size(); i++) { String first = name.get(i); Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; continue; } value = dereference(value, null, model); if (value instanceof Context) { ((Context) value).unbind(name.getSuffix(i + 1)); return; } else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } String first = name.get(i); model.unbind(first); } public void rename(String oldName, String newName) throws NamingException { Object value = lookup(oldName); unbindImpl(oldName, true); if (value instanceof ContextImpl) ((ContextImpl) value).setName(newName); bind(newName, value); } private void setName(String newName) { _name = newName; } public void rename(Name oldName, Name newName) throws NamingException { Object value = lookup(oldName); unbind(oldName); if (value instanceof ContextImpl) ((ContextImpl) value).setName(newName.toString()); bind(newName, value); } /** * List the names for a context. */ public NamingEnumeration list(String name) throws NamingException { String tail = name; AbstractModel model = _model; while (true) { String first = parseFirst(tail); String rest = parseRest(tail); if (first == null) { return new QNameClassEnumeration(create(model, _env), model.list()); } Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; tail = rest; continue; } value = dereference(value, null, model); if (value instanceof Context) { if (rest == null) return ((Context) value).list(""); else return ((Context) value).list(rest); } else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } } /** * Lists the names for the context. */ public NamingEnumeration list(Name name) throws NamingException { AbstractModel model = _model; if (name == null) { return new QNameClassEnumeration(create(model, _env), model.list()); } for (int i = 0; i < name.size(); i++) { String first = name.get(i); Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; continue; } value = dereference(value, null, model); if (value instanceof Context) return ((Context) value).list(name.getSuffix(i + 1)); else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } return new QNameClassEnumeration(create(model, _env), model.list()); } /** * List the bindings for a context. */ public NamingEnumeration listBindings(String name) throws NamingException { String tail = name; AbstractModel model = _model; while (true) { String first = parseFirst(tail); String rest = parseRest(tail); if (first == null) { return new QBindingEnumeration(create(model, _env), model.list()); } Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; tail = rest; continue; } value = dereference(value, null, model); if (value instanceof Context) return ((Context) value).listBindings(rest); else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } } /** * Lists the bindings for the given name. */ public NamingEnumeration listBindings(Name name) throws NamingException { AbstractModel model = _model; for (int i = 0; name != null && i < name.size(); i++) { String first = name.get(i); Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; continue; } value = dereference(value, null, model); if (value instanceof Context) return ((Context) value).listBindings(name.getSuffix(i + 1)); else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } return new QBindingEnumeration(create(model, _env), model.list()); } /** * Creates a subcontext for the current model. */ public Context createSubcontext(String name) throws NamingException { String tail = name; AbstractModel model = _model; while (true) { String first = parseFirst(tail); String rest = parseRest(tail); if (first == null) throw new NamingException(L.l("can't create root subcontext")); if (rest == null) { model = model.createSubcontext(first); return create(getFullPath(name), model, _env); } Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; tail = rest; continue; } value = dereference(value, null, model); if (value instanceof Context) return ((Context) value).createSubcontext(rest); else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } } public Context createSubcontext(Name name) throws NamingException { if (name.size() == 0) throw new NamingException(L.l("can't create root subcontext")); AbstractModel model = _model; int i = 0; for (; i + 1 < name.size(); i++) { String first = name.get(i); Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; continue; } value = dereference(value, null, model); if (value instanceof Context) { return ((Context) value).createSubcontext(name.getSuffix(i + 1)); } else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } String first = name.get(i); model = model.createSubcontext(first); return create(getFullPath(name), model, _env); } /** * Destroys the named subcontext. */ public void destroySubcontext(String name) throws NamingException { String tail = name; AbstractModel model = _model; while (true) { String first = parseFirst(tail); String rest = parseRest(tail); if (first == null) throw new NamingException(L.l("can't destroy root subcontext")); if (rest == null) { model.unbind(first); return; } Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; tail = rest; continue; } value = dereference(value, null, model); if (value instanceof Context) { ((Context) value).destroySubcontext(rest); return; } else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } } public void destroySubcontext(Name name) throws NamingException { if (name.size() == 0) throw new NamingException(L.l("can't destroy root subcontext")); AbstractModel model = _model; int i = 0; for (; i + 1 < name.size(); i++) { String first = name.get(i); Object value = model.lookup(first); if (value instanceof AbstractModel) { model = (AbstractModel) value; continue; } value = dereference(value, null, model); if (value instanceof Context) { ((Context) value).destroySubcontext(name.getSuffix(i + 1)); return; } else if (value != null) throw new NotContextException(L.l("{0}: expected intermediate context at `{1}'", getFullPath(name), value)); else throw new NameNotFoundException(getFullPath(name)); } String first = name.get(i); model.unbind(first); } public NameParser getNameParser(String name) throws NamingException { String first = parseFirst(name); String rest = parseRest(name); if (first == null) return new QNameParser(this); Object obj = lookupSingleObject(first); if (obj instanceof Context) return ((Context) obj).getNameParser(rest == null ? "" : rest); else return new QNameParser(this); } public NameParser getNameParser(Name name) throws NamingException { if (name.size() == 0) return new QNameParser(this); Object obj = lookupSingleObject(name.get(0)); if (obj instanceof Context) return ((Context) obj).getNameParser(name.getSuffix(1)); else return new QNameParser(this); } public String composeName(String suffix, String prefix) throws NamingException { if (suffix == null) throw new NamingException(L.l("suffix cannot be null")); else if (prefix == null) throw new NamingException(L.l("prefix cannot be null")); else if (prefix.length() == 0) return suffix; else return prefix + "/" + suffix; } public Name composeName(Name suffix, Name prefix) throws NamingException { if (suffix == null) throw new NamingException(L.l("suffix cannot be null")); else if (prefix == null) throw new NamingException(L.l("prefix cannot be null")); else return prefix.addAll(suffix); } public String getNameInNamespace() throws NamingException { throw new OperationNotSupportedException(); } /** * Looks up the object and dereferences any proxy objects. */ private Object lookupSingleObject(String name) throws NamingException { Object obj = lookupSingle(name); if (obj instanceof ObjectProxy) return ((ObjectProxy) obj).createObject(_env); else return obj; } /** * Returns the object named by the single name segment. * * @param name the name segment. * * @return the object bound to the context. */ protected Object lookupSingle(String name) throws NamingException { throw new UnsupportedOperationException(); } protected void rebindSingle(String name, Object obj) throws NamingException { throw new UnsupportedOperationException(); } protected void unbindSingle(String name) throws NamingException { throw new UnsupportedOperationException(); } protected Context createSingleSubcontext(String name) throws NamingException { throw new UnsupportedOperationException(); } protected void destroySingleSubcontext(String name) throws NamingException { unbindSingle(name); } protected Iterator listSingle() { throw new UnsupportedOperationException(); } protected String parseFirst(String name) throws NamingException { if (name == null || name.equals("")) return null; int p = name.indexOf(getSeparator()); if (p == 0) return parseFirst(name.substring(1)); else if (p > 0) return name.substring(0, p); else return name; } protected String parseRest(String name) throws NamingException { if (name == null || name.equals("")) return null; int p = name.indexOf(getSeparator()); if (p == 0) return parseRest(name.substring(1)); else if (p > 0) return name.substring(p + 1); else return null; } protected char getSeparator() { return '/'; } protected String getSeparatorString() { return "/"; } /** * Returns the full name for the context. */ protected String getFullPath(String name) { if (_name == null || _name.equals("")) return name; else if (name == null) return _name; String sep = getSeparatorString(); while (name.endsWith(sep)) name = name.substring(0, name.length() - sep.length()); if (name.equals("")) return _name; else if (name.startsWith(sep)) return _name + name; else return _name + sep + name; } /** * Returns the full name for the context. */ protected String getFullPath(Name name) { if (_name == null || _name.equals("")) return name.toString(); else if (name == null || name.size() == 0) return _name; String sep = getSeparatorString(); return _name + sep + name; } /** * Adds a property to the context environment. */ @Override public Object addToEnvironment(String prop, Object value) throws NamingException { Object old = _env.get(prop); _env.put(prop, value); return old; } /** * Removes a property from the context environment. */ public Object removeFromEnvironment(String prop) throws NamingException { Object old = _env.get(prop); _env.remove(prop); return old; } /** * Returns the context environment. */ public Hashtable getEnvironment() throws NamingException { return _env; } /** * Close is intended to free any transient data, like a cached * socket. It does not affect the JNDI tree. */ public void close() throws NamingException { } /** * Returns a string value. */ public String toString() { return "ContextImpl[" + _name + "]"; } private static class NullValue { static final NullValue NULL = new NullValue(); } }