blob: 48d700870e814cf0d8e15a3347224dbb53b0eb64 [file] [log] [blame]
/*
* Copyright (c) 1999, 2002, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.jndi.toolkit.dir;
import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.spi.*;
import java.util.*;
/**
* A sample service provider that implements a hierarchical directory in memory.
* Every operation begins by doing a lookup on the name passed to it and then
* calls a corresponding "do<OperationName>" on the result of the lookup. The
* "do<OperationName>" does the work without any further resolution (it assumes
* that it is the target context).
*/
public class HierMemDirCtx implements DirContext {
static private final boolean debug = false;
private static final NameParser defaultParser = new HierarchicalNameParser();
protected Hashtable myEnv;
protected Hashtable bindings;
protected Attributes attrs;
protected boolean ignoreCase = false;
protected NamingException readOnlyEx = null;
protected NameParser myParser = defaultParser;
private boolean alwaysUseFactory;
public void close() throws NamingException {
myEnv = null;
bindings = null;
attrs = null;
}
public String getNameInNamespace() throws NamingException {
throw new OperationNotSupportedException(
"Cannot determine full name");
}
public HierMemDirCtx() {
this(null, false, false);
}
public HierMemDirCtx(boolean ignoreCase) {
this(null, ignoreCase, false);
}
public HierMemDirCtx(Hashtable environment, boolean ignoreCase) {
this(environment, ignoreCase, false);
}
protected HierMemDirCtx(Hashtable environment, boolean ignoreCase,
boolean useFac) {
myEnv = environment;
this.ignoreCase = ignoreCase;
init();
this.alwaysUseFactory = useFac;
}
private void init() {
attrs = new BasicAttributes(ignoreCase);
bindings = new Hashtable(11, 0.75f);
}
public Object lookup(String name) throws NamingException {
return lookup(myParser.parse(name));
}
public Object lookup(Name name) throws NamingException {
return doLookup(name, alwaysUseFactory);
}
public Object doLookup(Name name, boolean useFactory)
throws NamingException {
Object target = null;
name = canonizeName(name);
switch(name.size()) {
case 0:
// name is empty, caller wants this object
target = this;
break;
case 1:
// name is atomic, caller wants one of this object's bindings
target = bindings.get(name);
break;
default:
// name is compound, delegate to child context
HierMemDirCtx ctx = (HierMemDirCtx)bindings.get(name.getPrefix(1));
if(ctx == null) {
target = null;
} else {
target = ctx.doLookup(name.getSuffix(1), false);
}
break;
}
if(target == null) {
throw new NameNotFoundException(name.toString());
}
if (useFactory) {
try {
return DirectoryManager.getObjectInstance(target,
name, this, myEnv,
(target instanceof HierMemDirCtx) ?
((HierMemDirCtx)target).attrs : null);
} catch (NamingException e) {
throw e;
} catch (Exception e) {
NamingException e2 = new NamingException(
"Problem calling getObjectInstance");
e2.setRootCause(e);
throw e2;
}
} else {
return target;
}
}
public void bind(String name, Object obj) throws NamingException {
bind(myParser.parse(name), obj);
}
public void bind(Name name, Object obj) throws NamingException {
doBind(name, obj, null, alwaysUseFactory);
}
public void bind(String name, Object obj, Attributes attrs)
throws NamingException {
bind(myParser.parse(name), obj, attrs);
}
public void bind(Name name, Object obj, Attributes attrs)
throws NamingException {
doBind(name, obj, attrs, alwaysUseFactory);
}
protected void doBind(Name name, Object obj, Attributes attrs,
boolean useFactory) throws NamingException {
if (name.isEmpty()) {
throw new InvalidNameException("Cannot bind empty name");
}
if (useFactory) {
DirStateFactory.Result res = DirectoryManager.getStateToBind(
obj, name, this, myEnv, attrs);
obj = res.getObject();
attrs = res.getAttributes();
}
HierMemDirCtx ctx= (HierMemDirCtx) doLookup(getInternalName(name), false);
ctx.doBindAux(getLeafName(name), obj);
if (attrs != null && attrs.size() > 0) {
modifyAttributes(name, ADD_ATTRIBUTE, attrs);
}
}
protected void doBindAux(Name name, Object obj) throws NamingException {
if (readOnlyEx != null) {
throw (NamingException) readOnlyEx.fillInStackTrace();
}
if (bindings.get(name) != null) {
throw new NameAlreadyBoundException(name.toString());
}
if(obj instanceof HierMemDirCtx) {
bindings.put(name, obj);
} else {
throw new SchemaViolationException(
"This context only supports binding objects of it's own kind");
}
}
public void rebind(String name, Object obj) throws NamingException {
rebind(myParser.parse(name), obj);
}
public void rebind(Name name, Object obj) throws NamingException {
doRebind(name, obj, null, alwaysUseFactory);
}
public void rebind(String name, Object obj, Attributes attrs)
throws NamingException {
rebind(myParser.parse(name), obj, attrs);
}
public void rebind(Name name, Object obj, Attributes attrs)
throws NamingException {
doRebind(name, obj, attrs, alwaysUseFactory);
}
protected void doRebind(Name name, Object obj, Attributes attrs,
boolean useFactory) throws NamingException {
if (name.isEmpty()) {
throw new InvalidNameException("Cannot rebind empty name");
}
if (useFactory) {
DirStateFactory.Result res = DirectoryManager.getStateToBind(
obj, name, this, myEnv, attrs);
obj = res.getObject();
attrs = res.getAttributes();
}
HierMemDirCtx ctx= (HierMemDirCtx) doLookup(getInternalName(name), false);
ctx.doRebindAux(getLeafName(name), obj);
//
// attrs == null -> use attrs from obj
// attrs != null -> use attrs
//
// %%% Strictly speaking, when attrs is non-null, we should
// take the explicit step of removing obj's attrs.
// We don't do that currently.
if (attrs != null && attrs.size() > 0) {
modifyAttributes(name, ADD_ATTRIBUTE, attrs);
}
}
protected void doRebindAux(Name name, Object obj) throws NamingException {
if (readOnlyEx != null) {
throw (NamingException) readOnlyEx.fillInStackTrace();
}
if(obj instanceof HierMemDirCtx) {
bindings.put(name, obj);
} else {
throw new SchemaViolationException(
"This context only supports binding objects of it's own kind");
}
}
public void unbind(String name) throws NamingException {
unbind(myParser.parse(name));
}
public void unbind(Name name) throws NamingException {
if (name.isEmpty()) {
throw new InvalidNameException("Cannot unbind empty name");
} else {
HierMemDirCtx ctx=
(HierMemDirCtx) doLookup(getInternalName(name), false);
ctx.doUnbind(getLeafName(name));
}
}
protected void doUnbind(Name name) throws NamingException {
if (readOnlyEx != null) {
throw (NamingException) readOnlyEx.fillInStackTrace();
}
bindings.remove(name); // attrs will also be removed along with ctx
}
public void rename(String oldname, String newname)
throws NamingException {
rename(myParser.parse(oldname), myParser.parse(newname));
}
public void rename(Name oldname, Name newname)
throws NamingException {
if(newname.isEmpty() || oldname.isEmpty()) {
throw new InvalidNameException("Cannot rename empty name");
}
if (!getInternalName(newname).equals(getInternalName(oldname))) {
throw new InvalidNameException("Cannot rename across contexts");
}
HierMemDirCtx ctx =
(HierMemDirCtx) doLookup(getInternalName(newname), false);
ctx.doRename(getLeafName(oldname), getLeafName(newname));
}
protected void doRename(Name oldname, Name newname) throws NamingException {
if (readOnlyEx != null) {
throw (NamingException) readOnlyEx.fillInStackTrace();
}
oldname = canonizeName(oldname);
newname = canonizeName(newname);
// Check if new name exists
if (bindings.get(newname) != null) {
throw new NameAlreadyBoundException(newname.toString());
}
// Check if old name is bound
Object oldBinding = bindings.remove(oldname);
if (oldBinding == null) {
throw new NameNotFoundException(oldname.toString());
}
bindings.put(newname, oldBinding);
}
public NamingEnumeration list(String name) throws NamingException {
return list(myParser.parse(name));
}
public NamingEnumeration list(Name name) throws NamingException {
HierMemDirCtx ctx = (HierMemDirCtx) doLookup(name, false);
return ctx.doList();
}
protected NamingEnumeration doList () throws NamingException {
return new FlatNames(bindings.keys());
}
public NamingEnumeration listBindings(String name) throws NamingException {
return listBindings(myParser.parse(name));
}
public NamingEnumeration listBindings(Name name) throws NamingException {
HierMemDirCtx ctx = (HierMemDirCtx)doLookup(name, false);
return ctx.doListBindings(alwaysUseFactory);
}
protected NamingEnumeration doListBindings(boolean useFactory)
throws NamingException {
return new FlatBindings(bindings, myEnv, useFactory);
}
public void destroySubcontext(String name) throws NamingException {
destroySubcontext(myParser.parse(name));
}
public void destroySubcontext(Name name) throws NamingException {
HierMemDirCtx ctx =
(HierMemDirCtx) doLookup(getInternalName(name), false);
ctx.doDestroySubcontext(getLeafName(name));
}
protected void doDestroySubcontext(Name name) throws NamingException {
if (readOnlyEx != null) {
throw (NamingException) readOnlyEx.fillInStackTrace();
}
name = canonizeName(name);
bindings.remove(name);
}
public Context createSubcontext(String name) throws NamingException {
return createSubcontext(myParser.parse(name));
}
public Context createSubcontext(Name name) throws NamingException {
return createSubcontext(name, null);
}
public DirContext createSubcontext(String name, Attributes attrs)
throws NamingException {
return createSubcontext(myParser.parse(name), attrs);
}
public DirContext createSubcontext(Name name, Attributes attrs)
throws NamingException {
HierMemDirCtx ctx =
(HierMemDirCtx) doLookup(getInternalName(name), false);
return ctx.doCreateSubcontext(getLeafName(name), attrs);
}
protected DirContext doCreateSubcontext(Name name, Attributes attrs)
throws NamingException {
if (readOnlyEx != null) {
throw (NamingException) readOnlyEx.fillInStackTrace();
}
name = canonizeName(name);
if (bindings.get(name) != null) {
throw new NameAlreadyBoundException(name.toString());
}
HierMemDirCtx newCtx = createNewCtx();
bindings.put(name, newCtx);
if(attrs != null) {
newCtx.modifyAttributes("", ADD_ATTRIBUTE, attrs);
}
return newCtx;
}
public Object lookupLink(String name) throws NamingException {
// This context does not treat links specially
return lookupLink(myParser.parse(name));
}
public Object lookupLink(Name name) throws NamingException {
// Flat namespace; no federation; just call string version
return lookup(name);
}
public NameParser getNameParser(String name) throws NamingException {
return myParser;
}
public NameParser getNameParser(Name name) throws NamingException {
return myParser;
}
public String composeName(String name, String prefix)
throws NamingException {
Name result = composeName(new CompositeName(name),
new CompositeName(prefix));
return result.toString();
}
public Name composeName(Name name, Name prefix)
throws NamingException {
name = canonizeName(name);
prefix = canonizeName(prefix);
Name result = (Name)(prefix.clone());
result.addAll(name);
return result;
}
public Object addToEnvironment(String propName, Object propVal)
throws NamingException {
myEnv = (myEnv == null) ?
new Hashtable(11, 0.75f) : (Hashtable)myEnv.clone();
return myEnv.put(propName, propVal);
}
public Object removeFromEnvironment(String propName)
throws NamingException {
if (myEnv == null)
return null;
myEnv = (Hashtable)myEnv.clone();
return myEnv.remove(propName);
}
public Hashtable getEnvironment() throws NamingException {
if (myEnv == null) {
return new Hashtable(5, 0.75f);
} else {
return (Hashtable)myEnv.clone();
}
}
public Attributes getAttributes(String name)
throws NamingException {
return getAttributes(myParser.parse(name));
}
public Attributes getAttributes(Name name)
throws NamingException {
HierMemDirCtx ctx = (HierMemDirCtx) doLookup(name, false);
return ctx.doGetAttributes();
}
protected Attributes doGetAttributes() throws NamingException {
return (Attributes)attrs.clone();
}
public Attributes getAttributes(String name, String[] attrIds)
throws NamingException {
return getAttributes(myParser.parse(name), attrIds);
}
public Attributes getAttributes(Name name, String[] attrIds)
throws NamingException {
HierMemDirCtx ctx = (HierMemDirCtx) doLookup(name, false);
return ctx.doGetAttributes(attrIds);
}
protected Attributes doGetAttributes(String[] attrIds)
throws NamingException {
if (attrIds == null) {
return doGetAttributes();
}
Attributes attrs = new BasicAttributes(ignoreCase);
Attribute attr = null;
for(int i=0; i<attrIds.length; i++) {
attr = this.attrs.get(attrIds[i]);
if (attr != null) {
attrs.put(attr);
}
}
return attrs;
}
public void modifyAttributes(String name, int mod_op, Attributes attrs)
throws NamingException {
modifyAttributes(myParser.parse(name), mod_op, attrs);
}
public void modifyAttributes(Name name, int mod_op, Attributes attrs)
throws NamingException {
if (attrs == null || attrs.size() == 0) {
throw new IllegalArgumentException(
"Cannot modify without an attribute");
}
// turn it into a modification Enumeration and pass it on
NamingEnumeration attrEnum = attrs.getAll();
ModificationItem[] mods = new ModificationItem[attrs.size()];
for (int i = 0; i < mods.length && attrEnum.hasMoreElements(); i++) {
mods[i] = new ModificationItem(mod_op, (Attribute)attrEnum.next());
}
modifyAttributes(name, mods);
}
public void modifyAttributes(String name, ModificationItem[] mods)
throws NamingException {
modifyAttributes(myParser.parse(name), mods);
}
public void modifyAttributes(Name name, ModificationItem[] mods)
throws NamingException {
HierMemDirCtx ctx = (HierMemDirCtx) doLookup(name, false);
ctx.doModifyAttributes(mods);
}
protected void doModifyAttributes(ModificationItem[] mods)
throws NamingException {
if (readOnlyEx != null) {
throw (NamingException) readOnlyEx.fillInStackTrace();
}
applyMods(mods, attrs);
}
protected static Attributes applyMods(ModificationItem[] mods,
Attributes orig) throws NamingException {
ModificationItem mod;
Attribute existingAttr, modAttr;
NamingEnumeration modVals;
for (int i = 0; i < mods.length; i++) {
mod = mods[i];
modAttr = mod.getAttribute();
switch(mod.getModificationOp()) {
case ADD_ATTRIBUTE:
if (debug) {
System.out.println("HierMemDSCtx: adding " +
mod.getAttribute().toString());
}
existingAttr = orig.get(modAttr.getID());
if (existingAttr == null) {
orig.put((Attribute)modAttr.clone());
} else {
// Add new attribute values to existing attribute
modVals = modAttr.getAll();
while (modVals.hasMore()) {
existingAttr.add(modVals.next());
}
}
break;
case REPLACE_ATTRIBUTE:
if (modAttr.size() == 0) {
orig.remove(modAttr.getID());
} else {
orig.put((Attribute)modAttr.clone());
}
break;
case REMOVE_ATTRIBUTE:
existingAttr = orig.get(modAttr.getID());
if (existingAttr != null) {
if (modAttr.size() == 0) {
orig.remove(modAttr.getID());
} else {
// Remove attribute values from existing attribute
modVals = modAttr.getAll();
while (modVals.hasMore()) {
existingAttr.remove(modVals.next());
}
if (existingAttr.size() == 0) {
orig.remove(modAttr.getID());
}
}
}
break;
default:
throw new AttributeModificationException("Unknown mod_op");
}
}
return orig;
}
public NamingEnumeration search(String name,
Attributes matchingAttributes)
throws NamingException {
return search(name, matchingAttributes, null);
}
public NamingEnumeration search(Name name,
Attributes matchingAttributes)
throws NamingException {
return search(name, matchingAttributes, null);
}
public NamingEnumeration search(String name,
Attributes matchingAttributes,
String[] attributesToReturn)
throws NamingException {
return search(myParser.parse(name), matchingAttributes,
attributesToReturn);
}
public NamingEnumeration search(Name name,
Attributes matchingAttributes,
String[] attributesToReturn)
throws NamingException {
HierMemDirCtx target = (HierMemDirCtx) doLookup(name, false);
SearchControls cons = new SearchControls();
cons.setReturningAttributes(attributesToReturn);
return new LazySearchEnumerationImpl(
target.doListBindings(false),
new ContainmentFilter(matchingAttributes),
cons, this, myEnv,
false); // alwaysUseFactory ignored because objReturnFlag == false
}
public NamingEnumeration search(Name name,
String filter,
SearchControls cons)
throws NamingException {
DirContext target = (DirContext) doLookup(name, false);
SearchFilter stringfilter = new SearchFilter(filter);
return new LazySearchEnumerationImpl(
new HierContextEnumerator(target,
(cons != null) ? cons.getSearchScope() :
SearchControls.ONELEVEL_SCOPE),
stringfilter,
cons, this, myEnv, alwaysUseFactory);
}
public NamingEnumeration search(Name name,
String filterExpr,
Object[] filterArgs,
SearchControls cons)
throws NamingException {
String strfilter = SearchFilter.format(filterExpr, filterArgs);
return search(name, strfilter, cons);
}
public NamingEnumeration search(String name,
String filter,
SearchControls cons)
throws NamingException {
return search(myParser.parse(name), filter, cons);
}
public NamingEnumeration search(String name,
String filterExpr,
Object[] filterArgs,
SearchControls cons)
throws NamingException {
return search(myParser.parse(name), filterExpr, filterArgs, cons);
}
// This function is called whenever a new object needs to be created.
// this is used so that if anyone subclasses us, they can override this
// and return object of their own kind.
protected HierMemDirCtx createNewCtx() throws NamingException {
return new HierMemDirCtx(myEnv, ignoreCase);
}
// If the supplied name is a composite name, return the name that
// is its first component.
protected Name canonizeName(Name name) throws NamingException {
Name canonicalName = name;
if(!(name instanceof HierarchicalName)) {
// If name is not of the correct type, make copy
canonicalName = new HierarchicalName();
int n = name.size();
for(int i = 0; i < n; i++) {
canonicalName.add(i, name.get(i));
}
}
return canonicalName;
}
protected Name getInternalName(Name name) throws NamingException {
return (name.getPrefix(name.size() - 1));
}
protected Name getLeafName(Name name) throws NamingException {
return (name.getSuffix(name.size() - 1));
}
public DirContext getSchema(String name) throws NamingException {
throw new OperationNotSupportedException();
}
public DirContext getSchema(Name name) throws NamingException {
throw new OperationNotSupportedException();
}
public DirContext getSchemaClassDefinition(String name)
throws NamingException {
throw new OperationNotSupportedException();
}
public DirContext getSchemaClassDefinition(Name name)
throws NamingException {
throw new OperationNotSupportedException();
}
// Set context in readonly mode; throw e when update operation attempted.
public void setReadOnly(NamingException e) {
readOnlyEx = e;
}
// Set context to support case-insensitive names
public void setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
}
public void setNameParser(NameParser parser) {
myParser = parser;
}
// Class for enumerating name/class pairs
private class FlatNames implements NamingEnumeration {
Enumeration names;
FlatNames (Enumeration names) {
this.names = names;
}
public boolean hasMoreElements() {
try {
return hasMore();
} catch (NamingException e) {
return false;
}
}
public boolean hasMore() throws NamingException {
return names.hasMoreElements();
}
public Object nextElement() {
try {
return next();
} catch (NamingException e) {
throw new NoSuchElementException(e.toString());
}
}
public Object next() throws NamingException {
Name name = (Name)names.nextElement();
String className = bindings.get(name).getClass().getName();
return new NameClassPair(name.toString(), className);
}
public void close() {
names = null;
}
}
// Class for enumerating bindings
private final class FlatBindings extends FlatNames {
private Hashtable bds;
private Hashtable env;
private boolean useFactory;
FlatBindings(Hashtable bindings, Hashtable env, boolean useFactory) {
super(bindings.keys());
this.env = env;
this.bds = bindings;
this.useFactory = useFactory;
}
public Object next() throws NamingException {
Name name = (Name)names.nextElement();
HierMemDirCtx obj = (HierMemDirCtx)bds.get(name);
Object answer = obj;
if (useFactory) {
Attributes attrs = obj.getAttributes(""); // only method available
try {
answer = DirectoryManager.getObjectInstance(obj,
name, HierMemDirCtx.this, env, attrs);
} catch (NamingException e) {
throw e;
} catch (Exception e) {
NamingException e2 = new NamingException(
"Problem calling getObjectInstance");
e2.setRootCause(e);
throw e2;
}
}
return new Binding(name.toString(), answer);
}
}
public class HierContextEnumerator extends ContextEnumerator {
public HierContextEnumerator(Context context, int scope)
throws NamingException {
super(context, scope);
}
protected HierContextEnumerator(Context context, int scope,
String contextName, boolean returnSelf) throws NamingException {
super(context, scope, contextName, returnSelf);
}
protected NamingEnumeration getImmediateChildren(Context ctx)
throws NamingException {
return ((HierMemDirCtx)ctx).doListBindings(false);
}
protected ContextEnumerator newEnumerator(Context ctx, int scope,
String contextName, boolean returnSelf) throws NamingException {
return new HierContextEnumerator(ctx, scope, contextName,
returnSelf);
}
}
}
// CompundNames's HashCode() method isn't good enough for many string.
// The only prupose of this subclass is to have a more discerning
// hash function. We'll make up for the performance hit by caching
// the hash value.
final class HierarchicalName extends CompoundName {
private int hashValue = -1;
// Creates an empty name
HierarchicalName() {
super(new Enumeration() {
public boolean hasMoreElements() {return false;}
public Object nextElement() {throw new NoSuchElementException();}
},
HierarchicalNameParser.mySyntax);
}
HierarchicalName(Enumeration comps, Properties syntax) {
super(comps, syntax);
}
HierarchicalName(String n, Properties syntax) throws InvalidNameException {
super(n, syntax);
}
// just like String.hashCode, only it pays no attention to length
public int hashCode() {
if (hashValue == -1) {
String name = toString().toUpperCase();
int len = name.length();
int off = 0;
char val[] = new char[len];
name.getChars(0, len, val, 0);
for (int i = len; i > 0; i--) {
hashValue = (hashValue * 37) + val[off++];
}
}
return hashValue;
}
public Name getPrefix(int posn) {
Enumeration comps = super.getPrefix(posn).getAll();
return (new HierarchicalName(comps, mySyntax));
}
public Name getSuffix(int posn) {
Enumeration comps = super.getSuffix(posn).getAll();
return (new HierarchicalName(comps, mySyntax));
}
public Object clone() {
return (new HierarchicalName(getAll(), mySyntax));
}
private static final long serialVersionUID = -6717336834584573168L;
}
// This is the default name parser (used if setNameParser is not called)
final class HierarchicalNameParser implements NameParser {
static final Properties mySyntax = new Properties();
static {
mySyntax.put("jndi.syntax.direction", "left_to_right");
mySyntax.put("jndi.syntax.separator", "/");
mySyntax.put("jndi.syntax.ignorecase", "true");
mySyntax.put("jndi.syntax.escape", "\\");
mySyntax.put("jndi.syntax.beginquote", "\"");
//mySyntax.put("jndi.syntax.separator.ava", "+");
//mySyntax.put("jndi.syntax.separator.typeval", "=");
mySyntax.put("jndi.syntax.trimblanks", "false");
};
public Name parse(String name) throws NamingException {
return new HierarchicalName(name, mySyntax);
}
}