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    package org.apache.directory.shared.ldap.entry.client;
020    
021    
022    import java.io.IOException;
023    import java.io.ObjectInput;
024    import java.io.ObjectOutput;
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.HashMap;
028    import java.util.Iterator;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.SortedMap;
032    import java.util.TreeMap;
033    
034    import javax.naming.NamingException;
035    
036    import org.apache.directory.shared.ldap.entry.AbstractEntry;
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.Value;
040    import org.apache.directory.shared.ldap.name.LdapDN;
041    import org.apache.directory.shared.ldap.util.StringTools;
042    import org.slf4j.Logger;
043    import org.slf4j.LoggerFactory;
044    
045    
046    /**
047     * A default implementation of a ServerEntry which should suite most
048     * use cases.
049     * 
050     * This class is final, it should not be extended.
051     *
052     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
053     * @version $Rev$, $Date$
054     */
055    public final class DefaultClientEntry extends AbstractEntry<String> implements ClientEntry
056    {
057        /** Used for serialization */
058        private static final long serialVersionUID = 2L;
059        
060        /** The logger for this class */
061        private static final Logger LOG = LoggerFactory.getLogger( DefaultClientEntry.class );
062    
063        //-------------------------------------------------------------------------
064        // Constructors
065        //-------------------------------------------------------------------------
066        /**
067         * Creates a new instance of DefaultClientEntry. 
068         * <p>
069         * This entry <b>must</b> be initialized before being used !
070         */
071        public DefaultClientEntry()
072        {
073            dn = LdapDN.EMPTY_LDAPDN;
074        }
075    
076    
077        /**
078         * Creates a new instance of DefaultServerEntry, with a 
079         * DN. 
080         * 
081         * @param dn The DN for this serverEntry. Can be null.
082         */
083        public DefaultClientEntry( LdapDN dn )
084        {
085            this.dn = dn;
086        }
087    
088    
089        /**
090         * Creates a new instance of DefaultServerEntry, with a 
091         * DN and a list of IDs. 
092         * 
093         * @param dn The DN for this serverEntry. Can be null.
094         * @param upIds The list of attributes to create.
095         */
096        public DefaultClientEntry( LdapDN dn, String... upIds )
097        {
098            this.dn = dn;
099    
100            for ( String upId:upIds )
101            {
102                // Add a new AttributeType without value
103                set( upId );
104            }
105        }
106    
107        
108        /**
109         * <p>
110         * Creates a new instance of DefaultClientEntry, with a 
111         * DN and a list of EntryAttributes.
112         * </p> 
113         * 
114         * @param dn The DN for this serverEntry. Can be null
115         * @param attributes The list of attributes to create
116         */
117        public DefaultClientEntry( LdapDN dn, EntryAttribute... attributes )
118        {
119            this.dn = dn;
120    
121            for ( EntryAttribute attribute:attributes )
122            {
123                if ( attribute == null )
124                {
125                    continue;
126                }
127                
128                // Store a new ClientAttribute
129                this.attributes.put( attribute.getId(), attribute );
130            }
131        }
132    
133        
134        //-------------------------------------------------------------------------
135        // Helper methods
136        //-------------------------------------------------------------------------
137        private String getId( String upId ) throws IllegalArgumentException
138        {
139            String id = StringTools.trim( StringTools.toLowerCase( upId ) );
140            
141            // If empty, throw an error
142            if ( ( id == null ) || ( id.length() == 0 ) ) 
143            {
144                String message = "The attributeType ID should not be null or empty";
145                LOG.error( message );
146                throw new IllegalArgumentException( message );
147            }
148            
149            return id;
150        }
151    
152        
153        //-------------------------------------------------------------------------
154        // Entry methods
155        //-------------------------------------------------------------------------
156        /**
157         * Add some Attributes to the current Entry.
158         *
159         * @param attributes The attributes to add
160         * @throws NamingException If we can't add any of the attributes
161         */
162        public void add( EntryAttribute... attributes ) throws NamingException
163        {
164            // Loop on all the added attributes
165            for ( EntryAttribute attribute:attributes )
166            {
167                // If the attribute already exist, we will add the new values.
168                if ( contains( attribute ) )
169                {
170                    EntryAttribute existingAttr = get( attribute.getId() );
171                    
172                    // Loop on all the values, and add them to the existing attribute
173                    for ( Value<?> value:attribute )
174                    {
175                        existingAttr.add( value );
176                    }
177                }
178                else
179                {
180                    // Stores the attribute into the entry
181                    this.attributes.put( attribute.getId(), (ClientAttribute)attribute );
182                }
183            }
184        }
185    
186        
187        /**
188         * Add an attribute (represented by its ID and binary values) into an entry. 
189         *
190         * @param upId The attribute ID
191         * @param values The list of binary values to inject. It can be empty
192         * @throws NamingException If the attribute does not exist
193         */
194        public void add( String upId, byte[]... values ) throws NamingException
195        {
196            // First, transform the upID to a valid ID
197            String id = getId( upId );
198            
199            // Now, check to see if we already have such an attribute
200            EntryAttribute attribute = attributes.get( id );
201            
202            if ( attribute != null )
203            {
204                // This Attribute already exist, we add the values 
205                // into it. (If the values already exists, they will
206                // not be added, but this is done in the add() method)
207                attribute.add( values );
208                attribute.setUpId( upId );
209            }
210            else
211            {
212                // We have to create a new Attribute and set the values
213                // and the upId
214                attributes.put( id, new DefaultClientAttribute( upId, values ) );
215            }
216        }
217    
218    
219        /**
220         * Add some String values to the current Entry.
221         *
222         * @param upId The user provided ID of the attribute we want to add 
223         * some values to
224         * @param values The list of String values to add
225         * @throws NamingException If we can't add any of the values
226         */
227        public void add( String upId, String... values ) throws NamingException
228        {
229            // First, transform the upID to a valid ID
230            String id = getId( upId );
231    
232            // Now, check to see if we already have such an attribute
233            EntryAttribute attribute = attributes.get( id );
234            
235            if ( attribute != null )
236            {
237                // This Attribute already exist, we add the values 
238                // into it. (If the values already exists, they will
239                // not be added, but this is done in the add() method)
240                attribute.add( values );
241                attribute.setUpId( upId );
242            }
243            else
244            {
245                // We have to create a new Attribute and set the values
246                // and the upId
247                attributes.put( id, new DefaultClientAttribute( upId, values ) );
248            }
249        }
250    
251    
252        /**
253         * Add an attribute (represented by its ID and Value values) into an entry. 
254         *
255         * @param upId The attribute ID
256         * @param values The list of Value values to inject. It can be empty
257         * @throws NamingException If the attribute does not exist
258         */
259        public void add( String upId, Value<?>... values ) throws NamingException
260        {
261            // First, transform the upID to a valid ID
262            String id = getId( upId );
263    
264            // Now, check to see if we already have such an attribute
265            EntryAttribute attribute = attributes.get( id );
266            
267            if ( attribute != null )
268            {
269                // This Attribute already exist, we add the values 
270                // into it. (If the values already exists, they will
271                // not be added, but this is done in the add() method)
272                attribute.add( values );
273                attribute.setUpId( upId );
274            }
275            else
276            {
277                // We have to create a new Attribute and set the values
278                // and the upId
279                attributes.put( id, new DefaultClientAttribute( upId, values ) );
280            }
281        }
282    
283    
284        /**
285         * Clone an entry. All the element are duplicated, so a modification on
286         * the original object won't affect the cloned object, as a modification
287         * on the cloned object has no impact on the original object
288         */
289        public Entry clone()
290        {
291            // First, clone the structure
292            DefaultClientEntry clone = (DefaultClientEntry)super.clone();
293            
294            // Just in case ... Should *never* happen
295            if ( clone == null )
296            {
297                return null;
298            }
299            
300            // An Entry has a DN and many attributes.
301            // First, clone the DN, if not null.
302            if ( dn != null )
303            {
304                clone.setDn( (LdapDN)dn.clone() );
305            }
306            
307            // then clone the ClientAttribute Map.
308            clone.attributes = (Map<String, EntryAttribute>)(((HashMap<String, EntryAttribute>)attributes).clone());
309            
310            // now clone all the attributes
311            clone.attributes.clear();
312            
313            for ( EntryAttribute attribute:attributes.values() )
314            {
315                clone.attributes.put( attribute.getId(), attribute.clone() );
316            }
317            
318            // We are done !
319            return clone;
320        }
321        
322    
323        /**
324         * <p>
325         * Checks if an entry contains a list of attributes.
326         * </p>
327         * <p>
328         * If the list is null or empty, this method will return <code>true</code>
329         * if the entry has no attribute, <code>false</code> otherwise.
330         * </p>
331         *
332         * @param attributes The Attributes to look for
333         * @return <code>true</code> if all the attributes are found within 
334         * the entry, <code>false</code> if at least one of them is not present.
335         * @throws NamingException If the attribute does not exist
336         */
337        public boolean contains( EntryAttribute... attributes ) throws NamingException
338        {
339            for ( EntryAttribute attribute:attributes )
340            {
341                if ( attribute == null )
342                {
343                    return this.attributes.size() == 0;
344                }
345                
346                if ( !this.attributes.containsKey( attribute.getId() ) )
347                {
348                    return false;
349                }
350            }
351            
352            return true;
353        }
354        
355        
356        /**
357         * Checks if an entry contains a specific attribute
358         *
359         * @param attributes The Attributes to look for
360         * @return <code>true</code> if the attributes are found within the entry
361         * @throws NamingException If the attribute does not exist
362         */
363        public boolean contains( String upId ) throws NamingException
364        {
365            String id = getId( upId );
366            
367            return attributes.containsKey( id );
368        }
369    
370        
371        /**
372         * Checks if an entry contains an attribute with some binary values.
373         *
374         * @param id The Attribute we are looking for.
375         * @param values The searched values.
376         * @return <code>true</code> if all the values are found within the attribute,
377         * false if at least one value is not present or if the ID is not valid. 
378         */
379        public boolean contains( String upId, byte[]... values )
380        {
381            String id = getId( upId );
382            
383            EntryAttribute attribute = attributes.get( id );
384            
385            if ( attribute == null )
386            {
387                return false;
388            }
389            
390            return attribute.contains( values );
391        }
392        
393        
394        /**
395         * Checks if an entry contains an attribute with some String values.
396         *
397         * @param id The Attribute we are looking for.
398         * @param values The searched values.
399         * @return <code>true</code> if all the values are found within the attribute,
400         * false if at least one value is not present or if the ID is not valid. 
401         */
402        public boolean contains( String upId, String... values )
403        {
404            String id = getId( upId );
405            
406            EntryAttribute attribute = attributes.get( id );
407            
408            if ( attribute == null )
409            {
410                return false;
411            }
412            
413            return attribute.contains( values );
414        }
415        
416        
417        /**
418         * Checks if an entry contains an attribute with some values.
419         *
420         * @param id The Attribute we are looking for.
421         * @param values The searched values.
422         * @return <code>true</code> if all the values are found within the attribute,
423         * false if at least one value is not present or if the ID is not valid. 
424         */
425        public boolean contains( String upId, Value<?>... values )
426        {
427            String id = getId( upId );
428            
429            EntryAttribute attribute = attributes.get( id );
430            
431            if ( attribute == null )
432            {
433                return false;
434            }
435            
436            return attribute.contains( values );
437        }
438        
439        
440        /**
441         * Checks if an entry contains some specific attributes.
442         *
443         * @param attributes The Attributes to look for.
444         * @return <code>true</code> if the attributes are all found within the entry.
445         */
446        public boolean containsAttribute( String... attributes )
447        {
448            for ( String attribute:attributes )
449            {
450                String id = getId( attribute );
451        
452                if ( !this.attributes.containsKey( id ) )
453                {
454                    return false;
455                }
456            }
457            
458            return true;
459        }
460    
461        
462        /**
463         * <p>
464         * Returns the attribute with the specified alias. The return value
465         * is <code>null</code> if no match is found.  
466         * </p>
467         * <p>An Attribute with an id different from the supplied alias may 
468         * be returned: for example a call with 'cn' may in some implementations 
469         * return an Attribute whose getId() field returns 'commonName'.
470         * </p>
471         *
472         * @param alias an aliased name of the attribute identifier
473         * @return the attribute associated with the alias
474         */
475        public EntryAttribute get( String alias )
476        {
477            try
478            {
479                String id = getId( alias );
480                
481                return attributes.get( id );
482            }
483            catch( IllegalArgumentException iea )
484            {
485                LOG.error( "An exception has been raised while looking for attribute id {}''", alias );
486                return null;
487            }
488        }
489    
490    
491        /**
492         * <p>
493         * Put an attribute (represented by its ID and some binary values) into an entry. 
494         * </p>
495         * <p> 
496         * If the attribute already exists, the previous attribute will be 
497         * replaced and returned.
498         * </p>
499         *
500         * @param upId The attribute ID
501         * @param values The list of binary values to put. It can be empty.
502         * @return The replaced attribute
503         */
504        public EntryAttribute put( String upId, byte[]... values )
505        {
506            // Get the normalized form of the ID
507            String id = getId( upId );
508            
509            // Create a new attribute
510            ClientAttribute clientAttribute = new DefaultClientAttribute( upId, values );
511    
512            // Replace the previous one, and return it back
513            return attributes.put( id, clientAttribute );
514        }
515    
516    
517        /**
518         * <p>
519         * Put an attribute (represented by its ID and some String values) into an entry. 
520         * </p>
521         * <p> 
522         * If the attribute already exists, the previous attribute will be 
523         * replaced and returned.
524         * </p>
525         *
526         * @param upId The attribute ID
527         * @param values The list of String values to put. It can be empty.
528         * @return The replaced attribute
529         */
530        public EntryAttribute put( String upId, String... values )
531        {
532            // Get the normalized form of the ID
533            String id = getId( upId );
534            
535            // Create a new attribute
536            ClientAttribute clientAttribute = new DefaultClientAttribute( upId, values );
537    
538            // Replace the previous one, and return it back
539            return attributes.put( id, clientAttribute );
540        }
541    
542    
543        /**
544         * <p>
545         * Put an attribute (represented by its ID and some values) into an entry. 
546         * </p>
547         * <p> 
548         * If the attribute already exists, the previous attribute will be 
549         * replaced and returned.
550         * </p>
551         *
552         * @param upId The attribute ID
553         * @param values The list of values to put. It can be empty.
554         * @return The replaced attribute
555         */
556        public EntryAttribute put( String upId, Value<?>... values )
557        {
558            // Get the normalized form of the ID
559            String id = getId( upId );
560            
561            // Create a new attribute
562            ClientAttribute clientAttribute = new DefaultClientAttribute( upId, values );
563    
564            // Replace the previous one, and return it back
565            return attributes.put( id, clientAttribute );
566        }
567    
568    
569        /**
570         * <p>
571         * Put some new ClientAttribute using the User Provided ID. 
572         * No value is inserted. 
573         * </p>
574         * <p>
575         * If an existing Attribute is found, it will be replaced by an
576         * empty attribute, and returned to the caller.
577         * </p>
578         * 
579         * @param upIds The user provided IDs of the AttributeTypes to add.
580         * @return A list of replaced Attributes.
581         */
582        public List<EntryAttribute> set( String... upIds )
583        {
584            if ( upIds == null )
585            {
586                String message = "The AttributeType list should not be null";
587                LOG.error( message );
588                throw new IllegalArgumentException( message );
589            }
590            
591            List<EntryAttribute> returnedClientAttributes = new ArrayList<EntryAttribute>();
592            
593            // Now, loop on all the attributeType to add
594            for ( String upId:upIds )
595            {
596                String id = StringTools.trim( StringTools.toLowerCase( upId ) );
597                
598                if ( id == null )
599                {
600                    String message = "The AttributeType list should not contain null values";
601                    LOG.error( message );
602                    throw new IllegalArgumentException( message );
603                }
604                
605                if ( attributes.containsKey( id ) )
606                {
607                    // Add the removed serverAttribute to the list
608                    returnedClientAttributes.add( attributes.remove( id ) );
609                }
610    
611                ClientAttribute newAttribute = new DefaultClientAttribute( upId );
612                attributes.put( id, newAttribute );
613            }
614            
615            return returnedClientAttributes;
616        }
617    
618        
619        /**
620         * <p>
621         * Places attributes in the attribute collection. 
622         * </p>
623         * <p>If there is already an attribute with the same ID as any of the 
624         * new attributes, the old ones are removed from the collection and 
625         * are returned by this method. If there was no attribute with the 
626         * same ID the return value is <code>null</code>.
627         *</p>
628         *
629         * @param attributes the attributes to be put
630         * @return the old attributes with the same OID, if exist; otherwise
631         *         <code>null</code>
632         * @exception NamingException if the operation fails
633         */
634        public List<EntryAttribute> put( EntryAttribute... attributes ) throws NamingException
635        {
636            // First, get the existing attributes
637            List<EntryAttribute> previous = new ArrayList<EntryAttribute>();
638            
639            for ( EntryAttribute attribute:attributes )
640            {
641                String id = attribute.getId();
642                
643                if ( contains( id ) )
644                {
645                    // Store the attribute and remove it from the list
646                    previous.add( get( id ) );
647                    this.attributes.remove( id );
648                }
649                
650                // add the new one
651                this.attributes.put( id, (ClientAttribute)attribute );            
652            }
653            
654            // return the previous attributes
655            return previous;
656        }
657    
658    
659        public List<EntryAttribute> remove( EntryAttribute... attributes ) throws NamingException
660        {
661            List<EntryAttribute> removedAttributes = new ArrayList<EntryAttribute>();
662            
663            for ( EntryAttribute attribute:attributes )
664            {
665                if ( contains( attribute.getId() ) )
666                {
667                    this.attributes.remove( attribute.getId() );
668                    removedAttributes.add( attribute );
669                }
670            }
671            
672            return removedAttributes;
673        }
674    
675    
676        /**
677         * <p>
678         * Removes the attribute with the specified alias. 
679         * </p>
680         * <p>
681         * The removed attribute are returned by this method. 
682         * </p>
683         * <p>
684         * If there is no attribute with the specified alias,
685         * the return value is <code>null</code>.
686         * </p>
687         *
688         * @param attributes an aliased name of the attribute to be removed
689         * @return the removed attributes, if any, as a list; otherwise <code>null</code>
690         */
691        public List<EntryAttribute> removeAttributes( String... attributes )
692        {
693            if ( attributes.length == 0 )
694            {
695                return null;
696            }
697            
698            List<EntryAttribute> removed = new ArrayList<EntryAttribute>( attributes.length );
699            
700            for ( String attribute:attributes )
701            {
702                EntryAttribute attr = get( attribute );
703                
704                if ( attr != null )
705                {
706                    removed.add( this.attributes.remove( attr.getId() ) );
707                }
708                else
709                {
710                    String message = "The attribute '" + attribute + "' does not exist in the entry";
711                    LOG.warn( message );
712                    continue;
713                }
714            }
715            
716            if ( removed.size() == 0 )
717            {
718                return null;
719            }
720            else
721            {
722                return removed;
723            }
724        }
725    
726    
727        /**
728         * <p>
729         * Removes the specified binary values from an attribute.
730         * </p>
731         * <p>
732         * If at least one value is removed, this method returns <code>true</code>.
733         * </p>
734         * <p>
735         * If there is no more value after having removed the values, the attribute
736         * will be removed too.
737         * </p>
738         * <p>
739         * If the attribute does not exist, nothing is done and the method returns 
740         * <code>false</code>
741         * </p> 
742         *
743         * @param upId The attribute ID  
744         * @param values the values to be removed
745         * @return <code>true</code> if at least a value is removed, <code>false</code>
746         * if not all the values have been removed or if the attribute does not exist. 
747         */
748        public boolean remove( String upId, byte[]... values ) throws NamingException
749        {
750            try
751            {
752                String id = getId( upId );
753                
754                EntryAttribute attribute = get( id );
755                
756                if ( attribute == null )
757                {
758                    // Can't remove values from a not existing attribute !
759                    return false;
760                }
761                
762                int nbOldValues = attribute.size();
763                
764                // Remove the values
765                attribute.remove( values );
766                
767                if ( attribute.size() == 0 )
768                {
769                    // No mare values, remove the attribute
770                    attributes.remove( id );
771                    
772                    return true;
773                }
774                
775                if ( nbOldValues != attribute.size() )
776                {
777                    // At least one value have been removed, return true.
778                    return true;
779                }
780                else
781                {
782                    // No values have been removed, return false.
783                    return false;
784                }
785            }
786            catch ( IllegalArgumentException iae )
787            {
788                LOG.error( "The removal of values for the missing '{}' attribute is not possible", upId );
789                return false;
790            }
791        }
792    
793    
794        /**
795         * <p>
796         * Removes the specified String values from an attribute.
797         * </p>
798         * <p>
799         * If at least one value is removed, this method returns <code>true</code>.
800         * </p>
801         * <p>
802         * If there is no more value after having removed the values, the attribute
803         * will be removed too.
804         * </p>
805         * <p>
806         * If the attribute does not exist, nothing is done and the method returns 
807         * <code>false</code>
808         * </p> 
809         *
810         * @param upId The attribute ID  
811         * @param attributes the attributes to be removed
812         * @return <code>true</code> if at least a value is removed, <code>false</code>
813         * if not all the values have been removed or if the attribute does not exist. 
814         */
815        public boolean remove( String upId, String... values ) throws NamingException
816        {
817            try
818            {
819                String id = getId( upId );
820                
821                EntryAttribute attribute = get( id );
822                
823                if ( attribute == null )
824                {
825                    // Can't remove values from a not existing attribute !
826                    return false;
827                }
828                
829                int nbOldValues = attribute.size();
830                
831                // Remove the values
832                attribute.remove( values );
833                
834                if ( attribute.size() == 0 )
835                {
836                    // No mare values, remove the attribute
837                    attributes.remove( id );
838                    
839                    return true;
840                }
841                
842                if ( nbOldValues != attribute.size() )
843                {
844                    // At least one value have been removed, return true.
845                    return true;
846                }
847                else
848                {
849                    // No values have been removed, return false.
850                    return false;
851                }
852            }
853            catch ( IllegalArgumentException iae )
854            {
855                LOG.error( "The removal of values for the missing '{}' attribute is not possible", upId );
856                return false;
857            }
858        }
859    
860    
861        /**
862         * <p>
863         * Removes the specified values from an attribute.
864         * </p>
865         * <p>
866         * If at least one value is removed, this method returns <code>true</code>.
867         * </p>
868         * <p>
869         * If there is no more value after having removed the values, the attribute
870         * will be removed too.
871         * </p>
872         * <p>
873         * If the attribute does not exist, nothing is done and the method returns 
874         * <code>false</code>
875         * </p> 
876         *
877         * @param upId The attribute ID  
878         * @param attributes the attributes to be removed
879         * @return <code>true</code> if at least a value is removed, <code>false</code>
880         * if not all the values have been removed or if the attribute does not exist. 
881         */
882        public boolean remove( String upId, Value<?>... values ) throws NamingException
883        {
884            try
885            {
886                String id = getId( upId );
887                
888                EntryAttribute attribute = get( id );
889                
890                if ( attribute == null )
891                {
892                    // Can't remove values from a not existing attribute !
893                    return false;
894                }
895                
896                int nbOldValues = attribute.size();
897                
898                // Remove the values
899                attribute.remove( values );
900                
901                if ( attribute.size() == 0 )
902                {
903                    // No mare values, remove the attribute
904                    attributes.remove( id );
905                    
906                    return true;
907                }
908                
909                if ( nbOldValues != attribute.size() )
910                {
911                    // At least one value have been removed, return true.
912                    return true;
913                }
914                else
915                {
916                    // No values have been removed, return false.
917                    return false;
918                }
919            }
920            catch ( IllegalArgumentException iae )
921            {
922                LOG.error( "The removal of values for the missing '{}' attribute is not possible", upId );
923                return false;
924            }
925        }
926    
927    
928        public Iterator<EntryAttribute> iterator()
929        {
930            return Collections.unmodifiableMap( attributes ).values().iterator();
931        }
932    
933    
934        /**
935         * @see Externalizable#writeExternal(ObjectOutput)<p>
936         * 
937         * This is the place where we serialize entries, and all theirs
938         * elements.
939         * <p>
940         * The structure used to store the entry is the following :
941         * <li>
942         * <b>[DN]</b> : If it's null, stores an empty DN
943         * </li>
944         * <li>
945         * <b>[attributes number]</b> : the number of attributes.
946         * </li>
947         * <li>
948         * <b>[attribute]*</b> : each attribute, if we have some
949         * </li>
950         */
951        public void writeExternal( ObjectOutput out ) throws IOException
952        {
953            // First, the DN
954            if ( dn == null )
955            {
956                // Write an empty DN
957                out.writeObject( LdapDN.EMPTY_LDAPDN );
958            }
959            else
960            {
961                // Write the DN
962                out.writeObject( dn );
963            }
964            
965            // Then the attributes. 
966            // Store the attributes' nulber first
967            out.writeInt( attributes.size() );
968            
969            // Iterate through the keys.
970            for ( EntryAttribute attribute:attributes.values() )
971            {
972                // Store the attribute
973                out.writeObject( attribute );
974            }
975            
976            out.flush();
977        }
978    
979        
980        /**
981         * @see Externalizable#readExternal(ObjectInput)
982         */
983        public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
984        {
985            // Read the DN
986            dn = (LdapDN)in.readObject();
987            
988            // Read the number of attributes
989            int nbAttributes = in.readInt();
990            
991            // Read the attributes
992            for ( int i = 0; i < nbAttributes; i++ )
993            {
994                // Read each attribute
995                EntryAttribute attribute = (DefaultClientAttribute)in.readObject();
996                
997                attributes.put( attribute.getId(), attribute );
998            }
999        }
1000        
1001        
1002        /**
1003         * Get the hash code of this ClientEntry.
1004         *
1005         * @see java.lang.Object#hashCode()
1006         * @return the instance's hash code 
1007         */
1008        public int hashCode()
1009        {
1010            int result = 37;
1011            
1012            result = result*17 + dn.hashCode();
1013            
1014            SortedMap<String, EntryAttribute> sortedMap = new TreeMap<String, EntryAttribute>();
1015            
1016            for ( String id:attributes.keySet() )
1017            {
1018                sortedMap.put( id, attributes.get( id ) );
1019            }
1020            
1021            for ( String id:sortedMap.keySet() )
1022            {
1023                result = result*17 + sortedMap.get( id ).hashCode();
1024            }
1025            
1026            return result;
1027        }
1028    
1029        
1030        /**
1031         * Tells if an entry has a specific ObjectClass value
1032         * 
1033         * @param objectClass The ObjectClass we want to check
1034         * @return <code>true</code> if the ObjectClass value is present 
1035         * in the ObjectClass attribute
1036         */
1037        public boolean hasObjectClass( String objectClass )
1038        {
1039            return contains( "objectclass", objectClass );
1040        }
1041    
1042    
1043        /**
1044         * @see Object#equals(Object)
1045         */
1046        public boolean equals( Object o )
1047        {
1048            // Short circuit
1049    
1050            if ( this == o )
1051            {
1052                return true;
1053            }
1054            
1055            if ( ! ( o instanceof DefaultClientEntry ) )
1056            {
1057                return false;
1058            }
1059            
1060            DefaultClientEntry other = (DefaultClientEntry)o;
1061            
1062            // Both DN must be equal
1063            if ( dn == null )
1064            {
1065                if ( other.getDn() != null )
1066                {
1067                    return false;
1068                }
1069            }
1070            else
1071            {
1072                if ( !dn.equals( other.getDn() ) )
1073                {
1074                    return false;
1075                }
1076            }
1077            
1078            // They must have the same number of attributes
1079            if ( size() != other.size() )
1080            {
1081                return false;
1082            }
1083            
1084            // Each attribute must be equal
1085            for ( EntryAttribute attribute:other )
1086            {
1087                if ( !attribute.equals( this.get( attribute.getId() ) ) )
1088                {
1089                    return false;
1090                }
1091            }
1092            
1093            return true;
1094        }
1095            
1096    
1097        /**
1098         * @see Object#toString()
1099         */
1100        public String toString()
1101        {
1102            StringBuilder sb = new StringBuilder();
1103            
1104            sb.append( "ClientEntry\n" );
1105            sb.append( "    dn: " ).append( dn.getUpName() ).append( '\n' );
1106            
1107            // First dump the ObjectClass attribute
1108            if ( containsAttribute( "objectClass" ) )
1109            {
1110                EntryAttribute objectClass = get( "objectclass" );
1111                
1112                sb.append( objectClass );
1113            }
1114            
1115            if ( attributes.size() != 0 )
1116            {
1117                for ( EntryAttribute attribute:attributes.values() )
1118                {
1119                    if ( !attribute.getId().equals( "objectclass" ) )
1120                    {
1121                        sb.append( attribute );
1122                    }
1123                }
1124            }
1125            
1126            return sb.toString();
1127        }
1128    }