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  package org.apache.directory.server.core.entry;
20  
21  import java.util.ArrayList;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.NoSuchElementException;
25  import java.util.Set;
26  
27  import javax.naming.NamingEnumeration;
28  import javax.naming.NamingException;
29  import javax.naming.directory.Attribute;
30  import javax.naming.directory.Attributes;
31  import javax.naming.directory.BasicAttribute;
32  import javax.naming.directory.BasicAttributes;
33  import javax.naming.directory.DirContext;
34  import javax.naming.directory.InvalidAttributeIdentifierException;
35  import javax.naming.directory.ModificationItem;
36  import javax.naming.directory.SearchResult;
37  
38  import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
39  import org.apache.directory.server.schema.registries.Registries;
40  import org.apache.directory.shared.ldap.constants.SchemaConstants;
41  import org.apache.directory.shared.ldap.entry.EntryAttribute;
42  import org.apache.directory.shared.ldap.entry.Modification;
43  import org.apache.directory.shared.ldap.entry.ModificationOperation;
44  import org.apache.directory.shared.ldap.entry.Value;
45  import org.apache.directory.shared.ldap.name.LdapDN;
46  import org.apache.directory.shared.ldap.schema.AttributeType;
47  import org.apache.directory.shared.ldap.schema.SchemaUtils;
48  import org.apache.directory.shared.ldap.util.EmptyEnumeration;
49  import org.apache.directory.shared.ldap.util.StringTools;
50  
51  /**
52   * A helper class used to manipulate Entries, Attributes and Values.
53   *
54   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
55   * @version $Rev$, $Date$
56   */
57  public class ServerEntryUtils
58  {
59      /**
60       * Convert a ServerAttribute into a BasicAttribute. The DN is lost
61       * during this conversion, as the Attributes object does not store
62       * this element.
63       *
64       * @return An instance of a AttributesImpl() object
65       */
66      public static Attribute toBasicAttribute( ServerAttribute entryAttribute )
67      {
68          AttributeType attributeType = entryAttribute.getAttributeType();
69          
70          Attribute attribute = new BasicAttribute( attributeType.getName() );
71          
72          for ( Value<?> value: entryAttribute )
73          {
74              attribute.add( value.get() );
75          }
76          
77          return attribute;
78      }
79      
80      
81      /**
82       * Convert a ServerEntry into a BasicAttributes. The DN is lost
83       * during this conversion, as the Attributes object does not store
84       * this element.
85       *
86       * @return An instance of a AttributesImpl() object
87       */
88      public static Attributes toBasicAttributes( ServerEntry entry )
89      {
90          if ( entry == null )
91          {
92              return null;
93          }
94          
95          Attributes attributes = new BasicAttributes( true );
96  
97          for ( AttributeType attributeType:entry.getAttributeTypes() )
98          {
99              EntryAttribute attr = entry.get( attributeType );
100             
101             // Deal with a special case : an entry without any ObjectClass
102             if ( attributeType.getOid().equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
103             {
104                 if ( attr.size() == 0 )
105                 {
106                     // We don't have any objectClass, just dismiss this element
107                     continue;
108                 }
109             }
110             
111             attributes.put( toBasicAttribute( (ServerAttribute)attr ) );
112         }
113         
114         return attributes;
115     }
116 
117 
118     /**
119      * Convert a BasicAttribute or a AttributeImpl to a ServerAtribute
120      *
121      * @param attribute the BasicAttributes or AttributesImpl instance to convert
122      * @param attributeType
123      * @return An instance of a ServerEntry object
124      * 
125      * @throws InvalidAttributeIdentifierException If we had an incorrect attribute
126      */
127     public static ServerAttribute toServerAttribute( Attribute attribute, AttributeType attributeType )
128     {
129         if ( attribute == null )
130         {
131             return null;
132         }
133         
134         try 
135         {
136             ServerAttribute serverAttribute = new DefaultServerAttribute( attributeType );
137         
138             for ( NamingEnumeration<?> values = attribute.getAll(); values.hasMoreElements(); )
139             {
140                 Object value = values.nextElement();
141                 
142                 if ( serverAttribute.isHR() )
143                 {
144                     if ( value instanceof String )
145                     {
146                         serverAttribute.add( (String)value );
147                     }
148                     else if ( value instanceof byte[] )
149                     {
150                         serverAttribute.add( StringTools.utf8ToString( (byte[])value ) );
151                     }
152                     else
153                     {
154                         return null;
155                     }
156                 }
157                 else
158                 {
159                     if ( value instanceof String )
160                     {
161                         serverAttribute.add( StringTools.getBytesUtf8( (String)value ) );
162                     }
163                     else if ( value instanceof byte[] )
164                     {
165                         serverAttribute.add( (byte[])value );
166                     }
167                     else
168                     {
169                         return null;
170                     }
171                 }
172             }
173             
174             return serverAttribute;
175         }
176         catch ( NamingException ne )
177         {
178             return null;
179         }
180     }
181 
182 
183     /**
184      * Convert a BasicAttributes or a AttributesImpl to a ServerEntry
185      *
186      * @param attributes the BasicAttributes or AttributesImpl instance to convert
187      * @param registries The registries, needed ro build a ServerEntry
188      * @param dn The DN which is needed by the ServerEntry 
189      * @return An instance of a ServerEntry object
190      * 
191      * @throws InvalidAttributeIdentifierException If we get an invalid attribute
192      */
193     public static ServerEntry toServerEntry( Attributes attributes, LdapDN dn, Registries registries ) 
194             throws InvalidAttributeIdentifierException
195     {
196         if ( attributes instanceof BasicAttributes )
197         {
198             try 
199             {
200                 ServerEntry entry = new DefaultServerEntry( registries, dn );
201     
202                 for ( NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMoreElements(); )
203                 {
204                     Attribute attr = attrs.nextElement();
205 
206                     String attributeId = attr.getID();
207                     String id = SchemaUtils.stripOptions( attributeId );
208                     Set<String> options = SchemaUtils.getOptions( attributeId );
209                     // TODO : handle options.
210                     AttributeType attributeType = registries.getAttributeTypeRegistry().lookup( id );
211                     ServerAttribute serverAttribute = ServerEntryUtils.toServerAttribute( attr, attributeType );
212                     
213                     if ( serverAttribute != null )
214                     {
215                         entry.put( serverAttribute );
216                     }
217                 }
218                 
219                 return entry;
220             }
221             catch ( NamingException ne )
222             {
223                 throw new InvalidAttributeIdentifierException( ne.getMessage() );
224             }
225         }
226         else
227         {
228             return null;
229         }
230     }
231 
232 
233     /**
234      * Gets the target entry as it would look after a modification operation 
235      * was performed on it.
236      * 
237      * @param mod the modification
238      * @param entry the source entry that is modified
239      * @return the resultant entry after the modification has taken place
240      * @throws NamingException if there are problems accessing attributes
241      */
242     public static ServerEntry getTargetEntry( Modification mod, ServerEntry entry, Registries registries ) throws NamingException
243     {
244         ServerEntry targetEntry = ( ServerEntry ) entry.clone();
245         ModificationOperation modOp = mod.getOperation();
246         String id = mod.getAttribute().getId();
247         AttributeType attributeType = registries.getAttributeTypeRegistry().lookup( id );
248         
249         switch ( modOp )
250         {
251             case REPLACE_ATTRIBUTE :
252                 targetEntry.put( (ServerAttribute)mod.getAttribute() );
253                 break;
254                 
255             case REMOVE_ATTRIBUTE :
256                 ServerAttribute toBeRemoved = (ServerAttribute)mod.getAttribute();
257 
258                 if ( toBeRemoved.size() == 0 )
259                 {
260                     targetEntry.removeAttributes( id );
261                 }
262                 else
263                 {
264                     EntryAttribute existing = targetEntry.get( id );
265 
266                     if ( existing != null )
267                     {
268                         for ( Value<?> value:toBeRemoved )
269                         {
270                             existing.remove( value );
271                         }
272                     }
273                 }
274                 break;
275                 
276             case ADD_ATTRIBUTE :
277                 ServerAttribute combined = new DefaultServerAttribute( id, attributeType );
278                 ServerAttribute toBeAdded = (ServerAttribute)mod.getAttribute();
279                 EntryAttribute existing = entry.get( id );
280 
281                 if ( existing != null )
282                 {
283                     for ( Value<?> value:existing )
284                     {
285                         combined.add( value );
286                     }
287                 }
288 
289                 for ( Value<?> value:toBeAdded )
290                 {
291                     combined.add( value );
292                 }
293                 
294                 targetEntry.put( combined );
295                 break;
296                 
297             default:
298                 throw new IllegalStateException( "undefined modification type: " + modOp );
299         }
300 
301         return targetEntry;
302     }
303 
304 
305     /**
306      * Creates a new attribute which contains the values representing the union
307      * of two attributes. If one attribute is null then the resultant attribute
308      * returned is a copy of the non-null attribute. If both are null then we
309      * cannot determine the attribute ID and an {@link IllegalArgumentException}
310      * is raised.
311      * 
312      * @param attr0 the first attribute
313      * @param attr1 the second attribute
314      * @return a new attribute with the union of values from both attribute
315      *         arguments
316      * @throws NamingException if there are problems accessing attribute values
317      */
318     public static ServerAttribute getUnion( ServerAttribute attr0, ServerAttribute attr1 )
319     {
320         if ( attr0 == null && attr1 == null )
321         {
322             throw new IllegalArgumentException( "Cannot figure out attribute ID if both args are null" );
323         }
324         else if ( attr0 == null )
325         {
326             return (ServerAttribute)attr1.clone();
327         }
328         else if ( attr1 == null )
329         {
330             return (ServerAttribute)attr0.clone();
331         }
332         else if ( !attr0.getAttributeType().equals( attr1.getAttributeType() ) )
333         {
334             throw new IllegalArgumentException( "Cannot take union of attributes with different IDs!" );
335         }
336 
337         ServerAttribute attr = (ServerAttribute)attr0.clone();
338 
339         for ( Value<?> value:attr1 )
340         {
341             attr.add( value );
342         }
343 
344         return attr;
345     }
346     
347     
348     /**
349      * Convert a ModificationItem to an instance of a ServerModification object
350      *
351      * @param modificationImpl the modification instance to convert
352      * @param attributeType the associated attributeType
353      * @return a instance of a ServerModification object
354      */
355     private static Modification toServerModification( ModificationItem modificationImpl, AttributeType attributeType ) 
356     {
357         ModificationOperation operation;
358         
359         switch ( modificationImpl.getModificationOp() )
360         {
361             case DirContext.REMOVE_ATTRIBUTE :
362                 operation = ModificationOperation.REMOVE_ATTRIBUTE;
363                 break;
364                 
365             case DirContext.REPLACE_ATTRIBUTE :
366                 operation = ModificationOperation.REPLACE_ATTRIBUTE;
367                 break;
368 
369             case DirContext.ADD_ATTRIBUTE :
370             default :
371                 operation = ModificationOperation.ADD_ATTRIBUTE;
372                 break;
373                 
374         }
375         
376         Modification modification = new ServerModification( 
377             operation,
378             ServerEntryUtils.toServerAttribute( modificationImpl.getAttribute(), attributeType ) ); 
379         
380         return modification;
381         
382     }
383 
384     
385     /**
386      * 
387      * Convert a list of ModificationItemImpl to a list of 
388      *
389      * @param modificationImpls
390      * @param atRegistry
391      * @return
392      * @throws NamingException
393      */
394     public static List<Modification> convertToServerModification( List<ModificationItem> modificationItems, 
395         AttributeTypeRegistry atRegistry ) throws NamingException
396     {
397         if ( modificationItems != null )
398         {
399             List<Modification> modifications = new ArrayList<Modification>( modificationItems.size() );
400 
401             for ( ModificationItem modificationItem: modificationItems )
402             {
403                 AttributeType attributeType = atRegistry.lookup( modificationItem.getAttribute().getID() );
404                 modifications.add( toServerModification( modificationItem, attributeType ) );
405             }
406         
407             return modifications;
408         }
409         else
410         {
411             return null;
412         }
413     }
414     
415     
416     /**
417      * Convert a Modification to an instance of a ServerModification object.
418      *
419      * @param modificationImpl the modification instance to convert
420      * @param attributeType the associated attributeType
421      * @return a instance of a ServerModification object
422      */
423     private static Modification toServerModification( Modification modification, AttributeType attributeType ) 
424     {
425         if ( modification instanceof ServerModification )
426         {
427             return modification;
428         }
429         
430         Modification serverModification = new ServerModification( 
431             modification.getOperation(),
432             new DefaultServerAttribute( attributeType, modification.getAttribute() ) ); 
433         
434         return serverModification;
435         
436     }
437 
438     
439     public static List<Modification> toServerModification( Modification[] modifications, 
440         AttributeTypeRegistry atRegistry ) throws NamingException
441     {
442         if ( modifications != null )
443         {
444             List<Modification> modificationsList = new ArrayList<Modification>();
445     
446             for ( Modification modification: modifications )
447             {
448                 String attributeId = modification.getAttribute().getId();
449                 String id = stripOptions( attributeId );
450                 modification.getAttribute().setId( id );
451                 Set<String> options = getOptions( attributeId );
452 
453                 // -------------------------------------------------------------------
454                 // DIRSERVER-646 Fix: Replacing an unknown attribute with no values 
455                 // (deletion) causes an error
456                 // -------------------------------------------------------------------
457                 
458                 // TODO - after removing JNDI we need to make the server handle 
459                 // this in the codec
460                 
461                 if ( ! atRegistry.hasAttributeType( id ) 
462                      && modification.getAttribute().size() == 0 
463                      && modification.getOperation() == ModificationOperation.REPLACE_ATTRIBUTE )
464                 {
465                     continue;
466                 }
467 
468                 // -------------------------------------------------------------------
469                 // END DIRSERVER-646 Fix
470                 // -------------------------------------------------------------------
471                 
472                 
473                 // TODO : handle options
474                 AttributeType attributeType = atRegistry.lookup( id );
475                 modificationsList.add( toServerModification( modification, attributeType ) );
476             }
477         
478             return modificationsList;
479         }
480         else
481         {
482             return null;
483         }
484     }
485 
486 
487     public static List<Modification> toServerModification( ModificationItem[] modifications, 
488         AttributeTypeRegistry atRegistry ) throws NamingException
489     {
490         if ( modifications != null )
491         {
492             List<Modification> modificationsList = new ArrayList<Modification>();
493     
494             for ( ModificationItem modification: modifications )
495             {
496                 String attributeId = modification.getAttribute().getID();
497                 String id = stripOptions( attributeId );
498                 Set<String> options = getOptions( attributeId );
499 
500                 // -------------------------------------------------------------------
501                 // DIRSERVER-646 Fix: Replacing an unknown attribute with no values 
502                 // (deletion) causes an error
503                 // -------------------------------------------------------------------
504                 
505                 // TODO - after removing JNDI we need to make the server handle 
506                 // this in the codec
507                 
508                 if ( ! atRegistry.hasAttributeType( id ) 
509                      && modification.getAttribute().size() == 0 
510                      && modification.getModificationOp() == DirContext.REPLACE_ATTRIBUTE )
511                 {
512                     continue;
513                 }
514 
515                 // -------------------------------------------------------------------
516                 // END DIRSERVER-646 Fix
517                 // -------------------------------------------------------------------
518                 
519                 
520                 // TODO : handle options
521                 AttributeType attributeType = atRegistry.lookup( id );
522                 modificationsList.add( toServerModification( (ModificationItem)modification, attributeType ) );
523             }
524         
525             return modificationsList;
526         }
527         else
528         {
529             return null;
530         }
531     }
532 
533 
534     /**
535      * Utility method to extract a modification item from an array of modifications.
536      * 
537      * @param mods the array of ModificationItems to extract the Attribute from.
538      * @param type the attributeType spec of the Attribute to extract
539      * @return the modification item on the attributeType specified
540      */
541     public static final Modification getModificationItem( List<Modification> mods, AttributeType type )
542     {
543         for ( Modification modification:mods )
544         {
545             ServerAttribute attribute = (ServerAttribute)modification.getAttribute();
546             
547             if ( attribute.getAttributeType() == type )
548             {
549                 return modification;
550             }
551         }
552         
553         return null;
554     }
555     
556     
557     /**
558      * Utility method to extract an attribute from a list of modifications.
559      * 
560      * @param mods the list of ModificationItems to extract the Attribute from.
561      * @param type the attributeType spec of the Attribute to extract
562      * @return the extract Attribute or null if no such attribute exists
563      */
564     public static ServerAttribute getAttribute( List<Modification> mods, AttributeType type )
565     {
566         Modification mod = getModificationItem( mods, type );
567         
568         if ( mod != null )
569         {
570             return (ServerAttribute)mod.getAttribute();
571         }
572         
573         return null;
574     }
575     
576     
577     /**
578      * Encapsulate a ServerSearchResult enumeration into a SearchResult enumeration
579      * @param result The ServerSearchResult enumeration
580      * @return A SearchResultEnumeration
581      */
582     public static NamingEnumeration<SearchResult> toSearchResultEnum( final NamingEnumeration<ServerSearchResult> result )
583     {
584         if ( result instanceof EmptyEnumeration<?> )
585         {
586             return new EmptyEnumeration<SearchResult>();
587         }
588         
589         return new NamingEnumeration<SearchResult> ()
590         {
591             public void close() throws NamingException
592             {
593                 result.close();
594             }
595 
596 
597             /**
598              * @see javax.naming.NamingEnumeration#hasMore()
599              */
600             public boolean hasMore() throws NamingException
601             {
602                 return result.hasMore();
603             }
604 
605 
606             /**
607              * @see javax.naming.NamingEnumeration#next()
608              */
609             public SearchResult next() throws NamingException
610             {
611                 ServerSearchResult rec = result.next();
612                 
613                 SearchResult searchResult = new SearchResult( 
614                         rec.getDn().getUpName(), 
615                         rec.getObject(), 
616                         toBasicAttributes( rec.getServerEntry() ), 
617                         rec.isRelative() );
618                 
619                 return searchResult;
620             }
621             
622             
623             /**
624              * @see java.util.Enumeration#hasMoreElements()
625              */
626             public boolean hasMoreElements()
627             {
628                 return result.hasMoreElements();
629             }
630 
631 
632             /**
633              * @see java.util.Enumeration#nextElement()
634              */
635             public SearchResult nextElement()
636             {
637                 try
638                 {
639                     ServerSearchResult rec = result.next();
640     
641                     SearchResult searchResult = new SearchResult( 
642                             rec.getDn().getUpName(), 
643                             rec.getObject(), 
644                             toBasicAttributes( rec.getServerEntry() ), 
645                             rec.isRelative() );
646                     
647                     return searchResult;
648                 }
649                 catch ( NamingException ne )
650                 {
651                     NoSuchElementException nsee = 
652                         new NoSuchElementException( "Encountered NamingException on underlying enumeration." );
653                     nsee.initCause( ne );
654                     throw nsee;
655                 }
656             }
657         };
658     }
659     
660     
661     /**
662      * Remove the options from the attributeType, and returns the ID.
663      * 
664      * RFC 4512 :
665      * attributedescription = attributetype options
666      * attributetype = oid
667      * options = *( SEMI option )
668      * option = 1*keychar
669      */
670     private static String stripOptions( String attributeId )
671     {
672         int optionsPos = attributeId.indexOf( ";" ); 
673         
674         if ( optionsPos != -1 )
675         {
676             return attributeId.substring( 0, optionsPos );
677         }
678         else
679         {
680             return attributeId;
681         }
682     }
683     
684 
685     /**
686      * Get the options from the attributeType.
687      * 
688      * For instance, given :
689      * jpegphoto;binary;lang=jp
690      * 
691      * your get back a set containing { "binary", "lang=jp" }
692      */
693     private static Set<String> getOptions( String attributeId )
694     {
695         int optionsPos = attributeId.indexOf( ";" ); 
696 
697         if ( optionsPos != -1 )
698         {
699             Set<String> options = new HashSet<String>();
700             
701             String[] res = attributeId.substring( optionsPos + 1 ).split( ";" );
702             
703             for ( String option:res )
704             {
705                 if ( !StringTools.isEmpty( option ) )
706                 {
707                     options.add( option );
708                 }
709             }
710             
711             return options;
712         }
713         else
714         {
715             return null;
716         }
717     }
718 }