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