opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.backends.jeb; @@ -53,6 +53,7 @@ import org.opends.server.types.Entry; import org.opends.server.types.LockType; import org.opends.server.types.Modification; import org.opends.server.types.RDN; import org.opends.server.types.ResultCode; import org.opends.server.types.SearchScope; import org.opends.server.util.StaticUtils; @@ -3100,8 +3101,22 @@ */ public static DN modDN(DN oldDN, int oldSuffixLen, DN newSuffixDN) { DN localName = oldDN.getLocalName(oldSuffixLen); return newSuffixDN.concat(localName); int oldDNNumComponents = oldDN.getNumComponents(); int oldDNKeepComponents = oldDNNumComponents - oldSuffixLen; int newSuffixDNComponents = newSuffixDN.getNumComponents(); RDN[] newDNComponents = new RDN[oldDNKeepComponents+newSuffixDNComponents]; for (int i=0; i < oldDNKeepComponents; i++) { newDNComponents[i] = oldDN.getRDN(i); } for (int i=oldDNKeepComponents, j=0; j < newSuffixDNComponents; i++,j++) { newDNComponents[i] = newSuffixDN.getRDN(j); } return new DN(newDNComponents); } /** opends/src/server/org/opends/server/tools/makeldif/TemplateEntry.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.tools.makeldif; @@ -148,29 +148,50 @@ /** * Retrieves teh DN for this template entry, if it is known. * Retrieves the DN for this template entry, if it is known. * * @return The DN for this template entry if it is known, or * <CODE>null</CODE> if it cannot yet be determined. */ public DN getDN() { if (dn == null) { RDN.Builder builder = RDN.createBuilder(); for (AttributeType type : template.getRDNAttributes()) { TemplateValue v = getValue(type); if (v == null) { if (dn == null) { RDN rdn; AttributeType[] rdnAttrs = template.getRDNAttributes(); if (rdnAttrs.length == 1) { AttributeType t = rdnAttrs[0]; TemplateValue v = getValue(t); if (v == null) { return null; } AttributeValue value = new AttributeValue(type, v.getValue().toString()); builder.append(type, value); AttributeValue value = new AttributeValue(t, v.getValue().toString()); rdn = new RDN(t, value); } else { String[] names = new String[rdnAttrs.length]; AttributeValue[] values = new AttributeValue[rdnAttrs.length]; for (int i=0; i < rdnAttrs.length; i++) { AttributeType t = rdnAttrs[i]; TemplateValue v = getValue(t); if (v == null) { return null; } dn = parentDN.concat(builder.getInstance()); names[i] = t.getPrimaryName(); values[i] = new AttributeValue(t, v.getValue().toString()); } rdn = new RDN(rdnAttrs, names, values); } dn = parentDN.concat(rdn); } return dn; opends/src/server/org/opends/server/types/AttributeValue.java
@@ -32,6 +32,7 @@ import org.opends.server.protocols.asn1.ASN1OctetString; import static org.opends.server.loggers.Debug.*; import static org.opends.server.util.StaticUtils.*; import static org.opends.server.util.Validator.*; @@ -257,6 +258,122 @@ /** * Retrieves a string representation of the user-defined form of * this attribute value in a form suitable for use in a DN. * * @return A string representation of the user-defined form of this * attribute value in a form suitable for use in a DN. */ public String getDNStringValue() { assert debugEnter(CLASS_NAME, "getDNStringValue"); return getDNValue(getStringValue()); } /** * Retrieves a string representation of the normalized form of this * attribute value in a form suitable for use in a DN. * * @return A string representation of the normalized form of this * attribute value in a form suitable for use in a DN. * * @throws DirectoryException If an error occurs while trying to * normalize the value (e.g., if it is * not acceptable for use with the * associated equality matching rule). */ public String getNormalizedDNStringValue() throws DirectoryException { assert debugEnter(CLASS_NAME, "getNormalizedDNStringValue"); return getDNValue(getNormalizedStringValue()); } /** * Retrieves a version of the provided value in a form that is * properly escaped for use in a DN or RDN. * * @param value The value to be represented in a DN-safe form. * * @return A version of the provided value in a form that is * properly escaped for use in a DN or RDN. */ private static String getDNValue(String value) { assert debugEnter(CLASS_NAME, "getDNValue", String.valueOf(value)); if ((value == null) || (value.length() == 0)) { return ""; } StringBuilder buffer = new StringBuilder(value); int length = buffer.length(); for (int i=0; i < length; i++) { char c = buffer.charAt(i); if ((c < ' ') || (c > '~')) { buffer.deleteCharAt(i); length -= 1; for (byte b : getBytes(String.valueOf(c))) { buffer.insert(i++, "\\"); buffer.insert(i++, byteToLowerHex(b)); i++; length += 3; } i -= 1; } else { switch (buffer.charAt(i)) { case ',': case '+': case '"': case '\\': case '<': case '>': case ';': buffer.insert(i++, '\\'); length++; } } } char c = buffer.charAt(0); if ((c == ' ') || (c == '#')) { buffer.insert(0, '\\'); length++; } if (buffer.charAt(length-1) == ' ') { buffer.insert(length-1, '\\'); length++; } return buffer.toString(); } /** * Determines whether this attribute value is equal to the provided * object. * opends/src/server/org/opends/server/types/DN.java
@@ -22,20 +22,23 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.types; import static org.opends.server.loggers.Debug.*; import static org.opends.server.util.Validator.ensureNotNull; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.opends.server.core.DirectoryServer; import org.opends.server.protocols.asn1.ASN1OctetString; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.loggers.Debug.*; import static org.opends.server.messages.MessageHandler.*; import static org.opends.server.messages.SchemaMessages.*; import static org.opends.server.util.StaticUtils.*; @@ -43,196 +46,58 @@ * This class defines a data structure for storing and interacting * with the distinguished names associated with entries in the * Directory Server. * <p> * All the methods in this class will throw a * <code>NullPointerException</code> when provided with * <code>null</code> reference parameters unless otherwise stated. */ public final class DN implements Comparable<DN>, Serializable { // FIXME: Don't store the normalized form and define equals(), // hashCode(), and compareTo() in terms of RDN components (which // cache their normalized form). This could potentially lead to less // memory utilization for very little performance loss. // FIXME: Optimize normalization for common use-cases. E.g. // concat(DN) can simply join the two normalized forms together. // Similarly, getParent() and getLocalName() can avoid // recalculating the normalized form and take a substring from the // source DN's normalized form (this requires parsing commas, but // could save memory). public class DN implements Comparable<DN>, Serializable { /** * The fully-qualified name of this class for debugging purposes. */ private static final String CLASS_NAME = "org.opends.server.types.DN"; /** * A singleton instance of the null DN (a DN with no components). */ public static DN NULL_DN = new DN(); /** * The serial version identifier required to satisfy the compiler * because this class implements the * <code>java.io.Serializable</code> interface. This value was * generated using the <code>serialver</code> command-line utility * <CODE>java.io.Serializable</CODE> interface. This value was * generated using the <CODE>serialver</CODE> command-line utility * included with the Java SDK. */ private static final long serialVersionUID = 1184263456768819888L; // The number of RDN components that comprise this DN. private final int numComponents; // The index of the first RDN component (furthest from the root) in // this DN. private final int offset; // The number of RDN components that comprise this DN. private int numComponents; // The set of RDN components that comprise this DN, arranged with // the suffix as the last element. private final RDN[] rdnComponents; private RDN[] rdnComponents; // The cached normalized string representation of this DN. private final String normalizedDN; // The string representation of this DN. private String dnString; // A DN comprising of zero RDN components. private static final DN EMPTY_DN = new DN(new RDN[0], 0, 0); // The normalized string representation of this DN. private String normalizedDN; /** * Creates a new DN comprising of zero RDN components (a null DN or * root DSE). * * @return Returns a new DN comprising of zero RDN components. * Creates a new DN with no RDN components (i.e., a null DN or root * DSE). */ public static DN nullDN() { return EMPTY_DN; } /** * Creates a new <code>DN</code> containing the specified RDN * sequence. * <p> * If the argument RDN sequence is empty, then the effect is the * same as if {@link #nullDN()} had been called. * * @param rdns * The RDN sequence that the new DN will represent. * @return Returns a DN that represents the specified RDN sequence * onto the end of this DN. */ public static DN create(RDN... rdns) { ensureNotNull(rdns); if (rdns.length == 0) { return nullDN(); } RDN[] allRDNs = new RDN[rdns.length]; System.arraycopy(rdns, 0, allRDNs, 0, rdns.length); return new DN(allRDNs, 0, rdns.length); } /** * Returns a <code>DN</code> object holding the value of the * specified <code>String</code>. The argument is interpreted as * representing the LDAP string representation of a DN. * <p> * This method is identical to {@link #decode(String)}. * * @param s * The string to be parsed, or <code>null</code> in * which case a <code>nullDN</code> will be returned. * @return Returns a <code>DN</code> holding the value represented * by the <code>string</code> argument. * @throws DirectoryException * If a problem occurs while trying to decode the provided * string as a DN. */ public static DN valueOf(String s) throws DirectoryException { return decode(s); } /** * Decodes the provided ASN.1 octet string as a DN. * * @param dnString * The ASN.1 octet string to decode as a DN, or * <code>null</code> in which case a <code>nullDN</code> * will be returned. * @return The decoded DN. * @throws DirectoryException * If a problem occurs while trying to decode the provided * ASN.1 octet string as a DN. */ public static DN decode(ByteString dnString) throws DirectoryException { assert debugEnter(CLASS_NAME, "decode", String.valueOf(dnString)); // A null or empty DN is acceptable. if (dnString == null) { return nullDN(); } byte[] dnBytes = dnString.value(); int length = dnBytes.length; if (length == 0) { return nullDN(); } // Use string-based decoder. return decode(dnString.stringValue()); } /** * Decodes the provided string as a DN. * * @param dnString * The string to decode as a DN, or <code>null</code> in * which case a <code>nullDN</code> will be returned. * @return The decoded DN. * @throws DirectoryException * If a problem occurs while trying to decode the provided * string as a DN. */ public static DN decode(String dnString) throws DirectoryException { assert debugEnter(CLASS_NAME, "decode", String.valueOf(dnString)); // A null or empty DN is acceptable. if (dnString == null || dnString.length() == 0) { return nullDN(); } // Parse the first RDN. int pos = 0; RDN.Builder builder = RDN.createBuilder(); pos = builder.parse(dnString, pos, true); if (builder.isEmpty()) { return nullDN(); } else { // Parse the remaining RDNs. List<RDN> rdns = new ArrayList<RDN>(10); rdns.add(builder.getInstance()); while (pos >= 0) { // Skip the RDN separator. pos++; // Parse the next RDN. builder.clear(); pos = builder.parse(dnString, pos, false); rdns.add(builder.getInstance()); } // Parse successful - create the DN. int sz = rdns.size(); return new DN(rdns.toArray(new RDN[sz]), 0, sz); } public DN() { this(new RDN[0]); } @@ -241,279 +106,69 @@ * Creates a new DN with the provided set of RDNs, arranged with the * suffix as the last element. * * @param rdnComponents * The set of RDN components that make up this DN. * @param offset * The index of the first RDN component (furthest from the * root) in this DN. * @param count * The number of RDNs to include in this DN. */ private DN(RDN[] rdnComponents, int offset, int count) { assert debugConstructor(CLASS_NAME, String.valueOf(rdnComponents), String.valueOf(offset)); this.rdnComponents = rdnComponents; this.offset = offset; this.numComponents = count; this.normalizedDN = normalize(); } /** * Concatenates the specified DN to the end of this DN. * <p> * If the argument DN is the null DN, then this DN is returned. * Conversely, if this DN is the null DN then the argument DN will * be returned. Otherwise, the returned DN will be a descendent of * @param rdnComponents The set of RDN components that make up * this DN. * * @param localName * The DN that will be concatenated to the end of this DN. * @return Returns a DN that represents the concatenation of the * specified DN onto the end of this DN. */ public DN concat(DN localName) { ensureNotNull(localName); if (localName.isNullDN()) { return this; } if (isNullDN()) { return localName; } RDN[] allRDNs = new RDN[numComponents + localName.numComponents]; System.arraycopy(localName.rdnComponents, localName.offset, allRDNs, 0, localName.numComponents); System.arraycopy(rdnComponents, offset, allRDNs, localName.numComponents, numComponents); return new DN(allRDNs, 0, allRDNs.length); } /** * Concatenates the specified RDN sequence to the end of this DN. * <p> * If the argument RDN sequence is empty, then this DN is returned. * Otherwise, the returned DN will be a descendent of this DN. * * @param rdns * The RDN sequence that will be concatenated to the end of * this DN. * @return Returns a DN that represents the concatenation of the * specified RDN sequence onto the end of this DN. */ public DN concat(RDN... rdns) { ensureNotNull(rdns); if (rdns.length == 0) { return this; } // Don't check if this is a nullDN, because were going to copy the // RDN sequence anyway. RDN[] allRDNs = new RDN[rdns.length + numComponents]; System.arraycopy(rdns, 0, allRDNs, 0, rdns.length); System.arraycopy(rdnComponents, offset, allRDNs, rdns.length, numComponents); return new DN(allRDNs, 0, allRDNs.length); } /** * Get the parent DN of this DN. * * @return Returns the parent DN of this DN, or <code>null</code> * if this DN does not have a parent (i.e. it is a DN having * a single RDN component, or the null DN). */ public DN getParent() { if (numComponents <= 1) { return null; } else { return new DN(rdnComponents, offset + 1, numComponents - 1); } } /** * Create a local name (a relative DN) from this DN. * <p> * Examples: <blockquote> * * <pre> * DN dn = DN.decode("cn=john,o=example,c=us"); * * dn.getLocalName(0) returns "cn=john,o=example,c=us" * dn.getLocalName(1) returns "cn=john,o=example" * dn.getLocalName(3) returns "" (null DN). * </pre> * * </blockquote> * * @param beginIndex * The index of the first RDN component (nearest the root), * inclusive. * @return Returns the specified local name. * @throws IndexOutOfBoundsException * If <code>beginIndex</code> is negative, or greater * than the number of RDN components in this DN. */ public DN getLocalName(int beginIndex) throws IndexOutOfBoundsException { return getLocalName(beginIndex, numComponents); } /** * Create a local name (a relative DN) from this DN. * <p> * Examples: <blockquote> * * <pre> * DN dn = DN.decode("cn=john,o=example,c=us"); * * dn.getLocalName(0, 3) returns "cn=john,o=example,c=us" * dn.getLocalName(1, 2) returns "o=example" * dn.getLocalName(2, 2) returns "" (null DN). * </pre> * * </blockquote> * * @param beginIndex * The index of the first RDN component (nearest the root), * inclusive. * @param endIndex * The index of the last RDN component (furthest from the * root), exclusive. * @return Returns the specified local name. * @throws IndexOutOfBoundsException * If <code>beginIndex</code> is negative, or * <code>endIndex</code> is larger than the number of * RDN components in this DN, or <code>beginIndex</code> * is larger than <code>endIndex</code>. */ public DN getLocalName(int beginIndex, int endIndex) throws IndexOutOfBoundsException { if (beginIndex < 0) { throw new IndexOutOfBoundsException("beginIndex out of range: " + beginIndex); } if (endIndex > numComponents) { throw new IndexOutOfBoundsException("endIndex out of range: " + endIndex); } if (beginIndex > endIndex) { throw new IndexOutOfBoundsException( "beginIndex greater than endIndex"); } if (beginIndex == 0 && endIndex == numComponents) { return this; } else { int i = offset + numComponents - endIndex; return new DN(rdnComponents, i, endIndex - beginIndex); } } /** * Get the number of RDN components that make up this DN. * * @return Returns the number of RDN components that make up this * DN. */ public int getNumComponents() { assert debugEnter(CLASS_NAME, "getNumComponents"); return numComponents; } /** * Retrieves the outermost RDN component for this DN (i.e., the one * that is furthest from the suffix). This method is equivalent to * calling <code>getRDN(0)</code> for non-null DNs. * * @return The outermost RDN component for this DN, or * <code>null</code> if there are no RDN components in the * DN. */ public RDN getRDN() { assert debugEnter(CLASS_NAME, "getRDN"); if (numComponents == 0) { return null; } else { return getRDN(0); } } /** * Get the RDN at the specified index. * * @param index * The index of the RDN to retrieve, where <code>0</code> * indicates the outermost RDN component (i.e. the one that * is furthest from the suffix). * @return Returns the RDN at the specified index. * @throws IndexOutOfBoundsException * If <code>index</code> is negative, or greater than or * equal to the number of RDN components in this DN. */ public RDN getRDN(int index) throws IndexOutOfBoundsException { if (index < 0) { throw new IndexOutOfBoundsException("index out of range: " + index); } if (index >= numComponents) { throw new IndexOutOfBoundsException("index out of range: " + index); } return rdnComponents[offset + index]; } /** * Retrieves the DN of the entry that is the immediate parent for * this entry. * * @return The DN of the entry that is the immediate parent for this * entry, or <code>null</code> if the entry with this DN * does not have a parent (either because there is only a * single RDN component or because this DN is a suffix * defined in the server). */ public DN getParentDNInSuffix() { assert debugEnter(CLASS_NAME, "getParentDNInSuffix"); if ((numComponents <= 1) || DirectoryServer.isNamingContext(this)) public DN(RDN[] rdnComponents) { return null; assert debugConstructor(CLASS_NAME, String.valueOf(rdnComponents)); if (rdnComponents == null) { this.rdnComponents = new RDN[0]; } else { this.rdnComponents = rdnComponents; } return getParent(); numComponents = this.rdnComponents.length; dnString = null; normalizedDN = toNormalizedString(); } /** * Creates a new DN with the provided set of RDNs, arranged with the * suffix as the last element. * * @param rdnComponents The set of RDN components that make up * this DN. */ public DN(ArrayList<RDN> rdnComponents) { assert debugConstructor(CLASS_NAME, String.valueOf(rdnComponents)); if ((rdnComponents == null) || rdnComponents.isEmpty()) { this.rdnComponents = new RDN[0]; } else { this.rdnComponents = new RDN[rdnComponents.size()]; rdnComponents.toArray(this.rdnComponents); } numComponents = this.rdnComponents.length; dnString = null; normalizedDN = toNormalizedString(); } /** * Retrieves a singleton instance of the null DN. * * @return A singleton instance of the null DN. */ public static DN nullDN() { assert debugEnter(CLASS_NAME, "nullDN"); return NULL_DN; } @@ -523,10 +178,11 @@ * the root DSE for the Directory Server, or the authorization DN * for an anonymous or unauthenticated client. * * @return <code>true</code> if this does represent a null DN, or * <code>false</code> if it does not. * @return <CODE>true</CODE> if this does represent a null DN, or * <CODE>false</CODE> if it does not. */ public boolean isNullDN() { public boolean isNullDN() { assert debugEnter(CLASS_NAME, "isNullDN"); return (numComponents == 0); @@ -535,28 +191,226 @@ /** * Retrieves the number of RDN components for this DN. * * @return The number of RDN components for this DN. */ public int getNumComponents() { assert debugEnter(CLASS_NAME, "getNumComponents"); return numComponents; } /** * Retrieves the outermost RDN component for this DN (i.e., the one * that is furthest from the suffix). * * @return The outermost RDN component for this DN, or * <CODE>null</CODE> if there are no RDN components in the * DN. */ public RDN getRDN() { assert debugEnter(CLASS_NAME, "getRDN"); if (numComponents == 0) { return null; } else { return rdnComponents[0]; } } /** * Retrieves the RDN component at the specified position in the set * of components for this DN. * * @param pos The position of the RDN component to retrieve. * * @return The RDN component at the specified position in the set * of components for this DN. */ public RDN getRDN(int pos) { assert debugEnter(CLASS_NAME, "getRDN", String.valueOf(pos)); return rdnComponents[pos]; } /** * Retrieves the DN of the entry that is the immediate parent for * this entry. Note that this method does not take the server's * naming context configuration into account when making the * determination. * * @return The DN of the entry that is the immediate parent for * this entry, or <CODE>null</CODE> if the entry with this * DN does not have a parent. */ public DN getParent() { assert debugEnter(CLASS_NAME, "getParent"); if (numComponents <= 1) { return null; } RDN[] parentComponents = new RDN[numComponents-1]; System.arraycopy(rdnComponents, 1, parentComponents, 0, numComponents-1); return new DN(parentComponents); } /** * Retrieves the DN of the entry that is the immediate parent for * this entry. This method does take the server's naming context * configuration into account, so if the current DN is a naming * context for the server, then it will not be considered to have a * parent. * * @return The DN of the entry that is the immediate parent for * this entry, or <CODE>null</CODE> if the entry with this * DN does not have a parent (either because there is only * a single RDN component or because this DN is a suffix * defined in the server). */ public DN getParentDNInSuffix() { assert debugEnter(CLASS_NAME, "getParent"); if ((numComponents <= 1) || DirectoryServer.isNamingContext(this)) { return null; } RDN[] parentComponents = new RDN[numComponents-1]; System.arraycopy(rdnComponents, 1, parentComponents, 0, numComponents-1); return new DN(parentComponents); } /** * Creates a new DN that is a child of this DN, using the specified * RDN. * * @param rdn The RDN for the child of this DN. * * @return A new DN that is a child of this DN, using the specified * RDN. */ public DN concat(RDN rdn) { assert debugEnter(CLASS_NAME, "concat", String.valueOf(rdn)); RDN[] newComponents = new RDN[rdnComponents.length+1]; newComponents[0] = rdn; System.arraycopy(rdnComponents, 0, newComponents, 1, rdnComponents.length); return new DN(newComponents); } /** * Creates a new DN that is a descendant of this DN, using the * specified RDN components. * * @param rdnComponents The RDN components for the descendant of * this DN. * * @return A new DN that is a descendant of this DN, using the * specified RDN components. */ public DN concat(RDN[] rdnComponents) { assert debugEnter(CLASS_NAME, "concat", String.valueOf(rdnComponents)); RDN[] newComponents = new RDN[rdnComponents.length+this.rdnComponents.length]; System.arraycopy(rdnComponents, 0, newComponents, 0, rdnComponents.length); System.arraycopy(this.rdnComponents, 0, newComponents, rdnComponents.length, this.rdnComponents.length); return new DN(newComponents); } /** * Creates a new DN that is a descendant of this DN, using the * specified DN as a relative base DN. That is, the resulting DN * will first have the components of the provided DN followed by the * components of this DN. * * @param relativeBaseDN The relative base DN to concatenate onto * this DN. * * @return A new DN that is a descendant of this DN, using the * specified DN as a relative base DN. */ public DN concat(DN relativeBaseDN) { assert debugEnter(CLASS_NAME, "concat", String.valueOf(relativeBaseDN)); RDN[] newComponents = new RDN[rdnComponents.length+ relativeBaseDN.rdnComponents.length]; System.arraycopy(relativeBaseDN.rdnComponents, 0, newComponents, 0, relativeBaseDN.rdnComponents.length); System.arraycopy(rdnComponents, 0, newComponents, relativeBaseDN.rdnComponents.length, rdnComponents.length); return new DN(newComponents); } /** * Indicates whether this DN is a descendant of the provided DN * (i.e., that the RDN components of the provided DN are the same as * the last RDN components for this DN). * * @param dn * The DN for which to make the determination. * @return <code>true</code> if this DN is a descendant of the * provided DN, or <code>false</code> if not. * @param dn The DN for which to make the determination. * * @return <CODE>true</CODE> if this DN is a descendant of the * provided DN, or <CODE>false</CODE> if not. */ public boolean isDescendantOf(DN dn) { assert debugEnter(CLASS_NAME, "isDescendantOf", String .valueOf(dn)); public boolean isDescendantOf(DN dn) { assert debugEnter(CLASS_NAME, "isDescendantOf", String.valueOf(dn)); ensureNotNull(dn); int diff = numComponents - dn.numComponents; if (diff < 0) { int offset = numComponents - dn.numComponents; if (offset < 0) { return false; } for (int i = 0; i < dn.numComponents; i++) { if (!getRDN(i + diff).equals(dn.getRDN(i))) { for (int i=0; i < dn.numComponents; i++) { if (! rdnComponents[i+offset].equals(dn.rdnComponents[i])) { return false; } } @@ -571,23 +425,25 @@ * (i.e., that the RDN components of this DN are the same as the * last RDN components for the provided DN). * * @param dn * The DN for which to make the determination. * @return <code>true</code> if this DN is an ancestor of the * provided DN, or <code>false</code> if not. * @param dn The DN for which to make the determination. * * @return <CODE>true</CODE> if this DN is an ancestor of the * provided DN, or <CODE>false</CODE> if not. */ public boolean isAncestorOf(DN dn) { public boolean isAncestorOf(DN dn) { assert debugEnter(CLASS_NAME, "isAncestorOf", String.valueOf(dn)); ensureNotNull(dn); int diff = dn.numComponents - numComponents; if (diff < 0) { int offset = dn.numComponents - numComponents; if (offset < 0) { return false; } for (int i = 0; i < numComponents; i++) { if (!getRDN(i).equals(dn.getRDN(i + diff))) { for (int i=0; i < numComponents; i++) { if (! rdnComponents[i].equals(dn.rdnComponents[i+offset])) { return false; } } @@ -598,26 +454,2312 @@ /** * Decodes the provided ASN.1 octet string as a DN. * * @param dnString The ASN.1 octet string to decode as a DN. * * @return The decoded DN. * * @throws DirectoryException If a problem occurs while trying to * decode the provided ASN.1 octet * string as a DN. */ public static DN decode(ByteString dnString) throws DirectoryException { assert debugEnter(CLASS_NAME, "decode", String.valueOf(dnString)); // A null or empty DN is acceptable. if (dnString == null) { return new DN(new ArrayList<RDN>(0)); } byte[] dnBytes = dnString.value(); int length = dnBytes.length; if (length == 0) { return new DN(new ArrayList<RDN>(0)); } // See if we are dealing with any non-ASCII characters, or any // escaped characters. If so, then the easiest and safest // approach is to convert the DN to a string and decode it that // way. for (byte b : dnBytes) { if (((b & 0x7F) != b) || (b == '\\')) { return decode(dnString.stringValue()); } } // Iterate through the DN string. The first thing to do is to get // rid of any leading spaces. int pos = 0; byte b = dnBytes[pos]; while (b == ' ') { pos++; if (pos == length) { // This means that the DN was completely comprised of spaces // and therefore should be considered the same as a null or // empty DN. return new DN(new ArrayList<RDN>(0)); } else { b = dnBytes[pos]; } } // We know that it's not an empty DN, so we can do the real // processing. Create a loop and iterate through all the RDN // components. boolean allowExceptions = DirectoryServer.allowAttributeNameExceptions(); ArrayList<RDN> rdnComponents = new ArrayList<RDN>(); while (true) { StringBuilder attributeName = new StringBuilder(); pos = parseAttributeName(dnBytes, pos, attributeName, allowExceptions); // Make sure that we're not at the end of the DN string because // that would be invalid. if (pos >= length) { int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME; String message = getMessage(msgID, dnString.stringValue(), attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces between the attribute name and its // value. b = dnBytes[pos]; while (b == ' ') { pos++; if (pos >= length) { // This means that we hit the end of the value before // finding a '='. This is illegal because there is no // attribute-value separator. int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME; String message = getMessage(msgID, dnString.stringValue(), attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else { b = dnBytes[pos]; } } // The next character must be an equal sign. If it is not, // then that's an error. if (b == '=') { pos++; } else { int msgID = MSGID_ATTR_SYNTAX_DN_NO_EQUAL; String message = getMessage(msgID, dnString.stringValue(), attributeName.toString(), (char) b); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces after the equal sign. while ((pos < length) && ((b = dnBytes[pos]) == ' ')) { pos++; } // If we are at the end of the DN string, then that must mean // that the attribute value was empty. This will probably never // happen in a real-world environment, but technically isn't // illegal. If it does happen, then go ahead and create the RDN // component and return the DN. if (pos >= length) { String name = attributeName.toString(); String lowerName = toLowerCase(name); AttributeType attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } AttributeValue value = new AttributeValue(new ASN1OctetString(), new ASN1OctetString()); rdnComponents.add(new RDN(attrType, name, value)); return new DN(rdnComponents); } // Parse the value for this RDN component. ByteString parsedValue = new ASN1OctetString(); pos = parseAttributeValue(dnBytes, pos, parsedValue); // Create the new RDN with the provided information. String name = attributeName.toString(); String lowerName = toLowerCase(name); AttributeType attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } AttributeValue value = new AttributeValue(attrType, parsedValue); RDN rdn = new RDN(attrType, name, value); // Skip over any spaces that might be after the attribute value. while ((pos < length) && ((b = dnBytes[pos]) == ' ')) { pos++; } // Most likely, we will be at either the end of the RDN // component or the end of the DN. If so, then handle that // appropriately. if (pos >= length) { // We're at the end of the DN string and should have a valid // DN so return it. rdnComponents.add(rdn); return new DN(rdnComponents); } else if ((b == ',') || (b == ';')) { // We're at the end of the RDN component, so add it to the // list, skip over the comma/semicolon, and start on the next // component. rdnComponents.add(rdn); pos++; continue; } else if (b != '+') { // This should not happen. At any rate, it's an illegal // character, so throw an exception. int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR; String message = getMessage(msgID, new String(dnBytes), (char) b, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // If we have gotten here, then this must be a multi-valued RDN. // In that case, parse the remaining attribute/value pairs and // add them to the RDN that we've already created. while (true) { // Skip over the plus sign and any spaces that may follow it // before the next attribute name. pos++; while ((pos < length) && (dnBytes[pos] == ' ')) { pos++; } // Parse the attribute name from the DN string. attributeName = new StringBuilder(); pos = parseAttributeName(dnBytes, pos, attributeName, allowExceptions); // Make sure that we're not at the end of the DN string // because that would be invalid. if (pos >= length) { int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME; String message = getMessage(msgID, dnString.stringValue(), attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces between the attribute name and its // value. b = dnBytes[pos]; while (b == ' ') { pos++; if (pos >= length) { // This means that we hit the end of the value before // finding a '='. This is illegal because there is no // attribute-value separator. int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME; String message = getMessage(msgID, dnString.stringValue(), attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else { b = dnBytes[pos]; } } // The next character must be an equal sign. If it is not, // then that's an error. if (b == '=') { pos++; } else { int msgID = MSGID_ATTR_SYNTAX_DN_NO_EQUAL; String message = getMessage(msgID, dnString.stringValue(), attributeName.toString(), (char) b); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces after the equal sign. while ((pos < length) && ((b = dnBytes[pos]) == ' ')) { pos++; } // If we are at the end of the DN string, then that must mean // that the attribute value was empty. This will probably // never happen in a real-world environment, but technically // isn't illegal. If it does happen, then go ahead and create // the RDN component and return the DN. if (pos >= length) { name = attributeName.toString(); lowerName = toLowerCase(name); attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know // about. In that case, we'll create a new attribute // using the default syntax. If this is a problem, it // will be caught later either by not finding the target // entry or by not allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } value = new AttributeValue(new ASN1OctetString(), new ASN1OctetString()); rdn.addValue(attrType, name, value); rdnComponents.add(rdn); return new DN(rdnComponents); } // Parse the value for this RDN component. parsedValue = new ASN1OctetString(); pos = parseAttributeValue(dnBytes, pos, parsedValue); // Create the new RDN with the provided information. name = attributeName.toString(); lowerName = toLowerCase(name); attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } value = new AttributeValue(attrType, parsedValue); rdn.addValue(attrType, name, value); // Skip over any spaces that might be after the attribute // value. while ((pos < length) && ((b = dnBytes[pos]) == ' ')) { pos++; } // Most likely, we will be at either the end of the RDN // component or the end of the DN. If so, then handle that // appropriately. if (pos >= length) { // We're at the end of the DN string and should have a valid // DN so return it. rdnComponents.add(rdn); return new DN(rdnComponents); } else if ((b == ',') || (b == ';')) { // We're at the end of the RDN component, so add it to the // list, skip over the comma/semicolon, and start on the // next component. rdnComponents.add(rdn); pos++; break; } else if (b != '+') { // This should not happen. At any rate, it's an illegal // character, so throw an exception. int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR; String message = getMessage(msgID, dnString.stringValue(), (char) b, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } } } /** * Decodes the provided string as a DN. * * @param dnString The string to decode as a DN. * * @return The decoded DN. * * @throws DirectoryException If a problem occurs while trying to * decode the provided string as a DN. */ public static DN decode(String dnString) throws DirectoryException { assert debugEnter(CLASS_NAME, "decode", String.valueOf(dnString)); // A null or empty DN is acceptable. if (dnString == null) { return new DN(new ArrayList<RDN>(0)); } int length = dnString.length(); if (length == 0) { return new DN(new ArrayList<RDN>(0)); } // Iterate through the DN string. The first thing to do is to get // rid of any leading spaces. int pos = 0; char c = dnString.charAt(pos); while (c == ' ') { pos++; if (pos == length) { // This means that the DN was completely comprised of spaces // and therefore should be considered the same as a null or // empty DN. return new DN(new ArrayList<RDN>(0)); } else { c = dnString.charAt(pos); } } // We know that it's not an empty DN, so we can do the real // processing. Create a loop and iterate through all the RDN // components. boolean allowExceptions = DirectoryServer.allowAttributeNameExceptions(); ArrayList<RDN> rdnComponents = new ArrayList<RDN>(); while (true) { StringBuilder attributeName = new StringBuilder(); pos = parseAttributeName(dnString, pos, attributeName, allowExceptions); // Make sure that we're not at the end of the DN string because // that would be invalid. if (pos >= length) { int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME; String message = getMessage(msgID, dnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces between the attribute name and its // value. c = dnString.charAt(pos); while (c == ' ') { pos++; if (pos >= length) { // This means that we hit the end of the value before // finding a '='. This is illegal because there is no // attribute-value separator. int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME; String message = getMessage(msgID, dnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else { c = dnString.charAt(pos); } } // The next character must be an equal sign. If it is not, then // that's an error. if (c == '=') { pos++; } else { int msgID = MSGID_ATTR_SYNTAX_DN_NO_EQUAL; String message = getMessage(msgID, dnString, attributeName.toString(), c); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces after the equal sign. while ((pos < length) && ((c = dnString.charAt(pos)) == ' ')) { pos++; } // If we are at the end of the DN string, then that must mean // that the attribute value was empty. This will probably never // happen in a real-world environment, but technically isn't // illegal. If it does happen, then go ahead and create the // RDN component and return the DN. if (pos >= length) { String name = attributeName.toString(); String lowerName = toLowerCase(name); AttributeType attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } AttributeValue value = new AttributeValue(new ASN1OctetString(), new ASN1OctetString()); rdnComponents.add(new RDN(attrType, name, value)); return new DN(rdnComponents); } // Parse the value for this RDN component. ByteString parsedValue = new ASN1OctetString(); pos = parseAttributeValue(dnString, pos, parsedValue); // Create the new RDN with the provided information. String name = attributeName.toString(); String lowerName = toLowerCase(name); AttributeType attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } AttributeValue value = new AttributeValue(attrType, parsedValue); RDN rdn = new RDN(attrType, name, value); // Skip over any spaces that might be after the attribute value. while ((pos < length) && ((c = dnString.charAt(pos)) == ' ')) { pos++; } // Most likely, we will be at either the end of the RDN // component or the end of the DN. If so, then handle that // appropriately. if (pos >= length) { // We're at the end of the DN string and should have a valid // DN so return it. rdnComponents.add(rdn); return new DN(rdnComponents); } else if ((c == ',') || (c == ';')) { // We're at the end of the RDN component, so add it to the // list, skip over the comma/semicolon, and start on the next // component. rdnComponents.add(rdn); pos++; continue; } else if (c != '+') { // This should not happen. At any rate, it's an illegal // character, so throw an exception. int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR; String message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // If we have gotten here, then this must be a multi-valued RDN. // In that case, parse the remaining attribute/value pairs and // add them to the RDN that we've already created. while (true) { // Skip over the plus sign and any spaces that may follow it // before the next attribute name. pos++; while ((pos < length) && (dnString.charAt(pos) == ' ')) { pos++; } // Parse the attribute name from the DN string. attributeName = new StringBuilder(); pos = parseAttributeName(dnString, pos, attributeName, allowExceptions); // Make sure that we're not at the end of the DN string // because that would be invalid. if (pos >= length) { int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME; String message = getMessage(msgID, dnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces between the attribute name and its // value. c = dnString.charAt(pos); while (c == ' ') { pos++; if (pos >= length) { // This means that we hit the end of the value before // finding a '='. This is illegal because there is no // attribute-value separator. int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME; String message = getMessage(msgID, dnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else { c = dnString.charAt(pos); } } // The next character must be an equal sign. If it is not, // then that's an error. if (c == '=') { pos++; } else { int msgID = MSGID_ATTR_SYNTAX_DN_NO_EQUAL; String message = getMessage(msgID, dnString, attributeName.toString(), c); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces after the equal sign. while ((pos < length) && ((c = dnString.charAt(pos)) == ' ')) { pos++; } // If we are at the end of the DN string, then that must mean // that the attribute value was empty. This will probably // never happen in a real-world environment, but technically // isn't illegal. If it does happen, then go ahead and create // the RDN component and return the DN. if (pos >= length) { name = attributeName.toString(); lowerName = toLowerCase(name); attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know // about. In that case, we'll create a new attribute // using the default syntax. If this is a problem, it // will be caught later either by not finding the target // entry or by not allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } value = new AttributeValue(new ASN1OctetString(), new ASN1OctetString()); rdn.addValue(attrType, name, value); rdnComponents.add(rdn); return new DN(rdnComponents); } // Parse the value for this RDN component. parsedValue = new ASN1OctetString(); pos = parseAttributeValue(dnString, pos, parsedValue); // Create the new RDN with the provided information. name = attributeName.toString(); lowerName = toLowerCase(name); attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } value = new AttributeValue(attrType, parsedValue); rdn.addValue(attrType, name, value); // Skip over any spaces that might be after the attribute // value. while ((pos < length) && ((c = dnString.charAt(pos)) == ' ')) { pos++; } // Most likely, we will be at either the end of the RDN // component or the end of the DN. If so, then handle that // appropriately. if (pos >= length) { // We're at the end of the DN string and should have a valid // DN so return it. rdnComponents.add(rdn); return new DN(rdnComponents); } else if ((c == ',') || (c == ';')) { // We're at the end of the RDN component, so add it to the // list, skip over the comma/semicolon, and start on the // next component. rdnComponents.add(rdn); pos++; break; } else if (c != '+') { // This should not happen. At any rate, it's an illegal // character, so throw an exception. int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR; String message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } } } /** * Parses an attribute name from the provided DN string starting at * the specified location. * * @param dnBytes The byte array containing the DN to * parse. * @param pos The position at which to start parsing * the attribute name. * @param attributeName The buffer to which to append the parsed * attribute name. * @param allowExceptions Indicates whether to allow certain * exceptions to the strict requirements * for attribute names. * * @return The position of the first character that is not part of * the attribute name. * * @throws DirectoryException If it was not possible to parse a * valid attribute name from the * provided DN string. */ public static int parseAttributeName(byte[] dnBytes, int pos, StringBuilder attributeName, boolean allowExceptions) throws DirectoryException { assert debugEnter(CLASS_NAME, "parseAttributeName", String.valueOf(dnBytes), String.valueOf(pos), "java.lang.StringBuilder"); int length = dnBytes.length; // Skip over any leading spaces. if (pos < length) { while (dnBytes[pos] == ' ') { pos++; if (pos == length) { // This means that the remainder of the DN was completely // comprised of spaces. If we have gotten here, then we // know that there is at least one RDN component, and // therefore the last non-space character of the DN must // have been a comma. This is not acceptable. int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_COMMA; String message = getMessage(msgID, new String(dnBytes)); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } } // Next, we should find the attribute name for this RDN component. // It may either be a name (with only letters, digits, and dashes // and starting with a letter) or an OID (with only digits and // periods, optionally prefixed with "oid."), and there is also a // special case in which we will allow underscores. Because of // the complexity involved, read the entire name first with // minimal validation and then do more thorough validation later. boolean checkForOID = false; boolean endOfName = false; while (pos < length) { // To make the switch more efficient, we'll include all ASCII // characters in the range of allowed values and then reject the // ones that aren't allowed. byte b = dnBytes[pos]; switch (b) { case ' ': // This should denote the end of the attribute name. endOfName = true; break; case '!': case '"': case '#': case '$': case '%': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': // None of these are allowed in an attribute name or any // character immediately following it. int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; String message = getMessage(msgID, new String(dnBytes), (char) b, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '-': // This will be allowed as long as it isn't the first // character in the attribute name. if (attributeName.length() > 0) { attributeName.append((char) b); } else { msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH; message = getMessage(msgID, new String(dnBytes), (char) b); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } break; case '.': // The period could be allowed if the attribute name is // actually expressed as an OID. We'll accept it for now, // but make sure to check it later. attributeName.append((char) b); checkForOID = true; break; case '/': // This is not allowed in an attribute name or any character // immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, new String(dnBytes), (char) b, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // Digits are always allowed if they are not the first // character. However, they may be allowed if they are the // first character if the valid is an OID or if the // attribute name exceptions option is enabled. Therefore, // we'll accept it now and check it later. attributeName.append((char) b); break; case ':': case ';': // NOTE: attribute options are not allowed in a DN. case '<': // None of these are allowed in an attribute name or any // character immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, new String(dnBytes), (char) b, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '=': // This should denote the end of the attribute name. endOfName = true; break; case '>': case '?': case '@': // None of these are allowed in an attribute name or any // character immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, new String(dnBytes), (char) b, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': // These will always be allowed. attributeName.append((char) b); break; case '[': case '\\': case ']': case '^': // None of these are allowed in an attribute name or any // character immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, new String(dnBytes), (char) b, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '_': // This will never be allowed as the first character. It // may be allowed for subsequent characters if the attribute // name exceptions option is enabled. if (attributeName.length() == 0) { msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_UNDERSCORE; message = getMessage(msgID, new String(dnBytes), ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else if (allowExceptions) { attributeName.append((char) b); } else { msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_UNDERSCORE_CHAR; message = getMessage(msgID, new String(dnBytes), ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } break; case '`': // This is not allowed in an attribute name or any character // immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, new String(dnBytes), (char) b, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': // These will always be allowed. attributeName.append((char) b); break; default: // This is not allowed in an attribute name or any character // immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, new String(dnBytes), (char) b, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } if (endOfName) { break; } pos++; } // We should now have the full attribute name. However, we may // still need to perform some validation, particularly if the name // contains a period or starts with a digit. It must also have at // least one character. if (attributeName.length() == 0) { int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_NO_NAME; String message = getMessage(msgID, new String(dnBytes)); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else if (checkForOID) { boolean validOID = true; int namePos = 0; int nameLength = attributeName.length(); char ch = attributeName.charAt(0); if ((ch == 'o') || (ch == 'O')) { if (nameLength <= 4) { validOID = false; } else { if ((((ch = attributeName.charAt(1)) == 'i') || (ch == 'I')) && (((ch = attributeName.charAt(2)) == 'd') || (ch == 'D')) && (attributeName.charAt(3) == '.')) { attributeName.delete(0, 4); nameLength -= 4; } else { validOID = false; } } } while (validOID && (namePos < nameLength)) { ch = attributeName.charAt(namePos++); if (isDigit(ch)) { while (validOID && (namePos < nameLength) && isDigit(attributeName.charAt(namePos))) { namePos++; } if ((namePos < nameLength) && (attributeName.charAt(namePos) != '.')) { validOID = false; } } else if (ch == '.') { if ((namePos == 1) || (attributeName.charAt(namePos-2) == '.')) { validOID = false; } } else { validOID = false; } } if (validOID && (attributeName.charAt(nameLength-1) == '.')) { validOID = false; } if (! validOID) { int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD; String message = getMessage(msgID, new String(dnBytes), attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } else if (isDigit(attributeName.charAt(0)) && (! allowExceptions)) { int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DIGIT; String message = getMessage(msgID, new String(dnBytes), attributeName.charAt(0), ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } return pos; } /** * Parses an attribute name from the provided DN string starting at * the specified location. * * @param dnString The DN string to be parsed. * @param pos The position at which to start parsing * the attribute name. * @param attributeName The buffer to which to append the parsed * attribute name. * @param allowExceptions Indicates whether to allow certain * exceptions to the strict requirements * for attribute names. * * @return The position of the first character that is not part of * the attribute name. * * @throws DirectoryException If it was not possible to parse a * valid attribute name from the * provided DN string. */ public static int parseAttributeName(String dnString, int pos, StringBuilder attributeName, boolean allowExceptions) throws DirectoryException { assert debugEnter(CLASS_NAME, "parseAttributeName", String.valueOf(dnString), String.valueOf(pos), "java.lang.StringBuilder"); int length = dnString.length(); // Skip over any leading spaces. if (pos < length) { while (dnString.charAt(pos) == ' ') { pos++; if (pos == length) { // This means that the remainder of the DN was completely // comprised of spaces. If we have gotten here, then we // know that there is at least one RDN component, and // therefore the last non-space character of the DN must // have been a comma. This is not acceptable. int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_COMMA; String message = getMessage(msgID, dnString); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } } // Next, we should find the attribute name for this RDN component. // It may either be a name (with only letters, digits, and dashes // and starting with a letter) or an OID (with only digits and // periods, optionally prefixed with "oid."), and there is also a // special case in which we will allow underscores. Because of // the complexity involved, read the entire name first with // minimal validation and then do more thorough validation later. boolean checkForOID = false; boolean endOfName = false; while (pos < length) { // To make the switch more efficient, we'll include all ASCII // characters in the range of allowed values and then reject the // ones that aren't allowed. char c = dnString.charAt(pos); switch (c) { case ' ': // This should denote the end of the attribute name. endOfName = true; break; case '!': case '"': case '#': case '$': case '%': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': // None of these are allowed in an attribute name or any // character immediately following it. int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; String message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '-': // This will be allowed as long as it isn't the first // character in the attribute name. if (attributeName.length() > 0) { attributeName.append(c); } else { msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH; message = getMessage(msgID, dnString, c); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } break; case '.': // The period could be allowed if the attribute name is // actually expressed as an OID. We'll accept it for now, // but make sure to check it later. attributeName.append(c); checkForOID = true; break; case '/': // This is not allowed in an attribute name or any character // immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // Digits are always allowed if they are not the first // character. However, they may be allowed if they are the // first character if the valid is an OID or if the // attribute name exceptions option is enabled. Therefore, // we'll accept it now and check it later. attributeName.append(c); break; case ':': case ';': // NOTE: attribute options are not allowed in a DN. case '<': // None of these are allowed in an attribute name or any // character immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '=': // This should denote the end of the attribute name. endOfName = true; break; case '>': case '?': case '@': // None of these are allowed in an attribute name or any // character immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': // These will always be allowed. attributeName.append(c); break; case '[': case '\\': case ']': case '^': // None of these are allowed in an attribute name or any // character immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '_': // This will never be allowed as the first character. It // may be allowed for subsequent characters if the attribute // name exceptions option is enabled. if (attributeName.length() == 0) { msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_UNDERSCORE; message = getMessage(msgID, dnString, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else if (allowExceptions) { attributeName.append(c); } else { msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_UNDERSCORE_CHAR; message = getMessage(msgID, dnString, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } break; case '`': // This is not allowed in an attribute name or any character // immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': // These will always be allowed. attributeName.append(c); break; default: // This is not allowed in an attribute name or any character // immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } if (endOfName) { break; } pos++; } // We should now have the full attribute name. However, we may // still need to perform some validation, particularly if the // name contains a period or starts with a digit. It must also // have at least one character. if (attributeName.length() == 0) { int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_NO_NAME; String message = getMessage(msgID, dnString); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else if (checkForOID) { boolean validOID = true; int namePos = 0; int nameLength = attributeName.length(); char ch = attributeName.charAt(0); if ((ch == 'o') || (ch == 'O')) { if (nameLength <= 4) { validOID = false; } else { if ((((ch = attributeName.charAt(1)) == 'i') || (ch == 'I')) && (((ch = attributeName.charAt(2)) == 'd') || (ch == 'D')) && (attributeName.charAt(3) == '.')) { attributeName.delete(0, 4); nameLength -= 4; } else { validOID = false; } } } while (validOID && (namePos < nameLength)) { ch = attributeName.charAt(namePos++); if (isDigit(ch)) { while (validOID && (namePos < nameLength) && isDigit(attributeName.charAt(namePos))) { namePos++; } if ((namePos < nameLength) && (attributeName.charAt(namePos) != '.')) { validOID = false; } } else if (ch == '.') { if ((namePos == 1) || (attributeName.charAt(namePos-2) == '.')) { validOID = false; } } else { validOID = false; } } if (validOID && (attributeName.charAt(nameLength-1) == '.')) { validOID = false; } if (! validOID) { int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD; String message = getMessage(msgID, dnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } else if (isDigit(attributeName.charAt(0)) && (! allowExceptions)) { int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DIGIT; String message = getMessage(msgID, dnString, attributeName.charAt(0), ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } return pos; } /** * Parses the attribute value from the provided DN string starting * at the specified location. When the value has been parsed, it * will be assigned to the provided ASN.1 octet string. * * @param dnBytes The byte array containing the DN to be * parsed. * @param pos The position of the first character in * the attribute value to parse. * @param attributeValue The ASN.1 octet string whose value should * be set to the parsed attribute value when * this method completes successfully. * * @return The position of the first character that is not part of * the attribute value. * * @throws DirectoryException If it was not possible to parse a * valid attribute value from the * provided DN string. */ public static int parseAttributeValue(byte[] dnBytes, int pos, ByteString attributeValue) throws DirectoryException { assert debugEnter(CLASS_NAME, "parseAttributeValue", String.valueOf(dnBytes), String.valueOf(pos), "java.lang.StringBuilder"); // All leading spaces have already been stripped so we can start // reading the value. However, it may be empty so check for that. int length = dnBytes.length; if (pos >= length) { attributeValue.setValue(""); return pos; } // Look at the first character. If it is an octothorpe (#), then // that means that the value should be a hex string. byte b = dnBytes[pos++]; if (b == '#') { // The first two characters must be hex characters. StringBuilder hexString = new StringBuilder(); if ((pos+2) > length) { int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT; String message = getMessage(msgID, new String(dnBytes)); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } for (int i=0; i < 2; i++) { b = dnBytes[pos++]; if (isHexDigit(b)) { hexString.append((char) b); } else { int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT; String message = getMessage(msgID, new String(dnBytes), (char) b); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } // The rest of the value must be a multiple of two hex // characters. The end of the value may be designated by the // end of the DN, a comma or semicolon, a plus sign, or a space. while (pos < length) { b = dnBytes[pos++]; if (isHexDigit(b)) { hexString.append((char) b); if (pos < length) { b = dnBytes[pos++]; if (isHexDigit(b)) { hexString.append((char) b); } else { int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT; String message = getMessage(msgID, new String(dnBytes), (char) b); throw new DirectoryException( ResultCode.INVALID_DN_SYNTAX, message, msgID); } } else { int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT; String message = getMessage(msgID, new String(dnBytes)); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } else if ((b == ' ') || (b == ',') || (b == ';') || (b == '+')) { // This denotes the end of the value. pos--; break; } else { int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT; String message = getMessage(msgID, new String(dnBytes), (char) b); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } // At this point, we should have a valid hex string. Convert it // to a byte array and set that as the value of the provided // octet string. try { attributeValue.setValue(hexStringToByteArray( hexString.toString())); return pos; } catch (Exception e) { assert debugException(CLASS_NAME, "parseAttributeValue", e); int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE; String message = getMessage(msgID, new String(dnBytes), String.valueOf(e)); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } // If the first character is a quotation mark, then the value // should continue until the corresponding closing quotation mark. else if (b == '"') { int valueStartPos = pos; // Keep reading until we find a closing quotation mark. while (true) { if (pos >= length) { // We hit the end of the DN before the closing quote. // That's an error. int msgID = MSGID_ATTR_SYNTAX_DN_UNMATCHED_QUOTE; String message = getMessage(msgID, new String(dnBytes)); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } if (dnBytes[pos++] == '"') { // This is the end of the value. break; } } byte[] valueBytes = new byte[pos - valueStartPos - 1]; System.arraycopy(dnBytes, valueStartPos, valueBytes, 0, valueBytes.length); try { attributeValue.setValue(valueBytes); } catch (Exception e) { assert debugException(CLASS_NAME, "parseAttributeValue", e); // This should never happen. Just in case, work around it by // converting to a string and back. String valueStr = new String(valueBytes); attributeValue.setValue(valueStr); } return pos; } // Otherwise, use general parsing to find the end of the value. else { // Keep reading until we find a comma/semicolon, a plus sign, or // the end of the DN. int valueStartPos = pos - 1; while (true) { if (pos >= length) { // This is the end of the DN and therefore the end of the // value. break; } b = dnBytes[pos++]; if ((b == ',') || (b == ';') || (b == '+')) { pos--; break; } } // Convert the byte buffer to an array. byte[] valueBytes = new byte[pos - valueStartPos]; System.arraycopy(dnBytes, valueStartPos, valueBytes, 0, valueBytes.length); // Strip off any unescaped spaces that may be at the end of the // value. boolean extraSpaces = false; int lastPos = valueBytes.length - 1; while (lastPos > 0) { if (valueBytes[lastPos] == ' ') { extraSpaces = true; lastPos--; } else { break; } } if (extraSpaces) { byte[] newValueBytes = new byte[lastPos+1]; System.arraycopy(valueBytes, 0, newValueBytes, 0, lastPos+1); valueBytes = newValueBytes; } try { attributeValue.setValue(valueBytes); } catch (Exception e) { assert debugException(CLASS_NAME, "parseAttributeValue", e); // This should never happen. Just in case, work around it by // converting to a string and back. String valueStr = new String(valueBytes); attributeValue.setValue(valueStr); } return pos; } } /** * Parses the attribute value from the provided DN string starting * at the specified location. When the value has been parsed, it * will be assigned to the provided ASN.1 octet string. * * @param dnString The DN string to be parsed. * @param pos The position of the first character in * the attribute value to parse. * @param attributeValue The ASN.1 octet string whose value should * be set to the parsed attribute value when * this method completes successfully. * * @return The position of the first character that is not part of * the attribute value. * * @throws DirectoryException If it was not possible to parse a * valid attribute value from the * provided DN string. */ public static int parseAttributeValue(String dnString, int pos, ByteString attributeValue) throws DirectoryException { assert debugEnter(CLASS_NAME, "parseAttributeValue", String.valueOf(dnString), String.valueOf(pos), "java.lang.StringBuilder"); // All leading spaces have already been stripped so we can start // reading the value. However, it may be empty so check for that. int length = dnString.length(); if (pos >= length) { attributeValue.setValue(""); return pos; } // Look at the first character. If it is an octothorpe (#), then // that means that the value should be a hex string. char c = dnString.charAt(pos++); if (c == '#') { // The first two characters must be hex characters. StringBuilder hexString = new StringBuilder(); if ((pos+2) > length) { int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT; String message = getMessage(msgID, dnString); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } for (int i=0; i < 2; i++) { c = dnString.charAt(pos++); if (isHexDigit(c)) { hexString.append(c); } else { int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT; String message = getMessage(msgID, dnString, c); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } // The rest of the value must be a multiple of two hex // characters. The end of the value may be designated by the // end of the DN, a comma or semicolon, or a space. while (pos < length) { c = dnString.charAt(pos++); if (isHexDigit(c)) { hexString.append(c); if (pos < length) { c = dnString.charAt(pos++); if (isHexDigit(c)) { hexString.append(c); } else { int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT; String message = getMessage(msgID, dnString, c); throw new DirectoryException( ResultCode.INVALID_DN_SYNTAX, message, msgID); } } else { int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT; String message = getMessage(msgID, dnString); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } else if ((c == ' ') || (c == ',') || (c == ';')) { // This denotes the end of the value. pos--; break; } else { int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT; String message = getMessage(msgID, dnString, c); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } // At this point, we should have a valid hex string. Convert it // to a byte array and set that as the value of the provided // octet string. try { attributeValue.setValue(hexStringToByteArray( hexString.toString())); return pos; } catch (Exception e) { assert debugException(CLASS_NAME, "parseAttributeValue", e); int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE; String message = getMessage(msgID, dnString, String.valueOf(e)); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } // If the first character is a quotation mark, then the value // should continue until the corresponding closing quotation mark. else if (c == '"') { // Keep reading until we find an unescaped closing quotation // mark. boolean escaped = false; StringBuilder valueString = new StringBuilder(); while (true) { if (pos >= length) { // We hit the end of the DN before the closing quote. // That's an error. int msgID = MSGID_ATTR_SYNTAX_DN_UNMATCHED_QUOTE; String message = getMessage(msgID, dnString); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } c = dnString.charAt(pos++); if (escaped) { // The previous character was an escape, so we'll take this // one no matter what. valueString.append(c); escaped = false; } else if (c == '\\') { // The next character is escaped. Set a flag to denote // this, but don't include the backslash. escaped = true; } else if (c == '"') { // This is the end of the value. break; } else { // This is just a regular character that should be in the // value. valueString.append(c); } } attributeValue.setValue(valueString.toString()); return pos; } // Otherwise, use general parsing to find the end of the value. else { boolean escaped; StringBuilder valueString = new StringBuilder(); StringBuilder hexChars = new StringBuilder(); if (c == '\\') { escaped = true; } else { escaped = false; valueString.append(c); } // Keep reading until we find an unescaped comma or plus sign or // the end of the DN. while (true) { if (pos >= length) { // This is the end of the DN and therefore the end of the // value. If there are any hex characters, then we need to // deal with them accordingly. appendHexChars(dnString, valueString, hexChars); break; } c = dnString.charAt(pos++); if (escaped) { // The previous character was an escape, so we'll take this // one. However, this could be a hex digit, and if that's // the case then the escape would actually be in front of // two hex digits that should be treated as a special // character. if (isHexDigit(c)) { // It is a hexadecimal digit, so the next digit must be // one too. However, this could be just one in a series // of escaped hex pairs that is used in a string // containing one or more multi-byte UTF-8 characters so // we can't just treat this byte in isolation. Collect // all the bytes together and make sure to take care of // these hex bytes before appending anything else to the // value. if (pos >= length) { int msgID = MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID; String message = getMessage(msgID, dnString); throw new DirectoryException( ResultCode.INVALID_DN_SYNTAX, message, msgID); } else { char c2 = dnString.charAt(pos++); if (isHexDigit(c2)) { hexChars.append(c); hexChars.append(c2); } else { int msgID = MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID; String message = getMessage(msgID, dnString); throw new DirectoryException( ResultCode.INVALID_DN_SYNTAX, message, msgID); } } } else { appendHexChars(dnString, valueString, hexChars); valueString.append(c); } escaped = false; } else if (c == '\\') { escaped = true; } else if ((c == ',') || (c == ';')) { appendHexChars(dnString, valueString, hexChars); pos--; break; } else if (c == '+') { appendHexChars(dnString, valueString, hexChars); pos--; break; } else { appendHexChars(dnString, valueString, hexChars); valueString.append(c); } } // Strip off any unescaped spaces that may be at the end of the // value. if (pos > 2 && dnString.charAt(pos-1) == ' ' && dnString.charAt(pos-2) != '\\') { int lastPos = valueString.length() - 1; while (lastPos > 0) { if (valueString.charAt(lastPos) == ' ') { valueString.delete(lastPos, lastPos+1); lastPos--; } else { break; } } } attributeValue.setValue(valueString.toString()); return pos; } } /** * Decodes a hexadecimal string from the provided * <CODE>hexChars</CODE> buffer, converts it to a byte array, and * then converts that to a UTF-8 string. The resulting UTF-8 string * will be appended to the provided <CODE>valueString</CODE> buffer, * and the <CODE>hexChars</CODE> buffer will be cleared. * * @param dnString The DN string that is being decoded. * @param valueString The buffer containing the value to which the * decoded string should be appended. * @param hexChars The buffer containing the hexadecimal * characters to decode to a UTF-8 string. * * @throws DirectoryException If any problem occurs during the * decoding process. */ public static void appendHexChars(String dnString, StringBuilder valueString, StringBuilder hexChars) throws DirectoryException { try { byte[] hexBytes = hexStringToByteArray(hexChars.toString()); valueString.append(new String(hexBytes, "UTF-8")); hexChars.delete(0, hexChars.length()); } catch (Exception e) { assert debugException(CLASS_NAME, "appendHexChars", e); int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE; String message = getMessage(msgID, dnString, String.valueOf(e)); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } /** * Indicates whether the provided object is equal to this DN. In * order for the object to be considered equal, it must be a DN with * the same number of RDN components and each corresponding RDN * component must be equal. * * @param o * The object for which to make the determination. * @return <code>true</code> if the provided object is a DN that * is equal to this DN, or <code>false</code> if it is * not. * @param o The object for which to make the determination. * * @return <CODE>true</CODE> if the provided object is a DN that is * equal to this DN, or <CODE>false</CODE> if it is not. */ public boolean equals(Object o) { public boolean equals(Object o) { assert debugEnter(CLASS_NAME, "equals", String.valueOf(o)); if (this == o) { if (this == o) { return true; } else if (o instanceof DN) { DN other = (DN) o; return normalizedDN.equals(other.normalizedDN); } else { } if (o == null) { return false; } try { return (normalizedDN.equals(((DN) o).normalizedDN)); } catch (Exception e) { // This most likely means that the object was null or wasn't a // DN. In either case, it's faster to assume that it is and // return false on an exception than to perform the checks to // see if it meets the appropriate // conditions. assert debugException(CLASS_NAME, "equals", e); return false; } } @@ -630,7 +2772,8 @@ * * @return The hash code for this DN. */ public int hashCode() { public int hashCode() { assert debugEnter(CLASS_NAME, "hashCode"); return normalizedDN.hashCode(); @@ -643,12 +2786,32 @@ * * @return A string representation of this DN. */ public String toString() { public String toString() { assert debugEnter(CLASS_NAME, "toString"); StringBuilder builder = new StringBuilder(); toString(builder); return builder.toString(); if (dnString == null) { if (numComponents == 0) { dnString = ""; } else { StringBuilder buffer = new StringBuilder(); rdnComponents[0].toString(buffer); for (int i=1; i < numComponents; i++) { buffer.append(","); rdnComponents[i].toString(buffer); } dnString = buffer.toString(); } } return dnString; } @@ -657,23 +2820,15 @@ * Appends a string representation of this DN to the provided * buffer. * * @param buffer * The buffer to which the information should be appended. * @param buffer The buffer to which the information should be * appended. */ public void toString(StringBuilder buffer) { public void toString(StringBuilder buffer) { assert debugEnter(CLASS_NAME, "toString", "java.lang.StringBuilder"); ensureNotNull(buffer); if (numComponents != 0) { getRDN(0).toString(buffer); for (int i = 1; i < numComponents; i++) { buffer.append(","); getRDN(i).toString(buffer); } } buffer.append(toString()); } @@ -683,9 +2838,31 @@ * * @return A normalized string representation of this DN. */ public String toNormalizedString() { public String toNormalizedString() { assert debugEnter(CLASS_NAME, "toNormalizedString"); if (normalizedDN == null) { if (numComponents == 0) { normalizedDN = ""; } else { StringBuilder buffer = new StringBuilder(); rdnComponents[0].toNormalizedString(buffer); for (int i=1; i < numComponents; i++) { buffer.append(','); rdnComponents[i].toNormalizedString(buffer); } normalizedDN = buffer.toString(); } } return normalizedDN; } @@ -695,16 +2872,15 @@ * Appends a normalized string representation of this DN to the * provided buffer. * * @param buffer * The buffer to which the information should be appended. * @param buffer The buffer to which the information should be * appended. */ public void toNormalizedString(StringBuilder buffer) { public void toNormalizedString(StringBuilder buffer) { assert debugEnter(CLASS_NAME, "toNormalizedString", "java.lang.StringBuilder"); ensureNotNull(buffer); buffer.append(normalizedDN); buffer.append(toNormalizedString()); } @@ -714,69 +2890,54 @@ * This order will be first hierarchical (ancestors will come before * descendants) and then alphabetical by attribute name(s) and * value(s). * <p> * NOTE: the implementation of this method does not perform a * lexicographic comparison of the DN's normalized form. Instead, * each individual RDN is compared using ordering matching rules * where possible. * * @param dn * The DN against which to compare this DN. * @param dn The DN against which to compare this DN. * * @return A negative integer if this DN should come before the * provided DN, a positive integer if this DN should come * after the provided DN, or zero if there is no difference * with regard to ordering. */ public int compareTo(DN dn) { assert debugEnter(CLASS_NAME, "compareTo", String.valueOf(this), String.valueOf(dn)); public int compareTo(DN dn) { assert debugEnter(CLASS_NAME, "compareTo", String.valueOf(dn)); ensureNotNull(dn); int index1 = numComponents - 1; int index2 = dn.numComponents - 1; while (true) { if (index1 >= 0) { if (index2 >= 0) { int value = getRDN(index1).compareTo(dn.getRDN(index2)); if (value != 0) { return value; } } else { return 1; } } else if (index2 >= 0) { return -1; } else { if (equals(dn)) { return 0; } index1--; index2--; else if (isNullDN()) { return -1; } else if (dn.isNullDN()) { return 1; } else if (isAncestorOf(dn)) { return -1; } else if (isDescendantOf(dn)) { return 1; } else { int minComps = Math.min(numComponents, dn.numComponents); for (int i=0; i < minComps; i++) { RDN r1 = rdnComponents[rdnComponents.length-1-i]; RDN r2 = dn.rdnComponents[dn.rdnComponents.length-1-i]; int result = r1.compareTo(r2); if (result != 0) { return result; } } /** * Construct the normalized form of this DN. * * @return Returns the normalized string representation of this DN. */ private String normalize() { if (numComponents == 0) { return ""; } else { StringBuilder buffer = new StringBuilder(); getRDN(0).toNormalizedString(buffer); for (int i = 1; i < numComponents; i++) { buffer.append(','); getRDN(i).toNormalizedString(buffer); } return buffer.toString(); return 0; } } } opends/src/server/org/opends/server/types/RDN.java
@@ -22,29 +22,25 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.types; import static org.opends.server.config.ConfigConstants.*; import static org.opends.server.loggers.Debug.*; import static org.opends.server.messages.CoreMessages.*; import static org.opends.server.messages.MessageHandler.getMessage; import static org.opends.server.messages.SchemaMessages.*; import static org.opends.server.util.StaticUtils.*; import static org.opends.server.util.Validator.ensureNotNull; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.TreeMap; import java.util.TreeSet; import org.opends.server.api.OrderingMatchingRule; import org.opends.server.core.DirectoryServer; import org.opends.server.protocols.asn1.ASN1OctetString; import org.opends.server.util.StaticUtils; import static org.opends.server.loggers.Debug.*; import static org.opends.server.messages.CoreMessages.*; import static org.opends.server.messages.MessageHandler.*; import static org.opends.server.util.StaticUtils.*; @@ -52,55 +48,59 @@ * This class defines a data structure for storing and interacting * with the relative distinguished names associated with entries in * the Directory Server. * <p> * All the methods in this class will throw a * <code>NullPointerException</code> when provided with * <code>null</code> reference parameters unless otherwise stated. */ public final class RDN implements Comparable<RDN> { // TODO: per-thread cache of common RDNs? // TODO: implement pimpl idiom and provide a "singleton" // implementation for common case where the RDN only has a single // type and value. This would result in less memory being used. public class RDN implements Comparable<RDN> { /** * The fully-qualified name of this class for debugging purposes. */ private static final String CLASS_NAME = "org.opends.server.types.RDN"; // The set of attribute types for the elements in this RDN. private final AttributeType[] attributeTypes; // The set of user-provided names for the attributes in this RDN. private final String[] attributeNames; // The set of attribute types for the elements in this RDN. private AttributeType[] attributeTypes; // The set of values for the elements in this RDN. private final AttributeValue[] attributeValues; private AttributeValue[] attributeValues; /** * The cached normalized string representation of this RDN. * * This non-final field will default to null. The Java memory model * guarantees that it will be initialized to null before being * visible to other threads. */ // The number of values for this RDN. private int numValues; // The string representation of the normalized form of this RDN. private String normalizedRDN; // The string representation of this RDN. private String rdnString; // The set of user-provided names for the attributes in this RDN. private String[] attributeNames; /** * Creates a new RDN with the provided information. * * @param type * The attribute type for this RDN. * @param value * The value for this RDN. * @return Returns the new RDN. * @param attributeType The attribute type for this RDN. It must * not be {@code null}. * @param attributeValue The value for this RDN. It must not be * {@code null}. */ public static RDN create(AttributeType type, AttributeValue value) { return create(type, type.getNameOrOID(), value); public RDN(AttributeType attributeType, AttributeValue attributeValue) { assert debugConstructor(CLASS_NAME, String.valueOf(attributeType), String.valueOf(attributeValue)); attributeTypes = new AttributeType[] { attributeType }; attributeNames = new String[] { attributeType.getPrimaryName() }; attributeValues = new AttributeValue[] { attributeValue }; numValues = 1; rdnString = null; normalizedRDN = null; } @@ -108,392 +108,98 @@ /** * Creates a new RDN with the provided information. * * @param type * The attribute type for this RDN. * @param name * The user-provided name for this RDN. * @param value * The value for this RDN. * @return Returns the new RDN. * @param attributeType The attribute type for this RDN. It must * not be {@code null}. * @param attributeName The user-provided name for this RDN. It * must not be {@code null}. * @param attributeValue The value for this RDN. It must not be * {@code null}. */ public static RDN create(AttributeType type, String name, AttributeValue value) { ensureNotNull(type, name, value); public RDN(AttributeType attributeType, String attributeName, AttributeValue attributeValue) { assert debugConstructor(CLASS_NAME, String.valueOf(attributeType), String.valueOf(attributeName), String.valueOf(attributeValue)); AttributeType[] types = new AttributeType[] { type }; String[] names = new String[] { name }; AttributeValue[] values = new AttributeValue[] { value }; attributeTypes = new AttributeType[] { attributeType }; attributeNames = new String[] { attributeName }; attributeValues = new AttributeValue[] { attributeValue }; return new RDN(types, names, values); numValues = 1; rdnString = null; normalizedRDN = null; } /** * Create a new RDN builder which can be used to incrementally build * a new RDN. * Creates a new RDN with the provided information. The number of * type, name, and value elements must be nonzero and equal. * * @return Returns the new RDN builder. * @param attributeTypes The set of attribute types for this RDN. * It must not be empty or {@code null}. * @param attributeNames The set of user-provided names for this * RDN. It must have the same number of * elements as the {@attributeTypes} * argument. * @param attributeValues The set of values for this RDN. It must * have the same number of elements as the * {@attributeTypes} argument. */ public static Builder createBuilder() { return new Builder(); public RDN(List<AttributeType> attributeTypes, List<String> attributeNames, List<AttributeValue> attributeValues) { assert debugConstructor(CLASS_NAME, String.valueOf(attributeTypes), String.valueOf(attributeNames), String.valueOf(attributeValues)); this.attributeTypes = new AttributeType[attributeTypes.size()]; this.attributeNames = new String[attributeNames.size()]; this.attributeValues = new AttributeValue[attributeValues.size()]; attributeTypes.toArray(this.attributeTypes); attributeNames.toArray(this.attributeNames); attributeValues.toArray(this.attributeValues); numValues = attributeTypes.size(); rdnString = null; normalizedRDN = null; } /** * This class provides an interface for constructing RDNs * incrementally. * <p> * Typically, an application will construct a new * <code>Builder</code> and append attribute value assertions * (AVAs) using the <code>append</code> method. When the RDN is * fully constructed, it can be retrieved using the * <code>getInstance</code> method. */ public static final class Builder { // The list of attribute types. private List<AttributeType> attributeTypes; // The list of user-provided attribute names. private List<String> attributeNames; // The list of attribute values. private List<AttributeValue> attributeValues; /** * Create the new empty RDN builder. */ private Builder() { clear(); } /** * Appends the provided attribute value assertion to the RDN. * Creates a new RDN with the provided information. The number of * type, name, and value elements must be nonzero and equal. * * @param type * The attribute type. * @param value * The attribute value. * @throws IllegalArgumentException * If the RDN being constructed already contains an * attribute value assertion for this attribute type. * @param attributeTypes The set of attribute types for this RDN. * It must not be empty or {@code null}. * @param attributeNames The set of user-provided names for this * RDN. It must have the same number of * elements as the {@attributeTypes} * argument. * @param attributeValues The set of values for this RDN. It must * have the same number of elements as the * {@attributeTypes} argument. */ public void append(AttributeType type, AttributeValue value) throws IllegalArgumentException { append(type, type.getNameOrOID(), value); } public RDN(AttributeType[] attributeTypes, String[] attributeNames, AttributeValue[] attributeValues) { assert debugConstructor(CLASS_NAME, String.valueOf(attributeTypes), String.valueOf(attributeNames), String.valueOf(attributeValues)); this.numValues = attributeTypes.length; this.attributeTypes = attributeTypes; this.attributeNames = attributeNames; this.attributeValues = attributeValues; /** * Appends the provided attribute value assertion to the RDN. * * @param type * The attribute type. * @param name * The user-provided attribute name. * @param value * The attribute value. * @throws IllegalArgumentException * If the RDN being constructed already contains an * attribute value assertion for this attribute type. */ public void append(AttributeType type, String name, AttributeValue value) throws IllegalArgumentException { ensureNotNull(type, name, value); if (attributeTypes.contains(type)) { throw new IllegalArgumentException( "Builder already contains the attribute type " + type.getNameOrOID()); } attributeTypes.add(type); attributeNames.add(name); attributeValues.add(value); } /** * Parses an RDN from the provided string starting at the * specified location, appending any AVAs to this RDN builder. * <p> * This method is package visible so that it can be used by * the DN decoder. It is not intended for use elsewhere. * * @param s * The string to be parsed. * @param pos * The position of the first character in the string to * parse. * @param allowEmpty * Flag indicating whether or not the parsed RDN can be * empty or not. * @return Returns <code>-1</code> if decoding was successful * and parsing consumed the remainder of the string * (including trailing space), or the position of the next * RDN separator character (i.e. a ',' or ';'). * @throws DirectoryException * If it was not possible to parse a valid RDN from the * provided string. */ int parse(String s, int pos, boolean allowEmpty) throws DirectoryException { assert debugEnter(CLASS_NAME, "parse", String.valueOf(s), String.valueOf(pos)); // There must be at least one AVA. int count = attributeTypes.size(); pos = parseAVA(s, pos); if (pos == -1 && !allowEmpty) { if (count == attributeTypes.size()) { // Nothing was parsed. int msgID = MSGID_RDN_DECODE_NULL; String message = getMessage(msgID); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } // Parse any remaining AVAs. while (pos != -1 && s.charAt(pos) == '+') { count = attributeTypes.size(); pos = parseAVA(s, pos + 1); if (pos == -1 && count == attributeTypes.size()) { // Nothing was parsed. int msgID = MSGID_RDN_UNEXPECTED_COMMA; String message = getMessage(msgID, s, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } return pos; } /** * Parse a single AVA. * * @param s * The string to be parsed. * @param pos * The position of the first character in the string to * parse. * @return Returns <code>-1</code> if decoding was successful * and parsing consumed the remainder of the possibly * empty string (including trailing space), or the * position of the next AVA separator character (i.e. a * '+', ',' or ';'). * @throws DirectoryException * If it was not possible to parse a valid AVA from the * provided string. */ private int parseAVA(String s, int pos) throws DirectoryException { int length = s.length(); // Skip over any spaces that may follow it // before the next attribute name. char c; while ((pos < length) && ((c = s.charAt(pos)) == ' ')) { pos++; } // Reached the end of the string - let the caller handle this. if (pos >= length) { return -1; } // Parse the attribute name. StringBuilder attributeName = new StringBuilder(); pos = parseAttributeName(s, pos, attributeName); // Make sure we're not at the end of the RDN. if (pos >= length) { int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME; String message = getMessage(msgID, s, attributeName .toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces between the attribute name and the // equal sign. c = s.charAt(pos); while (c == ' ') { pos++; if (pos >= length) { // This means that we hit the end of the string before // finding a '='. This is illegal because there is no // attribute-value separator. int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME; String message = getMessage(msgID, s, attributeName .toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else { c = s.charAt(pos); } } // The next character must be an equal sign. if (c == '=') { pos++; } else { int msgID = MSGID_ATTR_SYNTAX_DN_NO_EQUAL; String message = getMessage(msgID, s, attributeName .toString(), c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces after the equal sign. while ((pos < length) && ((c = s.charAt(pos)) == ' ')) { pos++; } // If we are at the end of the RDN string, then that must mean // that the attribute value was empty. This will probably // never happen in a real-world environment, but technically // isn't illegal. If it does happen, then go ahead and return // the RDN. if (pos >= length) { String name = attributeName.toString(); String lowerName = toLowerCase(name); AttributeType attrType = DirectoryServer .getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know // about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } AttributeValue value = new AttributeValue( new ASN1OctetString(), new ASN1OctetString()); append(attrType, name, value); return -1; } // Parse the value for this RDN component. ByteString parsedValue = new ASN1OctetString(); pos = parseAttributeValue(s, pos, parsedValue); // Update the RDN to include the new attribute/value. String name = attributeName.toString(); String lowerName = toLowerCase(name); AttributeType attrType = DirectoryServer .getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } AttributeValue value = new AttributeValue(attrType, parsedValue); append(attrType, name, value); // Skip over any spaces that might be after the attribute // value. while ((pos < length) && ((c = s.charAt(pos)) == ' ')) { pos++; } // If we're at the end of the string, then return the RDN. if (pos >= length) { return -1; } // If the next character is a comma or semicolon, then that is // not allowed. It would be legal for a DN but not an RDN. if ((c == ',') || (c == ';')) { return pos; } // If the next character is anything but a plus sign, then it // is illegal. if (c != '+') { int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_CHAR; String message = getMessage(msgID, s, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } return pos; } /** * Removes all the attribute value assertions from this RDN * builder. */ public void clear() { attributeTypes = new ArrayList<AttributeType>(3); attributeValues = new ArrayList<AttributeValue>(3); attributeNames = new ArrayList<String>(3); } /** * Returns <code>true</code> if this RDN builder is empty. * * @return Returns <code>true</code> if this RDN builder is * empty. */ public boolean isEmpty() { return attributeTypes.isEmpty(); } /** * Creates a new RDN instance based on the current contents of * this RDN builder. Subsequent changes to this RDN builder do not * affect the contents of the returned <code>RDN</code>. * * @return Returns a new RDN instance based on the current * contents of this RDN builder. * @throws IllegalStateException * If a new RDN could not be created because this RDN * builder is emtpy. */ public RDN getInstance() throws IllegalStateException { int sz = attributeTypes.size(); if (sz == 0) { throw new IllegalStateException("RDN builder is empty"); } AttributeType[] types = new AttributeType[sz]; String[] names = new String[sz]; AttributeValue[] values = new AttributeValue[sz]; attributeTypes.toArray(types); attributeNames.toArray(names); attributeValues.toArray(values); return new RDN(types, names, values); } rdnString = null; normalizedRDN = null; } @@ -501,21 +207,21 @@ /** * Creates a new RDN with the provided information. * * @param types * The attribute types for this RDN. * @param names * The user-provided names for this RDN. * @param values * The values for this RDN. * @param attributeType The attribute type for this RDN. It must * not be {@code null}. * @param attributeValue The value for this RDN. It must not be * {@code null}. * * @return The RDN created with the provided information. */ private RDN(AttributeType[] types, String[] names, AttributeValue[] values) { assert debugConstructor(CLASS_NAME, String.valueOf(types), String .valueOf(names), String.valueOf(values)); public static RDN create(AttributeType attributeType, AttributeValue attributeValue) { assert debugEnter(CLASS_NAME, "create", String.valueOf(attributeType), String.valueOf(attributeValue)); this.attributeTypes = types; this.attributeNames = names; this.attributeValues = values; return new RDN(attributeType, attributeValue); } @@ -527,86 +233,11 @@ * @return The number of attribute-value pairs contained in this * RDN. */ public int getNumValues() { public int getNumValues() { assert debugEnter(CLASS_NAME, "getNumValues"); return attributeTypes.length; } /** * Retrieves the attribute type at the specified AVA in this RDN. * * @param index * The index of the AVA in this RDN. * @return Returns the attribute type at the specified AVA in this * RDN. * @throws IndexOutOfBoundsException * If <code>index</code> is out of range * <code>(index < 0 || index >= getNumValues()</code>. */ public AttributeType getAttributeType(int index) throws IndexOutOfBoundsException { assert debugEnter(CLASS_NAME, "getAttributeType"); if (index < 0 || index >= attributeTypes.length) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + attributeTypes.length); } return attributeTypes[index]; } /** * Retrieves the user-defined attribute name at the specified AVA in * this RDN. * * @param index * The index of the AVA in this RDN. * @return Returns the user-defined attribute name at the specified * AVA in this RDN. * @throws IndexOutOfBoundsException * If <code>index</code> is out of range * <code>(index < 0 || index >= getNumValues()</code>. */ public String getAttributeName(int index) throws IndexOutOfBoundsException { assert debugEnter(CLASS_NAME, "getAttributeName"); if (index < 0 || index >= attributeTypes.length) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + attributeTypes.length); } return attributeNames[index]; } /** * Retrieves the attribute value at the specified AVA in this RDN. * <p> * Applications <b>must not</b> modify the contents of the returned * attribute value. * * @param index * The index of the AVA in this RDN. * @return Returns the attribute value at the specified AVA in this * RDN. * @throws IndexOutOfBoundsException * If <code>index</code> is out of range * <code>(index < 0 || index >= getNumValues()</code>. */ public AttributeValue getAttributeValue(int index) throws IndexOutOfBoundsException { assert debugEnter(CLASS_NAME, "getAttributeValue"); if (index < 0 || index >= attributeTypes.length) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + attributeTypes.length); } return attributeValues[index]; return numValues; } @@ -614,19 +245,21 @@ /** * Indicates whether this RDN includes the specified attribute type. * * @param attributeType * The attribute type for which to make the determination. * @return <code>true</code> if the RDN includes the specified * attribute type, or <code>false</code> if not. * @param attributeType The attribute type for which to make the * determination. * * @return <CODE>true</CODE> if the RDN includes the specified * attribute type, or <CODE>false</CODE> if not. */ public boolean hasAttributeType(AttributeType attributeType) { assert debugEnter(CLASS_NAME, "hasAttributeType", String .valueOf(attributeType)); public boolean hasAttributeType(AttributeType attributeType) { assert debugEnter(CLASS_NAME, "hasAttributeType", String.valueOf(attributeType)); ensureNotNull(attributeType); for (AttributeType t : attributeTypes) { if (t.equals(attributeType)) { for (AttributeType t : attributeTypes) { if (t.equals(attributeType)) { return true; } } @@ -637,28 +270,100 @@ /** * Indicates whether this RDN includes the specified attribute type. * * @param lowerName The name or OID for the attribute type for * which to make the determination, formatted in * all lowercase characters. * * @return <CODE>true</CODE> if the RDN includes the specified * attribute type, or <CODE>false</CODE> if not. */ public boolean hasAttributeType(String lowerName) { assert debugEnter(CLASS_NAME, "hasAttributeType", String.valueOf(lowerName)); for (AttributeType t : attributeTypes) { if (t.hasNameOrOID(lowerName)) { return true; } } for (String s : attributeNames) { if (s.equalsIgnoreCase(lowerName)) { return true; } } return false; } /** * Retrieves the attribute type at the specified position in the set * of attribute types for this RDN. * * @param pos The position of the attribute type to retrieve. * * @return The attribute type at the specified position in the set * of attribute types for this RDN. */ public AttributeType getAttributeType(int pos) { assert debugEnter(CLASS_NAME, "getAttributeType", String.valueOf(pos)); return attributeTypes[pos]; } /** * Retrieves the name for the attribute type at the specified * position in the set of attribute types for this RDN. * * @param pos The position of the attribute type for which to * retrieve the name. * * @return The name for the attribute type at the specified * position in the set of attribute types for this RDN. */ public String getAttributeName(int pos) { assert debugEnter(CLASS_NAME, "getAttributeName", String.valueOf(pos)); return attributeNames[pos]; } /** * Retrieves the attribute value that is associated with the * specified attribute type. * <p> * Applications <b>must not</b> modify the contents of the returned * attribute value. * * @param attributeType * The attribute type for which to retrieve the * corresponding value. * @param attributeType The attribute type for which to retrieve * the corresponding value. * * @return The value for the requested attribute type, or * <code>null</code> if the specified attribute type is * not present in the RDN. * <CODE>null</CODE> if the specified attribute type is not * present in the RDN. */ public AttributeValue getAttributeValue( AttributeType attributeType) { assert debugEnter(CLASS_NAME, "getAttributeValue", String .valueOf(attributeType)); public AttributeValue getAttributeValue(AttributeType attributeType) { assert debugEnter(CLASS_NAME, "getAttributeValue", String.valueOf(attributeType)); ensureNotNull(attributeType); for (int i = 0; i < attributeTypes.length; i++) { if (attributeTypes[i].equals(attributeType)) { for (int i=0; i < numValues; i++) { if (attributeTypes[i].equals(attributeType)) { return attributeValues[i]; } } @@ -669,59 +374,121 @@ /** * Retrieves the value for the attribute type at the specified * position in the set of attribute types for this RDN. * * @param pos The position of the attribute type for which to * retrieve the value. * * @return The value for the attribute type at the specified * position in the set of attribute types for this RDN. */ public AttributeValue getAttributeValue(int pos) { assert debugEnter(CLASS_NAME, "getAttributeName", String.valueOf(pos)); return attributeValues[pos]; } /** * Indicates whether this RDN is multivalued. * * @return <code>true</code> if this RDN is multivalued, or * <code>false</code> if not. * @return <CODE>true</CODE> if this RDN is multivalued, or * <CODE>false</CODE> if not. */ public boolean isMultiValued() { public boolean isMultiValued() { assert debugEnter(CLASS_NAME, "isMultiValued"); return (attributeTypes.length > 1); return (numValues > 1); } /** * Returns an <code>RDN</code> object holding the value of the * specified <code>String</code>. The argument is interpreted as * representing the LDAP string representation of an RDN. * <p> * This method is identical to {@link #decode(String)}. * Indicates whether this RDN contains the specified type-value * pair. * * @param s * The string to be parsed. * @return Returns a <code>RDN</code> holding the value * represented by the <code>string</code> argument. * @throws DirectoryException * If a problem occurs while trying to decode the provided * string as a RDN. * @param type The attribute type for which to make the * determination. * @param value The value for which to make the determination. * * @return <CODE>true</CODE> if this RDN contains the specified * attribute value, or <CODE>false</CODE> if not. */ public static RDN valueOf(String s) throws DirectoryException { return decode(s); public boolean hasValue(AttributeType type, AttributeValue value) { assert debugEnter(CLASS_NAME, "hasValue", String.valueOf(type), String.valueOf(value)); for (int i=0; i < numValues; i++) { if (attributeTypes[i].equals(type) && attributeValues[i].equals(value)) { return true; } } return false; } /** * Decodes the provided ASN.1 octet string as a RDN. * Adds the provided type-value pair from this RDN. Note that this * is intended only for internal use when constructing DN values. * * @param rdnString * The ASN.1 octet string to decode as a RDN. * @return The decoded RDN. * @throws DirectoryException * If a problem occurs while trying to decode the provided * ASN.1 octet string as a RDN. * @param type The attribute type of the pair to add. * @param name The user-provided name of the pair to add. * @param value The attribute value of the pair to add. * * @return <CODE>true</CODE> if the type-value pair was added to * this RDN, or <CODE>false</CODE> if it was not (e.g., it * was already present). */ public static RDN decode(ByteString rdnString) throws DirectoryException { assert debugEnter(CLASS_NAME, "decode", String.valueOf(rdnString)); boolean addValue(AttributeType type, String name, AttributeValue value) { assert debugEnter(CLASS_NAME, "addValue", String.valueOf(type), String.valueOf(name), String.valueOf(value)); ensureNotNull(rdnString); for (int i=0; i < numValues; i++) { if (attributeTypes[i].equals(type) && attributeValues[i].equals(value)) { return false; } } // Use string-based decoder. return decode(rdnString.stringValue()); numValues++; AttributeType[] newTypes = new AttributeType[numValues]; System.arraycopy(attributeTypes, 0, newTypes, 0, attributeTypes.length); newTypes[attributeTypes.length] = type; attributeTypes = newTypes; String[] newNames = new String[numValues]; System.arraycopy(attributeNames, 0, newNames, 0, attributeNames.length); newNames[attributeNames.length] = name; attributeNames = newNames; AttributeValue[] newValues = new AttributeValue[numValues]; System.arraycopy(attributeValues, 0, newValues, 0, attributeValues.length); newValues[attributeValues.length] = value; attributeValues = newValues; rdnString = null; normalizedRDN = null; return true; } @@ -729,75 +496,452 @@ /** * Decodes the provided string as an RDN. * * @param rdnString * The string to decode as an RDN. * @param rdnString The string to decode as an RDN. * * @return The decoded RDN. * @throws DirectoryException * If a problem occurs while trying to decode the provided * string as a RDN. * * @throws DirectoryException If a problem occurs while trying to * decode the provided string as a RDN. */ public static RDN decode(String rdnString) throws DirectoryException { throws DirectoryException { assert debugEnter(CLASS_NAME, "decode", String.valueOf(rdnString)); ensureNotNull(rdnString); // Use an RDN builder to parse the string. Builder builder = createBuilder(); int pos = builder.parse(rdnString, 0, false); // A null or empty RDN is not acceptable. if (rdnString == null) { int msgID = MSGID_RDN_DECODE_NULL; String message = getMessage(msgID); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Make sure that the string did not contain any trailing RDNs. if (pos != -1) { int length = rdnString.length(); if (length == 0) { int msgID = MSGID_RDN_DECODE_NULL; String message = getMessage(msgID); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Iterate through the RDN string. The first thing to do is to // get rid of any leading spaces. int pos = 0; char c = rdnString.charAt(pos); while (c == ' ') { pos++; if (pos == length) { // This means that the RDN was completely comprised of spaces, // which is not valid. int msgID = MSGID_RDN_DECODE_NULL; String message = getMessage(msgID); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else { c = rdnString.charAt(pos); } } // We know that it's not an empty RDN, so we can do the real // processing. First, parse the attribute name. We can borrow // the DN code for this. boolean allowExceptions = DirectoryServer.allowAttributeNameExceptions(); StringBuilder attributeName = new StringBuilder(); pos = DN.parseAttributeName(rdnString, pos, attributeName, allowExceptions); // Make sure that we're not at the end of the RDN string because // that would be invalid. if (pos >= length) { int msgID = MSGID_RDN_END_WITH_ATTR_NAME; String message = getMessage(msgID, rdnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces between the attribute name and its value. c = rdnString.charAt(pos); while (c == ' ') { pos++; if (pos >= length) { // This means that we hit the end of the string before // finding a '='. This is illegal because there is no // attribute-value separator. int msgID = MSGID_RDN_END_WITH_ATTR_NAME; String message = getMessage(msgID, rdnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else { c = rdnString.charAt(pos); } } // The next character must be an equal sign. If it is not, then // that's an error. if (c == '=') { pos++; } else { int msgID = MSGID_RDN_NO_EQUAL; String message = getMessage(msgID, rdnString, attributeName.toString(), c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces between the equal sign and the value. while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) { pos++; } // If we are at the end of the RDN string, then that must mean // that the attribute value was empty. This will probably never // happen in a real-world environment, but technically isn't // illegal. If it does happen, then go ahead and return the RDN. if (pos >= length) { String name = attributeName.toString(); String lowerName = toLowerCase(name); AttributeType attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } AttributeValue value = new AttributeValue(new ASN1OctetString(), new ASN1OctetString()); return new RDN(attrType, name, value); } // Parse the value for this RDN component. This can be done using // the DN code. ByteString parsedValue = new ASN1OctetString(); pos = DN.parseAttributeValue(rdnString, pos, parsedValue); // Create the new RDN with the provided information. However, // don't return it yet because this could be a multi-valued RDN. String name = attributeName.toString(); String lowerName = toLowerCase(name); AttributeType attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the default // syntax. If this is a problem, it will be caught later either // by not finding the target entry or by not allowing the entry // to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } AttributeValue value = new AttributeValue(attrType, parsedValue); RDN rdn = new RDN(attrType, name, value); // Skip over any spaces that might be after the attribute value. while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) { pos++; } // Most likely, this is the end of the RDN. If so, then return // it. if (pos >= length) { return rdn; } // If the next character is a comma or semicolon, then that is not // allowed. It would be legal for a DN but not an RDN. if ((c == ',') || (c == ';')) { int msgID = MSGID_RDN_UNEXPECTED_COMMA; String message = getMessage(msgID, rdnString, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Return the parsed RDN instance. return builder.getInstance(); // If the next character is anything but a plus sign, then it is // illegal. if (c != '+') { int msgID = MSGID_RDN_ILLEGAL_CHARACTER; String message = getMessage(msgID, rdnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // If we have gotten here, then it is a multi-valued RDN. Parse // the remaining attribute/value pairs and add them to the RDN // that we've already created. while (true) { // Skip over the plus sign and any spaces that may follow it // before the next attribute name. pos++; while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) { pos++; } // Parse the attribute name. attributeName = new StringBuilder(); pos = DN.parseAttributeName(rdnString, pos, attributeName, allowExceptions); // Make sure we're not at the end of the RDN. if (pos >= length) { int msgID = MSGID_RDN_END_WITH_ATTR_NAME; String message = getMessage(msgID, rdnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces between the attribute name and the equal // sign. c = rdnString.charAt(pos); while (c == ' ') { pos++; if (pos >= length) { // This means that we hit the end of the string before // finding a '='. This is illegal because there is no // attribute-value separator. int msgID = MSGID_RDN_END_WITH_ATTR_NAME; String message = getMessage(msgID, rdnString, attributeName.toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else { c = rdnString.charAt(pos); } } // The next character must be an equal sign. if (c == '=') { pos++; } else { int msgID = MSGID_RDN_NO_EQUAL; String message = getMessage(msgID, rdnString, attributeName.toString(), c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // Skip over any spaces after the equal sign. while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) { pos++; } // If we are at the end of the RDN string, then that must mean // that the attribute value was empty. This will probably never // happen in a real-world environment, but technically isn't // illegal. If it does happen, then go ahead and return the // RDN. if (pos >= length) { name = attributeName.toString(); lowerName = toLowerCase(name); attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } value = new AttributeValue(new ASN1OctetString(), new ASN1OctetString()); rdn.addValue(attrType, name, value); return rdn; } // Parse the value for this RDN component. parsedValue = new ASN1OctetString(); pos = DN.parseAttributeValue(rdnString, pos, parsedValue); // Update the RDN to include the new attribute/value. name = attributeName.toString(); lowerName = toLowerCase(name); attrType = DirectoryServer.getAttributeType(lowerName); if (attrType == null) { // This must be an attribute type that we don't know about. // In that case, we'll create a new attribute using the // default syntax. If this is a problem, it will be caught // later either by not finding the target entry or by not // allowing the entry to be added. attrType = DirectoryServer.getDefaultAttributeType(name); } value = new AttributeValue(attrType, parsedValue); rdn.addValue(attrType, name, value); // Skip over any spaces that might be after the attribute value. while ((pos < length) && ((c = rdnString.charAt(pos)) == ' ')) { pos++; } // If we're at the end of the string, then return the RDN. if (pos >= length) { return rdn; } // If the next character is a comma or semicolon, then that is // not allowed. It would be legal for a DN but not an RDN. if ((c == ',') || (c == ';')) { int msgID = MSGID_RDN_UNEXPECTED_COMMA; String message = getMessage(msgID, rdnString, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } // If the next character is anything but a plus sign, then it is // illegal. if (c != '+') { int msgID = MSGID_RDN_ILLEGAL_CHARACTER; String message = getMessage(msgID, rdnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } } /** * Creates a duplicate of this RDN that can be modified without * impacting this RDN. * * @return A duplicate of this RDN that can be modified without * impacting this RDN. */ public RDN duplicate() { assert debugEnter(CLASS_NAME, "duplicate"); AttributeType[] newTypes = new AttributeType[numValues]; System.arraycopy(attributeTypes, 0, newTypes, 0, numValues); String[] newNames = new String[numValues]; System.arraycopy(attributeNames, 0, newNames, 0, numValues); AttributeValue[] newValues = new AttributeValue[numValues]; System.arraycopy(attributeValues, 0, newValues, 0, numValues); return new RDN(newTypes, newNames, newValues); } /** * Indicates whether the provided object is equal to this RDN. It * will only be considered equal if it is an RDN object containing * the same attribute value assertions as this RDN (the order does * not matter). * will only be considered equal if it is an RDN object that * contains the same number of elements in the same order with the * same types and normalized values. * * @param o * The object for which to make the determination. * @return <code>true</code> if it is determined that the provided * object is equal to this RDN, or <code>false</code> if * @param o The object for which to make the determination. * * @return <CODE>true</CODE> if it is determined that the provided * object is equal to this RDN, or <CODE>false</CODE> if * not. */ public boolean equals(Object o) { public boolean equals(Object o) { assert debugEnter(CLASS_NAME, "equals", String.valueOf(o)); if (this == o) { if (this == o) { return true; } else if (o instanceof RDN) { RDN other = (RDN) o; } String nvalue1 = toNormalizedString(); String nvalue2 = other.toNormalizedString(); return nvalue1.equals(nvalue2); } else { if ((o == null) || (! (o instanceof RDN))) { return false; } RDN rdn = (RDN) o; return toNormalizedString().equals(rdn.toNormalizedString()); } /** * Retrieves the hash code for this RDN. It will be calculated as * the hash code of the RDN's normalized string representation. * the sum of the hash codes of the types and values. * * @return The hash code for this RDN. */ public int hashCode() { public int hashCode() { assert debugEnter(CLASS_NAME, "hashCode"); return toNormalizedString().hashCode(); @@ -810,12 +954,28 @@ * * @return A string representation of this RDN. */ public String toString() { assert debugEnter(CLASS_NAME, "toString"); public String toString() { if (rdnString == null) { StringBuilder buffer = new StringBuilder(); toString(buffer); return buffer.toString(); buffer.append(attributeNames[0]); buffer.append("="); buffer.append(attributeValues[0].getDNStringValue()); for (int i=1; i < numValues; i++) { buffer.append("+"); buffer.append(attributeNames[i]); buffer.append("="); buffer.append(attributeValues[i].getDNStringValue()); } rdnString = buffer.toString(); } return rdnString; } @@ -824,29 +984,15 @@ * Appends a string representation of this RDN to the provided * buffer. * * @param buffer * The buffer to which the string representation should be * appended. * @param buffer The buffer to which the string representation * should be appended. */ public void toString(StringBuilder buffer) { public void toString(StringBuilder buffer) { assert debugEnter(CLASS_NAME, "toString", "java.lang.StringBuilder"); ensureNotNull(buffer); buffer.append(attributeNames[0]); buffer.append("="); String value = attributeValues[0].getStringValue(); quoteAttributeValue(buffer, value); for (int i = 1; i < attributeTypes.length; i++) { buffer.append("+"); buffer.append(attributeNames[i]); buffer.append("="); value = attributeValues[i].getStringValue(); quoteAttributeValue(buffer, value); } buffer.append(toString()); } @@ -856,36 +1002,12 @@ * * @return A normalized string representation of this RDN. */ public String toNormalizedString() { if (normalizedRDN == null) { StringBuilder builder = new StringBuilder(); if (attributeNames.length == 1) { // Optimize for the common case of a single AVA. appendNormalizedAVA(builder, attributeTypes[0], attributeValues[0]); } else { // Multiple AVAs require sorting. TreeMap<String, Integer> map; map = new TreeMap<String, Integer>(); for (int i = 0; i < attributeTypes.length; i++) { map.put(attributeTypes[i].getNameOrOID(), i); } boolean isFirst = true; for (Integer i : map.values()) { if (!isFirst) { builder.append('+'); } else { isFirst = false; } appendNormalizedAVA(builder, attributeTypes[i], attributeValues[i]); } } normalizedRDN = builder.toString(); public String toNormalizedString() { if (normalizedRDN == null) { StringBuilder buffer = new StringBuilder(); toNormalizedString(buffer); } return normalizedRDN; @@ -897,899 +1019,262 @@ * Appends a normalized string representation of this RDN to the * provided buffer. * * @param buffer * The buffer to which to append the information. * @param buffer The buffer to which to append the information. */ public void toNormalizedString(StringBuilder buffer) { public void toNormalizedString(StringBuilder buffer) { assert debugEnter(CLASS_NAME, "toNormalizedString", "java.lang.StringBuilder"); ensureNotNull(buffer); if (normalizedRDN != null) { buffer.append(normalizedRDN); return; } buffer.append(toNormalizedString()); boolean bufferEmpty = (buffer.length() == 0); if (attributeNames.length == 1) { toLowerCase(attributeTypes[0].getNameOrOID(), buffer); buffer.append('='); try { buffer.append( attributeValues[0].getNormalizedDNStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "toNormalizedString", e); buffer.append(attributeValues[0].getStringValue()); } } else { TreeSet<String> rdnElementStrings = new TreeSet<String>(); for (int i=0; i < attributeNames.length; i++) { StringBuilder b2 = new StringBuilder(); toLowerCase(attributeTypes[i].getNameOrOID(), b2); b2.append('='); try { b2.append(attributeValues[i].getNormalizedStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "toNormalizedString", e); b2.append(attributeValues[i].getStringValue()); } rdnElementStrings.add(b2.toString()); } Iterator<String> iterator = rdnElementStrings.iterator(); buffer.append(iterator.next()); while (iterator.hasNext()) { buffer.append('+'); buffer.append(iterator.next()); } } if (bufferEmpty) { normalizedRDN = buffer.toString(); } } /** * Compares this RDN with the provided RDN. * <p> * The comparison will be done in order of the sorted RDN * components. It will attempt to use an ordering matching rule for * the associated attributes (if one is provided), but will fall * back on a bytewise comparison of the normalized values if * necessary. * Compares this RDN with the provided RDN based on an alphabetic * comparison of the attribute names and values. * * @param rdn * The RDN against which to compare this RDN. * @param rdn The RDN against which to compare this RDN. * * @return A negative integer if this RDN should come before the * provided RDN, a positive integer if this RDN should come * after the provided RDN, or zero if there is no difference * with regard to ordering. * after the provided RDN, or zero if there is no * difference with regard to ordering. */ public int compareTo(RDN rdn) { public int compareTo(RDN rdn) { assert debugEnter(CLASS_NAME, "compareTo", String.valueOf(rdn)); ensureNotNull(rdn); // Handle the common case efficiently. if (attributeTypes.length == 1 && rdn.attributeTypes.length == 1) { AttributeType type1 = attributeTypes[0]; AttributeType type2 = rdn.attributeTypes[0]; AttributeValue value1 = attributeValues[0]; AttributeValue value2 = rdn.attributeValues[0]; return compareAVA(type1, value1, type2, value2); if ((attributeTypes.length == 1) && (rdn.attributeTypes.length == 1)) { if (attributeTypes[0].equals(rdn.attributeTypes[0])) { int valueComparison; OrderingMatchingRule omr = attributeTypes[0].getOrderingMatchingRule(); if (omr == null) { try { return attributeValues[0].getNormalizedStringValue(). compareTo(rdn.attributeValues[0]. getNormalizedStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "compareTo", e); // We have at least one multi-valued RDNs, so we need to sort. TreeMap<String, Integer> map1; TreeMap<String, Integer> map2; map1 = new TreeMap<String, Integer>(); map2 = new TreeMap<String, Integer>(); for (int i = 0; i < attributeTypes.length; i++) { map1.put(attributeTypes[i].getNameOrOID(), i); } for (int i = 0; i < rdn.attributeTypes.length; i++) { map2.put(rdn.attributeTypes[i].getNameOrOID(), i); } // Now compare the sorted AVAs. Iterator<Integer> i1= map1.values().iterator(); Iterator<Integer> i2 = map2.values().iterator(); while (i1.hasNext() && i2.hasNext()) { int int1 = i1.next(); int int2 = i2.next(); AttributeType type1 = attributeTypes[int1]; AttributeType type2 = rdn.attributeTypes[int2]; AttributeValue value1 = attributeValues[int1]; AttributeValue value2 = rdn.attributeValues[int2]; int rc = compareAVA(type1, value1, type2, value2); if (rc != 0) { return rc; return attributeValues[0].getStringValue(). compareTo(rdn.attributeValues[0]. getStringValue()); } } else { try { return omr.compareValues( attributeValues[0].getNormalizedValue(), rdn.attributeValues[0].getNormalizedValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "compareTo", e); // At least one of the iterators has finished. if (i1.hasNext() == false && i2.hasNext() == false) { return 0; } else if (i1.hasNext() == false) { return -1; } else { return 1; return omr.compareValues( attributeValues[0].getValue(), rdn.attributeValues[0].getValue()); } } /** * Compare two AVAs for order. * * @param type1 * The attribute type of the first AVA. * @param value1 * The attribute value of the first AVA. * @param type2 * The attribute type of the second AVA. * @param value2 * The attribute value of the second AVA. * @return Returns a negative integer, zero, or a positive integer * if the first AVA is less than, equal to, or greater than * the second. */ private int compareAVA(AttributeType type1, AttributeValue value1, AttributeType type2, AttributeValue value2) { if (type1.equals(type2)) { OrderingMatchingRule rule = type1.getOrderingMatchingRule(); try { if (rule != null) { byte[] b1 = value1.getNormalizedValueBytes(); byte[] b2 = value2.getNormalizedValueBytes(); return rule.compare(b1, b2); } else { byte[] b1 = value1.getNormalizedValue().value(); byte[] b2 = value2.getNormalizedValue().value(); return StaticUtils.compare(b1, b2); } } catch (Exception e) { assert debugException(CLASS_NAME, "compareAVA", e); // Just get the raw values and do a comparison between them. byte[] b1 = value1.getValue().value(); byte[] b2 = value2.getValue().value(); return StaticUtils.compare(b1, b2); } } else { String name1 = toLowerCase(type1.getNameOrOID()); String name2 = toLowerCase(type2.getNameOrOID()); else { String name1 = toLowerCase(attributeTypes[0].getNameOrOID()); String name2 = toLowerCase(rdn.attributeTypes[0].getNameOrOID()); return name1.compareTo(name2); } } if (equals(rdn)) { return 0; } TreeMap<String,AttributeType> typeMap1 = new TreeMap<String,AttributeType>(); TreeMap<String,AttributeValue> valueMap1 = new TreeMap<String,AttributeValue>(); for (int i=0; i < attributeTypes.length; i++) { String lowerName = toLowerCase(attributeTypes[i].getNameOrOID()); typeMap1.put(lowerName, attributeTypes[i]); valueMap1.put(lowerName, attributeValues[i]); } /** * Normalize and append the provided attribute type and value to the * provided buffer. * * @param buffer * The string buffer. * @param type * The attribute type. * @param value * The attribute value. */ private void appendNormalizedAVA(StringBuilder buffer, AttributeType type, AttributeValue value) { toLowerCase(type.getNameOrOID(), buffer); buffer.append('='); TreeMap<String,AttributeType> typeMap2 = new TreeMap<String,AttributeType>(); TreeMap<String,AttributeValue> valueMap2 = new TreeMap<String,AttributeValue>(); for (int i=0; i < rdn.attributeTypes.length; i++) { String lowerName = toLowerCase(rdn.attributeTypes[i].getNameOrOID()); typeMap2.put(lowerName, rdn.attributeTypes[i]); valueMap2.put(lowerName, rdn.attributeValues[i]); } try { quoteAttributeValue(buffer, value.getNormalizedStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "toNormalizedString", e); quoteAttributeValue(buffer, value.getStringValue()); Iterator<String> iterator1 = valueMap1.keySet().iterator(); Iterator<String> iterator2 = valueMap2.keySet().iterator(); String name1 = iterator1.next(); String name2 = iterator2.next(); AttributeType type1 = typeMap1.get(name1); AttributeType type2 = typeMap2.get(name2); AttributeValue value1 = valueMap1.get(name1); AttributeValue value2 = valueMap2.get(name2); while (true) { if (type1.equals(type2)) { int valueComparison; OrderingMatchingRule omr = type1.getOrderingMatchingRule(); if (omr == null) { try { valueComparison = value1.getNormalizedStringValue().compareTo( value2.getNormalizedStringValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "compareTo", e); valueComparison = value1.getStringValue().compareTo( value2.getStringValue()); } } else { try { valueComparison = omr.compareValues(value1.getNormalizedValue(), value2.getNormalizedValue()); } catch (Exception e) { assert debugException(CLASS_NAME, "compareTo", e); valueComparison = omr.compareValues(value1.getValue(), value2.getValue()); } } /** * Encode an attribute value according to the DN string encoding * rules, and append it to the provided buffer. * * @param buffer * Append the attribtue value to this buffer. * @param value * The value to be represented in a DN-safe form. */ private void quoteAttributeValue(StringBuilder buffer, String value) { assert debugEnter(CLASS_NAME, "quoteAttributeValue", String .valueOf(value)); // Do nothing if the value is empty. int length = value.length(); if (length == 0) { return; if (valueComparison == 0) { if (! iterator1.hasNext()) { if (iterator2.hasNext()) { return -1; } else { return 0; } } // Assume 1-byte UTF8 and that no quoting will be required. buffer.ensureCapacity(buffer.length() + length); // Quote leading space or #. char c = value.charAt(0); if (c == ' ' || c == '#') { buffer.append('\\'); buffer.append(c); } else { quoteChar(buffer, c); if (! iterator2.hasNext()) { return 1; } // Process the remainder of the string. for (int i = 1; i < (length - 1); i++) { quoteChar(buffer, value.charAt(i)); name1 = iterator1.next(); name2 = iterator2.next(); type1 = typeMap1.get(name1); type2 = typeMap2.get(name2); value1 = valueMap1.get(name1); value2 = valueMap2.get(name2); } // Quote trailing space. if (length > 1) { c = value.charAt(length - 1); if (c == ' ') { buffer.append('\\'); buffer.append(c); } else { quoteChar(buffer, c); else { return valueComparison; } } else { return name1.compareTo(name2); } } } } /** * Encode a single attribute value from an RDN according to the DN * string encoding rules. * * @param buffer * Append the character to this buffer. * @param c * The character to be encoded. */ private void quoteChar(StringBuilder buffer, char c) { if ((c < ' ') || (c > '~')) { for (byte b : getBytes(String.valueOf(c))) { buffer.append('\\'); buffer.append(byteToLowerHex(b)); } } else { switch (c) { case ',': case '+': case '"': case '\\': case '<': case '>': case ';': buffer.append('\\'); } buffer.append(c); } } /** * Parses an attribute name from the provided DN string starting at * the specified location. * * @param dnString * The DN string to be parsed. * @param pos * The position at which to start parsing the attribute * name. * @param attributeName * The buffer to which to append the parsed attribute name. * @return The position of the first character that is not part of * the attribute name. * @throws DirectoryException * If it was not possible to parse a valid attribute name * from the provided DN string. */ private static int parseAttributeName(String dnString, int pos, StringBuilder attributeName) throws DirectoryException { assert debugEnter(CLASS_NAME, "parseAttributeName", String .valueOf(dnString), String.valueOf(pos), "java.lang.StringBuilder"); boolean allowExceptions = DirectoryServer .allowAttributeNameExceptions(); int length = dnString.length(); // Skip over any leading spaces. if (pos < length) { while (dnString.charAt(pos) == ' ') { pos++; if (pos == length) { // This means that the remainder of the DN was completely // comprised of spaces. If we have gotten here, then we // know that there is at least one RDN component, and // therefore the last non-space character of the DN must // have been a comma. This is not acceptable. int msgID = MSGID_ATTR_SYNTAX_DN_END_WITH_COMMA; String message = getMessage(msgID, dnString); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } } // Next, we should find the attribute name for this RDN component. // It may either be a name (with only letters, digits, and dashes // and starting with a letter) or an OID (with only digits and // periods, optionally prefixed with "oid."), and there is also a // special case in which we will allow underscores. Because of // the complexity involved, read the entire name first with // minimal validation and then do more thorough validation later. boolean checkForOID = false; boolean endOfName = false; while (pos < length) { // To make the switch more efficient, we'll include all ASCII // characters in the range of allowed values and then reject the // ones that aren't allowed. char c = dnString.charAt(pos); switch (c) { case ' ': // This should denote the end of the attribute name. endOfName = true; break; case '!': case '"': case '#': case '$': case '%': case '&': case '\'': case '(': case ')': case '*': case '+': case ',': // None of these are allowed in an attribute name or any // character immediately following it. int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; String message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '-': // This will be allowed as long as it isn't the first // character in the attribute name. if (attributeName.length() > 0) { attributeName.append(c); } else { msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH; message = getMessage(msgID, dnString, c); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } break; case '.': // The period could be allowed if the attribute name is // actually expressed as an OID. We'll accept it for now, // but make sure to check it later. attributeName.append(c); checkForOID = true; break; case '/': // This is not allowed in an attribute name or any character // immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // Digits are always allowed if they are not the first // character. However, they may be allowed if they are the // first character if the valid is an OID or if the // attribute name exceptions option is enabled. Therefore, // we'll accept it now and check it later. attributeName.append(c); break; case ':': case ';': // NOTE: attribute options are not allowed in a DN. case '<': // None of these are allowed in an attribute name or any // character immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '=': // This should denote the end of the attribute name. endOfName = true; break; case '>': case '?': case '@': // None of these are allowed in an attribute name or any // character immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': // These will always be allowed. attributeName.append(c); break; case '[': case '\\': case ']': case '^': // None of these are allowed in an attribute name or any // character immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case '_': // This will never be allowed as the first character. It // may be allowed for subsequent characters if the attribute // name exceptions option is enabled. if (attributeName.length() == 0) { msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_UNDERSCORE; message = getMessage(msgID, dnString, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else if (allowExceptions) { attributeName.append(c); } else { msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_UNDERSCORE_CHAR; message = getMessage(msgID, dnString, ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } break; case '`': // This is not allowed in an attribute name or any character // immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': // These will always be allowed. attributeName.append(c); break; default: // This is not allowed in an attribute name or any character // immediately following it. msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR; message = getMessage(msgID, dnString, c, pos); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } if (endOfName) { break; } pos++; } // We should now have the full attribute name. However, we may // still need to perform some validation, particularly if the // name contains a period or starts with a digit. It must also // have at least one character. if (attributeName.length() == 0) { int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_NO_NAME; String message = getMessage(msgID, dnString); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } else if (checkForOID) { boolean validOID = true; int namePos = 0; int nameLength = attributeName.length(); char ch = attributeName.charAt(0); if ((ch == 'o') || (ch == 'O')) { if (nameLength <= 4) { validOID = false; } else { if ((((ch = attributeName.charAt(1)) == 'i') || (ch == 'I')) && (((ch = attributeName.charAt(2)) == 'd') || (ch == 'D')) && (attributeName.charAt(3) == '.')) { attributeName.delete(0, 4); nameLength -= 4; } else { validOID = false; } } } while (validOID && (namePos < nameLength)) { ch = attributeName.charAt(namePos++); if (isDigit(ch)) { while (validOID && (namePos < nameLength) && isDigit(attributeName.charAt(namePos))) { namePos++; } if ((namePos < nameLength) && (attributeName.charAt(namePos) != '.')) { validOID = false; } } else if (ch == '.') { if ((namePos == 1) || (attributeName.charAt(namePos - 2) == '.')) { validOID = false; } } else { validOID = false; } } if (validOID && (attributeName.charAt(nameLength - 1) == '.')) { validOID = false; } if (!validOID) { int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD; String message = getMessage(msgID, dnString, attributeName .toString()); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } else if (isDigit(attributeName.charAt(0)) && (!allowExceptions)) { int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DIGIT; String message = getMessage(msgID, dnString, attributeName .charAt(0), ATTR_ALLOW_ATTRIBUTE_NAME_EXCEPTIONS); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } return pos; } /** * Parses the attribute value from the provided DN string starting * at the specified location. When the value has been parsed, it * will be assigned to the provided ASN.1 octet string. * * @param dnString * The DN string to be parsed. * @param pos * The position of the first character in the attribute * value to parse. * @param attributeValue * The ASN.1 octet string whose value should be set to the * parsed attribute value when this method completes * successfully. * @return The position of the first character that is not part of * the attribute value. * @throws DirectoryException * If it was not possible to parse a valid attribute value * from the provided DN string. */ private static int parseAttributeValue(String dnString, int pos, ByteString attributeValue) throws DirectoryException { assert debugEnter(CLASS_NAME, "parseAttributeValue", String .valueOf(dnString), String.valueOf(pos), "java.lang.StringBuilder"); // All leading spaces have already been stripped so we can start // reading the value. However, it may be empty so check for that. int length = dnString.length(); if (pos >= length) { attributeValue.setValue(""); return pos; } // Look at the first character. If it is an octothorpe (#), then // that means that the value should be a hex string. char c = dnString.charAt(pos++); if (c == '#') { // The first two characters must be hex characters. StringBuilder hexString = new StringBuilder(); if ((pos + 2) > length) { int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT; String message = getMessage(msgID, dnString); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } for (int i = 0; i < 2; i++) { c = dnString.charAt(pos++); if (isHexDigit(c)) { hexString.append(c); } else { int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT; String message = getMessage(msgID, dnString, c); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } // The rest of the value must be a multiple of two hex // characters. The end of the value may be designated by the // end of the DN, a comma or semicolon, or a space. while (pos < length) { c = dnString.charAt(pos++); if (isHexDigit(c)) { hexString.append(c); if (pos < length) { c = dnString.charAt(pos++); if (isHexDigit(c)) { hexString.append(c); } else { int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT; String message = getMessage(msgID, dnString, c); throw new DirectoryException( ResultCode.INVALID_DN_SYNTAX, message, msgID); } } else { int msgID = MSGID_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT; String message = getMessage(msgID, dnString); throw new DirectoryException( ResultCode.INVALID_DN_SYNTAX, message, msgID); } } else if ((c == ' ') || (c == ',') || (c == ';') || (c == '+')) { // This denotes the end of the value. pos--; break; } else { int msgID = MSGID_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT; String message = getMessage(msgID, dnString, c); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } // At this point, we should have a valid hex string. Convert it // to a byte array and set that as the value of the provided // octet string. try { attributeValue.setValue(hexStringToByteArray(hexString .toString())); return pos; } catch (Exception e) { assert debugException(CLASS_NAME, "parseAttributeValue", e); int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE; String message = getMessage(msgID, dnString, String .valueOf(e)); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } // If the first character is a quotation mark, then the value // should continue until the corresponding closing quotation mark. else if (c == '"') { // Keep reading until we find an unescaped closing quotation // mark. boolean escaped = false; StringBuilder valueString = new StringBuilder(); while (true) { if (pos >= length) { // We hit the end of the DN before the closing quote. // That's an error. int msgID = MSGID_ATTR_SYNTAX_DN_UNMATCHED_QUOTE; String message = getMessage(msgID, dnString); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } c = dnString.charAt(pos++); if (escaped) { // The previous character was an escape, so we'll take this // one no matter what. valueString.append(c); escaped = false; } else if (c == '\\') { // The next character is escaped. Set a flag to denote // this, but don't include the backslash. escaped = true; } else if (c == '"') { // This is the end of the value. break; } else { // This is just a regular character that should be in the // value. valueString.append(c); } } attributeValue.setValue(valueString.toString()); return pos; } // Otherwise, use general parsing to find the end of the value. else { boolean escaped; StringBuilder valueString = new StringBuilder(); StringBuilder hexChars = new StringBuilder(); if (c == '\\') { escaped = true; } else { escaped = false; valueString.append(c); } // Keep reading until we find an unescaped comma or plus sign or // the end of the DN. while (true) { if (pos >= length) { // This is the end of the DN and therefore the end of the // value. If there are any hex characters, then we need to // deal with them accordingly. appendHexChars(dnString, valueString, hexChars); break; } c = dnString.charAt(pos++); if (escaped) { // The previous character was an escape, so we'll take this // one. However, this could be a hex digit, and if that's // the case then the escape would actually be in front of // two hex digits that should be treated as a special // character. if (isHexDigit(c)) { // It is a hexadecimal digit, so the next digit must be // one too. However, this could be just one in a series // of escaped hex pairs that is used in a string // containing one or more multi-byte UTF-8 characters so // we can't just treat this byte in isolation. Collect // all the bytes together and make sure to take care of // these hex bytes before appending anything else to the // value. if (pos >= length) { int msgID = MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID; String message = getMessage(msgID, dnString); throw new DirectoryException( ResultCode.INVALID_DN_SYNTAX, message, msgID); } else { char c2 = dnString.charAt(pos++); if (isHexDigit(c2)) { hexChars.append(c); hexChars.append(c2); } else { int msgID = MSGID_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID; String message = getMessage(msgID, dnString); throw new DirectoryException( ResultCode.INVALID_DN_SYNTAX, message, msgID); } } } else { appendHexChars(dnString, valueString, hexChars); valueString.append(c); } escaped = false; } else if (c == '\\') { escaped = true; } else if ((c == ',') || (c == ';') || (c == '+')) { appendHexChars(dnString, valueString, hexChars); pos--; break; } else { appendHexChars(dnString, valueString, hexChars); valueString.append(c); } } // Strip off any unescaped spaces that may be at the end of the // value. if (pos > 2 && dnString.charAt(pos - 1) == ' ' && dnString.charAt(pos - 2) != '\\') { int lastPos = valueString.length() - 1; while (lastPos > 0) { if (valueString.charAt(lastPos) == ' ') { valueString.delete(lastPos, lastPos + 1); lastPos--; } else { break; } } } attributeValue.setValue(valueString.toString()); return pos; } } /** * Decodes a hexadecimal string from the provided * <code>hexChars</code> buffer, converts it to a byte array, and * then converts that to a UTF-8 string. The resulting UTF-8 string * will be appended to the provided <code>valueString</code> * buffer, and the <code>hexChars</code> buffer will be cleared. * * @param dnString * The DN string that is being decoded. * @param valueString * The buffer containing the value to which the decoded * string should be appended. * @param hexChars * The buffer containing the hexadecimal characters to * decode to a UTF-8 string. * @throws DirectoryException * If any problem occurs during the decoding process. */ private static void appendHexChars(String dnString, StringBuilder valueString, StringBuilder hexChars) throws DirectoryException { try { byte[] hexBytes = hexStringToByteArray(hexChars.toString()); valueString.append(new String(hexBytes, "UTF-8")); hexChars.delete(0, hexChars.length()); } catch (Exception e) { assert debugException(CLASS_NAME, "appendHexChars", e); int msgID = MSGID_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE; String message = getMessage(msgID, dnString, String.valueOf(e)); throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, msgID); } } } opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.backends.jeb; opends/tests/unit-tests-testng/src/server/org/opends/server/monitors/InternalSearchMonitorTestCase.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.monitors; @@ -141,9 +141,10 @@ { AttributeType cnType = DirectoryServer.getAttributeType(ATTR_COMMON_NAME); RDN rdn0 = RDN.create(cnType, new AttributeValue(cnType, monitorName)); RDN rdn1 = RDN.create(cnType, new AttributeValue(cnType, "monitor")); DN monitorDN = DN.create(rdn0, rdn1); RDN[] rdns = new RDN[2]; rdns[0] = RDN.create(cnType, new AttributeValue(cnType, monitorName)); rdns[1] = RDN.create(cnType, new AttributeValue(cnType, "monitor")); DN monitorDN = new DN(rdns); InternalClientConnection conn = InternalClientConnection.getRootConnection(); opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestAddResponseProtocolOp.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.protocols.ldap; @@ -82,9 +82,10 @@ AttributeValue attributeValue = new AttributeValue(attribute, "testValue"); RDN rdn = RDN.create(attribute, attributeValue); RDN[] rdns = new RDN[1]; rdns[0] = RDN.create(attribute, attributeValue); dn = DN.create(rdn); dn = new DN(rdns); } /** opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestCompareResponseProtocolOp.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.protocols.ldap; @@ -84,8 +84,9 @@ AttributeValue attributeValue = new AttributeValue(attribute, "testValue"); RDN rdn = RDN.create(attribute, attributeValue); dn = DN.create(rdn); RDN[] rdns = new RDN[1]; rdns[0] = RDN.create(attribute, attributeValue); dn = new DN(rdns); } /** opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestDeleteResponseProtocolOp.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.protocols.ldap; @@ -82,8 +82,9 @@ AttributeValue attributeValue = new AttributeValue(attribute, "testValue"); RDN rdn = RDN.create(attribute, attributeValue); dn = DN.create(rdn); RDN[] rdns = new RDN[1]; rdns[0] = RDN.create(attribute, attributeValue); dn = new DN(rdns); } /** opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyDNResponseProtocolOp.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.protocols.ldap; @@ -84,8 +84,9 @@ AttributeValue attributeValue = new AttributeValue(attribute, "testValue"); RDN rdn = RDN.create(attribute, attributeValue); dn = DN.create(rdn); RDN[] rdns = new RDN[1]; rdns[0] = RDN.create(attribute, attributeValue); dn = new DN(rdns); } /** opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestModifyResponseProtocolOp.java
@@ -22,7 +22,7 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.protocols.ldap; @@ -84,8 +84,9 @@ AttributeValue attributeValue = new AttributeValue(attribute, "testValue"); RDN rdn = RDN.create(attribute, attributeValue); dn = DN.create(rdn); RDN[] rdns = new RDN[1]; rdns[0] = RDN.create(attribute, attributeValue); dn = new DN(rdns); } /** opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestDN.java
@@ -22,12 +22,14 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.types; import java.util.ArrayList; import static org.testng.Assert.*; import org.opends.server.TestCaseUtils; @@ -54,7 +56,12 @@ return new Object[][] { { "", "", "" }, { " ", "", "" }, { "cn=", "cn=", "cn=" }, { "cn= ", "cn=", "cn=" }, { "cn =", "cn=", "cn=" }, { "cn = ", "cn=", "cn=" }, { "dc=com", "dc=com", "dc=com" }, { "dc=com+o=com", "dc=com+o=com", "dc=com+o=com" }, { "DC=COM", "dc=com", "DC=COM" }, { "dc = com", "dc=com", "dc=com" }, { " dc = com ", "dc=com", "dc=com" }, @@ -126,12 +133,17 @@ */ @DataProvider(name = "illegalDNs") public Object[][] createIllegalData() { return new Object[][] { { "manager" }, { "manager " }, { "cn+Jim" }, { "cn=Jim+" }, { "cn=Jim," }, { "cn=Jim, " }, { "cn+uid=Jim" }, { "-cn=Jim" }, { "/tmp=a" }, { "\\tmp=a" }, { "cn;lang-en=Jim" }, { "@cn=Jim" }, { "_name_=Jim" }, { "\u03c0=pi" }, { "v1.0=buggy" }, { "1.3.6.1.4.1.1466..0=#04024869" }, }; return new Object[][] { { "manager" }, { "manager " }, { "=Jim" }, { " =Jim" }, { "= Jim" }, { " = Jim" }, { "cn+Jim" }, { "cn + Jim" }, { "cn=Jim+" }, { "cn=Jim+manager" }, { "cn=Jim+manager " }, { "cn=Jim+manager," }, { "cn=Jim," }, { "cn=Jim, " }, { "c[n]=Jim" }, { "_cn=Jim" }, { "c_n=Jim" }, { "cn\"=Jim" }, { "c\"n=Jim" }, { "1cn=Jim" }, { "cn+uid=Jim" }, { "-cn=Jim" }, { "/tmp=a" }, { "\\tmp=a" }, { "cn;lang-en=Jim" }, { "@cn=Jim" }, { "_name_=Jim" }, { "\u03c0=pi" }, { "v1.0=buggy" }, { "1.=buggy" }, { ".1=buggy" }, { "oid.1." }, { "1.3.6.1.4.1.1466..0=#04024869" }, { "cn=#a" }, { "cn=#ag" }, { "cn=#ga" }, { "cn=#abcdefgh" }, { "cn=a\\b" }, { "cn=a\\bg" }, { "cn=\"hello" } }; } @@ -162,23 +174,9 @@ * @throws Exception * If the test failed unexpectedly. */ @Test(expectedExceptions = { NullPointerException.class, AssertionError.class }) public void testCreateNPE() throws Exception { DN.create((RDN[]) null); } /** * Tests the create method. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testCreateNullDN1() throws Exception { DN dn = DN.create(new RDN[0]); DN dn = new DN(new RDN[0]); assertEquals(dn, DN.nullDN()); } @@ -193,7 +191,37 @@ */ @Test public void testCreateNullDN2() throws Exception { DN dn = DN.create(); DN dn = new DN(); assertEquals(dn, DN.nullDN()); } /** * Tests the create method. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testCreateNullDN3() throws Exception { DN dn = new DN((RDN[]) null); assertEquals(dn, DN.nullDN()); } /** * Tests the create method. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testCreateNullDN4() throws Exception { DN dn = new DN((ArrayList<RDN>) null); assertEquals(dn, DN.nullDN()); } @@ -208,22 +236,7 @@ */ @Test public void testCreateWithSingleRDN1() throws Exception { DN dn = DN.create(new RDN[] { RDN.decode("dc=com") }); assertEquals(dn, DN.decode("dc=com")); } /** * Tests the create method. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testCreateWithSingleRDN2() throws Exception { DN dn = DN.create(RDN.decode("dc=com")); DN dn = new DN(new RDN[] { RDN.decode("dc=com") }); assertEquals(dn, DN.decode("dc=com")); } @@ -238,7 +251,7 @@ */ @Test public void testCreateWithMultipleRDNs1() throws Exception { DN dn = DN.create(new RDN[] { RDN.decode("dc=foo"), DN dn = new DN(new RDN[] { RDN.decode("dc=foo"), RDN.decode("dc=opends"), RDN.decode("dc=org") }); assertEquals(dn, DN.decode("dc=foo,dc=opends,dc=org")); @@ -254,8 +267,11 @@ */ @Test public void testCreateWithMultipleRDNs2() throws Exception { DN dn = DN.create(RDN.decode("dc=foo"), RDN.decode("dc=opends"), RDN.decode("dc=org")); ArrayList<RDN> rdnList = new ArrayList<RDN>(); rdnList.add(RDN.decode("dc=foo")); rdnList.add(RDN.decode("dc=opends")); rdnList.add(RDN.decode("dc=org")); DN dn = new DN(rdnList); assertEquals(dn, DN.decode("dc=foo,dc=opends,dc=org")); } @@ -309,23 +325,32 @@ /** * Tests the <CODE>valueOf</CODE> method which takes a String * argument. * Tests the toNoramlizedString methods. * * @param rawDN * Raw DN string representation. * @param normDN * Normalized DN string representation. * @param stringDN * String representation. * @throws Exception * If the test failed unexpectedly. */ @Test(dataProvider = "testDNs") public void testValueOf(String rawDN, String normDN, String stringDN) throws Exception { DN dn = DN.valueOf(rawDN); assertEquals(dn.toNormalizedString(), normDN); public void testToNormalizedString() throws Exception { DN dn = DN.decode("dc=example,dc=com"); StringBuilder buffer = new StringBuilder(); dn.toNormalizedString(buffer); assertEquals(buffer.toString(), "dc=example,dc=com"); assertEquals(dn.toNormalizedString(), "dc=example,dc=com"); } /** * Tests both variants of the {@code decode} method with null arguments. * * @throws Exception * If the test failed unexpectedly. */ public void testDecodeNull() throws Exception { assertEquals(DN.decode((ByteString) null), DN.nullDN()); assertEquals(DN.decode((String) null), DN.nullDN()); } @@ -386,32 +411,6 @@ /** * Test that decoding an illegal DN as a String throws an exception. * * @param dn * The illegal DN to be tested. * @throws Exception * If the test failed unexpectedly. */ @Test(dataProvider = "illegalDNs", expectedExceptions = DirectoryException.class) public void testIllegalValueOf(String dn) throws Exception { try { DN.valueOf(dn); } catch (DirectoryException e) { throw e; } catch (Exception e) { System.out.println("Illegal DN <" + dn + "> threw the wrong type of exception"); throw e; } throw new RuntimeException("Illegal DN <" + dn + "> did not throw an exception"); } /** * Test the nullDN method. * * @throws Exception @@ -530,6 +529,50 @@ /** * Retrieves the naming contexts defined in the server. */ @DataProvider(name = "namingContexts") public Object[][] getNamingContexts() { ArrayList<DN> contextList = new ArrayList<DN>(); for (DN baseDN : DirectoryServer.getPublicNamingContexts().keySet()) { contextList.add(baseDN); } for (DN baseDN : DirectoryServer.getPrivateNamingContexts().keySet()) { contextList.add(baseDN); } Object[][] contextArray = new Object[contextList.size()][1]; for (int i=0; i < contextArray.length; i++) { contextArray[i][0] = contextList.get(i); } return contextArray; } /** * Tests the getParentDNInSuffix method. * * @throws Exception * If the test failed unexpectedly. */ @Test(dataProvider = "namingContexts") public void testGetParentDNInSuffix(DN namingContext) throws Exception { assertNull(namingContext.getParentDNInSuffix()); DN childDN = namingContext.concat(RDN.decode("ou=People")); assertNotNull(childDN.getParentDNInSuffix()); assertEquals(childDN.getParentDNInSuffix(), namingContext); } /** * Test the getParent method's interaction with other methods. * * @throws Exception @@ -574,9 +617,6 @@ assertEquals(p.concat(RDN.decode("dc=foo")), c); assertEquals(p.concat(DN.decode("dc=xxx,dc=foo")), DN .decode("dc=xxx,dc=foo,dc=bar,dc=opends,dc=org")); assertEquals(p.getLocalName(1), DN.decode("dc=bar,dc=opends")); assertEquals(p.getLocalName(0, 2), DN.decode("dc=opends,dc=org")); } @@ -791,10 +831,6 @@ .decode("dc=xxx,dc=foo,dc=bar,dc=opends,dc=org")); assertEquals(c.concat(DN.decode("dc=xxx,dc=yyy")), DN .decode("dc=xxx,dc=yyy,dc=foo,dc=bar,dc=opends,dc=org")); assertEquals(c.getLocalName(1), DN .decode("dc=foo,dc=bar,dc=opends")); assertEquals(c.getLocalName(1, 3), DN.decode("dc=bar,dc=opends")); } @@ -843,21 +879,6 @@ * @throws Exception * If the test failed unexpectedly. */ @Test public void testConcatRDNNoOp() throws Exception { DN dn = DN.decode("dc=opends,dc=org"); assertEquals(dn.concat(), dn); } /** * Test the concat(RDN...) method. * * @throws Exception * If the test failed unexpectedly. */ @Test(expectedExceptions = { NullPointerException.class, AssertionError.class }) public void testConcatRDNException() throws Exception { @@ -898,52 +919,6 @@ /** * Test the concat(RDN[]...) method. * * @param s * The test DN string. * @param l * The local name to be appended. * @param e * The expected DN. * @throws Exception * If the test failed unexpectedly. */ @Test(dataProvider = "createConcatDNTestData") public void testConcatRDNSequence2(String s, String l, String e) throws Exception { DN dn = DN.decode(s); DN localName = DN.decode(l); DN expected = DN.decode(e); // Construct sequence. DN actual = null; switch (localName.getNumComponents()) { case 0: actual = dn.concat(); break; case 1: actual = dn.concat(localName.getRDN(0)); break; case 2: actual = dn.concat(localName.getRDN(0), localName.getRDN(1)); break; case 3: actual = dn.concat(localName.getRDN(0), localName.getRDN(1), localName.getRDN(2)); break; case 4: actual = dn.concat(localName.getRDN(0), localName.getRDN(1), localName.getRDN(3), localName.getRDN(3)); break; } assertEquals(actual, expected); } /** * Get local name test data provider. * * @return The array of test data. @@ -986,86 +961,6 @@ /** * Test the getLocalName methods. * * @param s * The test DN string. * @param beginIndex * The index of the uppermost RDN. * @param endIndex * The index of the lowest RDN or -1 if there is no end * index. * @param e * The expected result. * @throws Exception * If the test failed unexpectedly. */ @Test(dataProvider = "createGetLocalNameTestData") public void testGetLocalName(String s, int beginIndex, int endIndex, String e) throws Exception { DN dn = DN.decode(s); DN expected = DN.decode(e); if (endIndex < 0) { assertEquals(dn.getLocalName(beginIndex), expected); } else { assertEquals(dn.getLocalName(beginIndex, endIndex), expected); } } /** * Test the getLocalName method's interaction with other methods. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testGetLocalNameInteraction() throws Exception { DN p = DN.decode("dc=foo,dc=bar,dc=opends,dc=org"); DN e = DN.decode("dc=bar,dc=opends"); DN l = p.getLocalName(1, 3); assertFalse(l.isNullDN()); assertEquals(l.getNumComponents(), 2); assertEquals(l.compareTo(e), 0); assertEquals(e.compareTo(l), 0); assertTrue(l.isAncestorOf(DN.decode("dc=foo,dc=bar,dc=opends"))); assertFalse(DN.decode("dc=foo,dc=bar,dc=opends").isAncestorOf(l)); assertTrue(l.isDescendantOf(DN.decode("dc=opends"))); assertFalse(DN.decode("dc=opends").isDescendantOf(l)); assertEquals(l, e); assertEquals(l.hashCode(), e.hashCode()); assertEquals(l.toNormalizedString(), e.toNormalizedString()); assertEquals(l.toString(), e.toString()); assertEquals(l.getRDN(), RDN.decode("dc=bar")); assertEquals(l.getRDN(0), RDN.decode("dc=bar")); assertEquals(l.getRDN(1), RDN.decode("dc=opends")); assertEquals(l.getParent(), DN.decode("dc=opends")); assertEquals(l.getParent(), e.getParent()); assertEquals(l.concat(RDN.decode("dc=xxx")), DN .decode("dc=xxx,dc=bar,dc=opends")); assertEquals(l.concat(DN.decode("dc=xxx,dc=yyy")), DN .decode("dc=xxx,dc=yyy,dc=bar,dc=opends")); assertEquals(l.getLocalName(1), DN.decode("dc=bar")); assertEquals(l.getLocalName(0, 1), DN.decode("dc=opends")); } /** * Is ancestor of test data provider. * * @return The array of test data. @@ -1244,7 +1139,8 @@ { "dc=aaa,dc=ccc", "dc=bbb", 1 }, { "dc=bbb,dc=ccc", "dc=bbb", 1 }, { "dc=ccc,dc=ccc", "dc=bbb", 1 }, { "", "dc=bbb", -1 }, { "dc=bbb", "", 1 } }; } @@ -1280,6 +1176,21 @@ /** * Tests the equals method with a value that's not a DN. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testEqualsNonDN() throws Exception { DN dn = DN.decode("dc=example,dc=com"); assertFalse(dn.equals("not a DN")); } /** * Test DN hashCode * * @param first @@ -1366,5 +1277,5 @@ DN dn = DN.decode(rawDN); assertEquals(dn.toString(), stringDN); } } opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestRDN.java
@@ -22,12 +22,14 @@ * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.types; import java.util.ArrayList; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @@ -93,113 +95,6 @@ // First test the constructors. /** * Check the constructor throws a NPE when parameters are not * provided. * * @throws Exception * If the test failed unexpectedly. */ @Test(expectedExceptions = { NullPointerException.class, AssertionError.class }) public void testConstructorNPE1() throws Exception { RDN.create(AT_DC, null); } /** * Check the constructor throws a NPE when parameters are not * provided. * * @throws Exception * If the test failed unexpectedly. */ @Test(expectedExceptions = { NullPointerException.class, AssertionError.class }) public void testConstructorNPE2() throws Exception { RDN.create(null, AV_DC_ORG); } /** * Check the constructor throws a NPE when parameters are not * provided. * * @throws Exception * If the test failed unexpectedly. */ @Test(expectedExceptions = { NullPointerException.class, AssertionError.class }) public void testConstructorNPE3() throws Exception { RDN.create(AT_DC, "dc", null); } /** * Check the constructor throws a NPE when parameters are not * provided. * * @throws Exception * If the test failed unexpectedly. */ @Test(expectedExceptions = { NullPointerException.class, AssertionError.class }) public void testConstructorNPE4() throws Exception { RDN.create(AT_DC, null, AV_DC_ORG); } /** * Check the constructor throws a NPE when parameters are not * provided. * * @throws Exception * If the test failed unexpectedly. */ @Test(expectedExceptions = { NullPointerException.class, AssertionError.class }) public void testConstructorNPE5() throws Exception { RDN.create(null, "dc", AV_DC_ORG); } /** * Check the decode method throws a NPE when parameters are not * provided. * * @throws Exception * If the test failed unexpectedly. */ @Test(expectedExceptions = { NullPointerException.class, AssertionError.class }) public void testDecodeNPE() throws Exception { RDN.decode((String) null); } /** * Check the valueOf method throws a NPE when parameters are not * provided. * * @throws Exception * If the test failed unexpectedly. */ @Test(expectedExceptions = { NullPointerException.class, AssertionError.class }) public void testValueOfNPE() throws Exception { RDN.valueOf(null); } /** * Test RDN construction with single AVA. * @@ -208,7 +103,7 @@ */ @Test public void testConstructor() throws Exception { RDN rdn = RDN.create(AT_DC, AV_DC_ORG); RDN rdn = new RDN(AT_DC, AV_DC_ORG); assertEquals(rdn.getNumValues(), 1); assertEquals(rdn.getAttributeType(0), AT_DC); @@ -226,7 +121,7 @@ */ @Test public void testConstructorWithName() throws Exception { RDN rdn = RDN.create(AT_DC, "domainComponent", AV_DC_ORG); RDN rdn = new RDN(AT_DC, "domainComponent", AV_DC_ORG); assertEquals(rdn.getNumValues(), 1); assertEquals(rdn.getAttributeType(0), AT_DC); @@ -237,103 +132,54 @@ /** * Test RDN builder. * Test RDN construction with a multiple AVA elements. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testBuilder() throws Exception { RDN.Builder builder = RDN.createBuilder(); public void testConstructorMultiAVA() throws Exception { AttributeType[] attrTypes = { AT_DC, AT_CN }; String[] attrNames = { AT_DC.getNameOrOID(), AT_CN.getNameOrOID() }; AttributeValue[] attrValues = { AV_DC_ORG, AV_CN }; builder.append(AT_DC, AV_DC_ORG); RDN rdn = builder.getInstance(); RDN rdn = new RDN(attrTypes, attrNames, attrValues); assertEquals(rdn.getNumValues(), 1); assertEquals(rdn.getNumValues(), 2); assertEquals(rdn.getAttributeType(0), AT_DC); assertEquals(rdn.getAttributeName(0), AT_DC.getNameOrOID()); assertEquals(rdn.getAttributeValue(0), AV_DC_ORG); assertEquals(rdn.getAttributeType(1), AT_CN); assertEquals(rdn.getAttributeName(1), AT_CN.getNameOrOID()); assertEquals(rdn.getAttributeValue(1), AV_CN); } /** * Test RDN builder. * Test RDN construction with a multiple AVA elements. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testBuilderWithName() throws Exception { RDN.Builder builder = RDN.createBuilder(); public void testConstructorMultiAVAList() throws Exception { ArrayList<AttributeType> typeList = new ArrayList<AttributeType>(); ArrayList<String> nameList = new ArrayList<String>(); ArrayList<AttributeValue> valueList = new ArrayList<AttributeValue>(); builder.append(AT_DC, "domainComponent", AV_DC_ORG); RDN rdn = builder.getInstance(); typeList.add(AT_DC); nameList.add(AT_DC.getNameOrOID()); valueList.add(AV_DC_ORG); assertEquals(rdn.getNumValues(), 1); assertEquals(rdn.getAttributeType(0), AT_DC); assertEquals(rdn.getAttributeName(0), "domainComponent"); assertEquals(rdn.getAttributeValue(0), AV_DC_ORG); } typeList.add(AT_CN); nameList.add(AT_CN.getNameOrOID()); valueList.add(AV_CN); /** * Test RDN builder. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testBuilderIsEmpty() throws Exception { RDN.Builder builder = RDN.createBuilder(); assertTrue(builder.isEmpty()); builder.append(AT_DC, AV_DC_ORG); assertFalse(builder.isEmpty()); builder.getInstance(); assertFalse(builder.isEmpty()); builder.clear(); assertTrue(builder.isEmpty()); } /** * Test RDN builder. * * @throws Exception * If the test failed unexpectedly. */ @Test(expectedExceptions = IllegalArgumentException.class) public void testBuilderDupesNotAllowed() throws Exception { RDN.Builder builder = RDN.createBuilder(); builder.append(AT_DC, AV_DC_ORG); builder.append(AT_DC, AV_DC_OPENDS); } /** * Test RDN builder. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testBuilderMultiAVA() throws Exception { RDN.Builder builder = RDN.createBuilder(); builder.append(AT_DC, AV_DC_ORG); builder.append(AT_CN, AV_CN); RDN rdn = builder.getInstance(); RDN rdn = new RDN(typeList, nameList, valueList); assertEquals(rdn.getNumValues(), 2); @@ -357,6 +203,11 @@ public Object[][] createData() { return new Object[][] { { "dc=hello world", "dc=hello world", "dc=hello world" }, { "dc =hello world", "dc=hello world", "dc=hello world" }, { "dc =hello world", "dc=hello world", "dc=hello world" }, { "dc= hello world", "dc=hello world", "dc=hello world" }, { "dc= hello world", "dc=hello world", "dc=hello world" }, { "undefined=hello", "undefined=hello", "undefined=hello" }, { "DC=HELLO WORLD", "dc=hello world", "DC=HELLO WORLD" }, { "dc = hello world", "dc=hello world", "dc=hello world" }, @@ -417,61 +268,20 @@ /** * Test RDN byte string decoder. * * @param rawRDN * Raw RDN string representation. * @param normRDN * Normalized RDN string representation. * @param stringRDN * String representation. * @throws Exception * If the test failed unexpectedly. */ @Test(dataProvider = "testRDNs") public void testDecodeByteString(String rawRDN, String normRDN, String stringRDN) throws Exception { ASN1OctetString octetString = new ASN1OctetString(rawRDN); RDN rdn = RDN.decode(octetString); assertEquals(rdn.toNormalizedString(), normRDN); } /** * Test valueOf. * * @param rawRDN * Raw RDN string representation. * @param normRDN * Normalized RDN string representation. * @param stringRDN * String representation. * @throws Exception * If the test failed unexpectedly. */ @Test(dataProvider = "testRDNs") public void testValueOf(String rawRDN, String normRDN, String stringRDN) throws Exception { RDN rdn = RDN.valueOf(rawRDN); assertEquals(rdn.toNormalizedString(), normRDN); } /** * Illegal RDN test data provider. * * @return The array of illegal test RDN strings. */ @DataProvider(name = "illegalRDNs") public Object[][] createIllegalData() { return new Object[][] { { "" }, { "=" }, { "manager" }, { "manager " }, { "cn+Jim" }, { "cn=Jim+" }, { "cn=Jim+sn" }, { "cn=Jim," }, { "cn=Jim, " }, { "cn=Jim, sn=Jam " }, { "cn+uid=Jim" }, { "-cn=Jim" }, { "/tmp=a" }, { "\\tmp=a" }, { "cn;lang-en=Jim" }, { "@cn=Jim" }, { "_name_=Jim" }, { "\u03c0=pi" }, { "v1.0=buggy" }, return new Object[][] { { null }, { "" }, { " " }, { "=" }, { "manager" }, { "manager " }, { "cn+"}, { "cn+Jim" }, { "cn=Jim+" }, { "cn=Jim +" }, { "cn=Jim+ " }, { "cn=Jim+sn" }, { "cn=Jim+sn " }, { "cn=Jim+sn equals" }, { "cn=Jim," }, { "cn=Jim;" }, { "cn=Jim, " }, { "cn=Jim+sn=a," }, { "cn=Jim, sn=Jam " }, { "cn+uid=Jim" }, { "-cn=Jim" }, { "/tmp=a" }, { "\\tmp=a" }, { "cn;lang-en=Jim" }, { "@cn=Jim" }, { "_name_=Jim" }, { "\u03c0=pi" }, { "v1.0=buggy" }, { "cn=Jim+sn=Bob++" }, { "cn=Jim+sn=Bob+," }, { "1.3.6.1.4.1.1466..0=#04024869" }, }; } @@ -495,41 +305,6 @@ /** * Test RDN byte string decoder against illegal strings. * * @param rawRDN * Illegal RDN string representation. * @throws Exception * If the test failed unexpectedly. */ @Test(dataProvider = "illegalRDNs", expectedExceptions = DirectoryException.class) public void testDecodeByteString(String rawRDN) throws Exception { ASN1OctetString octetString = new ASN1OctetString(rawRDN); RDN.decode(octetString); fail("Expected exception for value \"" + rawRDN + "\""); } /** * Test valueOf against illegal strings. * * @param rawRDN * Illegal RDN string representation. * @throws Exception * If the test failed unexpectedly. */ @Test(dataProvider = "illegalRDNs", expectedExceptions = DirectoryException.class) public void testValueOf(String rawRDN) throws Exception { RDN.valueOf(rawRDN); fail("Expected exception for value \"" + rawRDN + "\""); } /** * Test getAttributeName. * * @throws Exception @@ -537,11 +312,12 @@ */ @Test public void testGetAttributeName() throws Exception { RDN.Builder builder = RDN.createBuilder(); AttributeType[] attrTypes = { AT_DC, AT_CN }; String[] attrNames = { AT_DC.getNameOrOID(), AT_CN.getNameOrOID() }; AttributeValue[] attrValues = { AV_DC_ORG, AV_CN }; builder.append(AT_DC, AV_DC_ORG); builder.append(AT_CN, AV_CN); RDN rdn = builder.getInstance(); RDN rdn = new RDN(attrTypes, attrNames, attrValues); assertEquals(rdn.getAttributeName(0), AT_DC.getNameOrOID()); assertEquals(rdn.getAttributeName(1), AT_CN.getNameOrOID()); @@ -557,10 +333,8 @@ */ @Test(expectedExceptions = IndexOutOfBoundsException.class) public void testGetAttributeNameException() throws Exception { RDN.Builder builder = RDN.createBuilder(); builder.append(AT_DC, AV_DC_ORG); RDN rdn = builder.getInstance(); RDN rdn = new RDN(new AttributeType[0], new String[0], new AttributeValue[0]); rdn.getAttributeName(1); } @@ -575,11 +349,12 @@ */ @Test public void testGetAttributeType() throws Exception { RDN.Builder builder = RDN.createBuilder(); AttributeType[] attrTypes = { AT_DC, AT_CN }; String[] attrNames = { AT_DC.getNameOrOID(), AT_CN.getNameOrOID() }; AttributeValue[] attrValues = { AV_DC_ORG, AV_CN }; builder.append(AT_DC, AV_DC_ORG); builder.append(AT_CN, AV_CN); RDN rdn = builder.getInstance(); RDN rdn = new RDN(attrTypes, attrNames, attrValues); assertEquals(rdn.getAttributeType(0), AT_DC); assertEquals(rdn.getAttributeType(1), AT_CN); @@ -595,10 +370,8 @@ */ @Test(expectedExceptions = IndexOutOfBoundsException.class) public void testGetAttributeTypeException() throws Exception { RDN.Builder builder = RDN.createBuilder(); builder.append(AT_DC, AV_DC_ORG); RDN rdn = builder.getInstance(); RDN rdn = new RDN(new AttributeType[0], new String[0], new AttributeValue[0]); rdn.getAttributeType(1); } @@ -613,11 +386,12 @@ */ @Test public void testGetAttributeValue() throws Exception { RDN.Builder builder = RDN.createBuilder(); AttributeType[] attrTypes = { AT_DC, AT_CN }; String[] attrNames = { AT_DC.getNameOrOID(), AT_CN.getNameOrOID() }; AttributeValue[] attrValues = { AV_DC_ORG, AV_CN }; builder.append(AT_DC, AV_DC_ORG); builder.append(AT_CN, AV_CN); RDN rdn = builder.getInstance(); RDN rdn = new RDN(attrTypes, attrNames, attrValues); assertEquals(rdn.getAttributeValue(0), AV_DC_ORG); assertEquals(rdn.getAttributeValue(1), AV_CN); @@ -633,10 +407,8 @@ */ @Test(expectedExceptions = IndexOutOfBoundsException.class) public void testGetAttributeValueException() throws Exception { RDN.Builder builder = RDN.createBuilder(); builder.append(AT_DC, AV_DC_ORG); RDN rdn = builder.getInstance(); RDN rdn = new RDN(new AttributeType[0], new String[0], new AttributeValue[0]); rdn.getAttributeValue(1); } @@ -651,10 +423,7 @@ */ @Test public void testGetAttributeValueByType() throws Exception { RDN.Builder builder = RDN.createBuilder(); builder.append(AT_DC, AV_DC_ORG); RDN rdn = builder.getInstance(); RDN rdn = new RDN(AT_DC, AV_DC_ORG); assertEquals(rdn.getAttributeValue(AT_DC), AV_DC_ORG); assertNull(rdn.getAttributeValue(AT_CN)); @@ -670,14 +439,10 @@ */ @Test public void testGetNumValues() throws Exception { RDN.Builder builder = RDN.createBuilder(); builder.append(AT_DC, AV_DC_ORG); RDN rdn = builder.getInstance(); RDN rdn = new RDN(AT_DC, AV_DC_ORG); assertEquals(rdn.getNumValues(), 1); builder.append(AT_CN, AV_CN); rdn = builder.getInstance(); rdn.addValue(AT_CN, AT_CN.getNameOrOID(), AV_CN); assertEquals(rdn.getNumValues(), 2); } @@ -690,13 +455,14 @@ * If the test failed unexpectedly. */ @Test public void testHasAttributeType() throws Exception { RDN.Builder builder = RDN.createBuilder(); builder.append(AT_DC, AV_DC_ORG); RDN rdn = builder.getInstance(); public void testHasAttributeType1() throws Exception { RDN rdn = new RDN(AT_DC, AV_DC_ORG); assertTrue(rdn.hasAttributeType(AT_DC)); assertTrue(rdn.hasAttributeType("dc")); assertTrue(rdn.hasAttributeType(AT_DC.getOID())); assertFalse(rdn.hasAttributeType(AT_CN)); assertFalse(rdn.hasAttributeType("cn")); } @@ -709,20 +475,50 @@ */ @Test public void testIsMultiValued() throws Exception { RDN.Builder builder = RDN.createBuilder(); builder.append(AT_DC, AV_DC_ORG); RDN rdn = builder.getInstance(); RDN rdn = new RDN(AT_DC, AV_DC_ORG); assertEquals(rdn.getNumValues(), 1); assertFalse(rdn.isMultiValued()); builder.append(AT_CN, AV_CN); rdn = builder.getInstance(); rdn.addValue(AT_CN, AT_CN.getNameOrOID(), AV_CN); assertTrue(rdn.isMultiValued()); } /** * Tests hasValue. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testHasValue() throws Exception { RDN rdn = new RDN(AT_DC, AV_DC_ORG); assertTrue(rdn.hasValue(AT_DC, AV_DC_ORG)); assertFalse(rdn.hasValue(AT_CN, AV_CN)); rdn.addValue(AT_CN, AT_CN.getNameOrOID(), AV_CN); assertTrue(rdn.hasValue(AT_DC, AV_DC_ORG)); assertTrue(rdn.hasValue(AT_CN, AV_CN)); } /** * Tests addValue with a duplicate value. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testAddDuplicateValue() throws Exception { RDN rdn = new RDN(AT_DC, AV_DC_ORG); assertFalse(rdn.addValue(AT_DC, AT_DC.getNameOrOID(), AV_DC_ORG)); } /** * Test RDN string decoder. * * @param rawRDN @@ -744,6 +540,44 @@ /** * Tests the duplicate method with a single-valued RDN. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testDuplicateSingle() { RDN rdn1 = new RDN(AT_DC, AV_DC_ORG); RDN rdn2 = rdn1.duplicate(); assertFalse(rdn1 == rdn2); assertEquals(rdn1, rdn2); } /** * Tests the duplicate method with a multivalued RDN. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testDuplicateMultiValued() { AttributeType[] types = new AttributeType[] { AT_DC, AT_CN }; String[] names = new String[] { "dc", "cn" }; AttributeValue[] values = new AttributeValue[] { AV_DC_ORG, AV_CN }; RDN rdn1 = new RDN(types, names, values); RDN rdn2 = rdn1.duplicate(); assertFalse(rdn1 == rdn2); assertEquals(rdn1, rdn2); } /** * RDN equality test data provider. * * @return The array of test RDN strings. @@ -758,6 +592,13 @@ { "cn=hello world\\ ", "cn=hello world", 0 }, { "cn=HELLO WORLD", "cn=hello world", 0 }, { "cn=HELLO+sn=WORLD", "sn=world+cn=hello", 0 }, { "cn=HELLO+sn=WORLD", "cn=hello+sn=nurse", 1 }, { "cn=HELLO+sn=WORLD", "cn=howdy+sn=yall", -1 }, { "cn=hello", "cn=hello+sn=world", -1 }, { "cn=hello+sn=world", "cn=hello", 1 }, { "cn=hello+sn=world", "cn=hello+description=world", 1 }, { "cn=hello", "sn=world", -1 }, { "sn=hello", "cn=world", 1 }, { "x-test-integer-type=10", "x-test-integer-type=9", 1 }, { "x-test-integer-type=999", "x-test-integer-type=1000", -1 }, { "x-test-integer-type=-1", "x-test-integer-type=0", -1 }, @@ -866,4 +707,34 @@ + second + ">."); } /** * Tests the equals method with a null argument. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testEqualityNull() { RDN rdn = new RDN(AT_DC, AV_DC_ORG); assertFalse(rdn.equals(null)); } /** * Tests the equals method with a non-RDN argument. * * @throws Exception * If the test failed unexpectedly. */ @Test public void testEqualityNonRDN() { RDN rdn = new RDN(AT_DC, AV_DC_ORG); assertFalse(rdn.equals("this isn't an RDN")); } }