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