blob: 9e4d802e89bccd39df2a6f017cc59bd63095b60d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.shared.ldap.name;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NamingException;
import org.apache.directory.shared.ldap.name.DefaultStringNormalizer;
import org.apache.directory.shared.ldap.schema.OidNormalizer;
import org.apache.directory.shared.ldap.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The LdapDN class contains a DN (Distinguished Name).
*
* Its specification can be found in RFC 2253,
* "UTF-8 String Representation of Distinguished Names".
*
* We will store two representation of a DN :
* - a user Provider represeentation, which is the parsed String given by a user
* - an internal representation.
*
* A DN is formed of RDNs, in a specific order :
* RDN[n], RDN[n-1], ... RDN[1], RDN[0]
*
* It represents a tree, in which the root is the last RDN (RDN[0]) and the leaf
* is the first RDN (RDN[n]).
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class LdapDN implements Name
{
/** The LoggerFactory used by this class */
protected static final Logger log = LoggerFactory.getLogger( LdapDN.class );
/**
* Declares the Serial Version Uid.
*
* @see <a
* href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
* Declare Serial Version Uid</a>
*/
private static final long serialVersionUID = 1L;
/** Value returned by the compareTo method if values are not equals */
public final static int NOT_EQUALS = -1;
/** Value returned by the compareTo method if values are equals */
public final static int EQUALS = 0;
/** A flag used to tell if the DN has been normalized */
private boolean normalized;
// ~ Static fields/initializers
// -----------------------------------------------------------------
/** The RDNs that are elements of the DN */
protected List<Rdn> rdns = new ArrayList<Rdn>( 5 );
/** The user provided name */
private String upName;
/** The normalized name */
private String normName;
/** The bytes representation of the normName */
private byte[] bytes;
/** A null LdapDN */
public static final LdapDN EMPTY_LDAPDN = new LdapDN();
// ~ Methods
// ------------------------------------------------------------------------------------
/**
* Construct an empty LdapDN object
*/
public LdapDN()
{
super();
upName = "";
normName = "";
normalized = true;
}
/**
* Transduces, or copies a Name to an LdapDN.
*
* @param name composed of String name components.
*/
public LdapDN( Name name ) throws InvalidNameException
{
if ( ( name != null ) && ( name.size() != 0 ) )
{
for ( int ii = 0; ii < name.size(); ii++ )
{
String nameComponent = name.get( ii );
add( nameComponent );
}
}
normalized = false;
}
/**
* Creates an ldap name using a list of NameComponents. Each NameComponent
* is a String
*
* @param list of String name components.
*/
LdapDN( List<String> list ) throws InvalidNameException
{
if ( ( list != null ) && ( list.size() != 0 ) )
{
for ( String nameComponent:list )
{
add( 0, nameComponent );
}
}
normalized = false;
}
/**
* Creates an ldap name using a list of name components.
*
* @param nameComponents
* List of String name components.
*/
LdapDN( Iterator nameComponents ) throws InvalidNameException
{
if ( nameComponents != null )
{
while ( nameComponents.hasNext() )
{
String nameComponent = ( String ) nameComponents.next();
add( 0, nameComponent );
}
}
normalized = false;
}
/**
* Parse a String and checks that it is a valid DN <br>
* <p>
* &lt;distinguishedName&gt; ::= &lt;name&gt; | e <br>
* &lt;name&gt; ::= &lt;name-component&gt; &lt;name-components&gt; <br>
* &lt;name-components&gt; ::= &lt;spaces&gt; &lt;separator&gt;
* &lt;spaces&gt; &lt;name-component&gt; &lt;name-components&gt; | e <br>
* </p>
*
* @param upName
* The String that contains the DN
* @exception InvalidNameException is thrown if the buffer does not
* contains a valid DN.
*/
public LdapDN( String upName ) throws InvalidNameException
{
if ( upName != null )
{
LdapDnParser.parseInternal( upName, rdns );
}
// Stores the representations of a DN : internal (as a string and as a
// byte[]) and external.
normalizeInternal();
normalized = false;
this.upName = upName;
}
/**
* Static factory which creates a normalized DN from a String and a Map of OIDs.
*
* @param name The DN as a String
* @param oidsMap The OID mapping
* @return A valid DN
* @throws InvalidNameException If the DN is invalid
*/
public static Name normalize( String name, Map<String, OidNormalizer> oidsMap ) throws InvalidNameException, NamingException
{
if ( ( name == null ) || ( name.length() == 0 ) || ( oidsMap == null ) || ( oidsMap.size() == 0 ) )
{
return LdapDN.EMPTY_LDAPDN;
}
try
{
LdapDN newDn = new LdapDN( name );
Enumeration<Rdn> rdns = newDn.getAllRdn();
// Loop on all RDNs
while ( rdns.hasMoreElements() )
{
Rdn rdn = rdns.nextElement();
String upName = rdn.getUpName();
rdnOidToName( rdn, oidsMap );
rdn.normalize();
rdn.setUpName( upName );
}
newDn.normalizeInternal();
newDn.normalized = true;
return newDn;
}
catch ( NamingException ne )
{
throw new InvalidNameException( ne.getMessage() );
}
}
/**
* Parse a buffer and checks that it is a valid DN <br>
* <p>
* &lt;distinguishedName&gt; ::= &lt;name&gt; | e <br>
* &lt;name&gt; ::= &lt;name-component&gt; &lt;name-components&gt; <br>
* &lt;name-components&gt; ::= &lt;spaces&gt; &lt;separator&gt;
* &lt;spaces&gt; &lt;name-component&gt; &lt;name-components&gt; | e <br>
* </p>
*
* @param bytes
* The byte buffer that contains the DN
* @exception A
* InvalidNameException is thrown if the buffer does not
* contains a valid DN.
*/
public LdapDN(byte[] bytes) throws InvalidNameException
{
try
{
upName = new String( bytes, "UTF-8" );
LdapDnParser.parseInternal( upName, rdns );
this.normName = toNormName();
normalized = false;
}
catch ( UnsupportedEncodingException uee )
{
log.error( "The byte array is not an UTF-8 encoded Unicode String : " + uee.getMessage() );
throw new InvalidNameException( "The byte array is not an UTF-8 encoded Unicode String : "
+ uee.getMessage() );
}
}
/**
* Normalize the DN by triming useless spaces and lowercasing names.
*
* @return a normalized form of the DN
*/
private void normalizeInternal()
{
normName = toNormName();
}
/**
* Build the normalized DN as a String,
*
* @return A String representing the normalized DN
*/
public String toNormName()
{
if ( ( rdns == null ) || ( rdns.size() == 0 ) )
{
bytes = null;
return "";
}
else
{
StringBuffer sb = new StringBuffer();
boolean isFirst = true;
for ( Rdn rdn:rdns )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( ',' );
}
sb.append( rdn );
}
String newNormName = sb.toString();
if ( ( normName == null ) || !normName.equals( newNormName ) )
{
bytes = StringTools.getBytesUtf8( newNormName );
normName = newNormName;
}
return normName;
}
}
/**
* Return the normalized DN as a String. It returns the same value as the
* getNormName method
*
* @return A String representing the normalized DN
*/
public String toString()
{
return normName == null ? "" : normName;
}
/**
* Return the User Provided DN as a String,
*
* @return A String representing the User Provided DN
*/
private String toUpName()
{
if ( ( rdns == null ) || ( rdns.size() == 0 ) )
{
upName = "";
}
else
{
StringBuffer sb = new StringBuffer();
boolean isFirst = true;
for ( Rdn rdn:rdns )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( ',' );
}
sb.append( rdn.getUpName() );
}
upName = sb.toString();
}
return upName;
}
/**
* Return the User Provided prefix representation of the DN starting at the
* posn position.
*
* If posn = 0, return an empty string.
*
* for DN : sn=smith, dc=apache, dc=org
* getUpname(0) -> ""
* getUpName(1) -> "dc=org"
* getUpname(3) -> "sn=smith, dc=apache, dc=org"
* getUpName(4) -> ArrayOutOfBoundException
*
* Warning ! The returned String is not exactly the
* user provided DN, as spaces before and after each RDNs have been trimmed.
*
* @param posn
* The starting position
* @return The truncated DN
*/
private String getUpNamePrefix( int posn )
{
if ( posn == 0 )
{
return "";
}
if ( posn > rdns.size() )
{
String message = "Impossible to get the position " + posn + ", the DN only has " + rdns.size() + " RDNs";
log.error( message );
throw new ArrayIndexOutOfBoundsException( message );
}
int start = rdns.size() - posn;
StringBuffer sb = new StringBuffer();
boolean isFirst = true;
for ( int i = start; i < rdns.size(); i++ )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( ',' );
}
sb.append( rdns.get( i ).getUpName() );
}
return sb.toString();
}
/**
* Return the User Provided suffix representation of the DN starting at the
* posn position.
* If posn = 0, return an empty string.
*
* for DN : sn=smith, dc=apache, dc=org
* getUpname(0) -> "sn=smith, dc=apache, dc=org"
* getUpName(1) -> "sn=smith, dc=apache"
* getUpname(3) -> "sn=smith"
* getUpName(4) -> ""
*
* Warning ! The returned String is not exactly the user
* provided DN, as spaces before and after each RDNs have been trimmed.
*
* @param posn The starting position
* @return The truncated DN
*/
private String getUpNameSuffix( int posn )
{
if ( posn > rdns.size() )
{
return "";
}
int end = rdns.size() - posn;
StringBuffer sb = new StringBuffer();
boolean isFirst = true;
for ( int i = 0; i < end; i++ )
{
if ( isFirst )
{
isFirst = false;
}
else
{
sb.append( ',' );
}
sb.append( rdns.get( i ).getUpName() );
}
return sb.toString();
}
/**
* Gets the hashcode of this name.
*
* @see java.lang.Object#hashCode()
*/
public int hashCode()
{
int result = 17;
if ( ( rdns != null ) && ( rdns.size() == 0 ) )
{
for ( Rdn rdn:rdns )
{
result = result * 37 + rdn.hashCode();
}
}
return result;
}
/**
* Get the initial DN (without normalization)
*
* @return The DN as a String
*/
public String getUpName()
{
return ( upName == null ? "" : upName );
}
/**
* Get the initial DN (without normalization)
*
* @return The DN as a String
*/
public String getNormName()
{
return ( normName == null ? "" : normName );
}
/**
* Get the number of NameComponent conatained in this LdapDN
*
* @return The number of NameComponent conatained in this LdapDN
*/
public int size()
{
return rdns.size();
}
/**
* Get the number of bytes necessary to store this DN
*
* @return A integer, which is the size of the UTF-8 byte array
*/
public static int getNbBytes( Name dn )
{
LdapDN ldapDn = ( LdapDN ) dn;
return ldapDn.bytes == null ? 0 : ldapDn.bytes.length;
}
/**
* Get an UTF-8 representation of the normalized form of the DN
*
* @return A byte[] representation of the DN
*/
public static byte[] getBytes( LdapDN dn )
{
return dn == null ? null : dn.bytes;
}
/**
* Determines whether this name starts with a specified prefix. A name
* <tt>name</tt> is a prefix if it is equal to
* <tt>getPrefix(name.size())</tt>.
*
* Be aware that for a specific DN like :
* cn=xxx, ou=yyy
* the startsWith method will return true with ou=yyy, and
* false with cn=xxx
*
* @param name
* the name to check
* @return true if <tt>name</tt> is a prefix of this name, false otherwise
*/
public boolean startsWith( Name name )
{
if ( name == null )
{
return true;
}
else if ( name instanceof LdapDN )
{
LdapDN nameDN = ( LdapDN ) name;
if ( nameDN.size() == 0 )
{
return true;
}
if ( nameDN.size() > size() )
{
// The name is longer than the current LdapDN.
return false;
}
// Ok, iterate through all the RDN of the name,
// starting a the end of the current list.
for ( int i = nameDN.size() - 1; i >= 0; i-- )
{
Rdn nameRdn = nameDN.rdns.get( nameDN.rdns.size() - i - 1 );
Rdn ldapRdn = rdns.get( rdns.size() - i - 1 );
if ( nameRdn.compareTo( ldapRdn ) != 0 )
{
return false;
}
}
return true;
}
else
{
if ( name.size() == 0 )
{
return true;
}
if ( name.size() > size() )
{
// The name is longer than the current LdapDN.
return false;
}
// Ok, iterate through all the RDN of the name,
// starting a the end of the current list.
for ( int i = name.size() - 1; i >= 0; i-- )
{
Rdn ldapRdn = rdns.get( rdns.size() - i - 1 );
Rdn nameRdn = null;
try
{
nameRdn = new Rdn( name.get( name.size() - i - 1 ) );
}
catch ( InvalidNameException e )
{
e.printStackTrace();
log.error( "Failed to parse RDN for name " + name.toString(), e );
return false;
}
if ( nameRdn.compareTo( ldapRdn ) != 0 )
{
return false;
}
}
return true;
}
}
/**
* Determines whether this name ends with a specified suffix. A name
* <tt>name</tt> is a suffix if it is equal to
* <tt>getSuffix(size()-name.size())</tt>.
*
* Be aware that for a specific
* DN like : cn=xxx, ou=yyy the endsWith method will return true with
* cn=xxx, and false with ou=yyy
*
* @param name
* the name to check
* @return true if <tt>name</tt> is a suffix of this name, false otherwise
*/
public boolean endsWith( Name name )
{
if ( name instanceof LdapDN )
{
LdapDN nameDN = ( LdapDN ) name;
if ( nameDN.size() == 0 )
{
return true;
}
if ( nameDN.size() > size() )
{
// The name is longer than the current LdapDN.
return false;
}
// Ok, iterate through all the RDN of the name
for ( int i = 0; i < nameDN.size(); i++ )
{
Rdn nameRdn = nameDN.rdns.get( i );
Rdn ldapRdn = rdns.get( i );
if ( nameRdn.compareTo( ldapRdn ) != 0 )
{
return false;
}
}
return true;
}
else
{
// We don't accept a Name which is not a LdapName
return name == null;
}
}
/**
* Determines whether this name is empty. An empty name is one with zero
* components.
*
* @return true if this name is empty, false otherwise
*/
public boolean isEmpty()
{
return ( rdns.size() == 0 );
}
/**
* Retrieves a component of this name.
*
* @param posn
* the 0-based index of the component to retrieve. Must be in the
* range [0,size()).
* @return the component at index posn
* @throws ArrayIndexOutOfBoundsException
* if posn is outside the specified range
*/
public String get( int posn )
{
if ( rdns.size() == 0 )
{
return "";
}
else
{
Rdn rdn = rdns.get( rdns.size() - posn - 1 );
return rdn.toString();
}
}
/**
* Retrieves a component of this name.
*
* @param posn
* the 0-based index of the component to retrieve. Must be in the
* range [0,size()).
* @return the component at index posn
* @throws ArrayIndexOutOfBoundsException
* if posn is outside the specified range
*/
public Rdn getRdn( int posn )
{
if ( rdns.size() == 0 )
{
return null;
}
else
{
Rdn rdn = rdns.get( rdns.size() - posn - 1 );
return rdn;
}
}
/**
* Retrieves the last component of this name.
*
* @return the last component of this DN
*/
public Rdn getRdn()
{
if ( rdns.size() == 0 )
{
return null;
}
else
{
return rdns.get( 0 );
}
}
/**
* Retrieves all the components of this name.
*
* @return All the components
*/
public List<Rdn> getRdns()
{
List<Rdn> newRdns = new ArrayList<Rdn>();
// We will clone the list, to avoid user modifications
for ( Rdn rdn:rdns )
{
newRdns.add( (Rdn)rdn.clone() );
}
return newRdns;
}
/**
* Retrieves the components of this name as an enumeration of strings. The
* effect on the enumeration of updates to this name is undefined. If the
* name has zero components, an empty (non-null) enumeration is returned.
*
* @return an enumeration of the components of this name, each as string
*/
public Enumeration<String> getAll()
{
/*
* Note that by accessing the name component using the get() method on
* the name rather than get() on the list we are reading components from
* right to left with increasing index values. LdapName.get() does the
* index translation on m_list for us.
*/
return new Enumeration<String>()
{
private int pos;
public boolean hasMoreElements()
{
return pos < rdns.size();
}
public String nextElement()
{
if ( pos >= rdns.size() )
{
log.error( "Exceeded number of elements in the current object" );
throw new NoSuchElementException();
}
Rdn rdn = rdns.get( rdns.size() - pos - 1 );
pos++;
return rdn.toString();
}
};
}
/**
* Retrieves the components of this name as an enumeration of strings. The
* effect on the enumeration of updates to this name is undefined. If the
* name has zero components, an empty (non-null) enumeration is returned.
*
* @return an enumeration of the components of this name, as Rdn
*/
public Enumeration<Rdn> getAllRdn()
{
/*
* Note that by accessing the name component using the get() method on
* the name rather than get() on the list we are reading components from
* right to left with increasing index values. LdapName.get() does the
* index translation on m_list for us.
*/
return new Enumeration<Rdn>()
{
private int pos;
public boolean hasMoreElements()
{
return pos < rdns.size();
}
public Rdn nextElement()
{
if ( pos >= rdns.size() )
{
log.error( "Exceeded number of elements in the current object" );
throw new NoSuchElementException();
}
Rdn rdn = rdns.get( rdns.size() - pos - 1 );
pos++;
return rdn;
}
};
}
/**
* Creates a name whose components consist of a prefix of the components of
* this name. Subsequent changes to this name will not affect the name that
* is returned and vice versa.
*
* @param posn
* the 0-based index of the component at which to stop. Must be
* in the range [0,size()].
* @return a name consisting of the components at indexes in the range
* [0,posn].
* @throws ArrayIndexOutOfBoundsException
* if posn is outside the specified range
*/
public Name getPrefix( int posn )
{
if ( rdns.size() == 0 )
{
return EMPTY_LDAPDN;
}
if ( ( posn < 0 ) || ( posn > rdns.size() ) )
{
String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]";
log.error( message );
throw new ArrayIndexOutOfBoundsException( message );
}
LdapDN newLdapDN = new LdapDN();
for ( int i = rdns.size() - posn; i < rdns.size(); i++ )
{
// Don't forget to clone the rdns !
newLdapDN.rdns.add( ( Rdn ) rdns.get( i ).clone() );
}
newLdapDN.normName = newLdapDN.toNormName();
newLdapDN.upName = getUpNamePrefix( posn );
return newLdapDN;
}
/**
* Creates a name whose components consist of a suffix of the components in
* this name. Subsequent changes to this name do not affect the name that is
* returned and vice versa.
*
* @param posn
* the 0-based index of the component at which to start. Must be
* in the range [0,size()].
* @return a name consisting of the components at indexes in the range
* [posn,size()]. If posn is equal to size(), an empty name is
* returned.
* @throws ArrayIndexOutOfBoundsException
* if posn is outside the specified range
*/
public Name getSuffix( int posn )
{
if ( rdns.size() == 0 )
{
return EMPTY_LDAPDN;
}
if ( ( posn < 0 ) || ( posn > rdns.size() ) )
{
String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]";
log.error( message );
throw new ArrayIndexOutOfBoundsException( message );
}
LdapDN newLdapDN = new LdapDN();
for ( int i = 0; i < size() - posn; i++ )
{
// Don't forget to clone the rdns !
newLdapDN.rdns.add( ( Rdn ) rdns.get( i ).clone() );
}
newLdapDN.normName = newLdapDN.toNormName();
newLdapDN.upName = getUpNameSuffix( posn );
return newLdapDN;
}
/**
* Adds the components of a name -- in order -- to the end of this name.
*
* @param suffix
* the components to add
* @return the updated name (not a new one)
* @throws InvalidNameException
* if <tt>suffix</tt> is not a valid name, or if the addition
* of the components would violate the syntax rules of this name
*/
public Name addAll( Name suffix ) throws InvalidNameException
{
addAll( rdns.size(), suffix );
normalizeInternal();
toUpName();
return this;
}
/**
* Adds the components of a name -- in order -- at a specified position
* within this name. Components of this name at or after the index of the
* first new component are shifted up (away from 0) to accommodate the new
* components.
*
* @param name
* the components to add
* @param posn
* the index in this name at which to add the new components.
* Must be in the range [0,size()].
* @return the updated name (not a new one)
* @throws ArrayIndexOutOfBoundsException
* if posn is outside the specified range
* @throws InvalidNameException
* if <tt>n</tt> is not a valid name, or if the addition of
* the components would violate the syntax rules of this name
*/
public Name addAll( int posn, Name name ) throws InvalidNameException
{
if ( name instanceof LdapDN )
{
if ( ( name == null ) || ( name.size() == 0 ) )
{
return this;
}
// Concatenate the rdns
rdns.addAll( size() - posn, ( ( LdapDN ) name ).rdns );
// Regenerate the normalized name and the original string
normalizeInternal();
toUpName();
}
else
{
if ( ( name == null ) || ( name.size() == 0 ) )
{
return this;
}
for ( int i = name.size() - 1; i >= 0; i-- )
{
Rdn rdn = new Rdn( name.get( i ) );
rdns.add( size() - posn, rdn );
}
normalizeInternal();
toUpName();
}
return this;
}
/**
* Adds a single component to the end of this name.
*
* @param comp
* the component to add
* @return the updated name (not a new one)
* @throws InvalidNameException
* if adding <tt>comp</tt> would violate the syntax rules of
* this name
*/
public Name add( String comp ) throws InvalidNameException
{
if ( comp.length() == 0 )
{
return this;
}
// We have to parse the nameComponent which is given as an argument
Rdn newRdn = new Rdn( comp );
rdns.add( 0, newRdn );
normalizeInternal();
toUpName();
return this;
}
/**
* Adds a single RDN to the end of this name.
*
* @param newRdn
* the RDN to add
* @return the updated name (not a new one)
* @throws InvalidNameException
* if adding <tt>RDN</tt> would violate the syntax rules of
* this name
*/
public Name add( Rdn newRdn )
{
rdns.add( 0, newRdn );
normalizeInternal();
toUpName();
return this;
}
/**
* Adds a single component at a specified position within this name.
* Components of this name at or after the index of the new component are
* shifted up by one (away from index 0) to accommodate the new component.
*
* @param comp
* the component to add
* @param posn
* the index at which to add the new component. Must be in the
* range [0,size()].
* @return the updated name (not a new one)
* @throws ArrayIndexOutOfBoundsException
* if posn is outside the specified range
* @throws InvalidNameException
* if adding <tt>comp</tt> would violate the syntax rules of
* this name
*/
public Name add( int posn, String comp ) throws InvalidNameException
{
if ( ( posn < 0 ) || ( posn > size() ) )
{
String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]";
log.error( message );
throw new ArrayIndexOutOfBoundsException( message );
}
// We have to parse the nameComponent which is given as an argument
Rdn newRdn = new Rdn( comp );
int realPos = size() - posn;
rdns.add( realPos, newRdn );
normalizeInternal();
toUpName();
return this;
}
/**
* Removes a component from this name. The component of this name at the
* specified position is removed. Components with indexes greater than this
* position are shifted down (toward index 0) by one.
*
* @param posn
* the index of the component to remove. Must be in the range
* [0,size()).
* @return the component removed (a String)
* @throws ArrayIndexOutOfBoundsException
* if posn is outside the specified range
* @throws InvalidNameException
* if deleting the component would violate the syntax rules of
* the name
*/
public Object remove( int posn ) throws InvalidNameException
{
if ( rdns.size() == 0 )
{
return EMPTY_LDAPDN;
}
if ( ( posn < 0 ) || ( posn >= rdns.size() ) )
{
String message = "The posn(" + posn + ") should be in the range [0, " + rdns.size() + "]";
log.error( message );
throw new ArrayIndexOutOfBoundsException( message );
}
int realPos = size() - posn - 1;
Rdn rdn = rdns.remove( realPos );
normalizeInternal();
toUpName();
return rdn;
}
/**
* Generates a new copy of this name. Subsequent changes to the components
* of this name will not affect the new copy, and vice versa.
*
* @return a copy of this name
* @see Object#clone()
*/
public Object clone()
{
try
{
LdapDN dn = ( LdapDN ) super.clone();
dn.rdns = new ArrayList<Rdn>();
for ( Rdn rdn:rdns )
{
dn.rdns.add( ( Rdn ) rdn.clone() );
}
return dn;
}
catch ( CloneNotSupportedException cnse )
{
log.error( "The clone operation has failed" );
throw new Error( "Assertion failure : cannot clone the object" );
}
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals( Object obj )
{
if ( obj instanceof String )
{
return normName.equals( obj );
}
else if ( obj instanceof LdapDN )
{
LdapDN name = ( LdapDN ) obj;
if ( name.size() != this.size() )
{
return false;
}
for ( int i = 0; i < this.size(); i++ )
{
if ( name.rdns.get( i ).compareTo( rdns.get( i ) ) != 0 )
{
return false;
}
}
// All components matched so we return true
return true;
}
else
{
return false;
}
}
/**
* Compares this name with another name for order. Returns a negative
* integer, zero, or a positive integer as this name is less than, equal to,
* or greater than the given name.
* <p>
* As with <tt>Object.equals()</tt>, the notion of ordering for names
* depends on the class that implements this interface. For example, the
* ordering may be based on lexicographical ordering of the name components.
* Specific attributes of the name, such as how it treats case, may affect
* the ordering. In general, two names of different classes may not be
* compared.
*
* @param obj
* the non-null object to compare against.
* @return a negative integer, zero, or a positive integer as this name is
* less than, equal to, or greater than the given name
* @throws ClassCastException
* if obj is not a <tt>Name</tt> of a type that may be
* compared with this name
* @see Comparable#compareTo(Object)
*/
public int compareTo( Object obj )
{
if ( obj instanceof LdapDN )
{
LdapDN ldapDN = ( LdapDN ) obj;
if ( ldapDN.size() != size() )
{
return size() - ldapDN.size();
}
for ( int i = rdns.size(); i > 0; i-- )
{
Rdn rdn1 = rdns.get( i - 1 );
Rdn rdn2 = ldapDN.rdns.get( i - 1 );
int res = rdn1.compareTo( rdn2 );
if ( res != 0 )
{
return res;
}
}
return EQUALS;
}
else
{
return 1;
}
}
private static AttributeTypeAndValue atavOidToName( AttributeTypeAndValue atav, Map<String, OidNormalizer> oidsMap )
throws InvalidNameException, NamingException
{
String type = StringTools.trim( atav.getNormType() );
if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) )
{
type = type.substring( 4 );
}
if ( StringTools.isNotEmpty( type ) )
{
if ( oidsMap == null )
{
return atav;
}
else
{
OidNormalizer oidNormalizer = oidsMap.get( type );
if ( oidNormalizer != null )
{
return new AttributeTypeAndValue( oidNormalizer.getAttributeTypeOid(), oidNormalizer.getNormalizer()
.normalize( atav.getValue() ) );
}
else
{
// We don't have a normalizer for this OID : just do nothing.
return atav;
}
}
}
else
{
// The type is empty : this is not possible...
log.error( "Empty type not allowed in a DN" );
throw new InvalidNameException( "Empty type not allowed in a DN" );
}
}
/**
* Transform a RDN by changing the value to its OID counterpart and
* normalizing the value accordingly to its type.
*
* @param rdn
* The RDN to modify
* @param oidsMap
* The map of all existing oids and normalizer
* @throws InvalidNameException
* If
* @throws NamingException
*/
private static void rdnOidToName( Rdn rdn, Map<String, OidNormalizer> oidsMap ) throws InvalidNameException, NamingException
{
if ( rdn.getNbAtavs() > 1 )
{
// We have more than one ATAV for this RDN. We will loop on all
// ATAVs
Rdn rdnCopy = ( Rdn ) rdn.clone();
rdn.clear();
Iterator atavs = rdnCopy.iterator();
while ( atavs.hasNext() )
{
Object val = atavs.next();
AttributeTypeAndValue newAtav = atavOidToName( ( AttributeTypeAndValue ) val, oidsMap );
rdn.addAttributeTypeAndValue( newAtav.getUpType(), newAtav.getValue() );
}
}
else
{
String type = rdn.getNormType();
if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) )
{
type = type.substring( 4 );
}
if ( StringTools.isNotEmpty( type ) )
{
if ( oidsMap == null )
{
return;
}
else
{
OidNormalizer oidNormalizer = oidsMap.get( type );
if ( oidNormalizer != null )
{
// Alex asks: Why clone here when we do not use the cloned copy?
Rdn rdnCopy = ( Rdn ) rdn.clone();
rdn.clear();
Object value = rdnCopy.getValue();
Object normValue = DefaultStringNormalizer.normalizeString( ( String ) value );
rdn.addAttributeTypeAndValue( oidNormalizer.getAttributeTypeOid(),
oidNormalizer.getNormalizer()
.normalize( normValue ) );
}
else
{
// We don't have a normalizer for this OID : just do
// nothing.
return;
}
}
}
else
{
// The type is empty : this is not possible...
log.error( "We should not have an empty DN" );
throw new InvalidNameException( "Empty type not allowed in a DN" );
}
}
}
/**
* Change the internal DN, using the first alias instead of oids or other
* aliases. As we still have the UP name of each RDN, we will be able to
* provide both representation of the DN. example : dn: 2.5.4.3=People,
* dc=example, domainComponent=com will be transformed to : cn=People,
* dc=example, dc=com because 2.5.4.3 is the OID for cn and dc is the first
* alias of the couple of aliases (dc, domaincomponent). This is really
* important do have such a representation, as 'cn' and 'commonname' share
* the same OID.
*
* @param dn
* The DN to transform
* @param oidsMap
* The mapping between names and oids.
* @return A normalized form of the DN
* @throws InvalidNameException
* If the DN is invalid
*/
public static LdapDN normalize( LdapDN dn, Map<String, OidNormalizer> oidsMap ) throws InvalidNameException, NamingException
{
if ( ( dn == null ) || ( dn.size() == 0 ) || ( oidsMap == null ) || ( oidsMap.size() == 0 ) )
{
return dn;
}
LdapDN newDn = ( LdapDN ) dn.clone();
Enumeration<Rdn> rdns = newDn.getAllRdn();
// Loop on all RDNs
while ( rdns.hasMoreElements() )
{
Rdn rdn = rdns.nextElement();
String upName = rdn.getUpName();
rdnOidToName( rdn, oidsMap );
rdn.normalize();
rdn.setUpName( upName );
}
newDn.normalizeInternal();
newDn.normalized = true;
return newDn;
}
/**
* Change the internal DN, using the first alias instead of oids or other
* aliases. As we still have the UP name of each RDN, we will be able to
* provide both representation of the DN. example : dn: 2.5.4.3=People,
* dc=example, domainComponent=com will be transformed to : cn=People,
* dc=example, dc=com because 2.5.4.3 is the OID for cn and dc is the first
* alias of the couple of aliases (dc, domaincomponent). This is really
* important do have such a representation, as 'cn' and 'commonname' share
* the same OID.
*
* @param oidsMap
* The mapping between names and oids.
* @throws InvalidNameException
* If the DN is invalid
*/
public void normalize( Map<String, OidNormalizer> oidsMap ) throws InvalidNameException, NamingException
{
if ( ( oidsMap == null ) || ( oidsMap.size() == 0 ) )
{
return;
}
if ( size() == 0 )
{
return;
}
Enumeration<Rdn> localRdns = getAllRdn();
// Loop on all RDNs
while ( localRdns.hasMoreElements() )
{
Rdn rdn = localRdns.nextElement();
String localUpName = rdn.getUpName();
rdnOidToName( rdn, oidsMap );
rdn.normalize();
rdn.setUpName( localUpName );
}
normalizeInternal();
normalized = true;
}
/**
* Check if a DistinguishedName is syntaxically valid
*
* @param dn The DN to validate
* @return <code>true></code> if the DN is valid, <code>false</code>
* otherwise
*/
public static boolean isValid( String dn )
{
return LdapDnParser.validateInternal( dn );
}
public boolean isNormalized()
{
return normalized;
}
}