| /* |
| * 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.api.ldap.model.name; |
| |
| |
| import java.io.Externalizable; |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.apache.commons.collections.list.UnmodifiableList; |
| import org.apache.directory.api.i18n.I18n; |
| import org.apache.directory.api.ldap.model.entry.BinaryValue; |
| import org.apache.directory.api.ldap.model.entry.StringValue; |
| import org.apache.directory.api.ldap.model.entry.Value; |
| import org.apache.directory.api.ldap.model.exception.LdapException; |
| import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; |
| import org.apache.directory.api.ldap.model.message.ResultCodeEnum; |
| import org.apache.directory.api.ldap.model.schema.AttributeType; |
| import org.apache.directory.api.ldap.model.schema.SchemaManager; |
| import org.apache.directory.api.ldap.model.schema.normalizers.OidNormalizer; |
| import org.apache.directory.api.util.Strings; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * The Dn class contains a Dn (Distinguished Name). This class is immutable. |
| * <br/> |
| * Its specification can be found in RFC 2253, |
| * "UTF-8 String Representation of Distinguished Names". |
| * <br/> |
| * We will store two representation of a Dn : |
| * <ul> |
| * <li>a user Provider representation, which is the parsed String given by a user</li> |
| * <li>an internal representation.</li> |
| * </ul> |
| * |
| * A Dn is formed of RDNs, in a specific order :<br/> |
| * Rdn[n], Rdn[n-1], ... Rdn[1], Rdn[0]<br/> |
| * |
| * It represents a position in a hierarchy, 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 Dn implements Iterable<Rdn>, Externalizable |
| { |
| /** The LoggerFactory used by this class */ |
| protected static final Logger LOG = LoggerFactory.getLogger( Dn.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 static final int NOT_EQUAL = -1; |
| |
| /** Value returned by the compareTo method if values are equals */ |
| public static final int EQUAL = 0; |
| |
| /** |
| * The RDNs that are elements of the Dn<br/> |
| * NOTE THAT THESE ARE IN THE OPPOSITE ORDER FROM THAT IMPLIED BY THE JAVADOC!<br/> |
| * Rdn[0] is rdns.get(n) and Rdn[n] is rdns.get(0) |
| * <br> |
| * For instance,if the Dn is "dc=c, dc=b, dc=a", then the RDNs are stored as : |
| * <ul> |
| * <li>[0] : dc=c</li> |
| * <li>[1] : dc=b</li> |
| * <li>[2] : dc=a</li> |
| * </ul> |
| */ |
| 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 Dn */ |
| public static final Dn EMPTY_DN = new Dn(); |
| |
| /** The rootDSE */ |
| public static final Dn ROOT_DSE = new Dn(); |
| |
| /** the schema manager */ |
| private SchemaManager schemaManager; |
| |
| /** |
| * An iterator over RDNs |
| */ |
| private final class RdnIterator implements Iterator<Rdn> |
| { |
| // The current index |
| int index; |
| |
| |
| private RdnIterator() |
| { |
| index = rdns != null ? rdns.size() - 1 : -1; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean hasNext() |
| { |
| return index >= 0; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Rdn next() |
| { |
| return index >= 0 ? rdns.get( index-- ) : null; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void remove() |
| { |
| // Not implemented |
| } |
| } |
| |
| |
| /** |
| * Construct an empty Dn object |
| */ |
| public Dn() |
| { |
| this( ( SchemaManager ) null ); |
| } |
| |
| |
| /** |
| * Construct an empty Schema aware Dn object |
| * |
| * @param schemaManager The SchemaManager to use |
| */ |
| public Dn( SchemaManager schemaManager ) |
| { |
| this.schemaManager = schemaManager; |
| upName = ""; |
| normName = ""; |
| } |
| |
| |
| /** |
| * Creates a new instance of Dn, using varargs to declare the RDNs. Each |
| * String is either a full Rdn, or a couple of AttributeType DI and a value. |
| * If the String contains a '=' symbol, the the constructor will assume that |
| * the String arg contains afull Rdn, otherwise, it will consider that the |
| * following arg is the value.<br/> |
| * The created Dn is Schema aware. |
| * <br/><br/> |
| * An example of usage would be : |
| * <pre> |
| * String exampleName = "example"; |
| * String baseDn = "dc=apache,dc=org"; |
| * |
| * Dn dn = new Dn( DefaultSchemaManager.INSTANCE, |
| * "cn=Test", |
| * "ou", exampleName, |
| * baseDn); |
| * </pre> |
| * |
| * @param schemaManager the schema manager |
| * @param upRdns The list of String composing the Dn |
| * @throws LdapInvalidDnException If the resulting Dn is invalid |
| */ |
| public Dn( String... upRdns ) throws LdapInvalidDnException |
| { |
| this( null, upRdns ); |
| } |
| |
| |
| /** |
| * Creates a new instance of schema aware Dn, using varargs to declare the RDNs. Each |
| * String is either a full Rdn, or a couple of AttributeType DI and a value. |
| * If the String contains a '=' symbol, the the constructor will assume that |
| * the String arg contains afull Rdn, otherwise, it will consider that the |
| * following arg is the value.<br/> |
| * The created Dn is Schema aware. |
| * <br/><br/> |
| * An example of usage would be : |
| * <pre> |
| * String exampleName = "example"; |
| * String baseDn = "dc=apache,dc=org"; |
| * |
| * Dn dn = new Dn( DefaultSchemaManager.INSTANCE, |
| * "cn=Test", |
| * "ou", exampleName, |
| * baseDn); |
| * </pre> |
| * |
| * @param schemaManager the schema manager |
| * @param upRdns The list of String composing the Dn |
| * @throws LdapInvalidDnException If the resulting Dn is invalid |
| */ |
| public Dn( SchemaManager schemaManager, String... upRdns ) throws LdapInvalidDnException |
| { |
| StringBuilder sb = new StringBuilder(); |
| boolean valueExpected = false; |
| boolean isFirst = true; |
| |
| for ( String upRdn : upRdns ) |
| { |
| if ( Strings.isEmpty( upRdn ) ) |
| { |
| continue; |
| } |
| |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else if ( !valueExpected ) |
| { |
| sb.append( ',' ); |
| } |
| |
| if ( !valueExpected ) |
| { |
| sb.append( upRdn ); |
| |
| if ( upRdn.indexOf( '=' ) == -1 ) |
| { |
| valueExpected = true; |
| } |
| } |
| else |
| { |
| sb.append( "=" ).append( upRdn ); |
| |
| valueExpected = false; |
| } |
| } |
| |
| if ( !isFirst && valueExpected ) |
| { |
| throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04202 ) ); |
| } |
| |
| // Stores the representations of a Dn : internal (as a string and as a |
| // byte[]) and external. |
| upName = sb.toString(); |
| parseInternal( upName, rdns ); |
| |
| apply( schemaManager ); |
| } |
| |
| |
| /** |
| * Create a schema aware Dn while deserializing it. |
| * <br/> |
| * Note : this constructor is used only by the deserialization method. |
| * |
| * @param schemaManager the schema manager |
| * @param upName The user provided name |
| * @param normName the normalized name |
| * @param rdns the list of RDNs for this Dn |
| */ |
| /* No protection */Dn( SchemaManager schemaManager, String upName, String normName, Rdn... rdns ) |
| { |
| this.schemaManager = schemaManager; |
| this.upName = upName; |
| this.normName = normName; |
| bytes = Strings.getBytesUtf8Ascii( upName ); |
| this.rdns = Arrays.asList( rdns ); |
| } |
| |
| |
| /** |
| * Creates a Dn from a list of Rdns. |
| * |
| * @param rdns the list of Rdns to be used for the Dn |
| * @throws LdapInvalidDnException If the resulting Dn is invalid |
| */ |
| public Dn( Rdn... rdns ) throws LdapInvalidDnException |
| { |
| if ( rdns == null ) |
| { |
| return; |
| } |
| |
| for ( Rdn rdn : rdns ) |
| { |
| this.rdns.add( rdn ); |
| } |
| |
| apply( null ); |
| toUpName(); |
| } |
| |
| |
| /** |
| * Creates a Dn concatenating a Rdn and a Dn. |
| * |
| * @param rdn the Rdn to add to the Dn |
| * @param dn the Dn |
| * @throws LdapInvalidDnException If the resulting Dn is invalid |
| */ |
| public Dn( Rdn rdn, Dn dn ) throws LdapInvalidDnException |
| { |
| if ( ( dn == null ) || ( rdn == null ) ) |
| { |
| throw new IllegalArgumentException( "Either the dn or the rdn is null" ); |
| } |
| |
| for ( Rdn rdnParent : dn ) |
| { |
| rdns.add( 0, rdnParent ); |
| } |
| |
| rdns.add( 0, rdn ); |
| |
| apply( dn.schemaManager ); |
| toUpName(); |
| } |
| |
| |
| /** |
| * Creates a Schema aware Dn from a list of Rdns. |
| * |
| * @param schemaManager The SchemaManager to use |
| * @param rdns the list of Rdns to be used for the Dn |
| * @throws LdapInvalidDnException If the resulting Dn is invalid |
| */ |
| public Dn( SchemaManager schemaManager, Rdn... rdns ) throws LdapInvalidDnException |
| { |
| if ( rdns == null ) |
| { |
| return; |
| } |
| |
| for ( Rdn rdn : rdns ) |
| { |
| this.rdns.add( rdn ); |
| } |
| |
| apply( schemaManager ); |
| toUpName(); |
| } |
| |
| |
| /** |
| * Get the associated SchemaManager if any. |
| * |
| * @return The SchemaManager |
| */ |
| public SchemaManager getSchemaManager() |
| { |
| return schemaManager; |
| } |
| |
| |
| /** |
| * Return the User Provided Dn as a String, |
| * |
| * @return A String representing the User Provided Dn |
| */ |
| private String toUpName() |
| { |
| if ( 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.getName() ); |
| } |
| |
| upName = sb.toString(); |
| } |
| |
| return upName; |
| } |
| |
| |
| /** |
| * Gets the hash code of this Dn. |
| * |
| * @see java.lang.Object#hashCode() |
| * @return the instance hash code |
| */ |
| @Override |
| public int hashCode() |
| { |
| int result = 37; |
| |
| for ( Rdn rdn : rdns ) |
| { |
| result = result * 17 + rdn.hashCode(); |
| } |
| |
| return result; |
| } |
| |
| |
| /** |
| * Get the user provided Dn |
| * |
| * @return The user provided Dn as a String |
| */ |
| public String getName() |
| { |
| return ( upName == null ? "" : upName ); |
| } |
| |
| |
| /** |
| * Sets the up name. |
| * |
| * Package private because Dn is immutable, only used by the Dn parser. |
| * |
| * @param upName the new up name |
| */ |
| /* No qualifier */void setUpName( String upName ) |
| { |
| this.upName = upName; |
| } |
| |
| |
| /** |
| * Get the normalized Dn. If the Dn is schema aware, the AttributeType |
| * will be represented using its OID :<br/> |
| * <pre> |
| * Dn dn = new Dn( schemaManager, "ou = Example , ou = com" ); |
| * assert( "2.5.4.11=example,2.5.4.11=com".equals( dn.getNormName ) ); |
| * </pre> |
| * Otherwise, it will return a Dn with the AttributeType in lower case |
| * and the value trimmed : <br/> |
| * <pre> |
| * Dn dn = new Dn( " CN = A Test " ); |
| * assertEquals( "cn=A Test", dn.getNormName() ); |
| * </pre> |
| * |
| * @return The normalized Dn as a String |
| */ |
| public String getNormName() |
| { |
| return normName; |
| } |
| |
| |
| /** |
| * Get the number of RDNs present in the DN |
| * @return The umber of RDNs in the DN |
| */ |
| public int size() |
| { |
| return rdns.size(); |
| } |
| |
| |
| /** |
| * Get the number of bytes necessary to store this Dn |
| |
| * @param dn The Dn. |
| * @return A integer, which is the size of the UTF-8 byte array |
| */ |
| public static int getNbBytes( Dn dn ) |
| { |
| return dn.bytes == null ? 0 : dn.bytes.length; |
| } |
| |
| |
| /** |
| * Get an UTF-8 representation of the normalized form of the Dn |
| * |
| * @param dn The Dn. |
| * @return A byte[] representation of the Dn |
| */ |
| public static byte[] getBytes( Dn dn ) |
| { |
| return dn == null ? null : dn.bytes; |
| } |
| |
| |
| /** |
| * Tells if the current Dn is a parent of another Dn.<br> |
| * For instance, <b>dc=com</b> is a ancestor |
| * of <b>dc=example, dc=com</b> |
| * |
| * @param dn The child |
| * @return true if the current Dn is a parent of the given Dn |
| */ |
| public boolean isAncestorOf( String dn ) |
| { |
| try |
| { |
| return isAncestorOf( new Dn( dn ) ); |
| } |
| catch ( LdapInvalidDnException lide ) |
| { |
| return false; |
| } |
| } |
| |
| |
| /** |
| * Tells if the current Dn is a parent of another Dn.<br> |
| * For instance, <b>dc=com</b> is a ancestor |
| * of <b>dc=example, dc=com</b> |
| * |
| * @param dn The child |
| * @return true if the current Dn is a parent of the given Dn |
| */ |
| public boolean isAncestorOf( Dn dn ) |
| { |
| if ( dn == null ) |
| { |
| return false; |
| } |
| |
| return dn.isDescendantOf( this ); |
| } |
| |
| |
| /** |
| * Tells if a Dn is a child of another Dn.<br> |
| * For instance, <b>dc=example, dc=com</b> is a descendant |
| * of <b>dc=com</b> |
| * |
| * @param dn The parent |
| * @return true if the current Dn is a child of the given Dn |
| */ |
| public boolean isDescendantOf( String dn ) |
| { |
| try |
| { |
| return isDescendantOf( new Dn( schemaManager, dn ) ); |
| } |
| catch ( LdapInvalidDnException lide ) |
| { |
| return false; |
| } |
| } |
| |
| |
| /** |
| * Tells if a Dn is a child of another Dn.<br> |
| * For instance, <b>dc=example, dc=apache, dc=com</b> is a descendant |
| * of <b>dc=com</b> |
| * |
| * @param dn The parent |
| * @return true if the current Dn is a child of the given Dn |
| */ |
| public boolean isDescendantOf( Dn dn ) |
| { |
| if ( ( dn == null ) || dn.isRootDse() ) |
| { |
| return true; |
| } |
| |
| if ( dn.size() > size() ) |
| { |
| // The name is longer than the current Dn. |
| return false; |
| } |
| |
| // Ok, iterate through all the Rdn of the name, |
| // starting a the end of the current list. |
| |
| for ( int i = dn.size() - 1; i >= 0; i-- ) |
| { |
| Rdn nameRdn = dn.rdns.get( dn.rdns.size() - i - 1 ); |
| Rdn ldapRdn = rdns.get( rdns.size() - i - 1 ); |
| |
| if ( !nameRdn.equals( ldapRdn ) ) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * Tells if the Dn contains no Rdn |
| * |
| * @return <code>true</code> if the Dn is empty |
| */ |
| public boolean isEmpty() |
| { |
| return ( rdns.size() == 0 ); |
| } |
| |
| |
| /** |
| * Tells if the Dn is the RootDSE Dn (ie, an empty Dn) |
| * |
| * @return <code>true</code> if the Dn is the RootDSE's Dn |
| */ |
| public boolean isRootDse() |
| { |
| 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 Rdn getRdn( int posn ) |
| { |
| if ( rdns.size() == 0 ) |
| { |
| return null; |
| } |
| |
| if ( ( posn < 0 ) || ( posn >= rdns.size() ) ) |
| { |
| throw new IllegalArgumentException( "Invalid position : " + posn ); |
| } |
| |
| Rdn rdn = rdns.get( posn ); |
| |
| return rdn; |
| } |
| |
| |
| /** |
| * Retrieves the last (leaf) component of this name. |
| * |
| * @return the last component of this Dn |
| */ |
| public Rdn getRdn() |
| { |
| if ( isNullOrEmpty( this ) ) |
| { |
| return Rdn.EMPTY_RDN; |
| } |
| |
| return rdns.get( 0 ); |
| } |
| |
| |
| /** |
| * Retrieves all the components of this name. |
| * |
| * @return All the components |
| */ |
| @SuppressWarnings("unchecked") |
| public List<Rdn> getRdns() |
| { |
| return UnmodifiableList.decorate( rdns ); |
| } |
| |
| |
| /** |
| * Get the descendant of a given DN, using the ancestr DN. Assuming that |
| * a DN has two parts :<br/> |
| * DN = [descendant DN][ancestor DN]<br/> |
| * To get back the descendant from the full DN, you just pass the ancestor DN |
| * as a parameter. Here is a working example : |
| * <pre> |
| * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" ); |
| * |
| * Dn descendant = dn.getDescendantOf( "dc=apache, dc=org" ); |
| * |
| * // At this point, the descendant contains cn=test, dc=server, dc=directory" |
| * </pre> |
| */ |
| public Dn getDescendantOf( String ancestor ) throws LdapInvalidDnException |
| { |
| return getDescendantOf( new Dn( schemaManager, ancestor ) ); |
| } |
| |
| |
| /** |
| * Get the descendant of a given DN, using the ancestr DN. Assuming that |
| * a DN has two parts :<br/> |
| * DN = [descendant DN][ancestor DN]<br/> |
| * To get back the descendant from the full DN, you just pass the ancestor DN |
| * as a parameter. Here is a working example : |
| * <pre> |
| * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" ); |
| * |
| * Dn descendant = dn.getDescendantOf( "dc=apache, dc=org" ); |
| * |
| * // At this point, the descendant contains cn=test, dc=server, dc=directory" |
| * </pre> |
| */ |
| public Dn getDescendantOf( Dn ancestor ) throws LdapInvalidDnException |
| { |
| if ( ( ancestor == null ) || ( ancestor.size() == 0 ) ) |
| { |
| return this; |
| } |
| |
| if ( rdns.size() == 0 ) |
| { |
| return EMPTY_DN; |
| } |
| |
| int length = ancestor.size(); |
| |
| if ( length > rdns.size() ) |
| { |
| String message = I18n.err( I18n.ERR_04206, length, rdns.size() ); |
| LOG.error( message ); |
| throw new ArrayIndexOutOfBoundsException( message ); |
| } |
| |
| Dn newDn = new Dn( schemaManager ); |
| List<Rdn> rdnsAncestor = ancestor.getRdns(); |
| |
| for ( int i = 0; i < ancestor.size(); i++ ) |
| { |
| Rdn rdn = rdns.get( size() - 1 - i ); |
| Rdn rdnDescendant = rdnsAncestor.get( ancestor.size() - 1 - i ); |
| |
| if ( !rdn.equals( rdnDescendant ) ) |
| { |
| throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX ); |
| } |
| } |
| |
| for ( int i = 0; i < rdns.size() - length; i++ ) |
| { |
| newDn.rdns.add( rdns.get( i ) ); |
| } |
| |
| newDn.toUpName(); |
| newDn.apply( schemaManager, true ); |
| |
| return newDn; |
| } |
| |
| |
| /** |
| * Get the ancestor of a given DN, using the descendant DN. Assuming that |
| * a DN has two parts :<br/> |
| * DN = [descendant DN][ancestor DN]<br/> |
| * To get back the ancestor from the full DN, you just pass the descendant DN |
| * as a parameter. Here is a working example : |
| * <pre> |
| * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" ); |
| * |
| * Dn ancestor = dn.getAncestorOf( "cn=test, dc=server, dc=directory" ); |
| * |
| * // At this point, the ancestor contains "dc=apache, dc=org" |
| * </pre> |
| */ |
| public Dn getAncestorOf( String descendant ) throws LdapInvalidDnException |
| { |
| return getAncestorOf( new Dn( schemaManager, descendant ) ); |
| } |
| |
| |
| /** |
| * Get the ancestor of a given DN, using the descendant DN. Assuming that |
| * a DN has two parts :<br/> |
| * DN = [descendant DN][ancestor DN]<br/> |
| * To get back the ancestor from the full DN, you just pass the descendant DN |
| * as a parameter. Here is a working example : |
| * <pre> |
| * Dn dn = new Dn( "cn=test, dc=server, dc=directory, dc=apache, dc=org" ); |
| * |
| * Dn ancestor = dn.getAncestorOf( new Dn( "cn=test, dc=server, dc=directory" ) ); |
| * |
| * // At this point, the ancestor contains "dc=apache, dc=org" |
| * </pre> |
| */ |
| public Dn getAncestorOf( Dn descendant ) throws LdapInvalidDnException |
| { |
| if ( ( descendant == null ) || ( descendant.size() == 0 ) ) |
| { |
| return this; |
| } |
| |
| if ( rdns.size() == 0 ) |
| { |
| return EMPTY_DN; |
| } |
| |
| int length = descendant.size(); |
| |
| if ( length > rdns.size() ) |
| { |
| String message = I18n.err( I18n.ERR_04206, length, rdns.size() ); |
| LOG.error( message ); |
| throw new ArrayIndexOutOfBoundsException( message ); |
| } |
| |
| Dn newDn = new Dn( schemaManager ); |
| List<Rdn> rdnsDescendant = descendant.getRdns(); |
| |
| for ( int i = 0; i < descendant.size(); i++ ) |
| { |
| Rdn rdn = rdns.get( i ); |
| Rdn rdnDescendant = rdnsDescendant.get( i ); |
| |
| if ( !rdn.equals( rdnDescendant ) ) |
| { |
| throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX ); |
| } |
| } |
| |
| for ( int i = length; i < rdns.size(); i++ ) |
| { |
| newDn.rdns.add( rdns.get( i ) ); |
| } |
| |
| newDn.toUpName(); |
| newDn.apply( schemaManager, true ); |
| |
| return newDn; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Dn add( Dn suffix ) throws LdapInvalidDnException |
| { |
| if ( ( suffix == null ) || ( suffix.size() == 0 ) ) |
| { |
| return this; |
| } |
| |
| Dn clonedDn = copy(); |
| |
| // Concatenate the rdns |
| clonedDn.rdns.addAll( 0, suffix.rdns ); |
| |
| // Regenerate the normalized name and the original string |
| if ( clonedDn.isSchemaAware() && suffix.isSchemaAware() ) |
| { |
| if ( clonedDn.size() != 0 ) |
| { |
| clonedDn.normName = suffix.getNormName() + "," + normName; |
| clonedDn.bytes = Strings.getBytesUtf8Ascii( normName ); |
| clonedDn.upName = suffix.getName() + "," + upName; |
| } |
| } |
| else |
| { |
| clonedDn.apply( schemaManager, true ); |
| clonedDn.toUpName(); |
| } |
| |
| return clonedDn; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Dn add( String comp ) throws LdapInvalidDnException |
| { |
| if ( comp.length() == 0 ) |
| { |
| return this; |
| } |
| |
| Dn clonedDn = copy(); |
| |
| // We have to parse the nameComponent which is given as an argument |
| Rdn newRdn = new Rdn( schemaManager, comp ); |
| |
| clonedDn.rdns.add( 0, newRdn ); |
| |
| clonedDn.apply( schemaManager, true ); |
| clonedDn.toUpName(); |
| |
| return clonedDn; |
| } |
| |
| |
| /** |
| * Adds a single Rdn to the (leaf) end of this name. |
| * |
| * @param newRdn the Rdn to add |
| * @return the updated cloned Dn |
| */ |
| public Dn add( Rdn newRdn ) throws LdapInvalidDnException |
| { |
| if ( ( newRdn == null ) || ( newRdn.size() == 0 ) ) |
| { |
| return this; |
| } |
| |
| Dn clonedDn = copy(); |
| |
| clonedDn.rdns.add( 0, newRdn ); |
| clonedDn.apply( schemaManager, true ); |
| clonedDn.toUpName(); |
| |
| return clonedDn; |
| } |
| |
| |
| /** |
| * Gets the parent Dn of this Dn. Null if this Dn doesn't have a parent, i.e. because it |
| * is the empty Dn.<br/> |
| * The Parent is the right part of the Dn, when the Rdn has been removed. |
| * |
| * @return the parent Dn of this Dn |
| */ |
| public Dn getParent() |
| { |
| if ( isNullOrEmpty( this ) ) |
| { |
| return this; |
| } |
| |
| int posn = rdns.size() - 1; |
| |
| Dn newDn = new Dn( schemaManager ); |
| |
| for ( int i = rdns.size() - posn; i < rdns.size(); i++ ) |
| { |
| newDn.rdns.add( rdns.get( i ) ); |
| } |
| |
| try |
| { |
| newDn.apply( schemaManager, true ); |
| } |
| catch ( LdapInvalidDnException e ) |
| { |
| LOG.error( e.getMessage(), e ); |
| } |
| |
| newDn.toUpName(); |
| |
| return newDn; |
| } |
| |
| |
| /** |
| * Create a copy of the current Dn |
| */ |
| private Dn copy() |
| { |
| Dn dn = new Dn( schemaManager ); |
| dn.rdns = new ArrayList<Rdn>(); |
| |
| for ( Rdn rdn : rdns ) |
| { |
| dn.rdns.add( rdn ); |
| } |
| |
| return dn; |
| } |
| |
| |
| /** |
| * @see java.lang.Object#equals(java.lang.Object) |
| * @return <code>true</code> if the two instances are equals |
| */ |
| @Override |
| public boolean equals( Object obj ) |
| { |
| if ( obj instanceof String ) |
| { |
| return normName.equals( obj ); |
| } |
| else if ( obj instanceof Dn ) |
| { |
| Dn name = ( Dn ) obj; |
| |
| if ( name.getNormName().equals( normName ) ) |
| { |
| return true; |
| } |
| |
| if ( name.size() != this.size() ) |
| { |
| return false; |
| } |
| |
| for ( int i = 0; i < this.size(); i++ ) |
| { |
| if ( !name.rdns.get( i ).equals( rdns.get( i ) ) ) |
| { |
| return false; |
| } |
| } |
| |
| // All components matched so we return true |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| |
| /** |
| * Normalize the Ava |
| */ |
| private static Ava atavOidToName( Ava atav, SchemaManager schemaManager ) |
| throws LdapInvalidDnException |
| { |
| Map<String, OidNormalizer> oidsMap = schemaManager.getNormalizerMapping(); |
| String type = Strings.trim( atav.getNormType() ); |
| |
| if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) ) |
| { |
| type = type.substring( 4 ); |
| } |
| |
| if ( Strings.isNotEmpty( type ) ) |
| { |
| if ( oidsMap == null ) |
| { |
| return atav; |
| } |
| |
| type = Strings.toLowerCaseAscii( type ); |
| |
| // Check that we have an existing AttributeType for this type |
| if ( !oidsMap.containsKey( type ) ) |
| { |
| // No AttributeType : this is an error |
| String msg = I18n.err( I18n.ERR_04268_OID_NOT_FOUND, atav.getType() ); |
| LOG.error( msg ); |
| throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, msg ); |
| } |
| |
| OidNormalizer oidNormalizer = oidsMap.get( type ); |
| |
| if ( oidNormalizer != null ) |
| { |
| try |
| { |
| AttributeType attributeType = schemaManager.getAttributeType( type ); |
| Value<?> atavValue = null; |
| Value<?> value = atav.getValue(); |
| |
| if ( value instanceof StringValue ) |
| { |
| if ( attributeType.getSyntax().isHumanReadable() ) |
| { |
| atavValue = new StringValue( attributeType, value.getString() ); |
| } |
| else |
| { |
| // This is a binary variable, transaform the StringValue to a BinaryValye |
| atavValue = new BinaryValue( attributeType, value.getBytes() ); |
| } |
| } |
| else |
| { |
| atavValue = new BinaryValue( attributeType, atav.getValue().getBytes() ); |
| } |
| |
| Ava newAva = new Ava( |
| attributeType, |
| atav.getType(), |
| oidNormalizer.getAttributeTypeOid(), |
| atavValue, |
| atav.getName() ); |
| |
| return newAva; |
| } |
| catch ( LdapException le ) |
| { |
| throw new LdapInvalidDnException( le.getMessage(), le ); |
| } |
| } |
| else |
| { |
| // We don't have a normalizer for this OID : just do nothing. |
| return atav; |
| } |
| } |
| else |
| { |
| // The type is empty : this is not possible... |
| String msg = I18n.err( I18n.ERR_04209_EMPTY_TYPE_NOT_ALLOWED ); |
| LOG.error( msg ); |
| throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, msg ); |
| } |
| } |
| |
| |
| /** |
| * Transform a Rdn by changing the value to its OID counterpart and |
| * normalizing the value accordingly to its type. We also sort the AVAs |
| * |
| * @param rdn The Rdn to modify. |
| * @param SchemaManager The schema manager |
| * @throws LdapInvalidDnException If the Rdn is invalid. |
| */ |
| /** No qualifier */ |
| static void rdnOidToName( Rdn rdn, SchemaManager schemaManager ) throws LdapInvalidDnException |
| { |
| // We have more than one ATAV for this Rdn. We will loop on all |
| // ATAVs |
| //Rdn rdnCopy = rdn.clone(); |
| //rdn.clear(); |
| |
| if ( rdn.size() < 2 ) |
| { |
| Ava newAtav = atavOidToName( rdn.getAva(), schemaManager ); |
| rdn.replaceAva( newAtav, 0 ); |
| } |
| else |
| { |
| Set<String> sortedOids = new TreeSet<String>(); |
| Map<String, Ava> avas = new HashMap<String, Ava>(); |
| |
| // Sort the OIDs |
| for ( Ava val : rdn ) |
| { |
| Ava newAtav = atavOidToName( val, schemaManager ); |
| String oid = newAtav.getAttributeType().getOid(); |
| sortedOids.add( oid ); |
| avas.put( oid, newAtav ); |
| } |
| |
| // And create the Rdn |
| int pos = 0; |
| |
| for ( String oid : sortedOids ) |
| { |
| rdn.replaceAva( avas.get( oid ), pos++ ); |
| } |
| } |
| } |
| |
| |
| /** |
| * Normalizes the Dn using the given the schema manager. If the flag is set to true, |
| * we will replace the inner SchemaManager by the provided one. |
| * |
| * @param schemaManager The schemaManagerto use to normalize the Dn |
| * @param force Tells if we should replace an existing SchemaManager by a new one |
| * @return The normalized Dn |
| * @throws LdapInvalidDnException If the Dn is invalid. |
| */ |
| public Dn apply( SchemaManager schemaManager, boolean force ) throws LdapInvalidDnException |
| { |
| if ( ( this.schemaManager == null ) || force ) |
| { |
| this.schemaManager = schemaManager; |
| |
| if ( this.schemaManager != null ) |
| { |
| synchronized ( this ) |
| { |
| if ( size() == 0 ) |
| { |
| bytes = null; |
| normName = ""; |
| |
| return this; |
| } |
| |
| StringBuilder sb = new StringBuilder(); |
| boolean isFirst = true; |
| |
| for ( Rdn rdn : rdns ) |
| { |
| rdn.apply( schemaManager ); |
| |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| sb.append( ',' ); |
| } |
| |
| sb.append( rdn.getNormName() ); |
| } |
| |
| String newNormName = sb.toString(); |
| |
| if ( ( normName == null ) || !normName.equals( newNormName ) ) |
| { |
| bytes = Strings.getBytesUtf8Ascii( newNormName ); |
| normName = newNormName; |
| } |
| } |
| } |
| else |
| { |
| if ( rdns.size() == 0 ) |
| { |
| bytes = null; |
| normName = ""; |
| } |
| else |
| { |
| StringBuffer sb = new StringBuffer(); |
| boolean isFirst = true; |
| |
| for ( Rdn rdn : rdns ) |
| { |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| sb.append( ',' ); |
| } |
| |
| sb.append( rdn.getNormName() ); |
| } |
| |
| String newNormName = sb.toString(); |
| |
| if ( ( normName == null ) || !normName.equals( newNormName ) ) |
| { |
| bytes = Strings.getBytesUtf8Ascii( newNormName ); |
| normName = newNormName; |
| } |
| } |
| } |
| } |
| |
| return this; |
| } |
| |
| |
| /** |
| * Normalizes the Dn using the given the schema manager, unless the Dn is already normalized |
| * |
| * @param schemaManager The schemaManagerto use to normalize the Dn |
| * @return The normalized Dn |
| * @throws LdapInvalidDnException If the Dn is invalid. |
| */ |
| public Dn apply( SchemaManager schemaManager ) throws LdapInvalidDnException |
| { |
| if ( this.schemaManager != null ) |
| { |
| return this; |
| } |
| else |
| { |
| return apply( schemaManager, true ); |
| } |
| } |
| |
| |
| /** |
| * Tells if the Dn is schema aware |
| * |
| * @return <code>true</code> if the Dn is schema aware. |
| */ |
| public boolean isSchemaAware() |
| { |
| return schemaManager != null; |
| } |
| |
| |
| /** |
| * Iterate over the inner Rdn. The Rdn are returned from |
| * the rightmost to the leftmost. For instance, the following code :<br/> |
| * <pre> |
| * Dn dn = new Dn( "sn=test, dc=apache, dc=org ); |
| * |
| * for ( Rdn rdn : dn ) |
| * { |
| * System.out.println( rdn.toString() ); |
| * } |
| * </pre> |
| * will produce this output : <br/> |
| * <pre> |
| * dc=org |
| * dc=apache |
| * sn=test |
| * </pre> |
| * |
| */ |
| public Iterator<Rdn> iterator() |
| { |
| return new RdnIterator(); |
| } |
| |
| |
| /** |
| * Check if a DistinguishedName is null or empty. |
| * |
| * @param dn The Dn to check |
| * @return <code>true></code> if the Dn is null or empty, <code>false</code> |
| * otherwise |
| */ |
| public static boolean isNullOrEmpty( Dn dn ) |
| { |
| return ( dn == null ) || dn.isEmpty(); |
| } |
| |
| |
| /** |
| * Check if a DistinguishedName is syntactically 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 name ) |
| { |
| Dn dn = new Dn(); |
| |
| try |
| { |
| parseInternal( name, dn.rdns ); |
| return true; |
| } |
| catch ( LdapInvalidDnException e ) |
| { |
| return false; |
| } |
| } |
| |
| |
| /** |
| * Parse a Dn. |
| * |
| * @param name The Dn to be parsed |
| * @param rdns The list that will contain the RDNs |
| * @throws LdapInvalidDnException If the Dn is invalid |
| */ |
| private static void parseInternal( String name, List<Rdn> rdns ) throws LdapInvalidDnException |
| { |
| try |
| { |
| FastDnParser.parseDn( name, rdns ); |
| } |
| catch ( TooComplexDnException e ) |
| { |
| rdns.clear(); |
| new ComplexDnParser().parseDn( name, rdns ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException |
| { |
| // Read the UPName |
| upName = in.readUTF(); |
| |
| // Read the NormName |
| normName = in.readUTF(); |
| |
| if ( normName.length() == 0 ) |
| { |
| // As the normName is equal to the upName, |
| // we didn't saved the nbnormName on disk. |
| // restore it by copying the upName. |
| normName = upName; |
| } |
| |
| // Read the RDNs. Is it's null, the number will be -1. |
| int nbRdns = in.readInt(); |
| |
| rdns = new ArrayList<Rdn>( nbRdns ); |
| |
| for ( int i = 0; i < nbRdns; i++ ) |
| { |
| Rdn rdn = new Rdn( schemaManager ); |
| rdn.readExternal( in ); |
| rdns.add( rdn ); |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void writeExternal( ObjectOutput out ) throws IOException |
| { |
| if ( upName == null ) |
| { |
| String message = "Cannot serialize a NULL Dn"; |
| LOG.error( message ); |
| throw new IOException( message ); |
| } |
| |
| // Write the UPName |
| out.writeUTF( upName ); |
| |
| // Write the NormName if different |
| if ( upName.equals( normName ) ) |
| { |
| out.writeUTF( "" ); |
| } |
| else |
| { |
| out.writeUTF( normName ); |
| } |
| |
| // Write the RDNs. |
| // First the number of RDNs |
| out.writeInt( size() ); |
| |
| // Loop on the RDNs |
| for ( Rdn rdn : rdns ) |
| { |
| rdn.writeExternal( out ); |
| } |
| |
| out.flush(); |
| } |
| |
| |
| /** |
| * Return the user provided Dn as a String. It returns the same value as the |
| * getName method |
| * |
| * @return A String representing the user provided Dn |
| */ |
| @Override |
| public String toString() |
| { |
| return getName(); |
| } |
| } |