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