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 package org.apache.directory.shared.ldap.util;
021
022
023 import java.text.ParseException;
024
025 import java.util.Arrays;
026 import java.util.Iterator;
027
028 import javax.naming.NamingEnumeration;
029 import javax.naming.NamingException;
030 import javax.naming.directory.Attribute;
031 import javax.naming.directory.Attributes;
032 import javax.naming.directory.BasicAttribute;
033 import javax.naming.directory.BasicAttributes;
034 import javax.naming.directory.InvalidAttributeIdentifierException;
035
036 import org.apache.directory.shared.ldap.entry.Entry;
037 import org.apache.directory.shared.ldap.entry.EntryAttribute;
038 import org.apache.directory.shared.ldap.entry.Modification;
039 import org.apache.directory.shared.ldap.entry.Value;
040 import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
041 import org.apache.directory.shared.ldap.entry.client.DefaultClientEntry;
042 import org.apache.directory.shared.ldap.name.LdapDN;
043 import org.apache.directory.shared.ldap.schema.AttributeType;
044 import org.apache.directory.shared.ldap.schema.MatchingRule;
045 import org.apache.directory.shared.ldap.schema.Normalizer;
046 import org.apache.directory.shared.ldap.schema.normalizers.NoOpNormalizer;
047
048
049 /**
050 * A set of utility fuctions for working with Attributes.
051 *
052 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
053 * @version $Rev: 798550 $
054 */
055 public class AttributeUtils
056 {
057 /**
058 * Correctly removes an attribute from an entry using it's attributeType information.
059 *
060 * @param type the attributeType of the attribute to remove
061 * @param entry the entry to remove the attribute from
062 * @return the Attribute that is removed
063 */
064 public static Attribute removeAttribute( AttributeType type, Attributes entry )
065 {
066 Attribute attr = entry.get( type.getOid() );
067
068 if ( attr == null )
069 {
070 String[] aliases = type.getNamesRef();
071
072 for ( String alias : aliases )
073 {
074 attr = entry.get( alias );
075
076 if ( attr != null )
077 {
078 return entry.remove( attr.getID() );
079 }
080 }
081 }
082
083 if ( attr == null )
084 {
085 return null;
086 }
087
088 return entry.remove( attr.getID() );
089 }
090
091
092 /**
093 * Compare two values and return true if they are equal.
094 *
095 * @param value1 The first value
096 * @param value2 The second value
097 * @return true if both value are null or if they are equal.
098 */
099 public static final boolean equals( Object value1, Object value2 )
100 {
101 if ( value1 == value2 )
102 {
103 return true;
104 }
105
106 if ( value1 == null )
107 {
108 return ( value2 == null );
109 }
110
111 if ( value1 instanceof byte[] )
112 {
113 if ( value2 instanceof byte[] )
114 {
115 return Arrays.equals( ( byte[] ) value1, ( byte[] ) value2 );
116 }
117 else
118 {
119 return false;
120 }
121 }
122 else
123 {
124 return value1.equals( value2 );
125 }
126 }
127
128
129 /**
130 * Clone the value. An attribute value is supposed to be either a String
131 * or a byte array. If it's a String, then we just return it ( as String
132 * is immutable, we don't need to copy it). If it's a bu=yte array, we
133 * create a new byte array and copy the bytes into it.
134 *
135 * @param value The value to clone
136 * @return The cloned value
137 */
138 public static Object cloneValue( Object value )
139 {
140 // First copy the value
141 Object newValue = null;
142
143 if ( value instanceof byte[] )
144 {
145 newValue = ( ( byte[] ) value ).clone();
146 }
147 else
148 {
149 newValue = value;
150 }
151
152 return newValue;
153 }
154
155
156 /**
157 * Switch from a BasicAttribute to a AttributeImpl. This is
158 * necessary to allow cloning to be correctly handled.
159 *
160 * @param attribute The attribute to transform
161 * @return A instance of AttributeImpl
162 */
163 public static final Attribute toBasicAttribute( Attribute attribute )
164 {
165 if ( attribute instanceof BasicAttribute )
166 {
167 // Just return the attribute
168 return attribute;
169 }
170 else
171 {
172 // Create a new AttributeImpl from the original attribute
173 Attribute newAttribute = new BasicAttribute( attribute.getID() );
174
175 try
176 {
177 NamingEnumeration<?> values = attribute.getAll();
178
179 while ( values.hasMoreElements() )
180 {
181 newAttribute.add( cloneValue( values.next() ) );
182 }
183
184 return newAttribute;
185 }
186 catch ( NamingException ne )
187 {
188 return newAttribute;
189 }
190 }
191 }
192
193
194 /**
195 * Utility method to extract an attribute from Attributes object using
196 * all combinationos of the name including aliases.
197 *
198 * @param attrs the Attributes to get the Attribute object from
199 * @param type the attribute type specification
200 * @return an Attribute with matching the attributeType spec or null
201 */
202 public static final Attribute getAttribute( Attributes attrs, AttributeType type )
203 {
204 // check if the attribute's OID is used
205 Attribute attr = attrs.get( type.getOid() );
206
207 if ( attr != null )
208 {
209 return attr;
210 }
211
212 // optimization bypass to avoid cost of the loop below
213 if ( type.getNamesRef().length == 1 )
214 {
215 attr = attrs.get( type.getNamesRef()[0] );
216
217 if ( attr != null )
218 {
219 return attr;
220 }
221 }
222
223 // iterate through aliases
224 for ( String alias : type.getNamesRef() )
225 {
226 attr = attrs.get( alias );
227
228 if ( attr != null )
229 {
230 return attr;
231 }
232 }
233
234 return null;
235 }
236
237
238 /**
239 * Check if an attribute contains a specific value, using the associated matchingRule for that
240 *
241 * @param attr The attribute we are searching in
242 * @param compared The object we are looking for
243 * @param type The attribute type
244 * @return <code>true</code> if the value exists in the attribute</code>
245 * @throws NamingException If something went wrong while accessing the data
246 */
247 public static boolean containsValue( Attribute attr, Value<?> compared, AttributeType type ) throws NamingException
248 {
249 // quick bypass test
250 if ( attr.contains( compared ) )
251 {
252 return true;
253 }
254
255 MatchingRule matchingRule = type.getEquality();
256
257 Normalizer normalizer = null;
258
259 if ( matchingRule != null )
260 {
261 normalizer = type.getEquality().getNormalizer();
262 }
263 else
264 {
265 normalizer = new NoOpNormalizer();
266 }
267
268 if ( type.getSyntax().isHumanReadable() )
269 {
270 String comparedStr = normalizer.normalize( compared.getString() );
271
272 for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); /**/)
273 {
274 String value = ( String ) values.nextElement();
275 if ( comparedStr.equals( normalizer.normalize( value ) ) )
276 {
277 return true;
278 }
279 }
280 }
281 else
282 {
283 byte[] comparedBytes = null;
284
285 if ( !compared.isBinary() )
286 {
287 if ( compared.getString().length() < 3 )
288 {
289 return false;
290 }
291
292 // Transform the String to a byte array
293 int state = 1;
294 comparedBytes = new byte[compared.getString().length() / 3];
295 int pos = 0;
296
297 for ( char c : compared.getString().toCharArray() )
298 {
299 switch ( state )
300 {
301 case 1:
302 if ( c != '\\' )
303 {
304 return false;
305 }
306
307 state++;
308 break;
309
310 case 2:
311 int high = StringTools.getHexValue( c );
312
313 if ( high == -1 )
314 {
315 return false;
316 }
317
318 comparedBytes[pos] = ( byte ) ( high << 4 );
319
320 state++;
321 break;
322
323 case 3:
324 int low = StringTools.getHexValue( c );
325
326 if ( low == -1 )
327 {
328 return false;
329 }
330
331 comparedBytes[pos] += ( byte ) low;
332 pos++;
333
334 state = 1;
335 break;
336 }
337 }
338 }
339 else
340 {
341 comparedBytes = compared.getBytes();
342 }
343
344 for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); /**/)
345 {
346 Object value = values.nextElement();
347
348 if ( value instanceof byte[] )
349 {
350 if ( ArrayUtils.isEquals( comparedBytes, value ) )
351 {
352 return true;
353 }
354 }
355 }
356 }
357
358 return false;
359 }
360
361
362 /**
363 * Check if an attribute contains a value. The test is case insensitive,
364 * and the value is supposed to be a String. If the value is a byte[],
365 * then the case sensitivity is useless.
366 *
367 * @param attr The attribute to check
368 * @param value The value to look for
369 * @return true if the value is present in the attribute
370 */
371 public static boolean containsValueCaseIgnore( Attribute attr, Object value )
372 {
373 // quick bypass test
374 if ( attr.contains( value ) )
375 {
376 return true;
377 }
378
379 try
380 {
381 if ( value instanceof String )
382 {
383 String strVal = ( String ) value;
384
385 NamingEnumeration<?> attrVals = attr.getAll();
386
387 while ( attrVals.hasMoreElements() )
388 {
389 Object attrVal = attrVals.nextElement();
390
391 if ( attrVal instanceof String )
392 {
393 if ( strVal.equalsIgnoreCase( ( String ) attrVal ) )
394 {
395 return true;
396 }
397 }
398 }
399 }
400 else
401 {
402 byte[] valueBytes = ( byte[] ) value;
403
404 NamingEnumeration<?> attrVals = attr.getAll();
405
406 while ( attrVals.hasMoreElements() )
407 {
408 Object attrVal = attrVals.nextElement();
409
410 if ( attrVal instanceof byte[] )
411 {
412 if ( Arrays.equals( ( byte[] ) attrVal, valueBytes ) )
413 {
414 return true;
415 }
416
417 }
418 }
419 }
420 }
421 catch ( NamingException ne )
422 {
423 return false;
424 }
425
426 return false;
427 }
428
429
430 /*
431 public static boolean containsAnyValues( Attribute attr, Object[] compared, AttributeType type )
432 throws NamingException
433 {
434 // quick bypass test
435 for ( Object object : compared )
436 {
437 if ( attr.contains( object ) )
438 {
439 return true;
440 }
441 }
442
443 Normalizer normalizer = type.getEquality().getNormalizer();
444
445 if ( type.getSyntax().isHumanReadable() )
446 {
447 for ( Object object : compared )
448 {
449 String comparedStr = ( String ) normalizer.normalize( object );
450
451 for ( int ii = attr.size(); ii >= 0; ii-- )
452 {
453 String value = ( String ) attr.get( ii );
454
455 if ( comparedStr.equals( normalizer.normalize( value ) ) )
456 {
457 return true;
458 }
459 }
460 }
461 }
462 else
463 {
464 for ( Object object : compared )
465 {
466 byte[] comparedBytes = ( byte[] ) object;
467
468 for ( int ii = attr.size(); ii >= 0; ii-- )
469 {
470 if ( ArrayUtils.isEquals( comparedBytes, attr.get( ii ) ) )
471 {
472 return true;
473 }
474 }
475 }
476 }
477
478 return false;
479 }
480 */
481
482
483 /**
484 * Creates a new attribute which contains the values representing the
485 * difference of two attributes. If both attributes are null then we cannot
486 * determine the attribute ID and an {@link IllegalArgumentException} is
487 * raised. Note that the order of arguments makes a difference.
488 *
489 * @param attr0
490 * the first attribute
491 * @param attr1
492 * the second attribute
493 * @return a new attribute with the difference of values from both attribute
494 * arguments
495 * @throws NamingException
496 * if there are problems accessing attribute values
497 */
498 public static Attribute getDifference( Attribute attr0, Attribute attr1 ) throws NamingException
499 {
500 String id;
501
502 if ( ( attr0 == null ) && ( attr1 == null ) )
503 {
504 throw new IllegalArgumentException( "Cannot figure out attribute ID if both args are null" );
505 }
506 else if ( attr0 == null )
507 {
508 return new BasicAttribute( attr1.getID() );
509 }
510 else if ( attr1 == null )
511 {
512 return ( Attribute ) attr0.clone();
513 }
514 else if ( !attr0.getID().equalsIgnoreCase( attr1.getID() ) )
515 {
516 throw new IllegalArgumentException( "Cannot take difference of attributes with different IDs!" );
517 }
518 else
519 {
520 id = attr0.getID();
521 }
522
523 Attribute attr = new BasicAttribute( id );
524
525 for ( int ii = 0; ii < attr0.size(); ii++ )
526 {
527 attr.add( attr0.get( ii ) );
528 }
529
530 for ( int ii = 0; ii < attr1.size(); ii++ )
531 {
532 attr.remove( attr1.get( ii ) );
533 }
534
535 return attr;
536 }
537
538
539 /**
540 * Creates a new attribute which contains the values representing the union
541 * of two attributes. If one attribute is null then the resultant attribute
542 * returned is a copy of the non-null attribute. If both are null then we
543 * cannot determine the attribute ID and an {@link IllegalArgumentException}
544 * is raised.
545 *
546 * @param attr0
547 * the first attribute
548 * @param attr1
549 * the second attribute
550 * @return a new attribute with the union of values from both attribute
551 * arguments
552 * @throws NamingException
553 * if there are problems accessing attribute values
554 */
555 public static Attribute getUnion( Attribute attr0, Attribute attr1 ) throws NamingException
556 {
557 String id;
558
559 if ( attr0 == null && attr1 == null )
560 {
561 throw new IllegalArgumentException( "Cannot figure out attribute ID if both args are null" );
562 }
563 else if ( attr0 == null )
564 {
565 id = attr1.getID();
566 }
567 else if ( attr1 == null )
568 {
569 id = attr0.getID();
570 }
571 else if ( !attr0.getID().equalsIgnoreCase( attr1.getID() ) )
572 {
573 throw new IllegalArgumentException( "Cannot take union of attributes with different IDs!" );
574 }
575 else
576 {
577 id = attr0.getID();
578 }
579
580 Attribute attr = new BasicAttribute( id );
581
582 if ( attr0 != null )
583 {
584 for ( int ii = 0; ii < attr0.size(); ii++ )
585 {
586 attr.add( attr0.get( ii ) );
587 }
588 }
589
590 if ( attr1 != null )
591 {
592 for ( int ii = 0; ii < attr1.size(); ii++ )
593 {
594 attr.add( attr1.get( ii ) );
595 }
596 }
597
598 return attr;
599 }
600
601
602 /**
603 * Check if the attributes is a BasicAttributes, and if so, switch
604 * the case sensitivity to false to avoid tricky problems in the server.
605 * (Ldap attributeTypes are *always* case insensitive)
606 *
607 * @param attributes The Attributes to check
608 */
609 public static Attributes toCaseInsensitive( Attributes attributes )
610 {
611 if ( attributes == null )
612 {
613 return attributes;
614 }
615
616 if ( attributes instanceof BasicAttributes )
617 {
618 if ( attributes.isCaseIgnored() )
619 {
620 // Just do nothing if the Attributes is already case insensitive
621 return attributes;
622 }
623 else
624 {
625 // Ok, bad news : we have to create a new BasicAttributes
626 // which will be case insensitive
627 Attributes newAttrs = new BasicAttributes( true );
628
629 NamingEnumeration<?> attrs = attributes.getAll();
630
631 if ( attrs != null )
632 {
633 // Iterate through the attributes now
634 while ( attrs.hasMoreElements() )
635 {
636 newAttrs.put( ( Attribute ) attrs.nextElement() );
637 }
638 }
639
640 return newAttrs;
641 }
642 }
643 else
644 {
645 // we can safely return the attributes if it's not a BasicAttributes
646 return attributes;
647 }
648 }
649
650
651 /**
652 * Return a string representing the attributes with tabs in front of the
653 * string
654 *
655 * @param tabs
656 * Spaces to be added before the string
657 * @param attribute
658 * The attribute to print
659 * @return A string
660 */
661 public static String toString( String tabs, Attribute attribute )
662 {
663 StringBuffer sb = new StringBuffer();
664
665 sb.append( tabs ).append( "Attribute\n" );
666
667 if ( attribute != null )
668 {
669 sb.append( tabs ).append( " Type : '" ).append( attribute.getID() ).append( "'\n" );
670
671 for ( int j = 0; j < attribute.size(); j++ )
672 {
673
674 try
675 {
676 Object attr = attribute.get( j );
677
678 if ( attr != null )
679 {
680 if ( attr instanceof String )
681 {
682 sb.append( tabs ).append( " Val[" ).append( j ).append( "] : " ).append( attr )
683 .append( " \n" );
684 }
685 else if ( attr instanceof byte[] )
686 {
687 String string = StringTools.utf8ToString( ( byte[] ) attr );
688
689 sb.append( tabs ).append( " Val[" ).append( j ).append( "] : " );
690 sb.append( string ).append( '/' );
691 sb.append( StringTools.dumpBytes( ( byte[] ) attr ) );
692 sb.append( " \n" );
693 }
694 else
695 {
696 sb.append( tabs ).append( " Val[" ).append( j ).append( "] : " ).append( attr )
697 .append( " \n" );
698 }
699 }
700 }
701 catch ( NamingException ne )
702 {
703 sb.append( "Bad attribute : " ).append( ne.getMessage() );
704 }
705 }
706 }
707
708 return sb.toString();
709 }
710
711
712 /**
713 * Return a string representing the attribute
714 *
715 * @param attribute
716 * The attribute to print
717 * @return A string
718 */
719 public static String toString( Attribute attribute )
720 {
721 return toString( "", attribute );
722 }
723
724
725 /**
726 * Return a string representing the attributes with tabs in front of the
727 * string
728 *
729 * @param tabs
730 * Spaces to be added before the string
731 * @param attributes
732 * The attributes to print
733 * @return A string
734 */
735 public static String toString( String tabs, Attributes attributes )
736 {
737 StringBuffer sb = new StringBuffer();
738 sb.append( tabs ).append( "Attributes\n" );
739
740 if ( attributes != null )
741 {
742 NamingEnumeration<?> attributesIterator = attributes.getAll();
743
744 while ( attributesIterator.hasMoreElements() )
745 {
746 Attribute attribute = ( Attribute ) attributesIterator.nextElement();
747 sb.append( tabs ).append( attribute.toString() );
748 }
749 }
750
751 return sb.toString();
752 }
753
754
755 /**
756 * Parse attribute's options :
757 *
758 * options = *( ';' option )
759 * option = 1*keychar
760 * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-'
761 */
762 private static void parseOptions( String str, Position pos ) throws ParseException
763 {
764 while ( StringTools.isCharASCII( str, pos.start, ';' ) )
765 {
766 pos.start++;
767
768 // We have an option
769 if ( !StringTools.isAlphaDigitMinus( str, pos.start ) )
770 {
771 // We must have at least one keychar
772 throw new ParseException( "An empty option is not allowed", pos.start );
773 }
774
775 pos.start++;
776
777 while ( StringTools.isAlphaDigitMinus( str, pos.start ) )
778 {
779 pos.start++;
780 }
781 }
782 }
783
784
785 /**
786 * Parse a number :
787 *
788 * number = '0' | '1'..'9' digits
789 * digits = '0'..'9'*
790 *
791 * @return true if a number has been found
792 */
793 private static boolean parseNumber( String filter, Position pos )
794 {
795 char c = StringTools.charAt( filter, pos.start );
796
797 switch ( c )
798 {
799 case '0':
800 // If we get a starting '0', we should get out
801 pos.start++;
802 return true;
803
804 case '1':
805 case '2':
806 case '3':
807 case '4':
808 case '5':
809 case '6':
810 case '7':
811 case '8':
812 case '9':
813 pos.start++;
814 break;
815
816 default:
817 // Not a number.
818 return false;
819 }
820
821 while ( StringTools.isDigit( filter, pos.start ) )
822 {
823 pos.start++;
824 }
825
826 return true;
827 }
828
829
830 /**
831 *
832 * Parse an OID.
833 *
834 * numericoid = number 1*( '.' number )
835 * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' )
836 *
837 * @param str The OID to parse
838 * @param pos The current position in the string
839 * @return A valid OID
840 * @throws ParseException If we don't have a valid OID
841 */
842 public static void parseOID( String str, Position pos ) throws ParseException
843 {
844 // We have an OID
845 parseNumber( str, pos );
846
847 // We must have at least one '.' number
848 if ( !StringTools.isCharASCII( str, pos.start, '.' ) )
849 {
850 throw new ParseException( "Invalid OID, missing '.'", pos.start );
851 }
852
853 pos.start++;
854
855 if ( !parseNumber( str, pos ) )
856 {
857 throw new ParseException( "Invalid OID, missing a number after a '.'", pos.start );
858 }
859
860 while ( true )
861 {
862 // Break if we get something which is not a '.'
863 if ( !StringTools.isCharASCII( str, pos.start, '.' ) )
864 {
865 break;
866 }
867
868 pos.start++;
869
870 if ( !parseNumber( str, pos ) )
871 {
872 throw new ParseException( "Invalid OID, missing a number after a '.'", pos.start );
873 }
874 }
875 }
876
877
878 /**
879 * Parse an attribute. The grammar is :
880 * attributedescription = attributetype options
881 * attributetype = oid
882 * oid = descr / numericoid
883 * descr = keystring
884 * numericoid = number 1*( '.' number )
885 * options = *( ';' option )
886 * option = 1*keychar
887 * keystring = leadkeychar *keychar
888 * leadkeychar = 'a'-z' | 'A'-'Z'
889 * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-'
890 * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' )
891 *
892 * @param str The parsed attribute,
893 * @param pos The position of the attribute in the current string
894 * @return The parsed attribute if valid
895 */
896 public static String parseAttribute( String str, Position pos, boolean withOption ) throws ParseException
897 {
898 // We must have an OID or an DESCR first
899 char c = StringTools.charAt( str, pos.start );
900
901 if ( c == '\0' )
902 {
903 throw new ParseException( "Empty attributes", pos.start );
904 }
905
906 int start = pos.start;
907
908 if ( StringTools.isAlpha( c ) )
909 {
910 // A DESCR
911 pos.start++;
912
913 while ( StringTools.isAlphaDigitMinus( str, pos.start ) )
914 {
915 pos.start++;
916 }
917
918 // Parse the options if needed
919 if ( withOption )
920 {
921 parseOptions( str, pos );
922 }
923
924 return str.substring( start, pos.start );
925 }
926 else if ( StringTools.isDigit( c ) )
927 {
928 // An OID
929 pos.start++;
930
931 // Parse the OID
932 parseOID( str, pos );
933
934 // Parse the options
935 if ( withOption )
936 {
937 parseOptions( str, pos );
938 }
939
940 return str.substring( start, pos.start );
941 }
942 else
943 {
944 throw new ParseException( "Bad char in attribute", pos.start );
945 }
946 }
947
948
949 /**
950 * Return a string representing the attributes
951 *
952 * @param attributes
953 * The attributes to print
954 * @return A string
955 */
956 public static String toString( Attributes attributes )
957 {
958 return toString( "", attributes );
959 }
960
961
962 /**
963 * A method to apply a modification to an existing entry.
964 *
965 * @param entry The entry on which we want to apply a modification
966 * @param modification the Modification to be applied
967 * @throws NamingException if some operation fails.
968 */
969 public static void applyModification( Entry entry, Modification modification ) throws NamingException
970 {
971 EntryAttribute modAttr = modification.getAttribute();
972 String modificationId = modAttr.getId();
973
974 switch ( modification.getOperation() )
975 {
976 case ADD_ATTRIBUTE:
977 EntryAttribute modifiedAttr = entry.get( modificationId );
978
979 if ( modifiedAttr == null )
980 {
981 // The attribute should be added.
982 entry.put( modAttr );
983 }
984 else
985 {
986 // The attribute exists : the values can be different,
987 // so we will just add the new values to the existing ones.
988 for ( Value<?> value : modAttr )
989 {
990 // If the value already exist, nothing is done.
991 // Note that the attribute *must* have been
992 // normalized before.
993 modifiedAttr.add( value );
994 }
995 }
996
997 break;
998
999 case REMOVE_ATTRIBUTE:
1000 if ( modAttr.get() == null )
1001 {
1002 // We have no value in the ModificationItem attribute :
1003 // we have to remove the whole attribute from the initial
1004 // entry
1005 entry.removeAttributes( modificationId );
1006 }
1007 else
1008 {
1009 // We just have to remove the values from the original
1010 // entry, if they exist.
1011 modifiedAttr = entry.get( modificationId );
1012
1013 if ( modifiedAttr == null )
1014 {
1015 break;
1016 }
1017
1018 for ( Value<?> value : modAttr )
1019 {
1020 // If the value does not exist, nothing is done.
1021 // Note that the attribute *must* have been
1022 // normalized before.
1023 modifiedAttr.remove( value );
1024 }
1025
1026 if ( modifiedAttr.size() == 0 )
1027 {
1028 // If this was the last value, remove the attribute
1029 entry.removeAttributes( modifiedAttr.getId() );
1030 }
1031 }
1032
1033 break;
1034
1035 case REPLACE_ATTRIBUTE:
1036 if ( modAttr.get() == null )
1037 {
1038 // If the modification does not have any value, we have
1039 // to delete the attribute from the entry.
1040 entry.removeAttributes( modificationId );
1041 }
1042 else
1043 {
1044 // otherwise, just substitute the existing attribute.
1045 entry.put( modAttr );
1046 }
1047
1048 break;
1049 }
1050 }
1051
1052
1053 /**
1054 * Check if an attribute contains a specific value and remove it using the associated
1055 * matchingRule for the attribute type supplied.
1056 *
1057 * @param attr the attribute we are searching in
1058 * @param compared the object we are looking for
1059 * @param type the attribute type
1060 * @return the value removed from the attribute, otherwise null
1061 * @throws NamingException if something went wrong while removing the value
1062 *
1063 public static Object removeValue( Attribute attr, Object compared, AttributeType type ) throws NamingException
1064 {
1065 // quick bypass test
1066 if ( attr.contains( compared ) )
1067 {
1068 return attr.remove( compared );
1069 }
1070
1071 MatchingRule matchingRule = type.getEquality();
1072 Normalizer normalizer;
1073
1074 if ( matchingRule != null )
1075 {
1076 normalizer = type.getEquality().getNormalizer();
1077 }
1078 else
1079 {
1080 normalizer = new NoOpNormalizer();
1081 }
1082
1083 if ( type.getSyntax().isHumanReadable() )
1084 {
1085 String comparedStr = ( String ) normalizer.normalize( compared );
1086
1087 for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); )
1088 {
1089 String value = ( String ) values.nextElement();
1090 if ( comparedStr.equals( normalizer.normalize( value ) ) )
1091 {
1092 return attr.remove( value );
1093 }
1094 }
1095 }
1096 else
1097 {
1098 byte[] comparedBytes = null;
1099
1100 if ( compared instanceof String )
1101 {
1102 if ( ( ( String ) compared ).length() < 3 )
1103 {
1104 return null;
1105 }
1106
1107 // Tansform the String to a byte array
1108 int state = 1;
1109 comparedBytes = new byte[( ( String ) compared ).length() / 3];
1110 int pos = 0;
1111
1112 for ( char c : ( ( String ) compared ).toCharArray() )
1113 {
1114 switch ( state )
1115 {
1116 case 1:
1117 if ( c != '\\' )
1118 {
1119 return null;
1120 }
1121
1122 state++;
1123 break;
1124
1125 case 2:
1126 int high = StringTools.getHexValue( c );
1127
1128 if ( high == -1 )
1129 {
1130 return null;
1131 }
1132
1133 comparedBytes[pos] = ( byte ) ( high << 4 );
1134
1135 state++;
1136 break;
1137
1138 case 3:
1139 int low = StringTools.getHexValue( c );
1140
1141 if ( low == -1 )
1142 {
1143 return null;
1144 }
1145
1146 comparedBytes[pos] += ( byte ) low;
1147 pos++;
1148
1149 state = 1;
1150 break;
1151 }
1152 }
1153 }
1154 else
1155 {
1156 comparedBytes = ( byte[] ) compared;
1157 }
1158
1159 for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); )
1160 {
1161 Object value = values.nextElement();
1162
1163 if ( value instanceof byte[] )
1164 {
1165 if ( ArrayUtils.isEquals( comparedBytes, value ) )
1166 {
1167 return attr.remove( value );
1168 }
1169 }
1170 }
1171 }
1172
1173 return null;
1174 }
1175
1176
1177 /**
1178 * Convert a BasicAttributes or a AttributesImpl to a ServerEntry
1179 *
1180 * @param attributes the BasicAttributes or AttributesImpl instance to convert
1181 * @param registries The registries, needed ro build a ServerEntry
1182 * @param dn The DN which is needed by the ServerEntry
1183 * @return An instance of a ServerEntry object
1184 *
1185 * @throws InvalidAttributeIdentifierException If we get an invalid attribute
1186 */
1187 public static Entry toClientEntry( Attributes attributes, LdapDN dn ) throws InvalidAttributeIdentifierException
1188 {
1189 if ( attributes instanceof BasicAttributes )
1190 {
1191 try
1192 {
1193 Entry entry = new DefaultClientEntry( dn );
1194
1195 for ( NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMoreElements(); )
1196 {
1197 Attribute attr = attrs.nextElement();
1198
1199 EntryAttribute entryAttribute = toClientAttribute( attr );
1200
1201 if ( entryAttribute != null )
1202 {
1203 entry.put( entryAttribute );
1204 }
1205 }
1206
1207 return entry;
1208 }
1209 catch ( NamingException ne )
1210 {
1211 throw new InvalidAttributeIdentifierException( ne.getMessage() );
1212 }
1213 }
1214 else
1215 {
1216 return null;
1217 }
1218 }
1219
1220
1221 /**
1222 * Converts an {@link Entry} to an {@link Attributes}.
1223 *
1224 * @param entry
1225 * the {@link Entry} to convert
1226 * @return
1227 * the equivalent {@link Attributes}
1228 */
1229 public static Attributes toAttributes( Entry entry )
1230 {
1231 if ( entry != null )
1232 {
1233 Attributes attributes = new BasicAttributes();
1234
1235 // Looping on attributes
1236 for ( Iterator<EntryAttribute> attributeIterator = entry.iterator(); attributeIterator.hasNext(); )
1237 {
1238 EntryAttribute entryAttribute = ( EntryAttribute ) attributeIterator.next();
1239
1240 attributes.put( toAttribute( entryAttribute ) );
1241 }
1242
1243 return attributes;
1244 }
1245
1246 return null;
1247 }
1248
1249
1250 /**
1251 * Converts an {@link EntryAttribute} to an {@link Attribute}.
1252 *
1253 * @param entryAttribute
1254 * the {@link EntryAttribute} to convert
1255 * @return
1256 * the equivalent {@link Attribute}
1257 */
1258 public static Attribute toAttribute( EntryAttribute entryAttribute )
1259 {
1260 if ( entryAttribute != null )
1261 {
1262 Attribute attribute = new BasicAttribute( entryAttribute.getId() );
1263
1264 // Looping on values
1265 for ( Iterator<Value<?>> valueIterator = entryAttribute.iterator(); valueIterator.hasNext(); )
1266 {
1267 Value<?> value = valueIterator.next();
1268 attribute.add( value.get() );
1269 }
1270
1271 return attribute;
1272 }
1273
1274 return null;
1275 }
1276
1277
1278 /**
1279 * Convert a BasicAttribute or a AttributeImpl to a EntryAttribute
1280 *
1281 * @param attribute the BasicAttributes or AttributesImpl instance to convert
1282 * @param attributeType
1283 * @return An instance of a ClientEntry object
1284 *
1285 * @throws InvalidAttributeIdentifierException If we had an incorrect attribute
1286 */
1287 public static EntryAttribute toClientAttribute( Attribute attribute )
1288 {
1289 if ( attribute == null )
1290 {
1291 return null;
1292 }
1293
1294 try
1295 {
1296 EntryAttribute clientAttribute = new DefaultClientAttribute( attribute.getID() );
1297
1298 for ( NamingEnumeration<?> values = attribute.getAll(); values.hasMoreElements(); )
1299 {
1300 Object value = values.nextElement();
1301
1302 if ( value instanceof String )
1303 {
1304 clientAttribute.add( ( String ) value );
1305 }
1306 else if ( value instanceof byte[] )
1307 {
1308 clientAttribute.add( ( byte[] ) value );
1309 }
1310 else
1311 {
1312 clientAttribute.add( ( String ) null );
1313 }
1314 }
1315
1316 return clientAttribute;
1317 }
1318 catch ( NamingException ne )
1319 {
1320 return null;
1321 }
1322 }
1323 }