package com.idega.core.ldap.client.naming; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Vector; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import com.idega.core.ldap.client.cbutil.CBArray; import com.idega.core.ldap.client.cbutil.CBUtility; import com.idega.core.ldap.client.jndi.SchemaOps; /** * This class is a container for a collection of * Attribute objects. It is schema aware, and will * search schema to find a complete set of attributes * for its component objectClass(s).<p> * * The class is built around a hashtable of id/attribute * pairs. */ // nb this class must work with both oid's (2.3.1.2.3.1.2.41.23.1.pi.2.e.34.phi) // and ldap names ('c'). Ideally it should get the long form as well // (i.e. 'country') but the server doesn't always oblige... // I try to document the difference by referring to ids as either oids or ldap ids. // if the distinction isn't obvious, it's probably an ldap id (i.e. a string name) // XXX a lot of effort is gone to to trim out ';binary' from the names of // XXX ldap attributes. THis is a grubby hack, and handling should be improved // XXX somehow. public class DXAttributes implements Attributes { static Hashtable attOIDs = new Hashtable(100); // a global hashset of all attribute oid-s and corresponding ldap descr names known to the program... static int ID = 0; int id; // unique debug id. Hashtable atts; // a hashtable of attribute id/ attribute object values, keyed by lowercase id. HashSet must; // a hashset of attribute ldap ids that *must* be entered // - set in checkSchema() boolean ignoreCase = true; // whether attribute IDs are case sensitive boolean schemaChecked = false; // indicates a schema search has been made, // and a full list of objectclass(s) attribute obtained Attribute allObjectClasses; // a list of allObjectClasses, including parents. //String baseObjectClass = null; // the deepest ObjectClass. String objectClassName = null; // the current name of 'objectclass' or 'objectClass' Vector orderedSOCs = new Vector(); // structural object classes, in deepest-first order static SchemaOps schema; // the schema context static Hashtable knownParents = new Hashtable(30); // hash of DXAttributes containing the known object class parents for a particular object class subset (e.g. 'inetorgperson' -> 'orgperson','person','top') static Hashtable knownSubSets = new Hashtable(30); // hash of known object class subsets static Hashtable objectClassDepths = new Hashtable(30); // hash of known object class 'depths' in object class inheritance tree static { objectClassDepths.put("top", new Integer(0)); // pre set 'synthetic' schema attributes objectClassDepths.put("schema", new Integer(1)); objectClassDepths.put("AttributeDefinition", new Integer(2)); objectClassDepths.put("ClassDefinition", new Integer(2)); objectClassDepths.put("SyntaxDefinition", new Integer(2)); } // common code run by all the more basic constructors. void basicInit() { this.id = ID++; this.atts = new Hashtable(); this.must = new HashSet(); // schema = null; } /** * Initialises an empty set of attributes with no schema */ public DXAttributes() { basicInit(); } /** * Initialises a set of attributes with a single attribute */ public DXAttributes(Attribute a) { basicInit(); put(a); } /** * Copies an existing Attributes object into a DXAttributes * object. */ public DXAttributes(Attributes a) { if (a==null) { this.atts = new Hashtable(); this.must = new HashSet(); } else { this.atts = new Hashtable(a.size()+10); this.must = new HashSet(a.size()); // overkill, but what the heck... Enumeration e = a.getAll(); while (e.hasMoreElements()) { DXAttribute newAtt = new DXAttribute((Attribute)e.nextElement()); put (newAtt); } } } /** * Initialises a set of DXattributes using * a Hashtable of existing id/attribute pairs... * @param newAtts hashtable of id/attribute pairs to * initialise the new DXAttributes object */ public DXAttributes(Hashtable newAtts) { this.atts = (Hashtable) newAtts.clone(); } /** * Initialises a set of DXattributes using * a NamingEnumeration of existing attribute(s). * @param newAtts namingenumeration of attributes to * initialise the new DXAttributes object */ public DXAttributes(NamingEnumeration newAtts) { this.atts = new Hashtable(); while (newAtts.hasMoreElements()) { Attribute current = (Attribute) newAtts.nextElement(); this.atts.put(current.getID().toLowerCase(), current); } } /** * This sets the standard schema to use while this connection is open. * (It may be possible in future releases to set schema on a per-Attribute * basis - it is not clear yet whether this would be useful.) */ public static void setDefaultSchema(SchemaOps defaultSchema) { schema = defaultSchema; } public int getID() { return this.id; } /** * creates a new DXAttributes, copying each Attribute object. * @return a new DXAttributes object */ public Object clone() { return new DXAttributes(this.atts); } /** * gets an Attribute specified by its ID * @param attrID the ID of the attribute to retrieve * @return the specified attribute (actually a DXAttribute) * - null if it can't be found. */ public Attribute get(java.lang.String attrID) { Attribute ret = (Attribute) this.atts.get(attrID.toLowerCase()); if (ret==null) { ret = (Attribute) this.atts.get(attrID.toLowerCase() + ";binary"); } return ret; } /** * returns all the Attribute(s) in this DXAttributes object. * - the NamingEnumeration is (evily) pre-sorted alphabetically... * @return enumeration of all stored Attribute objects */ public NamingEnumeration getAll() { return (new DXNamingEnumeration (this.atts.elements())).sort(); } /** * For convenience and display, DXAttributes objects have a complete * list of all attribute(s) that the objectclass(s) represented might * possibly contain. However sometimes we need an <b>Attributes</b> * object that has no null-valued attributes (i.e. when adding it to * the directory). * @return an Attributes object containing no null valued attribute(s). */ public Attributes getAsNonNullAttributes() { return new DXAttributes(getAllNonNull()); } /** * returns all the Attribute(s) that have non-null values * in this DXAttributes object. * - the NamingEnumeration is (evily) pre-sorted alphabetically...<p> * * Warning: whether an attribute is added is undefined if the * attribute is multi-valued and contains one or more null values in * addition to other non-null values. * * @return enumeration of all stored Attribute objects with non-null values */ public NamingEnumeration getAllNonNull() { DXNamingEnumeration returnEnumeration = new DXNamingEnumeration (); Enumeration allatts = getAll(); while (allatts.hasMoreElements()) { Attribute fnord = (Attribute) allatts.nextElement(); if (fnord != null) // should never happen... { try { if (fnord.get() != null) { returnEnumeration.add(fnord); // add it to the list } } catch (NoSuchElementException e) // 'expected' exception (love that jndi) { } catch (NamingException e2) { CBUtility.log("whoops: Naming Exception reading " + fnord.getID(),0); } } } returnEnumeration.sort(); return returnEnumeration; } /** * returns all the mandatory 'MUST' have Attribute(s) in this DXAttributes object. * - the NamingEnumeration is (evily) pre-sorted alphabetically... * @return enumeration of all stored Attribute objects */ public NamingEnumeration getMandatory() { DXNamingEnumeration returnEnumeration = new DXNamingEnumeration (); if (this.must==null) { return returnEnumeration; // return empty enumeration if not initialised... } Iterator musts = this.must.iterator(); while (musts.hasNext()) { String s = (String)musts.next(); returnEnumeration.add(get(s)); } returnEnumeration.sort(); return returnEnumeration; } /** * Returns the list of mandatory attribute IDs (HashSet:must). * @return the list of mandatory attribute IDs (e.g. objectClass, cn, sn). */ public HashSet getMandatoryIDs() { return this.must; } /** * returns all the optional 'MAY' Attribute(s) in this DXAttributes object. * - the NamingEnumeration is (evily) pre-sorted alphabetically... * @return enumeration of all stored Attribute objects */ public NamingEnumeration getOptional() { DXNamingEnumeration returnEnumeration = new DXNamingEnumeration (); Enumeration allIDs = this.atts.keys(); while (allIDs.hasMoreElements()) { String id = (String) allIDs.nextElement(); if (this.must.contains(id)==false) { returnEnumeration.add(get(id)); // add it to the optional list } } returnEnumeration.sort(); return returnEnumeration; } /** * returns all the attribute IDs held in this DXAttributes object. * @return all attribute IDs */ public NamingEnumeration getIDs() { // cannot simply return hash keys, as they are standardised to lower case. DXNamingEnumeration ret = new DXNamingEnumeration(); NamingEnumeration allAtts = getAll(); while (allAtts.hasMoreElements()) { ret.add(((Attribute)allAtts.nextElement()).getID()); } return ret; //return (NamingEnumeration)(new DXNamingEnumeration (atts.keys())); } /** * returns whether attribute IDs are stored in a case * sensitive manner; i.e. whether 'person' is different * from 'Person'. The default is <i>false</i>, implying * case sensitivity. * @return whether case is ignored for attribute IDs */ public boolean isCaseIgnored() { return this.ignoreCase; } /** * adds an attribute to the DXAttributes collection, * using the attribute.getID() method to find a key. * NB: doco seems unclear on whether this adds to, * or replaces, any existing attribute with the same * ID. At the moment this <b>replaces</b> the values... * @param attr the attribute to add * @return the previous attribute (if any) with this key. */ public Attribute put(Attribute attr) { if (attr == null) { return null; // sanity check - can't add a null attribute... } Attribute old = get(attr.getID().toLowerCase()); this.schemaChecked = false; if (old!=null) { this.atts.remove(old.getID().toLowerCase()); // code for *replacing* existing attribute values } String ID = attr.getID().toLowerCase(); if (attr instanceof DXAttribute) { this.atts.put(ID, attr); } else { this.atts.put(ID, new DXAttribute(attr)); } return old; } /** * creates an attribute with the specified values and adds it * to the DXAttributes collection, * using the attrID string as a key. * NB: doco seems unclear on whether this adds to, or replaces, * an existing attribute with the same ID. This implementation * <b>adds</b> the new object value... * * @param attrID the String ID of the newly added attribute * @param val the value to associate with the ID for the newly * created attribute * @return the previous attribute (if any) with this key. */ public Attribute put(java.lang.String attrID, java.lang.Object val) { this.schemaChecked = false; return put(new DXAttribute(attrID.toLowerCase(), val)); } /** * Adds an Enumeration of Attribute(s) to a DXAttribute * * @param attributeList the list of attributes to add. */ public void put(Enumeration attributeList) { while (attributeList.hasMoreElements()) { Attribute a = (Attribute)attributeList.nextElement(); if (a instanceof DXAttribute) { put(a); } else { put(new DXAttribute(a)); } } } /** * removes the attribute containing this key (if any). * @param attrID the ID of the attribute to remove * @return the removed attribute (if any) */ public Attribute remove(java.lang.String attrID) { this.schemaChecked = false; return (Attribute) this.atts.remove(attrID.toLowerCase()); } /** * returns the number of Attribute objects held. * * @return number of attribute objects */ public int size() { return this.atts.size(); } /** * searches the schema for all the possible attributes that the * objectClasses present *could* have, and adds new * attributes that are not already present to the list * of current Attribute objects as empty attributes. */ /* public void checkSchema() { if ((schema == null)|| (schemaChecked==true)) return; // nothing to do try { // update the hashtable with complete list // of attributes as read from schema expandAllAttributes(getAllObjectClasses()); //print(); } catch (NamingException e) { CBUtility.log("Naming Exception in Schema Read of DXAttribute\n " + e); } schemaChecked = true; } */ /* public String getBaseObjectClass() { if (baseObjectClass == null) { setAllObjectClasses(); if (baseObjectClass == null) // Still? Must be an error somewhere... return "top"; } return baseObjectClass; } */ public void setAllObjectClasses() { this.allObjectClasses = getAllObjectClasses(); } /** * Return a list of object classes as a vector from deepest (at pos 0) to 'top' (at pos (size()-1) ). */ public Vector getOrderedOCs() { Vector ret = null; try { Attribute oc = getAllObjectClasses(); if (oc == null) { return null; } ret = new Vector(oc.size()); NamingEnumeration vals = oc.getAll(); while (vals.hasMore()) { ret.add(vals.next()); } return ret; } catch (NamingException e) { CBUtility.log("Yet another rare naming exception - DXAttributes:getOrderedOCs " + e); return new Vector(0); } } public Attribute getAllObjectClasses() { Attribute att = get("objectclass"); if (att != null) //TE: check that the attribute is set. { if (att instanceof DXAttribute) { return getAllObjectClasses((DXAttribute)att); } else { return getAllObjectClasses(new DXAttribute(att)); } } return null; //TE: return null if att is null. } /** * Some directories don't include all of an entry's object classes, * but only the lowest level ones. This looks up all the parents * the low level object classes are inherited from and, forms a * complete ordered list of all object classes for this Attributes object. * * @return an Attribute containing the names of all the object classes... */ // XXX objecClass should no longer be case sensitive. When happy this works, // XXX delete code below. public static DXAttribute getAllObjectClasses(DXAttribute oc) { if (oc==null) { return null; // no object classes (may be virtual entry such as DSA prefix) } if (knownSubSets.containsKey(oc)) { return(DXAttribute) knownSubSets.get(oc); } try { DXAttribute orig = new DXAttribute(oc); Enumeration vals = oc.getAll(); while (vals.hasMoreElements()) { Attribute parents = getParentObjectClasses((String)vals.nextElement()); if (parents != null) { Enumeration parentVals = parents.getAll(); while (parentVals.hasMoreElements()) { String parent = (String) parentVals.nextElement(); if (oc.contains(parent) == false) { oc.add(parent); } } } } DXAttribute fullOC = sortOCByDepth(oc); knownSubSets.put(orig, fullOC); return fullOC; } catch (NamingException e) { CBUtility.log("NamingException in getAllObjectClasses " + e); return oc; } } /** * Takes a list of *all* object class values, and returns them * sorted by depth. This requires the objectClassDepths hashtable * to be set (which is done by getParentObjectClasses() ). */ protected static DXAttribute sortOCByDepth(Attribute oc) { DXAttribute ret = new DXAttribute("objectClass"); ret.setOrdered(true); try { Enumeration vals = oc.getAll(); while (vals.hasMoreElements()) { String val = (String) vals.nextElement(); Integer myInt = (Integer)objectClassDepths.get(val); if (myInt == null) // shouldn't happen (if schema is correct), but in case we missed one... { getParentObjectClasses(val); // try again to set the objectClassDepths hash for this value. (probably won't work). myInt = (Integer)objectClassDepths.get(val); // and try to reget the value. if (myInt == null) { myInt = new Integer(0); } } int depth = myInt.intValue(); int i; for (i=ret.size()-1; i>=0; i--) { int existing = ( (Integer)objectClassDepths.get(ret.get(i)) ).intValue(); if ( existing >= depth) { ret.add(i+1, val); break; } } if (i == -1) { ret.add(0, val); } } return ret; } catch (NamingException e) { CBUtility.log("Naming Exception in DXAttributes sortOCByDepth()" + e); return new DXAttribute(oc); } } /** * recursively builds up a complete ordered list of all the parents of a particular * object class (including the child object class) from schema. * * @param childOC the child Object Class to search for parents of * @return an attribute containing the child class and all parents */ public static DXAttribute getParentObjectClasses(String childOC) throws NamingException { if (schema == null) // in the absence of a schema, everything is at level '1', just below 'top' { objectClassDepths.put(childOC, new Integer(1)); return null; } if ("schema attributedefinition classdefinition syntaxdefinition matchingrule".indexOf(childOC.toLowerCase()) != -1) { return null; // don't bother looking up synthetic object classes. } if (knownParents.containsKey(childOC)) { return (DXAttribute) knownParents.get(childOC); } DXAttribute parents = new DXAttribute("objectClass"); // this is the attribute returned String schemaParent = null; try { //schemaParents = schema.getAttributes("ClassDefinition/" + childOC, new String[] {"SUP"}); Attributes schemaDef = schema.getAttributes("ClassDefinition/" + childOC); if (schemaDef!=null) { Attribute sup = schemaDef.get("SUP"); if (sup!=null) { schemaParent = (String)sup.get(); } } } catch (NamingException e) // easily throws a name-not-found exception { CBUtility.log("Possible Schema Error: class definition for " + childOC + " could not be found"); objectClassDepths.put(childOC, new Integer(1)); // BAD SCHEMA! NO BISCUIT! Set to one 'cause we don't know true depth (and top is always zero). return null; // do nothing } // TODO: this is silly; why don't we just reuse the DXAttribute object returned? // XXX - no time to fix now; maybe later... if (schemaParent != null) // may not: e.g. 'top' has no parent { DXAttribute oc = getParentObjectClasses(schemaParent); // recurse -> this should also set the depth in objectClassDepths if (oc != null) { Enumeration vals = oc.getAll(); while (vals.hasMoreElements()) // load up the return attribute { parents.add(vals.nextElement()); // (o.k. to add the same value twice...) } } int depth = ((Integer)objectClassDepths.get(schemaParent)).intValue(); if (objectClassDepths.containsKey(childOC) == false) { objectClassDepths.put(childOC, new Integer(depth+1)); } else { int oldDepth = ((Integer)objectClassDepths.get(childOC)).intValue(); if (oldDepth <= depth) { objectClassDepths.put(childOC, new Integer(depth+1)); } } } else // no schemaParents { objectClassDepths.put(childOC, new Integer(1)); // BAD SCHEMA! NO BISCUIT! Set to one 'cause we don't know true depth (and top is always zero). //schemaParents = null; // so store a blank place holder } parents.add(childOC); // *** Note Cunning Recursive Magic - this is where base stuff actually gets added to parents... *** knownParents.put(childOC, parents); return parents; } /** * Get structural object classes, in deepest oc first order. * @return vector of structural OCs (may be null if can't be resolved) */ /* public Vector getOrderedStructOCs() { try { if (orderedSOCs.size() != 0) return orderedSOCs; if (baseObjectClass == null) setAllObjectClasses(); setOrderedSOCs(baseObjectClass); return orderedSOCs; } catch (Exception e) { return orderedSOCs; } } */ /** * Sets the vector of structural object classes * in this attribute set. * @param oc the object class to find (and add) the structural parents of. */ void setOrderedSOCs(String oc) throws NamingException { this.orderedSOCs.add(oc); if (oc.equalsIgnoreCase("top")) { return; // recursive search finished. } String parent = schema.schemaLookup("ClassDefinition/" + oc, "SUP"); String struct = schema.schemaLookup("ClassDefinition/" + parent, "STRUCTURAL"); // try to figure out if that was a structural object class... if ("true".equalsIgnoreCase(struct) ) { setOrderedSOCs(parent); // recurse to next set of parents. return; // finished. } /* Attributes parents = null; parents = schema.getAttributes("ClassDefinition/" + oc, new String[] {"SUP"}); // returns one attribute NamingEnumeration parentList = parents.get("SUP").getAll(); // get that attribute's values. while (parentList.hasMore()) { String id = parentList.next().toString(); Attributes struct = schema.getAttributes("ClassDefinition/" + id, new String[] {"STRUCTURAL"}); // try to figure out if that was a structural object class... if (struct != null && struct.get("STRUCTURAL") != null && ("true".equalsIgnoreCase(struct.get("STRUCTURAL").get().toString())) ) { setOrderedSOCs(id); // recurse to next set of parents. return; // finished. } } */ } /** * Adds an OID / ldapName combination to the hashtable * of all oid / ldapNames.<p> * * If the oid is actually an ldap String (different servers * return different things...) it doesn't bother registering * the string... * * @param oid the numeric oid to register * @param ldapName the name of the corresponding ldap descr * @return true if new oid was registered, false if it was already known */ public boolean registerOID(String oid, String ldapName) { // Don't register non-oids... char test = oid.charAt(0); if (test < '0' || test > '9') // not an oid { return false; } // XXX ;binary hack. if (oid.endsWith(";binary")) { oid = oid.substring(0,oid.indexOf(";binary")); } if (ldapName.endsWith(";binary")) { ldapName = ldapName.substring(0,ldapName.indexOf(";binary")); } if (attOIDs.contains(oid)==true) { return false; } attOIDs.put(oid, ldapName); // add it to the list... return true; } /** * This method trims all empty attributes (attributes without values) from * the DXAttributes object. */ public void removeEmptyAttributes() { Enumeration atts = getAll(); while (atts.hasMoreElements()) { Attribute att = (Attribute)atts.nextElement(); if (att.size() == 0) { remove(att.getID()); } } } /** * Sets the internal list of all attribute IDs needed for * a given set of object classes, as well as noting which * are mandatory and which optional.<p> * * As an added wrinkle, this must be able to cope with attribute * ID's expressed either as ldap strings, or as numeric OIDs. It * does this by automatically detecting OIDs, and translating them * to strings using schema lookups.<p> * * This method uses the schema to create empty valued attributes for * attributes which don't currently exist, but which are allowed. * */ public void expandAllAttributes() { if (schema == null) { return; } Attribute oc = null; oc = getAllObjectClasses(); try { // Quick Hack to eliminate 'fake attributes' used for top level of syntaxes... //XXX Might want to redo if efficiency is ever a concern :-) if (oc.contains(SchemaOps.SCHEMA_FAKE_OBJECT_CLASS_NAME) ) { return; // ignore the synthetic 'schema' object classes... } NamingEnumeration ocs = oc.getAll(); // cycle through all object classes, finding attributes for each while (ocs.hasMore()) { String objectClass = (String)ocs.next(); Attributes ocAttrs = schema.getAttributes("ClassDefinition/" + objectClass); Attribute mustAtt = ocAttrs.get("MUST"); // get mandatory attribute IDs Attribute mayAtt = ocAttrs.get("MAY"); // get optional attribute IDs if (mustAtt != null) { NamingEnumeration musts = mustAtt.getAll(); while (musts.hasMore()) { String attOID = (String) musts.next(); //XXX binary hack if (attOID.indexOf(";binary")>0) { attOID = attOID.substring(0,attOID.indexOf(";binary")); } String ldapName = getldapName(attOID); registerOID(attOID, ldapName); if (get(ldapName)==null) // if we don't already have this attribute... { put(new DXAttribute(getldapName(attOID))); // ... add it to the list //CB empty atts now. put(new DXAttribute(getldapName(attOID), null)); // ... add it to the list } if (this.must.contains(ldapName.toLowerCase())==false) // and if it isn't already mandatory { this.must.add(ldapName.toLowerCase()); // ... add it to the mandatory list as well } } } if (mayAtt != null) { NamingEnumeration mays = mayAtt.getAll(); while (mays.hasMore()) { String attOID = (String) mays.next(); //XXX binary hack if (attOID.indexOf(";binary")>0) { attOID = attOID.substring(0,attOID.indexOf(";binary")); } String ldapName = getldapName(attOID); registerOID(attOID, ldapName); if (get(ldapName)==null) // if we don't already have this one... { put(new DXAttribute(getldapName(attOID))); // ... add it to the list //put(new DXAttribute(getldapName(attOID), null)); // ... add it to the list } } } } } catch (NamingException e) { CBUtility.log("unable to read attribute list for object classes: "); try { CBUtility.printEnumeration(oc.getAll()); } catch (NamingException e2) { CBUtility.log("...(further error: can't print object class list)..."); } CBUtility.log(" ... error is: " + e); return; } catch (NullPointerException e) { CBUtility.log("ERROR: unable to read list of object classes from schema - some functionality will not be available"); StackTraceElement trace[] = e.getStackTrace(); for (int i=0; i<trace.length; i++) { CBUtility.log(" " + trace[i].toString()); } } } /** * This method does it's darndnest to return a string ldap name.<p> * First, it checks whether the string is <i>already</i> an ldap * name; if it is, it returns it unchanged.<p> * * Secondly, it tries to find the ldap text name for an oid * (i.e. converts 2.5.4.0 to 'objectClass').<p> * * Finally, if it <b>can't</b> find the name it returns the oid instead... * (This shouldn't really happen, but means that the system may still work, * although the raw OIDs aren't very user friendly)<p> */ public String getldapName(String attOID) { return (schema==null?attOID:schema.translateOID(attOID)); /* try { // Don't register non-oids... char test = attOID.charAt(0); if (test < '0' || test > '9') // not an oid { return attOID; } if (attOIDs.containsKey(attOID)) // check if we already have one return (String) attOIDs.get(attOID); // specify search constraints to search subtree SearchControls constraints = new SearchControls(); constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE); constraints.setCountLimit(1); constraints.setTimeLimit(0); String searchfilter = "(NUMERICOID="+attOID + ")"; NamingEnumeration results = schema.search("AttributeDefinition", searchfilter, constraints); if (results.hasMoreElements()) { SearchResult result = (SearchResult) results.next(); Attributes a = result.getAttributes(); // may throw null pointer exception, caught below? Attribute ldapName = (Attribute)a.get("NAME"); // may throw null pointer exception, caught below? String name = (String)ldapName.get(); // may throw null pointer exception, caught below? //XXX ;binary hack if (name.indexOf(";binary")>0) name = name.substring(0,name.indexOf(";binary")); return name; } else {CBUtility.log("can't read schema for: " + attOID + "... no results"); return attOID; } } catch (Exception e) { {CBUtility.log("can't read schema for: " + attOID + "\n " +e); return attOID; } } */ } public String toString() { StringBuffer text = new StringBuffer("size (" + size() + ")\n"); NamingEnumeration allatts = this.getAll(); while (allatts.hasMoreElements()) { Attribute fnord = (Attribute) allatts.nextElement(); if (fnord == null) { CBUtility.log("bizarre null attribute in element list"); } else { if (this.must != null && this.must.contains(fnord.getID())) { text.append("must "); } if (fnord instanceof DXAttribute) { text.append("dx " + fnord.toString()); } else { String ID = fnord.getID(); text.append("\n " + ID + " (not DXAttribute)" ); try { if (fnord.size() == 0) { text.append(" " + " (empty) "); } else { Enumeration vals = fnord.getAll(); while (vals.hasMoreElements()) { Object val = vals.nextElement(); String fnordel = (val==null)?"*null*":val.toString(); text.append(" " + fnordel); } } } catch (NamingException e) { CBUtility.log("whoops: Naming Exception reading " + ID + "\n " + e); } } } } return text.toString(); } public void print() { print(null); } public void print(String msg) { if (msg!=null) { System.out.println(msg); } printAttributes(this); } public static void printAttributes(Attributes a) { if (a==null) { System.out.println("null attributes set"); } NamingEnumeration allatts = a.getAll(); printAttributeList(allatts); } public static void printAttributeList(NamingEnumeration enumer) { while (enumer.hasMoreElements()) { Attribute fnord = (Attribute) enumer.nextElement(); if (fnord == null) { CBUtility.log("bizarre null attribute in element list"); } else { String ID = fnord.getID(); System.out.println(" " + ID); try { Enumeration vals = fnord.getAll(); while (vals.hasMoreElements()) { Object val = vals.nextElement(); String fnordel = (val==null)?"*null*":val.toString(); System.out.println(" " + fnordel); } } catch (NamingException e) { CBUtility.log("whoops: Naming Exception reading " + ID + "\n " + e); } } } } /** * <p>Returns the set of attributes that are in the newSet, but * not in the old set, excluding the rdnclass).</p> * * <p>It also returns partial attributes that contain values that * are in the newSet but not in the oldSet, when either a) * either attribute has a size larger than one, or b) the attribute has * a distinguished value.</p> * * <p>The logic for case a) is that we can use adds and deletes on * individual attributes to simulate a 'replace' operation, but we * need to avoid doing this for single valued attributes (for which * we *always* use replace). So if Attribute A has values {1,2,3}, * and is changed to A' {1,3,5,6}, this method returns an 'add' {5,6}.</p> * * <p>The logic for case b) is that we cannot use 'replace' on an * attribute with a naming value in it, so we *must* use adds and * deletes - and we'll have to take our chances that it is not * single valued. (Possibly later on we can check this from schema). * This method cannot change the distinguished value, but produces an * 'add' for any other changed. So if, for entry cn=fred, Attribute cn * has values {fred,erik} * and cn' has values {fred, nigel], this method produces an 'add' {nigel}.</p> * * * @param newRDN the new RDN of the entry that is being created * (usually this is the same as the RDN of the original entry) * May be null if it is not to be checked. * @param oldSet the set of already existing attributes * @param newSet the set of new attributes to test * @return attributes that must be added to the object. */ public static DXAttributes getAdditionSet(RDN newRDN, Attributes oldSet, Attributes newSet) throws NamingException { DXAttributes additionSet = new DXAttributes(); NamingEnumeration listOfNewAttributes = newSet.getAll(); while (listOfNewAttributes.hasMore()) { Attribute newAtt = (Attribute)listOfNewAttributes.next(); String attributeName = newAtt.getID(); boolean isNamingAttribute = newRDN.contains(attributeName); Attribute oldAtt = oldSet.get(attributeName); if (! emptyAtt(newAtt)) // don't add empty atts! { /* * Check for simple "whole attribute" adds * if the old Att is null, or only had null values add it. * (It was probably created by the browser, * and doesn't exist in the database) */ if ((isNamingAttribute == false) && (oldAtt == null || emptyAtt(oldAtt))) { additionSet.put(newAtt); } /* * Check for attribute values that have been changed in attributes * that are larger than 1, or that are naming attributes */ // TODO: - clean this up for DSML etc. ... else if (isNamingAttribute || (oldAtt.size() > 1 || newAtt.size() > 1 )) { DXNamingEnumeration valuesToAdd = getMissingValues(oldAtt.getAll(), newAtt.getAll()); // check for distinguished value, and ignore it... if (isNamingAttribute) { removeAnyDistinguishedValues(newRDN, attributeName, valuesToAdd); } if (valuesToAdd.size()>0) { additionSet.put(new DXAttribute(attributeName, valuesToAdd)); } } } } return additionSet; } /** * <p>Returns all single valued attributes whose values have changed - * that is, exist in both the new set and the old set, but have different values. * Note that this function ignores the naming attribute.</p> * * <p>We need this function to cope with mandatory single valued attributes * (otherwise we could just use add and delete).</p> * * <p>All other attribute combinations are handled by attribute value adds and * deletes. (This is slightly more efficient, and is required to modify * non-distinguished values of the naming attribute anyway).</p> * * @param newRDN the RDN of the newer entry. * (usually this is the same as the RDN of the original entry) * May be null if it is not to be checked. * @param oldSet the set of already existing attributes * @param newSet the set of new attributes to test * @param alwaysReplace option to always include a given attribute, even if it * is unchanged (special purpose hack for OS390 group) * @return attributes that require updating */ // LOGIC NOTE - only replace attributes that are single valued (old & new) and NOT naming values. public static DXAttributes getReplacementSet(RDN newRDN, Attributes oldSet, Attributes newSet) throws NamingException { DXAttributes replacementSet = new DXAttributes(); NamingEnumeration listOfNewAttributes = newSet.getAll(); while (listOfNewAttributes.hasMore()) // find changed attributes { Attribute newAtt = (Attribute)listOfNewAttributes.next(); if (newAtt != null && newAtt.size() == 1) // only consider a single valued new attribute { String attributeName = newAtt.getID(); if (newRDN.contains(attributeName) == false) // skip any naming attributes { Attribute oldAtt = oldSet.get(attributeName); if (oldAtt != null && oldAtt.size() == 1) // only look at changed single valued attributes. { // if a single valued attribute has changed, make it a 'replace' op. if (attributesEqual(newAtt, oldAtt)==false) { replacementSet.put(newAtt); } } } } } return replacementSet; } /** * <p>Returns the set of attributes that are in the oldSet, but * not in the new set, and thus must be deleted. </p> * * <p>It also returns the set of attribute *values* that are in * the old set, but not in the new set. E.g. if attribute A * has values {1,2,3,4}, and the new attribute A' has {1,4,6,7}, * this returns {2,3} for deletion.</p> * * <p>This method will ignore naming values, but will correctly * handle other values of the naming attribute.</p> * * @param newRDN the RDN of the newer entry. * (usually this is the same as the RDN of the original entry). * May be null if it is not to be checked. * @param oldSet the set of already existing attributes * @param newSet the set of new attributes to test */ public static DXAttributes getDeletionSet(RDN newRDN, Attributes oldSet, Attributes newSet) throws NamingException { DXAttributes deletionSet = new DXAttributes(); NamingEnumeration listOfOldAttributes = oldSet.getAll(); while (listOfOldAttributes.hasMore()) { Attribute oldAtt = (Attribute)listOfOldAttributes.next(); if (! emptyAtt(oldAtt)) // don't delete empty atts! { String attributeName = oldAtt.getID(); boolean isNamingAttribute = newRDN.contains(attributeName); Attribute newAtt = newSet.get(attributeName); if (newAtt == null) { newAtt = new DXAttribute(attributeName); } /* * Check for simple "whole attribute" deletes */ if (emptyAtt(newAtt) && !isNamingAttribute) { deletionSet.put(newAtt); } /* * Check for attribute values that have been dropped, in attributes * that are larger than 1 */ else if (isNamingAttribute || oldAtt.size() > 1 || newAtt.size() > 1 ) { DXNamingEnumeration valuesToDelete = getMissingValues(newAtt.getAll(), oldAtt.getAll()); // check for distinguished value, and ignore it... if (isNamingAttribute) { removeAnyDistinguishedValues(newRDN, attributeName, valuesToDelete); } if (valuesToDelete.size()>0) { deletionSet.put(new DXAttribute(attributeName, valuesToDelete)); } } } } return deletionSet; } /** * Checks two 'Attribute' objects for equality. * XXX - should this be moved to DXAttribute? */ private static boolean attributesEqual(Attribute a, Attribute b) throws NamingException { // sanity checks... if (a == null && b == null) { return true; } if (a == null || b == null) { return false; } if (a.size() == 0 && b.size() == 0) { return true; } if (a.size() != b.size()) { return false; } if (a.get() == null && b.get() == null) { return true; } if (a.get() == null || b.get() == null) { return false; } if (a.getID().equalsIgnoreCase(b.getID())==false) { return false; } try { Object[] A = CBArray.enumerationToArray(a.getAll()); Object[] B = CBArray.enumerationToArray(b.getAll()); return CBArray.isUnorderedEqual(A,B); } catch (NamingException e) { CBUtility.log("Naming Exception testing attributes " + a.getID() + " & " + b.getID() + " in DXAttributes:attributesEqual()"); } return false; // only here if error occurred. } /** * Checks whether two 'Attributes' objects are equivalent (including naming attributes, if any). */ public static boolean attributesEqual(Attributes a, Attributes b) { if (a == null && b == null) { return true; } if (a == null || b == null) { return false; } return a.equals(b); } public boolean equals(Object o) { if (o == null) { return false; } try { if (o instanceof Attributes) { return this.equals((Attributes) o); } } catch (NamingException e) { return false; // suppress exception :-(... } return false; } public boolean equals(Attributes atts) throws NamingException { // some quick and simple equality checks if (atts == null) { return false; } if (size() == 0 && atts.size() == 0) { return true; } if (size() != atts.size()) { return false; } // at this stage, we have equality candidates - two equally sized attributes... NamingEnumeration testAtts = getAll(); while (testAtts.hasMore()) // find changed attributes { Attribute testAtt = (Attribute)testAtts.next(); String ID = testAtt.getID(); Attribute bAtt = atts.get(ID); if ( emptyAtt(bAtt) ^ emptyAtt(testAtt) ) { return false; } if (attributesEqual(testAtt, bAtt) == false) { return false; } } // if we're here, the attributes must be equal! return true; } /** * Checks the naming enumeration and removes any distinguished values that * occur in the RDN. * @param newRDN the rdn to check for values in * @param attributeName the name of the attribute (potentially) in the RDN * @param values the list of values to potentially remove the distinguished value from */ private static void removeAnyDistinguishedValues(RDN newRDN, String attributeName, DXNamingEnumeration values) { String distinguishedValue = newRDN.getRawVal(attributeName); values.remove(distinguishedValue); // remove dist. val. (if it is there) } /** * Returns all the values in B that are missing in A. (i.e. return B minus (B union A)). * @param A the list of values to exclude. * @param B the list of values to include. * @return all elements of B not found in A. */ static private DXNamingEnumeration getMissingValues(NamingEnumeration A, NamingEnumeration B) throws NamingException { DXNamingEnumeration ret = new DXNamingEnumeration(B); if (A == null) { return ret; } while (A.hasMore()) { ret.remove(A.next()); } return ret; } // public DirContext getSchema() { return schema; } public String[] toIDStringArray() { DXNamingEnumeration ret = new DXNamingEnumeration(getIDs()); return ret.toStringArray(); } /** * A quick test - do we have any numeric OIDs * */ public boolean hasOIDs() { return (attOIDs.size()>0); } /** * Utility ftn: checks that an attribute is not null and has at least * one non-null value. */ public static boolean emptyAtt(Attribute att) { return DXAttribute.isEmpty(att); } }