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