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