View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    * 
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   * 
19   */
20  package org.apache.directory.api.ldap.model.entry;
21  
22  
23  import java.text.ParseException;
24  import java.util.Arrays;
25  import java.util.Iterator;
26  
27  import javax.naming.NamingEnumeration;
28  import javax.naming.NamingException;
29  import javax.naming.directory.Attributes;
30  import javax.naming.directory.BasicAttribute;
31  import javax.naming.directory.BasicAttributes;
32  
33  import org.apache.directory.api.i18n.I18n;
34  import org.apache.directory.api.ldap.model.exception.LdapException;
35  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeTypeException;
36  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
37  import org.apache.directory.api.ldap.model.name.Dn;
38  import org.apache.directory.api.util.Chars;
39  import org.apache.directory.api.util.Position;
40  import org.apache.directory.api.util.Strings;
41  
42  
43  /**
44   * A set of utility fuctions for working with Attributes.
45   * 
46   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
47   */
48  public final class AttributeUtils
49  {
50  
51      /**
52       * Check if an attribute contains a value. The test is case insensitive,
53       * and the value is supposed to be a String. If the value is a byte[],
54       * then the case sensitivity is useless.
55       *
56       * @param attr The attribute to check
57       * @param value The value to look for
58       * @return true if the value is present in the attribute
59       */
60      public static boolean containsValueCaseIgnore( javax.naming.directory.Attribute attr, Object value )
61      {
62          // quick bypass test
63          if ( attr.contains( value ) )
64          {
65              return true;
66          }
67  
68          try
69          {
70              if ( value instanceof String )
71              {
72                  String strVal = ( String ) value;
73  
74                  NamingEnumeration<?> attrVals = attr.getAll();
75  
76                  while ( attrVals.hasMoreElements() )
77                  {
78                      Object attrVal = attrVals.nextElement();
79  
80                      if ( attrVal instanceof String && strVal.equalsIgnoreCase( ( String ) attrVal ) )
81                      {
82                          return true;
83                      }
84                  }
85              }
86              else
87              {
88                  byte[] valueBytes = ( byte[] ) value;
89  
90                  NamingEnumeration<?> attrVals = attr.getAll();
91  
92                  while ( attrVals.hasMoreElements() )
93                  {
94                      Object attrVal = attrVals.nextElement();
95  
96                      if ( attrVal instanceof byte[] && Arrays.equals( ( byte[] ) attrVal, valueBytes ) )
97                      {
98                          return true;
99                      }
100                 }
101             }
102         }
103         catch ( NamingException ne )
104         {
105             return false;
106         }
107 
108         return false;
109     }
110 
111 
112     /**
113      * Check if the attributes is a BasicAttributes, and if so, switch
114      * the case sensitivity to false to avoid tricky problems in the server.
115      * (Ldap attributeTypes are *always* case insensitive)
116      * 
117      * @param attributes The Attributes to check
118      */
119     public static Attributes toCaseInsensitive( Attributes attributes )
120     {
121         if ( attributes == null )
122         {
123             return attributes;
124         }
125 
126         if ( attributes instanceof BasicAttributes )
127         {
128             if ( attributes.isCaseIgnored() )
129             {
130                 // Just do nothing if the Attributes is already case insensitive
131                 return attributes;
132             }
133             else
134             {
135                 // Ok, bad news : we have to create a new BasicAttributes
136                 // which will be case insensitive
137                 Attributes newAttrs = new BasicAttributes( true );
138 
139                 NamingEnumeration<?> attrs = attributes.getAll();
140 
141                 if ( attrs != null )
142                 {
143                     // Iterate through the attributes now
144                     while ( attrs.hasMoreElements() )
145                     {
146                         newAttrs.put( ( javax.naming.directory.Attribute ) attrs.nextElement() );
147                     }
148                 }
149 
150                 return newAttrs;
151             }
152         }
153         else
154         {
155             // we can safely return the attributes if it's not a BasicAttributes
156             return attributes;
157         }
158     }
159 
160 
161     /**
162      * Parse attribute's options :
163      * 
164      * options = *( ';' option )
165      * option = 1*keychar
166      * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-'
167      */
168     private static void parseOptions( byte[] str, Position pos ) throws ParseException
169     {
170         while ( Strings.isCharASCII( str, pos.start, ';' ) )
171         {
172             pos.start++;
173 
174             // We have an option
175             if ( !Chars.isAlphaDigitMinus( str, pos.start ) )
176             {
177                 // We must have at least one keychar
178                 throw new ParseException( I18n.err( I18n.ERR_04343 ), pos.start );
179             }
180 
181             pos.start++;
182 
183             while ( Chars.isAlphaDigitMinus( str, pos.start ) )
184             {
185                 pos.start++;
186             }
187         }
188     }
189 
190 
191     /**
192      * Parse a number :
193      * 
194      * number = '0' | '1'..'9' digits
195      * digits = '0'..'9'*
196      * 
197      * @return true if a number has been found
198      */
199     private static boolean parseNumber( byte[] filter, Position pos )
200     {
201         byte b = Strings.byteAt( filter, pos.start );
202 
203         switch ( b )
204         {
205             case '0':
206                 // If we get a starting '0', we should get out
207                 pos.start++;
208                 return true;
209 
210             case '1':
211             case '2':
212             case '3':
213             case '4':
214             case '5':
215             case '6':
216             case '7':
217             case '8':
218             case '9':
219                 pos.start++;
220                 break;
221 
222             default:
223                 // Not a number.
224                 return false;
225         }
226 
227         while ( Chars.isDigit( filter, pos.start ) )
228         {
229             pos.start++;
230         }
231 
232         return true;
233     }
234 
235 
236     /**
237      * 
238      * Parse an OID.
239      *
240      * numericoid = number 1*( '.' number )
241      * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' )
242      *
243      * @param str The OID to parse
244      * @param pos The current position in the string
245      * @throws ParseException If we don't have a valid OID
246      */
247     private static void parseOID( byte[] str, Position pos ) throws ParseException
248     {
249         // We have an OID
250         parseNumber( str, pos );
251 
252         // We must have at least one '.' number
253         if ( !Strings.isCharASCII( str, pos.start, '.' ) )
254         {
255             throw new ParseException( I18n.err( I18n.ERR_04344 ), pos.start );
256         }
257 
258         pos.start++;
259 
260         if ( !parseNumber( str, pos ) )
261         {
262             throw new ParseException( I18n.err( I18n.ERR_04345 ), pos.start );
263         }
264 
265         while ( true )
266         {
267             // Break if we get something which is not a '.'
268             if ( !Strings.isCharASCII( str, pos.start, '.' ) )
269             {
270                 break;
271             }
272 
273             pos.start++;
274 
275             if ( !parseNumber( str, pos ) )
276             {
277                 throw new ParseException( I18n.err( I18n.ERR_04345 ), pos.start );
278             }
279         }
280     }
281 
282 
283     /**
284      * Parse an attribute. The grammar is :
285      * attributedescription = attributetype options
286      * attributetype = oid
287      * oid = descr / numericoid
288      * descr = keystring
289      * numericoid = number 1*( '.' number )
290      * options = *( ';' option )
291      * option = 1*keychar
292      * keystring = leadkeychar *keychar
293      * leadkeychar = 'a'-z' | 'A'-'Z'
294      * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-'
295      * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' )
296      *
297      * @param str The parsed attribute,
298      * @param pos The position of the attribute in the current string
299      * @return The parsed attribute if valid
300      */
301     public static String parseAttribute( byte[] str, Position pos, boolean withOption, boolean relaxed )
302         throws ParseException
303     {
304         // We must have an OID or an DESCR first
305         byte b = Strings.byteAt( str, pos.start );
306 
307         if ( b == '\0' )
308         {
309             throw new ParseException( I18n.err( I18n.ERR_04346 ), pos.start );
310         }
311 
312         int start = pos.start;
313 
314         if ( Chars.isAlpha( b ) )
315         {
316             // A DESCR
317             pos.start++;
318 
319             while ( Chars.isAlphaDigitMinus( str, pos.start ) || ( relaxed && Chars.isUnderscore( str, pos.start ) ) )
320             {
321                 pos.start++;
322             }
323 
324             // Parse the options if needed
325             if ( withOption )
326             {
327                 parseOptions( str, pos );
328             }
329 
330             return Strings.getString( str, start, pos.start - start, "UTF-8" );
331         }
332         else if ( Chars.isDigit( b ) )
333         {
334             // An OID
335             pos.start++;
336 
337             // Parse the OID
338             parseOID( str, pos );
339 
340             // Parse the options
341             if ( withOption )
342             {
343                 parseOptions( str, pos );
344             }
345 
346             return Strings.getString( str,  start, pos.start - start, "UTF-8" );
347         }
348         else
349         {
350             throw new ParseException( I18n.err( I18n.ERR_04347 ), pos.start );
351         }
352     }
353 
354 
355     /**
356      * A method to apply a modification to an existing entry.
357      * 
358      * @param entry The entry on which we want to apply a modification
359      * @param modification the Modification to be applied
360      * @throws org.apache.directory.api.ldap.model.exception.LdapException if some operation fails.
361      */
362     public static void applyModification( Entry entry, Modification modification ) throws LdapException
363     {
364         Attribute modAttr = modification.getAttribute();
365         String modificationId = modAttr.getUpId();
366 
367         switch ( modification.getOperation() )
368         {
369             case ADD_ATTRIBUTE:
370                 Attribute modifiedAttr = entry.get( modificationId );
371 
372                 if ( modifiedAttr == null )
373                 {
374                     // The attribute should be added.
375                     entry.put( modAttr );
376                 }
377                 else
378                 {
379                     // The attribute exists : the values can be different,
380                     // so we will just add the new values to the existing ones.
381                     for ( Value<?> value : modAttr )
382                     {
383                         // If the value already exist, nothing is done.
384                         // Note that the attribute *must* have been
385                         // normalized before.
386                         modifiedAttr.add( value );
387                     }
388                 }
389 
390                 break;
391 
392             case REMOVE_ATTRIBUTE:
393                 if ( modAttr.get() == null )
394                 {
395                     // We have no value in the ModificationItem attribute :
396                     // we have to remove the whole attribute from the initial
397                     // entry
398                     entry.removeAttributes( modificationId );
399                 }
400                 else
401                 {
402                     // We just have to remove the values from the original
403                     // entry, if they exist.
404                     modifiedAttr = entry.get( modificationId );
405 
406                     if ( modifiedAttr == null )
407                     {
408                         break;
409                     }
410 
411                     for ( Value<?> value : modAttr )
412                     {
413                         // If the value does not exist, nothing is done.
414                         // Note that the attribute *must* have been
415                         // normalized before.
416                         modifiedAttr.remove( value );
417                     }
418 
419                     if ( modifiedAttr.size() == 0 )
420                     {
421                         // If this was the last value, remove the attribute
422                         entry.removeAttributes( modifiedAttr.getUpId() );
423                     }
424                 }
425 
426                 break;
427 
428             case REPLACE_ATTRIBUTE:
429                 if ( modAttr.get() == null )
430                 {
431                     // If the modification does not have any value, we have
432                     // to delete the attribute from the entry.
433                     entry.removeAttributes( modificationId );
434                 }
435                 else
436                 {
437                     // otherwise, just substitute the existing attribute.
438                     entry.put( modAttr );
439                 }
440 
441                 break;
442             default:
443                 break;
444         }
445     }
446 
447 
448     /**
449      * Convert a BasicAttributes or a AttributesImpl to an Entry
450      *
451      * @param attributes the BasicAttributes or AttributesImpl instance to convert
452      * @param dn The Dn which is needed by the Entry
453      * @return An instance of a Entry object
454      * 
455      * @throws LdapException If we get an invalid attribute
456      */
457     public static Entry toEntry( Attributes attributes, Dn dn ) throws LdapException
458     {
459         if ( attributes instanceof BasicAttributes )
460         {
461             try
462             {
463                 Entry entry = new DefaultEntry( dn );
464 
465                 for ( NamingEnumeration<? extends javax.naming.directory.Attribute> attrs = attributes.getAll(); attrs
466                     .hasMoreElements(); )
467                 {
468                     javax.naming.directory.Attribute attr = attrs.nextElement();
469 
470                     Attribute entryAttribute = toApiAttribute( attr );
471 
472                     if ( entryAttribute != null )
473                     {
474                         entry.put( entryAttribute );
475                     }
476                 }
477 
478                 return entry;
479             }
480             catch ( LdapException ne )
481             {
482                 throw new LdapInvalidAttributeTypeException( ne.getMessage(), ne );
483             }
484         }
485         else
486         {
487             return null;
488         }
489     }
490 
491 
492     /**
493      * Converts an {@link Entry} to an {@link Attributes}.
494      *
495      * @param entry
496      *      the {@link Entry} to convert
497      * @return
498      *      the equivalent {@link Attributes}
499      */
500     public static Attributes toAttributes( Entry entry )
501     {
502         if ( entry != null )
503         {
504             Attributes attributes = new BasicAttributes( true );
505 
506             // Looping on attributes
507             for ( Iterator<Attribute> attributeIterator = entry.iterator(); attributeIterator.hasNext(); )
508             {
509                 Attribute entryAttribute = attributeIterator.next();
510 
511                 attributes.put( toJndiAttribute( entryAttribute ) );
512             }
513 
514             return attributes;
515         }
516 
517         return null;
518     }
519 
520 
521     /**
522      * Converts an {@link Attribute} to a JNDI Attribute.
523      *
524      * @param attribute the {@link Attribute} to convert
525      * @return the equivalent JNDI Attribute
526      */
527     public static javax.naming.directory.Attribute toJndiAttribute( Attribute attribute )
528     {
529         if ( attribute != null )
530         {
531             javax.naming.directory.Attribute jndiAttribute = new BasicAttribute( attribute.getUpId() );
532 
533             // Looping on values
534             for ( Iterator<Value<?>> valueIterator = attribute.iterator(); valueIterator.hasNext(); )
535             {
536                 Value<?> value = valueIterator.next();
537                 jndiAttribute.add( value.getValue() );
538             }
539 
540             return jndiAttribute;
541         }
542 
543         return null;
544     }
545 
546 
547     /**
548      * Convert a JNDI Attribute to an LDAP API Attribute
549      *
550      * @param jndiAttribute the JNDI Attribute instance to convert
551      * @return An instance of a LDAP API Attribute object
552      */
553     public static Attribute toApiAttribute( javax.naming.directory.Attribute jndiAttribute )
554         throws LdapInvalidAttributeValueException
555     {
556         if ( jndiAttribute == null )
557         {
558             return null;
559         }
560 
561         try
562         {
563             Attribute attribute = new DefaultAttribute( jndiAttribute.getID() );
564 
565             for ( NamingEnumeration<?> values = jndiAttribute.getAll(); values.hasMoreElements(); )
566             {
567                 Object value = values.nextElement();
568 
569                 if ( value instanceof String )
570                 {
571                     attribute.add( ( String ) value );
572                 }
573                 else if ( value instanceof byte[] )
574                 {
575                     attribute.add( ( byte[] ) value );
576                 }
577                 else
578                 {
579                     attribute.add( ( String ) null );
580                 }
581             }
582 
583             return attribute;
584         }
585         catch ( NamingException ne )
586         {
587             return null;
588         }
589     }
590 }