/*
 * 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" on the result of the lookup. The "do" does the work without any further resolution (it assumes that it is the target context).
/** * 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); } }