001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020
021 package org.apache.directory.shared.ldap.name;
022
023
024 import java.io.Externalizable;
025 import java.io.IOException;
026 import java.io.ObjectInput;
027 import java.io.ObjectOutput;
028 import java.io.Serializable;
029 import java.util.ArrayList;
030 import java.util.Enumeration;
031 import java.util.Iterator;
032 import java.util.List;
033 import java.util.Map;
034 import java.util.NoSuchElementException;
035
036 import javax.naming.InvalidNameException;
037 import javax.naming.Name;
038 import javax.naming.ldap.LdapName;
039
040 import org.apache.directory.shared.i18n.I18n;
041 import org.apache.directory.shared.ldap.exception.LdapException;
042 import org.apache.directory.shared.ldap.exception.LdapInvalidDnException;
043 import org.apache.directory.shared.ldap.message.ResultCodeEnum;
044 import org.apache.directory.shared.ldap.schema.normalizers.OidNormalizer;
045 import org.apache.directory.shared.ldap.util.StringTools;
046 import org.slf4j.Logger;
047 import org.slf4j.LoggerFactory;
048
049
050 /**
051 * The DN class contains a DN (Distinguished Name).
052 *
053 * Its specification can be found in RFC 2253,
054 * "UTF-8 String Representation of Distinguished Names".
055 *
056 * We will store two representation of a DN :
057 * - a user Provider representation, which is the parsed String given by a user
058 * - an internal representation.
059 *
060 * A DN is formed of RDNs, in a specific order :
061 * RDN[n], RDN[n-1], ... RDN[1], RDN[0]
062 *
063 * It represents a tree, in which the root is the last RDN (RDN[0]) and the leaf
064 * is the first RDN (RDN[n]).
065 *
066 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
067 * @version $Rev: 930282 $, $Date: 2010-04-02 17:29:58 +0300 (Fri, 02 Apr 2010) $
068 */
069 public class DN implements Cloneable, Serializable, Comparable<DN>, Iterable<RDN>
070 {
071 /** The LoggerFactory used by this class */
072 protected static final Logger LOG = LoggerFactory.getLogger( DN.class );
073
074 /**
075 * Declares the Serial Version Uid.
076 *
077 * @see <a
078 * href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
079 * Declare Serial Version Uid</a>
080 */
081 private static final long serialVersionUID = 1L;
082
083 /** Value returned by the compareTo method if values are not equals */
084 public static final int NOT_EQUAL = -1;
085
086 /** Value returned by the compareTo method if values are equals */
087 public static final int EQUAL = 0;
088
089 /** A flag used to tell if the DN has been normalized */
090 private boolean normalized;
091
092 // ~ Static fields/initializers
093 // -----------------------------------------------------------------
094 /**
095 * The RDNs that are elements of the DN
096 * NOTE THAT THESE ARE IN THE OPPOSITE ORDER FROM THAT IMPLIED BY THE JAVADOC!
097 * Rdn[0] is rdns.get(n) and Rdn[n] is rdns.get(0)
098 */
099 protected List<RDN> rdns = new ArrayList<RDN>( 5 );
100
101 /** The user provided name */
102 private String upName;
103
104 /** The normalized name */
105 private String normName;
106
107 /** The bytes representation of the normName */
108 private byte[] bytes;
109
110 /** A null DN */
111 public static final DN EMPTY_DN = new DN();
112
113
114 // ~ Methods
115 // ------------------------------------------------------------------------------------
116
117 /**
118 * Construct an empty DN object
119 */
120 public DN()
121 {
122 upName = "";
123 normName = null;
124 normalized = true;
125 }
126
127
128 /**
129 * Copies a DN to an DN.
130 *
131 * @param name composed of String name components.
132 * @throws LdapInvalidDnException If the Name is invalid.
133 */
134 public DN( DN dn ) throws LdapInvalidDnException
135 {
136 if ( ( dn != null ) && ( dn.size() != 0 ) )
137 {
138 for ( int ii = 0; ii < dn.size(); ii++ )
139 {
140 String nameComponent = dn.get( ii );
141 add( nameComponent );
142 }
143 }
144
145 normalized = false;
146 }
147
148
149 /**
150 * Parse a String and checks that it is a valid DN <br>
151 * <p>
152 * <distinguishedName> ::= <name> | e <br>
153 * <name> ::= <name-component> <name-components> <br>
154 * <name-components> ::= <spaces> <separator>
155 * <spaces> <name-component> <name-components> | e <br>
156 * </p>
157 *
158 * @param upName The String that contains the DN.
159 * @throws LdapInvalidNameException if the String does not contain a valid DN.
160 */
161 public DN( String upName ) throws LdapInvalidDnException
162 {
163 if ( upName != null )
164 {
165 DnParser.parseInternal( upName, rdns );
166 }
167
168 // Stores the representations of a DN : internal (as a string and as a
169 // byte[]) and external.
170 normalizeInternal();
171 normalized = false;
172
173 this.upName = upName;
174 }
175
176
177 /**
178 * Creates a new instance of DN, using varargs to declare the RDNs. Each
179 * String is either a full RDN, or a couple of AttributeType DI and a value.
180 * If the String contains a '=' symbol, the the constructor will assume that
181 * the String arg contains afull RDN, otherwise, it will consider that the
182 * following arg is the value.
183 * An example of usage would be :
184 * <pre>
185 * String exampleName = "example";
186 * String baseDn = "dc=apache,dc=org";
187 *
188 * DN dn = new DN(
189 * "cn=Test",
190 * "ou", exampleName,
191 * baseDn);
192 * </pre>
193 *
194 * @param upNames
195 * @throws LdapInvalidDnException
196 */
197 public DN( String... upRdns ) throws LdapInvalidDnException
198 {
199 StringBuilder sb = new StringBuilder();
200 boolean valueExpected = false;
201 boolean isFirst = true;
202
203 for ( String upRdn : upRdns )
204 {
205 if ( isFirst )
206 {
207 isFirst = false;
208 }
209 else if ( !valueExpected )
210 {
211 sb.append( ',' );
212 }
213
214 if ( !valueExpected )
215 {
216 sb.append( upRdn );
217
218 if ( upRdn.indexOf( '=' ) == -1 )
219 {
220 valueExpected = true;
221 }
222 }
223 else
224 {
225 sb.append( "=" ).append( upRdn );
226
227 valueExpected = false;
228 }
229 }
230
231 if ( valueExpected )
232 {
233 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04202 ) );
234 }
235
236 // Stores the representations of a DN : internal (as a string and as a
237 // byte[]) and external.
238 upName = sb.toString();
239 DnParser.parseInternal( upName, rdns );
240 normalizeInternal();
241 normalized = false;
242 }
243
244 /**
245 * Create a DN when deserializing it.
246 *
247 * Note : this constructor is used only by the deserialization method.
248 * @param upName The user provided name
249 * @param normName the normalized name
250 * @param bytes the name as a byte[]
251 */
252 DN( String upName, String normName, byte[] bytes )
253 {
254 normalized = true;
255 this.upName = upName;
256 this.normName = normName;
257 this.bytes = bytes;
258 }
259
260
261 /**
262 * Static factory which creates a normalized DN from a String and a Map of OIDs.
263 *
264 * @param name The DN as a String
265 * @param oidsMap The OID mapping
266 * @return A valid DN
267 * @throws LdapInvalidNameException If the DN is invalid.
268 * @throws LdapInvalidDnException If something went wrong.
269 */
270 public static DN normalize( String name, Map<String, OidNormalizer> oidsMap ) throws LdapInvalidDnException
271 {
272 if ( ( name == null ) || ( name.length() == 0 ) || ( oidsMap == null ) || ( oidsMap.size() == 0 ) )
273 {
274 return DN.EMPTY_DN;
275 }
276
277 DN newDn = new DN( name );
278
279 Enumeration<RDN> rdns = newDn.getAllRdn();
280
281 // Loop on all RDNs
282 while ( rdns.hasMoreElements() )
283 {
284 RDN rdn = rdns.nextElement();
285 String upName = rdn.getName();
286 rdnOidToName( rdn, oidsMap );
287 rdn.normalize();
288 rdn.setUpName( upName );
289 }
290
291 newDn.normalizeInternal();
292 newDn.normalized = true;
293
294 return newDn;
295 }
296
297
298 /**
299 * Normalize the DN by triming useless spaces and lowercasing names.
300 */
301 void normalizeInternal()
302 {
303 normName = toNormName();
304 }
305
306
307 /**
308 * Build the normalized DN as a String,
309 *
310 * @return A String representing the normalized DN
311 */
312 private String toNormName()
313 {
314 if ( rdns.size() == 0 )
315 {
316 bytes = null;
317 return "";
318 }
319 else
320 {
321 StringBuffer sb = new StringBuffer();
322 boolean isFirst = true;
323
324 for ( RDN rdn : rdns )
325 {
326 if ( isFirst )
327 {
328 isFirst = false;
329 }
330 else
331 {
332 sb.append( ',' );
333 }
334
335 sb.append( rdn.getNormName() );
336 }
337
338 String newNormName = sb.toString();
339
340 if ( ( normName == null ) || !normName.equals( newNormName ) )
341 {
342 bytes = StringTools.getBytesUtf8( newNormName );
343 normName = newNormName;
344 }
345
346 return normName;
347 }
348 }
349
350
351 /**
352 * Return the normalized DN as a String. It returns the same value as the
353 * getNormName method
354 *
355 * @return A String representing the normalized DN
356 */
357 public String toString()
358 {
359 return getName();
360 }
361
362
363 /**
364 * Return the User Provided DN as a String,
365 *
366 * @return A String representing the User Provided DN
367 */
368 private String toUpName()
369 {
370 if ( rdns.size() == 0 )
371 {
372 upName = "";
373 }
374 else
375 {
376 StringBuffer sb = new StringBuffer();
377 boolean isFirst = true;
378
379 for ( RDN rdn : rdns )
380 {
381 if ( isFirst )
382 {
383 isFirst = false;
384 }
385 else
386 {
387 sb.append( ',' );
388 }
389
390 sb.append( rdn.getName() );
391 }
392
393 upName = sb.toString();
394 }
395
396 return upName;
397 }
398
399
400 /**
401 * Return the User Provided prefix representation of the DN starting at the
402 * posn position.
403 *
404 * If posn = 0, return an empty string.
405 *
406 * for DN : sn=smith, dc=apache, dc=org
407 * getUpname(0) -> ""
408 * getUpName(1) -> "dc=org"
409 * getUpname(3) -> "sn=smith, dc=apache, dc=org"
410 * getUpName(4) -> ArrayOutOfBoundException
411 *
412 * Warning ! The returned String is not exactly the
413 * user provided DN, as spaces before and after each RDNs have been trimmed.
414 *
415 * @param posn
416 * The starting position
417 * @return The truncated DN
418 */
419 private String getUpNamePrefix( int posn )
420 {
421 if ( posn == 0 )
422 {
423 return "";
424 }
425
426 if ( posn > rdns.size() )
427 {
428 String message = I18n.err( I18n.ERR_04203, posn, rdns.size() );
429 LOG.error( message );
430 throw new ArrayIndexOutOfBoundsException( message );
431 }
432
433 int start = rdns.size() - posn;
434 StringBuffer sb = new StringBuffer();
435 boolean isFirst = true;
436
437 for ( int i = start; i < rdns.size(); i++ )
438 {
439 if ( isFirst )
440 {
441 isFirst = false;
442 }
443 else
444 {
445 sb.append( ',' );
446 }
447
448 sb.append( rdns.get( i ).getName() );
449 }
450
451 return sb.toString();
452 }
453
454
455 /**
456 * Return the User Provided suffix representation of the DN starting at the
457 * posn position.
458 * If posn = 0, return an empty string.
459 *
460 * for DN : sn=smith, dc=apache, dc=org
461 * getUpname(0) -> "sn=smith, dc=apache, dc=org"
462 * getUpName(1) -> "sn=smith, dc=apache"
463 * getUpname(3) -> "sn=smith"
464 * getUpName(4) -> ""
465 *
466 * Warning ! The returned String is not exactly the user
467 * provided DN, as spaces before and after each RDNs have been trimmed.
468 *
469 * @param posn The starting position
470 * @return The truncated DN
471 */
472 private String getUpNameSuffix( int posn )
473 {
474 if ( posn > rdns.size() )
475 {
476 return "";
477 }
478
479 int end = rdns.size() - posn;
480 StringBuffer sb = new StringBuffer();
481 boolean isFirst = true;
482
483 for ( int i = 0; i < end; i++ )
484 {
485 if ( isFirst )
486 {
487 isFirst = false;
488 }
489 else
490 {
491 sb.append( ',' );
492 }
493
494 sb.append( rdns.get( i ).getName() );
495 }
496
497 return sb.toString();
498 }
499
500
501 /**
502 * Gets the hash code of this name.
503 *
504 * @see java.lang.Object#hashCode()
505 * @return the instance hash code
506 */
507 public int hashCode()
508 {
509 int result = 37;
510
511 for ( RDN rdn : rdns )
512 {
513 result = result * 17 + rdn.hashCode();
514 }
515
516 return result;
517 }
518
519
520 /**
521 * Get the initial DN
522 *
523 * @return The DN as a String
524 */
525 public String getName()
526 {
527 return ( upName == null ? "" : upName );
528 }
529
530
531 /**
532 * Sets the up name.
533 *
534 * @param upName the new up name
535 */
536 void setUpName( String upName )
537 {
538 this.upName = upName;
539 }
540
541
542 /**
543 * Get the normalized DN
544 *
545 * @return The DN as a String
546 */
547 public String getNormName()
548 {
549 if ( normName == null )
550 {
551 normName = toNormName();
552 }
553
554 return normName;
555 }
556
557
558 /**
559 * {@inheritDoc}
560 */
561 public int size()
562 {
563 return rdns.size();
564 }
565
566
567 /**
568 * Get the number of bytes necessary to store this DN
569
570 * @param dn The DN.
571 * @return A integer, which is the size of the UTF-8 byte array
572 */
573 public static int getNbBytes( DN dn )
574 {
575 return dn.bytes == null ? 0 : dn.bytes.length;
576 }
577
578
579 /**
580 * Get an UTF-8 representation of the normalized form of the DN
581 *
582 * @param dn The DN.
583 * @return A byte[] representation of the DN
584 */
585 public static byte[] getBytes( DN dn )
586 {
587 return dn == null ? null : dn.bytes;
588 }
589
590
591 /**
592 * Tells if the current DN is a parent of another DN.<br>
593 * For instance, <b>dc=com</b> is a parent
594 * of <b>dc=example, dc=com</b>
595 *
596 * @param dn The child
597 * @return true if the current DN is a parent of the given DN
598 */
599 public boolean isParentOf( String dn )
600 {
601 try
602 {
603 return isParentOf( new DN( dn ) );
604 }
605 catch( LdapInvalidDnException lide )
606 {
607 return false;
608 }
609 }
610
611
612 /**
613 * Tells if the current DN is a parent of another DN.<br>
614 * For instance, <b>dc=com</b> is a parent
615 * of <b>dc=example, dc=com</b>
616 *
617 * @param dn The child
618 * @return true if the current DN is a parent of the given DN
619 */
620 public boolean isParentOf( DN dn )
621 {
622 if ( dn == null )
623 {
624 return false;
625 }
626
627 return dn.isChildOf( this );
628 }
629
630
631 /**
632 * Tells if a DN is a child of another DN.<br>
633 * For instance, <b>dc=example, dc=com</b> is a child
634 * of <b>dc=com</b>
635 *
636 * @param dn The parent
637 * @return true if the current DN is a child of the given DN
638 */
639 public boolean isChildOf( String dn )
640 {
641 try
642 {
643 return isChildOf( new DN( dn ) );
644 }
645 catch( LdapInvalidDnException lide )
646 {
647 return false;
648 }
649 }
650
651
652 /**
653 * Tells if a DN is a child of another DN.<br>
654 * For instance, <b>dc=example, dc=com</b> is a child
655 * of <b>dc=com</b>
656 *
657 * @param dn The parent
658 * @return true if the current DN is a child of the given DN
659 */
660 public boolean isChildOf( DN dn )
661 {
662 if ( dn == null )
663 {
664 return true;
665 }
666
667 if ( dn.size() == 0 )
668 {
669 return true;
670 }
671
672 if ( dn.size() > size() )
673 {
674 // The name is longer than the current DN.
675 return false;
676 }
677
678 // Ok, iterate through all the RDN of the name,
679 // starting a the end of the current list.
680
681 for ( int i = dn.size() - 1; i >= 0; i-- )
682 {
683 RDN nameRdn = dn.rdns.get( dn.rdns.size() - i - 1 );
684 RDN ldapRdn = rdns.get( rdns.size() - i - 1 );
685
686 if ( nameRdn.compareTo( ldapRdn ) != 0 )
687 {
688 return false;
689 }
690 }
691
692 return true;
693 }
694
695
696 /**
697 * Determines whether this name has a specific suffix. A name
698 * <tt>name</tt> has a DN as a suffix if its right part contains the given DN
699 *
700 * Be aware that for a specific
701 * DN like : <b>cn=xxx, ou=yyy</b> the hasSuffix method will return false with
702 * <b>ou=yyy</b>, and true with <b>cn=xxx</b>
703 *
704 * @param dn the name to check
705 * @return true if <tt>dn</tt> is a suffix of this name, false otherwise
706 */
707 public boolean hasSuffix( DN dn )
708 {
709
710 if ( dn == null )
711 {
712 return true;
713 }
714
715 if ( dn.size() == 0 )
716 {
717 return true;
718 }
719
720 if ( dn.size() > size() )
721 {
722 // The name is longer than the current DN.
723 return false;
724 }
725
726 // Ok, iterate through all the RDN of the name,
727 // starting a the end of the current list.
728
729 for ( int i = 0; i < dn.size(); i++ )
730 {
731 RDN nameRdn = dn.rdns.get( i );
732 RDN ldapRdn = rdns.get( i );
733
734 if ( nameRdn.compareTo( ldapRdn ) != 0 )
735 {
736 return false;
737 }
738 }
739
740 return true;
741 }
742
743
744 /**
745 * return true if this DN contains no RDNs
746 */
747 public boolean isEmpty()
748 {
749 return ( rdns.size() == 0 );
750 }
751
752
753 /**
754 * Get the given RDN as a String. The position is used in the
755 * reverse order. Assuming that we have a DN like
756 * <pre>dc=example,dc=apache,dc=org</pre>
757 * then :
758 * <li><code>get(0)</code> will return dc=org</li>
759 * <li><code>get(1)</code> will return dc=apache</li>
760 * <li><code>get(2)</code> will return dc=example</li>
761 *
762 * @param posn The position of the wanted RDN in the DN.
763 */
764 public String get( int posn )
765 {
766 if ( rdns.size() == 0 )
767 {
768 return "";
769 }
770 else
771 {
772 RDN rdn = rdns.get( rdns.size() - posn - 1 );
773
774 return rdn.getNormName();
775 }
776 }
777
778
779 /**
780 * Retrieves a component of this name.
781 *
782 * @param posn
783 * the 0-based index of the component to retrieve. Must be in the
784 * range [0,size()).
785 * @return the component at index posn
786 * @throws ArrayIndexOutOfBoundsException
787 * if posn is outside the specified range
788 */
789 public RDN getRdn( int posn )
790 {
791 if ( rdns.size() == 0 )
792 {
793 return null;
794 }
795 else
796 {
797 RDN rdn = rdns.get( rdns.size() - posn - 1 );
798
799 return rdn;
800 }
801 }
802
803
804 /**
805 * Retrieves the last (leaf) component of this name.
806 *
807 * @return the last component of this DN
808 */
809 public RDN getRdn()
810 {
811 if ( rdns.size() == 0 )
812 {
813 return null;
814 }
815 else
816 {
817 return rdns.get( 0 );
818 }
819 }
820
821
822 /**
823 * Retrieves all the components of this name.
824 *
825 * @return All the components
826 */
827 public List<RDN> getRdns()
828 {
829 List<RDN> newRdns = new ArrayList<RDN>();
830
831 // We will clone the list, to avoid user modifications
832 for ( RDN rdn : rdns )
833 {
834 newRdns.add( ( RDN ) rdn.clone() );
835 }
836
837 return newRdns;
838 }
839
840
841 /**
842 * Retrieves the components of this name as an enumeration of strings. The
843 * effect on the enumeration of updates to this name is undefined. If the
844 * name has zero components, an empty (non-null) enumeration is returned.
845 * This starts at the root (rightmost) rdn.
846 *
847 * @return an enumeration of the components of this name, as Rdn
848 */
849 public Enumeration<RDN> getAllRdn()
850 {
851 /*
852 * Note that by accessing the name component using the get() method on
853 * the name rather than get() on the list we are reading components from
854 * right to left with increasing index values. LdapName.get() does the
855 * index translation on m_list for us.
856 */
857 return new Enumeration<RDN>()
858 {
859 private int pos;
860
861
862 public boolean hasMoreElements()
863 {
864 return pos < rdns.size();
865 }
866
867
868 public RDN nextElement()
869 {
870 if ( pos >= rdns.size() )
871 {
872 LOG.error( I18n.err( I18n.ERR_04205 ) );
873 throw new NoSuchElementException();
874 }
875
876 RDN rdn = rdns.get( rdns.size() - pos - 1 );
877 pos++;
878 return rdn;
879 }
880 };
881 }
882
883
884 /**
885 * {@inheritDoc}
886 */
887 public DN getPrefix( int posn )
888 {
889 if ( rdns.size() == 0 )
890 {
891 return EMPTY_DN;
892 }
893
894 if ( ( posn < 0 ) || ( posn > rdns.size() ) )
895 {
896 String message = I18n.err( I18n.ERR_04206, posn, rdns.size() );
897 LOG.error( message );
898 throw new ArrayIndexOutOfBoundsException( message );
899 }
900
901 DN newDN = new DN();
902
903 for ( int i = rdns.size() - posn; i < rdns.size(); i++ )
904 {
905 // Don't forget to clone the rdns !
906 newDN.rdns.add( ( RDN ) rdns.get( i ).clone() );
907 }
908
909 newDN.normName = newDN.toNormName();
910 newDN.upName = getUpNamePrefix( posn );
911
912 return newDN;
913 }
914
915
916 /**
917 * {@inheritDoc}
918 */
919 public DN getSuffix( int posn )
920 {
921 if ( rdns.size() == 0 )
922 {
923 return EMPTY_DN;
924 }
925
926 if ( ( posn < 0 ) || ( posn > rdns.size() ) )
927 {
928 String message = I18n.err( I18n.ERR_04206, posn, rdns.size() );
929 LOG.error( message );
930 throw new ArrayIndexOutOfBoundsException( message );
931 }
932
933 DN newDN = new DN();
934
935 for ( int i = 0; i < size() - posn; i++ )
936 {
937 // Don't forget to clone the rdns !
938 newDN.rdns.add( ( RDN ) rdns.get( i ).clone() );
939 }
940
941 newDN.normName = newDN.toNormName();
942 newDN.upName = getUpNameSuffix( posn );
943
944 return newDN;
945 }
946
947
948 /**
949 * Adds the components of a name -- in order -- at a specified position
950 * within this name. Components of this name at or after the index of the
951 * first new component are shifted up (away from 0) to accommodate the new
952 * components. Compoenents are supposed to be normalized.
953 *
954 * @param posn the index in this name at which to add the new components.
955 * Must be in the range [0,size()]. Note this is from the opposite end as rnds.get(posn)
956 * @param name the components to add
957 * @return the updated name (not a new one)
958 * @throws ArrayIndexOutOfBoundsException
959 * if posn is outside the specified range
960 * @throws LdapInvalidDnException
961 * if <tt>n</tt> is not a valid name, or if the addition of
962 * the components would violate the syntax rules of this name
963 */
964 public DN addAllNormalized( int posn, DN name ) throws LdapInvalidDnException
965 {
966 if ( name instanceof DN )
967 {
968 DN dn = (DN)name;
969
970 if ( ( dn == null ) || ( dn.size() == 0 ) )
971 {
972 return this;
973 }
974
975 // Concatenate the rdns
976 rdns.addAll( size() - posn, dn.rdns );
977
978 if ( StringTools.isEmpty( normName ) )
979 {
980 normName = dn.normName;
981 bytes = dn.bytes;
982 upName = dn.upName;
983 }
984 else
985 {
986 normName = dn.normName + "," + normName;
987 bytes = StringTools.getBytesUtf8( normName );
988 upName = dn.upName + "," + upName;
989 }
990 }
991 else
992 {
993 if ( ( name == null ) || ( name.size() == 0 ) )
994 {
995 return this;
996 }
997
998 for ( int i = name.size() - 1; i >= 0; i-- )
999 {
1000 RDN rdn = new RDN( name.get( i ) );
1001 rdns.add( size() - posn, rdn );
1002 }
1003
1004 normalizeInternal();
1005 toUpName();
1006 }
1007
1008 return this;
1009 }
1010
1011 /**
1012 * {@inheritDoc}
1013 */
1014 public DN addAll( DN suffix ) throws LdapInvalidDnException
1015 {
1016 addAll( rdns.size(), suffix );
1017 normalizeInternal();
1018 toUpName();
1019
1020 return this;
1021 }
1022
1023
1024 /**
1025 * {@inheritDoc}
1026 */
1027 public DN addAll( int posn, Name name ) throws InvalidNameException, LdapInvalidDnException
1028 {
1029 if ( ( name == null ) || ( name.size() == 0 ) )
1030 {
1031 return this;
1032 }
1033
1034 for ( int i = name.size() - 1; i >= 0; i-- )
1035 {
1036 RDN rdn = new RDN( name.get( i ) );
1037 rdns.add( size() - posn, rdn );
1038 }
1039
1040 normalizeInternal();
1041 toUpName();
1042
1043 return this;
1044 }
1045
1046
1047 /**
1048 * {@inheritDoc}
1049 */
1050 public DN addAll( int posn, DN dn ) throws LdapInvalidDnException
1051 {
1052 if ( ( dn == null ) || ( dn.size() == 0 ) )
1053 {
1054 return this;
1055 }
1056
1057 // Concatenate the rdns
1058 rdns.addAll( size() - posn, dn.rdns );
1059
1060 // Regenerate the normalized name and the original string
1061 if ( this.isNormalized() && dn.isNormalized() )
1062 {
1063 if ( this.size() != 0 )
1064 {
1065 normName = dn.getNormName() + "," + normName;
1066 bytes = StringTools.getBytesUtf8( normName );
1067 upName = dn.getName() + "," + upName;
1068 }
1069 }
1070 else
1071 {
1072 normalizeInternal();
1073 toUpName();
1074 }
1075
1076 return this;
1077 }
1078
1079
1080 /**
1081 * {@inheritDoc}
1082 */
1083 public DN add( String comp ) throws LdapInvalidDnException
1084 {
1085 if ( comp.length() == 0 )
1086 {
1087 return this;
1088 }
1089
1090 //FIXME this try-catch block is for the time being, during removal of
1091 // java.naming.Name we have to remove this
1092 try
1093 {
1094 // We have to parse the nameComponent which is given as an argument
1095 RDN newRdn = new RDN( comp );
1096
1097 rdns.add( 0, newRdn );
1098 }
1099 catch( LdapInvalidDnException le )
1100 {
1101 throw new LdapInvalidDnException( le.getMessage() );
1102 }
1103
1104 normalizeInternal();
1105 toUpName();
1106
1107 return this;
1108 }
1109
1110
1111 /**
1112 * Adds a single RDN to the (leaf) end of this name.
1113 *
1114 * @param newRdn the RDN to add
1115 * @return the updated name (not a new one)
1116 */
1117 public DN add( RDN newRdn )
1118 {
1119 rdns.add( 0, newRdn );
1120
1121 normalizeInternal();
1122 toUpName();
1123
1124 return this;
1125 }
1126
1127
1128 /**
1129 * Adds a single RDN to a specific position.
1130 *
1131 * @param newRdn the RDN to add
1132 * @param pos The position where we want to add the Rdn
1133 * @return the updated name (not a new one)
1134 */
1135 public DN add( int pos, RDN newRdn )
1136 {
1137 rdns.add( newRdn );
1138
1139 normalizeInternal();
1140 toUpName();
1141
1142 return this;
1143 }
1144
1145
1146 /**
1147 * Adds a single normalized RDN to the (leaf) end of this name.
1148 *
1149 * @param newRdn the RDN to add
1150 * @return the updated name (not a new one)
1151 */
1152 public DN addNormalized( RDN newRdn )
1153 {
1154 rdns.add( 0, newRdn );
1155
1156 // Avoid a call to the toNormName() method which
1157 // will iterate through all the rdns, when we only
1158 // have to build a new normName by using the current
1159 // RDN normalized name. The very same for upName.
1160 if (rdns.size() == 1 )
1161 {
1162 normName = newRdn.getNormName();
1163 upName = newRdn.getName();
1164 }
1165 else
1166 {
1167 normName = newRdn + "," + normName;
1168 upName = newRdn.getName() + "," + upName;
1169 }
1170
1171 bytes = StringTools.getBytesUtf8( normName );
1172
1173 return this;
1174 }
1175
1176
1177 /**
1178 * {@inheritDoc}
1179 */
1180 public DN add( int posn, String comp ) throws LdapInvalidDnException
1181 {
1182 if ( ( posn < 0 ) || ( posn > size() ) )
1183 {
1184 String message = I18n.err( I18n.ERR_04206, posn, rdns.size() );
1185 LOG.error( message );
1186 throw new ArrayIndexOutOfBoundsException( message );
1187 }
1188
1189 //FIXME this try-catch block is for the time being, during removal of
1190 // java.naming.Name we have to remove this
1191 try
1192 {
1193 // We have to parse the nameComponent which is given as an argument
1194 RDN newRdn = new RDN( comp );
1195
1196 int realPos = size() - posn;
1197 rdns.add( realPos, newRdn );
1198 }
1199 catch( LdapInvalidDnException le )
1200 {
1201 throw new LdapInvalidDnException( le.getMessage() );
1202 }
1203
1204 normalizeInternal();
1205 toUpName();
1206
1207 return this;
1208 }
1209
1210
1211 /**
1212 * {@inheritDoc}
1213 */
1214 public RDN remove( int posn ) throws LdapInvalidDnException
1215 {
1216 if ( rdns.size() == 0 )
1217 {
1218 return RDN.EMPTY_RDN;
1219 }
1220
1221 if ( ( posn < 0 ) || ( posn >= rdns.size() ) )
1222 {
1223 String message = I18n.err( I18n.ERR_04206, posn, rdns.size() );
1224 LOG.error( message );
1225 throw new ArrayIndexOutOfBoundsException( message );
1226 }
1227
1228 int realPos = size() - posn - 1;
1229 RDN rdn = rdns.remove( realPos );
1230
1231 normalizeInternal();
1232 toUpName();
1233
1234 return rdn;
1235 }
1236
1237
1238 /**
1239 * {@inheritDoc}
1240 */
1241 public Object clone()
1242 {
1243 try
1244 {
1245 DN dn = ( DN ) super.clone();
1246 dn.rdns = new ArrayList<RDN>();
1247
1248 for ( RDN rdn : rdns )
1249 {
1250 dn.rdns.add( ( RDN ) rdn.clone() );
1251 }
1252
1253 return dn;
1254 }
1255 catch ( CloneNotSupportedException cnse )
1256 {
1257 LOG.error( I18n.err( I18n.ERR_04207 ) );
1258 throw new Error( I18n.err( I18n.ERR_04208 ) );
1259 }
1260 }
1261
1262
1263 /**
1264 * @see java.lang.Object#equals(java.lang.Object)
1265 * @return <code>true</code> if the two instances are equals
1266 */
1267 public boolean equals( Object obj )
1268 {
1269 if ( obj instanceof String )
1270 {
1271 return normName.equals( obj );
1272 }
1273 else if ( obj instanceof DN )
1274 {
1275 DN name = ( DN ) obj;
1276
1277 if ( name.size() != this.size() )
1278 {
1279 return false;
1280 }
1281
1282 for ( int i = 0; i < this.size(); i++ )
1283 {
1284 if ( name.rdns.get( i ).compareTo( rdns.get( i ) ) != 0 )
1285 {
1286 return false;
1287 }
1288 }
1289
1290 // All components matched so we return true
1291 return true;
1292 }
1293 else
1294 {
1295 return false;
1296 }
1297 }
1298
1299
1300 /**
1301 * {@inheritDoc}
1302 */
1303 public int compareTo( DN dn )
1304 {
1305 if ( dn.size() != size() )
1306 {
1307 return size() - dn.size();
1308 }
1309
1310 for ( int i = rdns.size(); i > 0; i-- )
1311 {
1312 RDN rdn1 = rdns.get( i - 1 );
1313 RDN rdn2 = dn.rdns.get( i - 1 );
1314 int res = rdn1.compareTo( rdn2 );
1315
1316 if ( res != 0 )
1317 {
1318 return res;
1319 }
1320 }
1321
1322 return EQUAL;
1323 }
1324
1325
1326 private static AVA atavOidToName( AVA atav, Map<String, OidNormalizer> oidsMap )
1327 throws LdapInvalidDnException
1328 {
1329 String type = StringTools.trim( atav.getNormType() );
1330
1331 if ( ( type.startsWith( "oid." ) ) || ( type.startsWith( "OID." ) ) )
1332 {
1333 type = type.substring( 4 );
1334 }
1335
1336 if ( StringTools.isNotEmpty( type ) )
1337 {
1338 if ( oidsMap == null )
1339 {
1340 return atav;
1341 }
1342 else
1343 {
1344 OidNormalizer oidNormalizer = oidsMap.get( type.toLowerCase() );
1345
1346 if ( oidNormalizer != null )
1347 {
1348 try
1349 {
1350 return new AVA(
1351 atav.getUpType(),
1352 oidNormalizer.getAttributeTypeOid(),
1353 atav.getUpValue(),
1354 oidNormalizer.getNormalizer().normalize( atav.getNormValue() ),
1355 atav.getUpName() );
1356 }
1357 catch ( LdapException le )
1358 {
1359 throw new LdapInvalidDnException( le.getMessage() );
1360 }
1361 }
1362 else
1363 {
1364 // We don't have a normalizer for this OID : just do nothing.
1365 return atav;
1366 }
1367 }
1368 }
1369 else
1370 {
1371 // The type is empty : this is not possible...
1372 LOG.error( I18n.err( I18n.ERR_04209 ) );
1373 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04209 ) );
1374 }
1375 }
1376
1377
1378 /**
1379 * Transform a RDN by changing the value to its OID counterpart and
1380 * normalizing the value accordingly to its type.
1381 *
1382 * @param rdn The RDN to modify.
1383 * @param oidsMap The map of all existing oids and normalizer.
1384 * @throws LdapInvalidDnException If the RDN is invalid.
1385 */
1386 /** No qualifier */ static void rdnOidToName( RDN rdn, Map<String, OidNormalizer> oidsMap ) throws LdapInvalidDnException
1387 {
1388 if ( rdn.getNbAtavs() > 1 )
1389 {
1390 // We have more than one ATAV for this RDN. We will loop on all
1391 // ATAVs
1392 RDN rdnCopy = ( RDN ) rdn.clone();
1393 rdn.clear();
1394
1395 for ( AVA val:rdnCopy )
1396 {
1397 AVA newAtav = atavOidToName( val, oidsMap );
1398 rdn.addAttributeTypeAndValue( newAtav );
1399 }
1400 }
1401 else
1402 {
1403 AVA val = rdn.getAtav();
1404 rdn.clear();
1405 AVA newAtav = atavOidToName( val, oidsMap );
1406 rdn.addAttributeTypeAndValue( newAtav );
1407 }
1408 }
1409
1410
1411 /**
1412 * Change the internal DN, using the OID instead of the first name or other
1413 * aliases. As we still have the UP name of each RDN, we will be able to
1414 * provide both representation of the DN. example : dn: 2.5.4.3=People,
1415 * dc=example, domainComponent=com will be transformed to : 2.5.4.3=People,
1416 * 0.9.2342.19200300.100.1.25=example, 0.9.2342.19200300.100.1.25=com
1417 * because 2.5.4.3 is the OID for cn and dc is the first
1418 * alias of the couple of aliases (dc, domaincomponent), which OID is
1419 * 0.9.2342.19200300.100.1.25.
1420 * This is really important do have such a representation, as 'cn' and
1421 * 'commonname' share the same OID.
1422 *
1423 * @param dn The DN to transform.
1424 * @param oidsMap The mapping between names and oids.
1425 * @return A normalized form of the DN.
1426 * @throws LdapInvalidDnException If something went wrong.
1427 */
1428 public static DN normalize( DN dn, Map<String, OidNormalizer> oidsMap ) throws LdapInvalidDnException
1429 {
1430 if ( ( dn == null ) || ( dn.size() == 0 ) || ( oidsMap == null ) || ( oidsMap.size() == 0 ) )
1431 {
1432 return dn;
1433 }
1434
1435 Enumeration<RDN> rdns = dn.getAllRdn();
1436
1437 // Loop on all RDNs
1438 while ( rdns.hasMoreElements() )
1439 {
1440 RDN rdn = rdns.nextElement();
1441 String upName = rdn.getName();
1442 rdnOidToName( rdn, oidsMap );
1443 rdn.normalize();
1444 rdn.setUpName( upName );
1445 }
1446
1447 dn.normalizeInternal();
1448
1449 dn.normalized = true;
1450 return dn;
1451 }
1452
1453
1454 /**
1455 * Change the internal DN, using the OID instead of the first name or other
1456 * aliases. As we still have the UP name of each RDN, we will be able to
1457 * provide both representation of the DN. example : dn: 2.5.4.3=People,
1458 * dc=example, domainComponent=com will be transformed to : 2.5.4.3=People,
1459 * 0.9.2342.19200300.100.1.25=example, 0.9.2342.19200300.100.1.25=com
1460 * because 2.5.4.3 is the OID for cn and dc is the first
1461 * alias of the couple of aliases (dc, domaincomponent), which OID is
1462 * 0.9.2342.19200300.100.1.25.
1463 * This is really important do have such a representation, as 'cn' and
1464 * 'commonname' share the same OID.
1465 *
1466 * @param oidsMap The mapping between names and oids.
1467 * @throws LdapInvalidDnException If something went wrong.
1468 * @return The normalized DN
1469 */
1470 public DN normalize( Map<String, OidNormalizer> oidsMap ) throws LdapInvalidDnException
1471 {
1472 if ( ( oidsMap == null ) || ( oidsMap.size() == 0 ) )
1473 {
1474 return this;
1475 }
1476
1477 if ( size() == 0 )
1478 {
1479 normalized = true;
1480 return this;
1481 }
1482
1483 Enumeration<RDN> localRdns = getAllRdn();
1484
1485 // Loop on all RDNs
1486 while ( localRdns.hasMoreElements() )
1487 {
1488 RDN rdn = localRdns.nextElement();
1489 String localUpName = rdn.getName();
1490 rdnOidToName( rdn, oidsMap );
1491 rdn.normalize();
1492 rdn.setUpName( localUpName );
1493 }
1494
1495 normalizeInternal();
1496 normalized = true;
1497 return this;
1498 }
1499
1500
1501 /**
1502 * Check if a DistinguishedName is syntactically valid.
1503 *
1504 * @param dn The DN to validate
1505 * @return <code>true></code> if the DN is valid, <code>false</code>
1506 * otherwise
1507 */
1508 public static boolean isValid( String dn )
1509 {
1510 return DnParser.validateInternal( dn );
1511 }
1512
1513
1514 /**
1515 * Tells if the DN has already been normalized or not
1516 *
1517 * @return <code>true</code> if the DN is already normalized.
1518 */
1519 public boolean isNormalized()
1520 {
1521 return normalized;
1522 }
1523
1524
1525 /**
1526 * @see Externalizable#readExternal(ObjectInput)<p>
1527 *
1528 * We have to store a DN data efficiently. Here is the structure :
1529 *
1530 * <li>upName</li> The User provided DN<p>
1531 * <li>normName</li> May be null if the normName is equaivalent to
1532 * the upName<p>
1533 * <li>rdns</li> The rdn's List.<p>
1534 *
1535 * for each rdn :
1536 * <li>call the RDN write method</li>
1537 *
1538 *@param out The stream in which the DN will be serialized
1539 *@throws IOException If the serialization fail
1540 */
1541 public void writeExternal( ObjectOutput out ) throws IOException
1542 {
1543 if ( upName == null )
1544 {
1545 String message = I18n.err( I18n.ERR_04210 );
1546 LOG.error( message );
1547 throw new IOException( message );
1548 }
1549
1550 // Write the UPName
1551 out.writeUTF( upName );
1552
1553 // Write the NormName if different
1554 if ( isNormalized() )
1555 {
1556 if ( upName.equals( normName ) )
1557 {
1558 out.writeUTF( "" );
1559 }
1560 else
1561 {
1562 out.writeUTF( normName );
1563 }
1564 }
1565 else
1566 {
1567 String message = I18n.err( I18n.ERR_04211 );
1568 LOG.error( message );
1569 throw new IOException( message );
1570 }
1571
1572 // Should we store the byte[] ???
1573
1574 // Write the RDNs. Is it's null, the number will be -1.
1575 out.writeInt( rdns.size() );
1576
1577 // Loop on the RDNs
1578 for ( RDN rdn:rdns )
1579 {
1580 out.writeObject( rdn );
1581 }
1582 }
1583
1584
1585 /**
1586 * @see Externalizable#readExternal(ObjectInput)
1587 *
1588 * We read back the data to create a new DN. The structure
1589 * read is exposed in the {@link DN#writeExternal(ObjectOutput)}
1590 * method<p>
1591 *
1592 * @param in The stream from which the DN is read
1593 * @throws IOException If the stream can't be read
1594 * @throws ClassNotFoundException If the RDN can't be created
1595 */
1596 public void readExternal( ObjectInput in ) throws IOException , ClassNotFoundException
1597 {
1598 // Read the UPName
1599 upName = in.readUTF();
1600
1601 // Read the NormName
1602 normName = in.readUTF();
1603
1604 if ( normName.length() == 0 )
1605 {
1606 // As the normName is equal to the upName,
1607 // we didn't saved the nbnormName on disk.
1608 // restore it by copying the upName.
1609 normName = upName;
1610 }
1611
1612 // A serialized DN is always normalized.
1613 normalized = true;
1614
1615 // Should we read the byte[] ???
1616 bytes = StringTools.getBytesUtf8( upName );
1617
1618 // Read the RDNs. Is it's null, the number will be -1.
1619 int nbRdns = in.readInt();
1620 rdns = new ArrayList<RDN>( nbRdns );
1621
1622 for ( int i = 0; i < nbRdns; i++ )
1623 {
1624 RDN rdn = (RDN)in.readObject();
1625 rdns.add( rdn );
1626 }
1627 }
1628
1629
1630 /**
1631 * Convert a {@link javax.naming.Name} to a DN
1632 *
1633 * @param name The Name to convert
1634 * @return A DN
1635 */
1636 public static DN fromName( Name name )
1637 {
1638 try
1639 {
1640 DN dn = new DN( name.toString() );
1641
1642 return dn;
1643 }
1644 catch ( LdapInvalidDnException lide )
1645 {
1646 // TODO : check if we must throw an exception.
1647 // Logically, the Name must be valid.
1648 return null;
1649 }
1650 }
1651
1652
1653 /**
1654 * Convert a DN to a {@link javax.naming.Name}
1655 *
1656 * @param name The DN to convert
1657 * @return A Name
1658 */
1659 public static Name toName( DN dn )
1660 {
1661 try
1662 {
1663 Name name = new LdapName( dn.toString() );
1664
1665 return name;
1666 }
1667 catch ( InvalidNameException ine )
1668 {
1669 // TODO : check if we must throw an exception.
1670 // Logically, the DN must be valid.
1671 return null;
1672 }
1673 }
1674
1675
1676 /**
1677 * {@inheritDoc}
1678 */
1679 public Iterator<RDN> iterator()
1680 {
1681 return rdns.iterator();
1682 }
1683 }