package com.ca.commons.naming;
import java.lang.String;
import java.util.Vector;
import java.util.Enumeration;
import javax.naming.*;
//import com.ca.commons.cbutil.CBIntText;
/**
* A Data class that encapsulated the idea of an
* ldap Distinguished Name of the form:
* ou=frog farmers,o=frogcorp,c=au
* - and provides a bunch of utility methods for modifying
* and reading these values, especially the various bits
* of each rdn in various ways. <p>
*
* implements javax.naming.Name.<p>
*
* Why don't we just use CompoundName or CompositeName?<br>
*
* - basically because we're not supporting multiple
* naming systems - we're *only* supporting ldap. So Name is implemented
* for support with existing jndi ftns, but also a lot of other stuff
* is needed for rdns, and multi-value rdns. This could be architected
* as, say, compound name with another helper class, but that seems
* clumsy.<p>
*
*/
//
// Can be used stand alone with com.ca.commons.naming.RDN, com.ca.commons.naming.NameUtility, com.ca.commons.cbutil.CBParse
//
//
//
public class DN implements Name
{
// these variables are all there is! Basically the magic is in the vector of
// rdns - all the code below is simply utility stuff for parsing and manipulating
// those rdns.
private Vector RDNs; // a list of segment RDNs, as strings, e.g. 'ou=frog farmers'
// element 0 is the 'root' RDN (i.e. 'c=au')
// element (RDNs.size()-1) is the lowest RDN (i.e. 'cn=fred').
boolean binary = false; // whether the dn contains isNonString data, and should
// be base64 encoded before being written out...
// XXX Candidate for refactoring!
// Rather than throwing errors (which they probably should) some methods
// cache error information and expect the caller to check the error status
// what can I say. I was young. - CB
String errorString = "";
// the cached root exception.
NamingException rootException = null;
// boolean empty = false; // whether this is the blank DN "".
/**
* Default constructor creates a DN with no value set.
*/
public static String BLANKBASEDN = "World";
public DN()
{
RDNs = new Vector();
}
/**
* Copy constructor creates a new DN with an item by item
* <i>copy</i> of the parameter DN.
*
* @param copyMe the DN to be copied
*/
public DN(DN copyMe)
{
try
{
RDNs = new Vector();
if (copyMe != null)
{
for (int i=0; i<copyMe.size(); i++)
{
add(new String(copyMe.get(i)));
}
}
}
catch (InvalidNameException e) // 'impossible' error - if copyMe is DN, how can this fail?
{
setError("error cloningDN " + copyMe.toString(), e);
clear();
}
}
/**
* Main Constructor takes an ldap Distinguished Name string
* (e.g. 'ou=wombat botherers,o=nutters inc,c=au') and
* breaks it up into a vector of RDNs
*
* @param ldapDN the ldap distinguished name to be parsed.
*/
public DN(String ldapDN)
{
try
{
RDNs = new Vector();
if ("".equals(ldapDN) || BLANKBASEDN.equals(ldapDN))
{
return;
}
int start = 0;
int end = NameUtility.next(ldapDN, 0, ',');
// get the RDNs in the form xxx=xxx,xxx=xxx,xxx=xxx
while (end!=-1)
{
String rdn = ldapDN.substring(start,end);
add(0,rdn);
start = end+1;
end = NameUtility.next(ldapDN, start, ',');
}
// ... and the last bit...
add(0,ldapDN.substring(start).trim());
}
catch (InvalidNameException e)
{
setError("unable to make DN from " + ldapDN ,e);
clear();
}
}
/**
* This Constructor takes an existing jndi Name,
* And initialises itself by taking that Name's rdn elements
* an element at a time, and converting them to RDN objects.
*
* @param name the ldap distinguished name to be parsed.
*/
public DN(Name name)
{
try
{
RDNs = new Vector();
if (name.isEmpty()) return;
for (int i=0; i<name.size(); i++)
{
add(i,name.get(i));
}
}
catch (InvalidNameException e)
{
setError("unable to create DN from name: " + name.toString(), e);
clear();
}
}
/*
public DN(byte[] name)
{
setError("Binary Distinguished Names not yet implemented ");
}
*/
/**
* Spits back the DN as an escaped ldap DN string
*
* @return ldap DN in normal form (e.g. 'ou=linux fanatics,o=penguin fanciers pty. ltd.,c=us')
*/
public String toString()
{
String ldapDN = "";
for (int i=0; i<RDNs.size(); i++)
ldapDN = get(i) + (i!=0?",":"") + ldapDN;
if (ldapDN.endsWith(","))
{
if (ldapDN.charAt(ldapDN.length()-2) != '\\')
{
ldapDN = ldapDN.substring(0,ldapDN.length()-1);
}
}
return ldapDN;
}
/**
* Spits back the DN as a tree-level formatted string (mainly for debugging)
*
* @return ldap DN in level form (e.g. <pre>\nou=linux fanatics \no=penguin fanciers pty. ltd.\n c=us\n</pre>)
*/
public String toFormattedString()
{
String ldapDN = "";
for (int i=0; i<RDNs.size(); i++)
ldapDN += get(i) + "\n";
return ldapDN;
}
/**
* a synonym for 'toString()' this returns the full ldap DN.
* @deprecated - use toString().
* @return the full ldap Distinguished Name
*/
public String getDN() { return toString(); }
/**
* gets the ldap 'class' (e.g. 'c' or 'cn') for a particular
* RDN. If there are multiple attributes (for a multi-valued
* rdn) only the first is returned (XXX).
*
* @param i the index of the RDN class to return.
* @return the ldap class name for the specified RDN
*/
public String getRDNAttribute(int i)
{
if (isEmpty()) return "";
if (i >= size()) return "";
if (i < 0) return "";
return getRDN(i).getAtt();
}
/**
* gets the ldap value (e.g. 'au' or 'Silverstone, Alicia') for a particular
* RDN. If there are multiple values, only the first is
* returned (XXX).
*
* @param i the index of the RDN value to return.
* @return the actual value for the specified RDN.
*/
public String getRDNValue(int i)
{
if (isEmpty()) return "";
if (i >= size()) return "";
if (i < 0) return "";
return getRDN(i).getRawVal();
}
/**
* dumps the dn in a structured form, demonstrating parsing.
*/
public void debugPrint()
{
System.out.print("\n");
for (int i=0; i<size(); i++)
{
System.out.print("element [" + i + "] = " + get(i).toString() + "\n");
getRDN(i).dump();
}
}
/**
* gets the full RDN (e.g. 'c=au' or 'cn=Englebert Humperdink') for a particular
* indexed RDN.
*
* @param rdn the ldap RDN string name for the specified index
* @param i the index of the RDN to set.
*/
public void setRDN(RDN rdn, int i)
{
if (i<size() && i>= 0)
RDNs.setElementAt(rdn, i);
}
/**
* gets the full RDN (e.g. 'c=au' or 'cn=Englebert Humperdink') for a particular
* indexed RDN.
*
* @param i the index of the RDN to return.
* @return the ldap RDN string name for the specified index
*/
public RDN getRDN(int i)
{
if (i==0 && isEmpty()) return new RDN(); // return empty RDN for empty DN
if (i<0) return new RDN();
if (i >= size()) new RDN();
return (RDN) RDNs.elementAt(i);
}
/**
* Returns the root RDN as a string (e.g. 'c=au')
*
* @return the root RDN string.
*/
public RDN getRootRDN()
{
if (isEmpty())
return new RDN("");
else
return getRDN(0);
}
/**
* Gets the value of the lowest LDAP RDN.
* That is, the furthest-from-the-root class value of the DN.
* For example, 'ou=frog fanciers' in 'ou=frog fanciers,o=nutters,c=uk'
*
* @return the lowest level ldap value for the DN
*/
public RDN getLowestRDN()
{
return getRDN(size()-1);
}
/**
* Adds an RDN to an existing DN at the highest level
* - mainly used internally
* while parsing a DN.
*
* @param rdn the RDN string to apend to the DN
*/
public void addParentRDN(String rdn)
{
try
{
add(0,rdn);
}
catch (InvalidNameException e)
{
setError("Error adding RDN in DN.addParentRDN()", e);
}
}
/**
* Adds a new 'deepest level' RDN to a DN
*
* @param rdn the RDN to append to the end of the DN
*/
public void addChildRDN(String rdn)
throws InvalidNameException
{
add(rdn);
}
/**
* Adds a new 'deepest level' RDN to a DN
*
* @param rdn the RDN to append to the end of the DN
*/
public void addChildRDN(RDN rdn)
throws InvalidNameException
{
add(rdn);
}
/**
* sets (or more often <i>re</i>sets) the lowest (furthest-from-root)
* value of the DN
*
* @param value the (raw, unescaped) new lowest RDN value to overwrite the existing lowest RDN value with
*/
// XXX code should be turned into wrapper for add(0,...) when that handles multi-val rdns properly
public void setLowestRDNRawValue(String value)
{
try
{
RDN rdn = getRDN(size()-1);
rdn.setRawVal(value);
}
catch (InvalidNameException e)
{
setError("Error setting DN.setLowestRDNRawValue: to " + value, e);
}
}
/**
* Exchanges the value of an rdn att=val element, and returns
*/
protected String exchangeRDNelementValue(String rdn, String value)
{
return rdn.substring(0,NameUtility.next(rdn,0,'=')) + "=" + value;
}
/**
* Check whether this DN is equal to another DN...
*
* @param testDN the DN to compare against this DN
*/
public boolean equals(DN testDN)
{
//XXX return (toString().equals(testDN.toString()));
if (testDN == null) return false;
if (testDN.size()!= size()) return false;
for (int i=0; i<size(); i++)
{
if (getRDN(i).equals(testDN.getRDN(i)) == false)
return false;
}
return true;
}
/**
* implement the object.equals(object) method for genericity and unit testing.
* Note that this is slower than DN.equals(DN), since it requires instanceof checks.
* @param o a DN or Name object to test against
*/
public boolean equals(Object o)
{
if (o == null)
return false;
if (o instanceof DN)
return equals((DN)o);
else if (o instanceof Name)
return (compareTo((Name)o) == 0);
else
return false; // cannot be equal in any sense if not a name
}
/**
* Checks whether the testDN is a subset of the current DN,
* starting from the root. Currently case insensitive.
*
* @param testDN the subset DN to test against
*/
public boolean startsWith(DN testDN)
{
return startsWith((Name)testDN);
}
/**
* Test if the DNs are identical except for the
* lowest RDN.
* In other words, test if they are leaves on the same branch.
*
* @param testDN the putatitive sibling DN
*/
public boolean sharesParent(DN testDN)
{
if (testDN.size()!= size()) return false;
for (int i=0; i<size()-1; i++)
{
if ((testDN.getRDN(i).equals(getRDN(i)))==false)
return false;
}
return true;
}
/**
* Return the full DN of this DN's immediate parent.
* In other words, return this DN after removing the lowest RDN.
*
* @return the parent of this DN, or an empty DN if this is the top level DN.
*/
public DN parentDN()
{
// XXX what to do if this is already an empty DN? The same?
if (size()<=1) return new DN(); // return empty DN for top level DNs
DN newDN = new DN(this);
newDN.RDNs.removeElementAt(size()-1);
return newDN;
}
/**
* reverse the order of elements in a DN...
*/
public void reverse()
{
Vector rev = new Vector();
for (int i=RDNs.size()-1; i>=0; i--)
rev.add(RDNs.elementAt(i));
RDNs = rev;
}
/**
* Empties the DN of all RDNs.
*/
public void clear()
{
RDNs.clear();
errorString = null;
}
/**
* Overload this method for app specific error handling.
*/
public void setError(String msg, NamingException e)
{
errorString = msg;
rootException = e;
System.out.println(e);
}
/**
* Whether there was an error using this DN (i.e. when creating it).
*/
public boolean error()
{
return (errorString == null);
}
/**
* Gets the error message (if any) associated with this DN.
*/
public String getError()
{
return errorString;
}
/**
* Gets the root exception (if any) associated with this DN
*/
public NamingException getNamingException()
{
return rootException;
}
/**
* Prepare a dn for jndi transmission
*/
/*
public void escape()
{
for (int i=0; i<size(); i++)
{
getRDN(i).escape();
}
}
*/
/**
* Unescape a dn that has been *normally* escaped using ldap v3 (i.e. by the
* preceeding ftn.).
*/
/*
public void unescape()
throws InvalidNameException
{
for (int i=0; i<size(); i++)
{
getRDN(i).unescape();
}
}
*/
/** (Obsolete)
* Unescape a dn that has been returned by jndi, that may contain either
* ldap v2 escaping, or the multiple-slash wierdness bug.
*/
/*
public void unescapeJndiReturn()
throws InvalidNameException // shouldn't happen...
{
for (int i=0; i<size(); i++)
{
getRDN(i).unescapeJndiReturn();
}
}
*/
/**
* Add an RDN to the end of the DN.
*/
public Name add(RDN rdn)
{
//RDNs.insertElementAt(rdn,size());
add(size(), rdn);
return this;
}
/**
* The core method for adding RDN objects to the name.
* Called by all add methods.
* @param posn the position in the DN to add the RDN at (0 = root)
* @param rdn the RDN to add (may be multi-valued).
*/
public Name add(int posn, RDN rdn)
{
RDNs.insertElementAt(rdn,posn);
return this;
}
// NN N A MM MM EEEEEE
// NNN N A A M MMM M EE
// N NN N A A M M M EEEE (Interface Def.)
// N NNN AAAAAAA M M M EE
// N NN AA AA M M M EEEEEE
/*
* Adds a single component at a specified position within this name.
*/
// These two ftns should be used by all code to add rdns... the RDN array
// should not be accessed directly.
public Name add(int posn, String rdn)
throws InvalidNameException
{
RDN r = new RDN(rdn); // may throw invalidName Exception
add(posn, r);
return this;
}
/*
* Adds a single component to the end of this name.
*/
public Name add(String rdn)
throws InvalidNameException
{
RDN r = new RDN(rdn); // may throw invalidName Exception
add(size(), r);
return this;
}
/*
* Adds the components of a name -- in order -- at a specified position within this name.
*/
public Name addAll(int posn, Name n)
throws InvalidNameException
{
Enumeration e = n.getAll();
while (e.hasMoreElements())
add(posn++, e.nextElement().toString());
return this;
}
/*
* Adds the components of a name -- in order -- to the end of this name.
*/
public Name addAll(Name suffix)
throws InvalidNameException
{
Enumeration e = suffix.getAll();
while (e.hasMoreElements())
add(e.nextElement().toString());
return this;
}
/*
* Generates a new copy of this name.
*/
public Object clone()
{
return new DN(this);
}
/*
* Compares this name with another name for order.
* ... for the time being, ordering is alphabetical by rdns ordered
* right to left. Damn but the ldap rdn ordering system is screwed.
*/
public int compareTo(Object obj)
{
int val = 0;
int pos = 1;
if (obj instanceof Name)
{
Name compareMe = (Name)obj;
int size = size();
int compSize = compareMe.size();
while (val == 0)
{
String RDN = get(size-pos);
String compRDN = compareMe.get(compSize-pos);
int rdnOrder = RDN.compareTo(compRDN);
if (rdnOrder != 0)
return rdnOrder; // return alphabetic order of rdn.
pos++;
if (pos>size || pos>compSize)
{
if (size==compSize)
return 0; // names are equal
if (pos>size)
return -1; // shorter dns first
else
return 1;
}
}
}
else
throw new ClassCastException("non Name object in DN.compareTo - object was " + obj.getClass());
return 0; // never reached.
}
/*
* Determines whether this name ends with a specified suffix.
*/
public boolean endsWith(Name n)
{
return false;
}
/*
* Retrieves a component of this name. Returns zero length string for
* an empty DN's first element - otherwise throws a ArrayIndexException.
* (is this correct behaviour?).
*/
public String get(int posn)
{
if (posn==0 && isEmpty()) return ""; // return empty string for empty DN
return RDNs.elementAt(posn).toString();
}
/*
* Retrieves the components of this name as an enumeration of strings.
*/
public java.util.Enumeration getAll()
{
DXNamingEnumeration ret = new DXNamingEnumeration();
for (int i=0; i<size(); i++)
ret.add(get(i));
return ret;
}
/*
* Creates a name whose components consist of a prefix of the components of this name.
*/
public Name getPrefix(int posn)
{
DN returnMe = new DN();
try
{
for (int i=0; i<posn; i++)
returnMe.add(get(i));
return returnMe;
}
catch (InvalidNameException e)
{
System.err.println("unexpected error in DN:\n " + e);
return new DN();
}
}
/*
* Creates a name whose components consist of a suffix of the components in this name.
*/
public Name getSuffix(int posn)
{
DN returnMe = new DN();
for (int i=posn; i<size(); i++)
{
returnMe.add(new RDN(getRDN(i)));
}
return returnMe;
}
/**
* returns true if this is an 'empty', or root DN (i.e. \"\")
* @return empty status
*/
public boolean isEmpty()
{
return (size()==0);
}
/*
* Removes a component from this name.
*/
public Object remove(int posn)
{
return RDNs.remove(posn);
}
/*
* Returns the number of components in this name,
* and hence the level (the number
* of nodes from root) of the DN.
*/
public int size()
{
return RDNs.size();
}
/*
* Returns the number of components in this name,
* and hence the level (the number
* of nodes from root) of the DN.
* (synonym of 'size()'. Why java didn't standardise on just one...)
*/
/*
public int length()
{
return RDNs.size();
}
*/
/*
* Determines whether this name starts with a specified prefix.
*/
public boolean startsWith(Name n)
{
int pos = 0;
Enumeration e = n.getAll();
while (e.hasMoreElements())
if (e.nextElement().toString().equalsIgnoreCase(get(pos++).toString())==false)
return false;
return true; // falls through - all tested components must be equal!
}
}