/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opendj3/legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opendj3/legal-notices/CDDLv1_0.txt. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2009-2010 Sun Microsystems, Inc. * Portions copyright 2011-2012 ForgeRock AS. */ package org.forgerock.opendj.ldap; import static org.forgerock.opendj.ldap.CoreMessages.ERR_DN_TYPE_NOT_FOUND; import java.util.*; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.LocalizedIllegalArgumentException; import org.forgerock.opendj.ldap.schema.Schema; import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException; import com.forgerock.opendj.util.SubstringReader; import com.forgerock.opendj.util.Validator; /** * A distinguished name (DN) as defined in RFC 4512 section 2.3 is the * concatenation of its relative distinguished name (RDN) and its immediate * superior's DN. A DN unambiguously refers to an entry in the Directory. *
* The following are examples of string representations of DNs: * *
* UID=nobody@example.com,DC=example,DC=com CN=John * Smith,OU=Sales,O=ACME Limited,L=Moab,ST=Utah,C=US ** * @see RFC 4512 - * Lightweight Directory Access Protocol (LDAP): Directory Information * Models */ public final class DN implements Iterable
* Note: the child DN whose RDN is {@link RDN#maxValue()} compares * greater than all other possible child DNs, and may be used to construct * range queries against DN keyed sorted collections such as {@code SortedSet} * and {@code SortedMap}. * * @param rdn * The RDN for the child DN. * @return The child DN. * @throws NullPointerException * If {@code rdn} was {@code null}. * @see RDN#maxValue() */ public DN child(final RDN rdn) { Validator.ensureNotNull(rdn); return new DN(this, rdn, null); } /** * Returns a DN which is subordinate to this DN and having the additional RDN * components contained in the provided DN decoded using the default schema. * * @param dn * The DN containing the RDN components to be added to this DN. * @return The subordinate DN. * @throws LocalizedIllegalArgumentException * If {@code dn} is not a valid LDAP string representation of a DN. * @throws NullPointerException * If {@code dn} was {@code null}. */ public DN child(final String dn) { Validator.ensureNotNull(dn); return child(valueOf(dn)); } /** * {@inheritDoc} */ public int compareTo(final DN dn) { return compareTo(this, dn); } /** * {@inheritDoc} */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } else if (obj instanceof DN) { DN other = (DN)obj; if(size == other.size()) { if(size == 0) { return true; } if(rdn.equals(other.rdn)) { return parent.equals(other.parent); } } } return false; } /** * {@inheritDoc} */ @Override public int hashCode() { if (size == 0) { return 0; } else { return 31 * parent.hashCode() + rdn.hashCode(); } } /** * Returns {@code true} if this DN is an immediate child of the provided DN. * * @param dn * The potential parent DN. * @return {@code true} if this DN is the immediate child of the provided DN, * otherwise {@code false}. * @throws NullPointerException * If {@code dn} was {@code null}. */ public boolean isChildOf(final DN dn) { // If this is the Root DN then parent will be null but this is ok. return dn.equals(parent); } /** * Returns {@code true} if this DN is an immediate child of the provided DN * decoded using the default schema. * * @param dn * The potential parent DN. * @return {@code true} if this DN is the immediate child of the provided DN, * otherwise {@code false}. * @throws LocalizedIllegalArgumentException * If {@code dn} is not a valid LDAP string representation of a DN. * @throws NullPointerException * If {@code dn} was {@code null}. */ public boolean isChildOf(final String dn) { // If this is the Root DN then parent will be null but this is ok. return isChildOf(valueOf(dn)); } /** * Returns {@code true} if this DN matches the provided base DN and search * scope. * * @param dn * The base DN. * @param scope * The search scope. * @return {@code true} if this DN matches the provided base DN and search * scope, otherwise {@code false}. * @throws NullPointerException * If {@code dn} or {@code scope} was {@code null}. */ public boolean isInScopeOf(DN dn, SearchScope scope) { if (scope == SearchScope.BASE_OBJECT) { // The base DN must equal this DN. return equals(dn); } else if (scope == SearchScope.SINGLE_LEVEL) { // The parent DN must equal the base DN. return isChildOf(dn); } else if (scope == SearchScope.SUBORDINATES) { // This DN must be a descendant of the provided base DN, but // not equal to it. return isSubordinateOrEqualTo(dn) && !equals(dn); } else if (scope == SearchScope.WHOLE_SUBTREE) { // This DN must be a descendant of the provided base DN. return isSubordinateOrEqualTo(dn); } else { // This is a scope that we don't recognize. return false; } } /** * Returns {@code true} if this DN matches the provided base DN and search * scope. * * @param dn * The base DN. * @param scope * The search scope. * @return {@code true} if this DN matches the provided base DN and search * scope, otherwise {@code false}. * @throws LocalizedIllegalArgumentException * If {@code dn} is not a valid LDAP string representation of a DN. * @throws NullPointerException * If {@code dn} or {@code scope} was {@code null}. */ public boolean isInScopeOf(String dn, SearchScope scope) { return isInScopeOf(valueOf(dn), scope); } /** * Returns {@code true} if this DN is the immediate parent of the provided DN. * * @param dn * The potential child DN. * @return {@code true} if this DN is the immediate parent of the provided DN, * otherwise {@code false}. * @throws NullPointerException * If {@code dn} was {@code null}. */ public boolean isParentOf(final DN dn) { // If dn is the Root DN then parent will be null but this is ok. return equals(dn.parent); } /** * Returns {@code true} if this DN is the immediate parent of the provided DN. * * @param dn * The potential child DN. * @return {@code true} if this DN is the immediate parent of the provided DN, * otherwise {@code false}. * @throws LocalizedIllegalArgumentException * If {@code dn} is not a valid LDAP string representation of a DN. * @throws NullPointerException * If {@code dn} was {@code null}. */ public boolean isParentOf(final String dn) { // If dn is the Root DN then parent will be null but this is ok. return isParentOf(valueOf(dn)); } /** * Returns {@code true} if this DN is the Root DN. * * @return {@code true} if this DN is the Root DN, otherwise {@code false}. */ public boolean isRootDN() { return size == 0; } /** * Returns {@code true} if this DN is subordinate to or equal to the provided * DN. * * @param dn * The potential child DN. * @return {@code true} if this DN is subordinate to or equal to the provided * DN, otherwise {@code false}. * @throws NullPointerException * If {@code dn} was {@code null}. */ public boolean isSubordinateOrEqualTo(final DN dn) { if (size < dn.size) { return false; } else if (size == dn.size) { return equals(dn); } else { // dn is a potential superior of this. return parent(size - dn.size).equals(dn); } } /** * Returns {@code true} if this DN is subordinate to or equal to the provided * DN. * * @param dn * The potential child DN. * @return {@code true} if this DN is subordinate to or equal to the provided * DN, otherwise {@code false}. * @throws LocalizedIllegalArgumentException * If {@code dn} is not a valid LDAP string representation of a DN. * @throws NullPointerException * If {@code dn} was {@code null}. */ public boolean isSubordinateOrEqualTo(final String dn) { return isSubordinateOrEqualTo(valueOf(dn)); } /** * Returns {@code true} if this DN is superior to or equal to the provided DN. * * @param dn * The potential child DN. * @return {@code true} if this DN is superior to or equal to the provided DN, * otherwise {@code false}. * @throws NullPointerException * If {@code dn} was {@code null}. */ public boolean isSuperiorOrEqualTo(final DN dn) { if (size > dn.size) { return false; } else if (size == dn.size) { return equals(dn); } else { // dn is a potential subordinate of this. return dn.parent(dn.size - size).equals(this); } } /** * Returns {@code true} if this DN is superior to or equal to the provided DN. * * @param dn * The potential child DN. * @return {@code true} if this DN is superior to or equal to the provided DN, * otherwise {@code false}. * @throws LocalizedIllegalArgumentException * If {@code dn} is not a valid LDAP string representation of a DN. * @throws NullPointerException * If {@code dn} was {@code null}. */ public boolean isSuperiorOrEqualTo(final String dn) { return isSuperiorOrEqualTo(valueOf(dn)); } /** * Returns an iterator of the RDNs contained in this DN. The RDNs will be * returned in the order starting with this DN's RDN, followed by the RDN of * the parent DN, and so on. *
* Attempts to remove RDNs using an iterator's {@code remove()} method are not
* permitted and will result in an {@code UnsupportedOperationException} being
* thrown.
*
* @return An iterator of the RDNs contained in this DN.
*/
public Iterator
* This method is equivalent to:
*
*
* dn.localName(0).isRootDN();
* dn.localName(1).equals(rootDN.child(dn.rdn()));
* dn.localName(dn.size()).equals(dn);
*
*
* @param index
* The number of RDNs to be included in the local name.
* @return The DN whose content is the specified number of RDNs from this DN.
* @throws IllegalArgumentException
* If {@code index} is less than zero.
*/
public DN localName(final int index)
{
Validator.ensureTrue(index >= 0, "index less than zero");
if (index == 0)
{
return ROOT_DN;
}
else if (index >= size)
{
return this;
}
else
{
final DN localName = new DN(null, rdn, null, index);
DN nextLocalName = localName;
DN lastDN = parent;
for (int i = index - 1; i > 0; i--)
{
nextLocalName.parent = new DN(null, lastDN.rdn, null, i);
nextLocalName = nextLocalName.parent;
lastDN = lastDN.parent;
}
nextLocalName.parent = ROOT_DN;
return localName;
}
}
/**
* Returns the DN which is the immediate parent of this DN, or {@code null} if
* this DN is the Root DN.
*
* parent(1);
*
*
* @return The DN which is the immediate parent of this DN, or {@code null} if
* this DN is the Root DN.
*/
public DN parent()
{
return parent;
}
/**
* Returns the DN which is equal to this DN with the specified number of RDNs
* removed. Note that if {@code index} is zero then this DN will be returned
* (identity).
*
* @param index
* The number of RDNs to be removed.
* @return The DN which is equal to this DN with the specified number of RDNs
* removed, or {@code null} if the parent of the Root DN is reached.
* @throws IllegalArgumentException
* If {@code index} is less than zero.
*/
public DN parent(final int index)
{
// We allow size + 1 so that we can return null as the parent of the
// Root DN.
Validator.ensureTrue(index >= 0, "index less than zero");
DN parentDN = this;
for (int i = 0; parentDN != null && i < index; i++)
{
parentDN = parentDN.parent;
}
return parentDN;
}
/**
* Returns the RDN of this DN, or {@code null} if this DN is the Root DN.
*
* @return The RDN of this DN, or {@code null} if this DN is the Root DN.
*/
public RDN rdn()
{
return rdn;
}
/**
* Returns a copy of this DN whose parent DN, {@code fromDN}, has been renamed
* to the new parent DN, {@code toDN}. If this DN is not subordinate or equal
* to {@code fromDN} then this DN is returned (i.e. the DN is not renamed).
*
* @param fromDN
* The old parent DN.
* @param toDN
* The new parent DN.
* @return The renamed DN, or this DN if no renaming was performed.
* @throws NullPointerException
* If {@code fromDN} or {@code toDN} was {@code null}.
*/
public DN rename(final DN fromDN, final DN toDN)
{
Validator.ensureNotNull(fromDN, toDN);
if (!isSubordinateOrEqualTo(fromDN))
{
return this;
}
else if (equals(fromDN))
{
return toDN;
}
else
{
return toDN.child(localName(size - fromDN.size));
}
}
/**
* Returns the number of RDN components in this DN.
*
* @return The number of RDN components in this DN.
*/
public int size()
{
return size;
}
/**
* Returns the RFC 4514 string representation of this DN.
*
* @return The RFC 4514 string representation of this DN.
* @see RFC 4514 - Lightweight
* Directory Access Protocol (LDAP): String Representation of
* Distinguished Names
*/
@Override
public String toString()
{
// We don't care about potential race conditions here.
if (stringValue == null)
{
final StringBuilder builder = new StringBuilder();
rdn.toString(builder);
if (!parent.isRootDN())
{
builder.append(',');
builder.append(parent.toString());
}
stringValue = builder.toString();
}
return stringValue;
}
}