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 *  https://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 */
019package org.apache.directory.api.ldap.model.entry;
020
021
022import java.io.IOException;
023import java.io.ObjectInput;
024import java.io.ObjectOutput;
025import java.nio.charset.StandardCharsets;
026import java.util.ArrayList;
027import java.util.Base64;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.Iterator;
032import java.util.List;
033import java.util.Map;
034
035import org.apache.directory.api.i18n.I18n;
036import org.apache.directory.api.ldap.model.constants.SchemaConstants;
037import org.apache.directory.api.ldap.model.exception.LdapException;
038import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
039import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
040import org.apache.directory.api.ldap.model.ldif.LdapLdifException;
041import org.apache.directory.api.ldap.model.ldif.LdifAttributesReader;
042import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
043import org.apache.directory.api.ldap.model.name.Dn;
044import org.apache.directory.api.ldap.model.schema.AttributeType;
045import org.apache.directory.api.ldap.model.schema.SchemaManager;
046import org.apache.directory.api.util.Strings;
047import org.slf4j.Logger;
048import org.slf4j.LoggerFactory;
049
050
051/**
052 * A default implementation of a ServerEntry which should suite most
053 * use cases.<br>
054 * <br>
055 * This class is final, it should not be extended.
056 *
057 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
058 */
059public final class DefaultEntry implements Entry
060{
061    /** Used for serialization */
062    private static final long serialVersionUID = 2L;
063
064    /** The logger for this class */
065    private static final Logger LOG = LoggerFactory.getLogger( DefaultEntry.class );
066
067    /** The Dn for this entry */
068    private Dn dn;
069
070    /** A map containing all the attributes for this entry */
071    private Map<String, Attribute> attributes = new HashMap<>();
072
073    /** A speedup to get the ObjectClass attribute */
074    private static AttributeType objectClassAttributeType;
075
076    /** The SchemaManager */
077    private transient SchemaManager schemaManager;
078
079    /** The computed hashcode. We don't want to compute it each time the hashcode() method is called */
080    private volatile int h;
081
082    /** A mutex to manage synchronization*/
083    private static final Object MUTEX = new Object();
084
085
086    //-------------------------------------------------------------------------
087    // Constructors
088    //-------------------------------------------------------------------------
089    /**
090     * Creates a new instance of DefaultEntry.
091     * <p>
092     * This entry <b>must</b> be initialized before being used !
093     */
094    public DefaultEntry()
095    {
096        this( ( SchemaManager ) null );
097    }
098
099
100    /**
101     * <p>
102     * Creates a new instance of DefaultEntry, schema aware.
103     * </p>
104     * <p>
105     * No attributes will be created.
106     * </p>
107     *
108     * @param schemaManager The reference to the schemaManager
109     */
110    public DefaultEntry( SchemaManager schemaManager )
111    {
112        this.schemaManager = schemaManager;
113        dn = Dn.EMPTY_DN;
114
115        // Initialize the ObjectClass object
116        if ( schemaManager != null )
117        {
118            initObjectClassAT();
119        }
120    }
121
122
123    /**
124     * Creates a new instance of DefaultEntry, with a Dn.
125     *
126     * @param dn The String Dn for this serverEntry. Can be null.
127     * @throws LdapInvalidDnException If the Dn is invalid
128     */
129    public DefaultEntry( String dn ) throws LdapInvalidDnException
130    {
131        this.dn = new Dn( dn );
132    }
133
134
135    /**
136     * Creates a new instance of DefaultEntry, with a Dn.
137     *
138     * @param dn The Dn for this serverEntry. Can be null.
139     */
140    public DefaultEntry( Dn dn )
141    {
142        this.dn = dn;
143    }
144
145
146    /**
147     * <p>
148     * Creates a new instance of DefaultEntry, schema aware.
149     * </p>
150     * <p>
151     * No attributes will be created.
152     * </p>
153     *
154     * @param schemaManager The reference to the schemaManager
155     * @param dn The String Dn for this serverEntry. Can be null.
156     * @throws LdapInvalidDnException If the Dn is invalid
157     */
158    public DefaultEntry( SchemaManager schemaManager, String dn ) throws LdapInvalidDnException
159    {
160        this.schemaManager = schemaManager;
161
162        if ( Strings.isEmpty( dn ) )
163        {
164            this.dn = Dn.EMPTY_DN;
165        }
166        else
167        {
168            this.dn = new Dn( schemaManager, dn );
169        }
170
171        // Initialize the ObjectClass object
172        initObjectClassAT();
173    }
174
175
176    /**
177     * <p>
178     * Creates a new instance of DefaultEntry, schema aware.
179     * </p>
180     * <p>
181     * No attributes will be created.
182     * </p>
183     *
184     * @param schemaManager The reference to the schemaManager
185     * @param dn The Dn for this serverEntry. Can be null.
186     */
187    public DefaultEntry( SchemaManager schemaManager, Dn dn )
188    {
189        this.schemaManager = schemaManager;
190
191        if ( dn == null )
192        {
193            this.dn = Dn.EMPTY_DN;
194        }
195        else
196        {
197            this.dn = normalizeDn( dn );
198        }
199
200        // Initialize the ObjectClass object
201        initObjectClassAT();
202    }
203
204
205    /**
206     * Creates a new instance of DefaultEntry, with a
207     * Dn and a list of IDs.
208     *
209     * @param dn The Dn for this serverEntry. Can be null.
210     * @param elements The list of elements to inject in the entry
211     * @throws LdapException If the elements are invalid
212     * @throws LdapException If the provided Dn or elements are invalid
213     */
214    public DefaultEntry( String dn, Object... elements ) throws LdapException
215    {
216        this( null, dn, elements );
217    }
218
219
220    /**
221     * Creates a new instance of DefaultEntry, with a
222     * Dn and a list of IDs.
223     *
224     * @param dn The Dn for this serverEntry. Can be null.
225     * @param elements The list of attributes to create.
226     * @throws LdapException If the provided Dn or elements are invalid
227     */
228    public DefaultEntry( Dn dn, Object... elements ) throws LdapException
229    {
230        this( null, dn, elements );
231    }
232
233
234    /**
235     * Creates a new instance of DefaultEntry, with a
236     * Dn and a list of IDs.
237     *
238     * @param schemaManager The SchemaManager
239     * @param dn The Dn for this serverEntry. Can be null.
240     * @param elements The list of attributes to create.
241     * @throws LdapException If the provided Dn or elements are invalid
242     */
243    public DefaultEntry( SchemaManager schemaManager, String dn, Object... elements ) throws LdapException
244    {
245        this( schemaManager, new Dn( schemaManager, dn ), elements );
246    }
247
248
249    /**
250     * Creates a new instance of DefaultEntry, with a
251     * Dn and a list of IDs.
252     *
253     * @param schemaManager The reference to the schemaManager
254     * @param dn The Dn for this serverEntry. Can be null.
255     * @param elements The list of attributes to create.
256     * @throws LdapException If the provided Dn or Elements are invalid
257     */
258    public DefaultEntry( SchemaManager schemaManager, Dn dn, Object... elements ) throws LdapException
259    {
260        DefaultEntry entry = ( DefaultEntry ) createEntry( schemaManager, elements );
261
262        this.dn = dn;
263        this.attributes = entry.attributes;
264        this.schemaManager = schemaManager;
265
266        if ( schemaManager != null )
267        {
268            if ( !dn.isSchemaAware() )
269            {
270                this.dn = new Dn( schemaManager, dn );
271            }
272
273            initObjectClassAT();
274        }
275    }
276
277
278    /**
279     * <p>
280     * Creates a new instance of DefaultEntry, copying
281     * another entry.
282     * </p>
283     * <p>
284     * No attributes will be created.
285     * </p>
286     *
287     * @param schemaManager The reference to the schemaManager
288     * @param entry the entry to copy
289     * @throws LdapException If the provided entry is invalid
290     */
291    public DefaultEntry( SchemaManager schemaManager, Entry entry ) throws LdapException
292    {
293        this.schemaManager = schemaManager;
294
295        // Initialize the ObjectClass object
296        initObjectClassAT();
297
298        // We will clone the existing entry, because it may be normalized
299        if ( entry.getDn() != null )
300        {
301            dn = normalizeDn( entry.getDn() );
302        }
303        else
304        {
305            dn = Dn.EMPTY_DN;
306        }
307
308        // Init the attributes map
309        attributes = new HashMap<>( entry.size() );
310
311        // and copy all the attributes
312        for ( Attribute attribute : entry )
313        {
314            try
315            {
316                // First get the AttributeType
317                AttributeType attributeType = attribute.getAttributeType();
318
319                if ( attributeType == null )
320                {
321                    attributeType = schemaManager.lookupAttributeTypeRegistry( attribute.getId() );
322                }
323
324                // Create a new ServerAttribute.
325                Attribute serverAttribute = new DefaultAttribute( attributeType, attribute );
326
327                // And store it
328                add( serverAttribute );
329            }
330            catch ( Exception ne )
331            {
332                // Just log a warning
333                if ( LOG.isWarnEnabled() )
334                {
335                    LOG.warn( I18n.msg( I18n.MSG_13200_CANT_STORE_ATTRIBUTE, attribute.getId() ) );
336                }
337
338                throw ne;
339            }
340        }
341    }
342
343
344    //-------------------------------------------------------------------------
345    // Helper methods
346    //-------------------------------------------------------------------------
347    private Entry createEntry( SchemaManager schemaManager, Object... elements )
348        throws LdapInvalidAttributeValueException, LdapLdifException
349    {
350        StringBuilder sb = new StringBuilder();
351        int pos = 0;
352        boolean valueExpected = false;
353
354        for ( Object element : elements )
355        {
356            if ( !valueExpected )
357            {
358                if ( !( element instanceof String ) )
359                {
360                    throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err(
361                        I18n.ERR_13233_ATTRIBUTE_ID_MUST_BE_A_STRING, pos + 1 ) );
362                }
363
364                String attribute = ( String ) element;
365                sb.append( attribute );
366
367                if ( attribute.indexOf( ':' ) != -1 )
368                {
369                    sb.append( '\n' );
370                }
371                else
372                {
373                    valueExpected = true;
374                }
375            }
376            else
377            {
378                if ( element instanceof String )
379                {
380                    sb.append( ": " ).append( ( String ) element ).append( '\n' );
381                }
382                else if ( element instanceof byte[] )
383                {
384                    sb.append( ":: " );
385                    sb.append( new String( Base64.getEncoder().encode( ( byte[] ) element ), StandardCharsets.UTF_8 ) );
386                    sb.append( '\n' );
387                }
388                else
389                {
390                    throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err(
391                        I18n.ERR_13234_ATTRIBUTE_VAL_STRING_OR_BYTE, pos + 1 ) );
392                }
393
394                valueExpected = false;
395            }
396        }
397
398        if ( valueExpected )
399        {
400            throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n
401                .err( I18n.ERR_13250_VALUE_MISSING_AT_THE_END ) );
402        }
403
404        try ( LdifAttributesReader reader = new LdifAttributesReader() )
405        {
406            return reader.parseEntry( schemaManager, sb.toString() );
407        }
408        catch ( IOException e )
409        {
410            throw new LdapLdifException( I18n.err( I18n.ERR_13248_CANNOT_READ_ENTRY ), e );
411        }
412    }
413
414
415    /**
416     * Get the trimmed and lower cased entry ID
417     *
418     * @param upId The ID
419     * @return The retrieved ID
420     */
421    private String getId( String upId )
422    {
423        String id = Strings.trim( Strings.toLowerCaseAscii( upId ) );
424
425        // If empty, throw an error
426        if ( Strings.isEmpty( id ) )
427        {
428            String message = I18n.err( I18n.ERR_13216_AT_ID_NULL );
429            LOG.error( message );
430            throw new IllegalArgumentException( message );
431        }
432
433        return id;
434    }
435
436
437    /**
438     * Get the UpId if it is null.
439     *
440     * @param upId The ID
441     * @param attributeType The AttributeType to retrieve
442     * @return the retrieved ID
443     */
444    private String getUpId( String upId, AttributeType attributeType )
445    {
446        String normUpId = Strings.trim( upId );
447
448        if ( attributeType == null )
449        {
450            if ( Strings.isEmpty( normUpId ) )
451            {
452                String message = I18n.err( I18n.ERR_13226_CANNOT_ADD_ATTRIBUTE_NO_ID );
453                LOG.error( message );
454                throw new IllegalArgumentException( message );
455            }
456
457            return upId;
458        }
459        else if ( Strings.isEmpty( normUpId ) )
460        {
461            String id = attributeType.getName();
462
463            if ( Strings.isEmpty( id ) )
464            {
465                id = attributeType.getOid();
466            }
467
468            return id;
469        }
470        else
471        {
472            return upId;
473        }
474    }
475
476
477    /**
478     * This method is used to initialize the OBJECT_CLASS_AT attributeType.
479     *
480     * We want to do it only once, so it's a synchronized method. Note that
481     * the alternative would be to call the lookup() every time, but this won't
482     * be very efficient, as it will get the AT from a map, which is also
483     * synchronized, so here, we have a very minimal cost.
484     *
485     * We can't do it once as a static part in the body of this class, because
486     * the access to the registries is mandatory to get back the AttributeType.
487     */
488    private void initObjectClassAT()
489    {
490        if ( schemaManager == null )
491        {
492            return;
493        }
494
495        try
496        {
497            synchronized ( MUTEX )
498            {
499                if ( objectClassAttributeType == null )
500                {
501                    objectClassAttributeType = schemaManager
502                        .lookupAttributeTypeRegistry( SchemaConstants.OBJECT_CLASS_AT );
503                }
504            }
505        }
506        catch ( LdapException ne )
507        {
508            // do nothing...
509        }
510    }
511
512
513    /**
514     * Normalizes the given Dn if it was not already normalized
515     *
516     * @param dn the Dn to be normalized
517     * @return The normalized Dn
518     */
519    private Dn normalizeDn( Dn dn )
520    {
521        if ( !dn.isSchemaAware() )
522        {
523            try
524            {
525                // The dn must be normalized
526                return new Dn( schemaManager, dn );
527            }
528            catch ( LdapException ne )
529            {
530                if ( LOG.isWarnEnabled() )
531                {
532                    LOG.warn( I18n.msg( I18n.MSG_13201_DN_CANT_BE_NORMALIZED, dn ) );
533                }
534
535                return dn;
536            }
537        }
538        else
539        {
540            return dn;
541        }
542    }
543
544
545    /**
546     * A helper method to recompute the hash code
547     */
548    private void rehash()
549    {
550        int hTmp = 37;
551        h = hTmp * 17 + dn.hashCode();
552    }
553
554
555    /**
556     * Add a new EntryAttribute, with its upId. If the upId is null,
557     * default to the AttributeType name.
558     *
559     * Updates the AttributeMap.
560     *
561     * @param upId The user provided ID for the attribute to create
562     * @param attributeType The AttributeType to use
563     * @param values The values to add to this attribute
564     * @throws LdapInvalidAttributeValueException If one of the value is incorrect
565     */
566    protected void createAttribute( String upId, AttributeType attributeType, byte[]... values )
567        throws LdapInvalidAttributeValueException
568    {
569        Attribute attribute = new DefaultAttribute( attributeType, values );
570        attribute.setUpId( upId, attributeType );
571        attributes.put( attributeType.getOid(), attribute );
572    }
573
574
575    /**
576     * Add a new EntryAttribute, with its upId. If the upId is null,
577     * default to the AttributeType name.
578     *
579     * Updates the AttributeMap.
580     *
581     * @param upId The user provided ID for the attribute to create
582     * @param attributeType The AttributeType to use
583     * @param values The values to add to this attribute
584     * @throws LdapInvalidAttributeValueException If one of the value is incorrect
585     */
586    protected void createAttribute( String upId, AttributeType attributeType, String... values )
587        throws LdapInvalidAttributeValueException
588    {
589        Attribute attribute = new DefaultAttribute( attributeType, values );
590        attribute.setUpId( upId, attributeType );
591        attributes.put( attributeType.getOid(), attribute );
592    }
593
594
595    /**
596     * Add a new EntryAttribute, with its upId. If the upId is null,
597     * default to the AttributeType name.
598     *
599     * Updates the AttributeMap.
600     *
601     * @param upId The user provided ID for the attribute to create
602     * @param attributeType The AttributeType to use
603     * @param values The values to add to this attribute
604     * @throws LdapInvalidAttributeValueException If one of the value is incorrect
605     */
606    protected void createAttribute( String upId, AttributeType attributeType, Value... values )
607        throws LdapInvalidAttributeValueException
608    {
609        Attribute attribute = new DefaultAttribute( attributeType, values );
610        attribute.setUpId( upId, attributeType );
611        attributes.put( attributeType.getOid(), attribute );
612    }
613
614
615    /**
616     * Returns the attributeType from an Attribute ID.
617     *
618     * @param upId The ID we are looking for
619     * @return The found attributeType
620     * @throws LdapException If the lookup failed
621     */
622    protected AttributeType getAttributeType( String upId ) throws LdapException
623    {
624        if ( Strings.isEmpty( Strings.trim( upId ) ) )
625        {
626            String message = I18n.err( I18n.ERR_13204_NULL_ATTRIBUTE_ID );
627            LOG.error( message );
628            throw new IllegalArgumentException( message );
629        }
630
631        return schemaManager.lookupAttributeTypeRegistry( upId );
632    }
633
634
635    //-------------------------------------------------------------------------
636    // Entry methods
637    //-------------------------------------------------------------------------
638    /**
639     * {@inheritDoc}
640     */
641    @Override
642    public Entry add( AttributeType attributeType, byte[]... values ) throws LdapException
643    {
644        if ( attributeType == null )
645        {
646            String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED );
647            LOG.error( message );
648            throw new IllegalArgumentException( message );
649        }
650
651        if ( ( values == null ) || ( values.length == 0 ) )
652        {
653            String message = I18n.err( I18n.ERR_13232_NO_VALUE_NOT_ALLOWED );
654            LOG.error( message );
655            throw new IllegalArgumentException( message );
656        }
657
658        // ObjectClass with binary values are not allowed
659        if ( attributeType.equals( objectClassAttributeType ) )
660        {
661            String message = I18n.err( I18n.ERR_13227_NON_STRING_VALUE_NOT_ALLOWED );
662            LOG.error( message );
663            throw new UnsupportedOperationException( message );
664        }
665
666        Attribute attribute = attributes.get( attributeType.getOid() );
667
668        if ( attribute != null )
669        {
670            // This Attribute already exist, we add the values
671            // into it
672            attribute.add( values );
673        }
674        else
675        {
676            // We have to create a new Attribute and set the values.
677            // The upId, which is set to null, will be setup by the
678            // createAttribute method
679            createAttribute( null, attributeType, values );
680        }
681
682        return this;
683    }
684
685
686    /**
687     * {@inheritDoc}
688     */
689    @Override
690    public Entry add( AttributeType attributeType, String... values ) throws LdapException
691    {
692        if ( attributeType == null )
693        {
694            String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED );
695            LOG.error( message );
696            throw new IllegalArgumentException( message );
697        }
698
699        Attribute attribute = attributes.get( attributeType.getOid() );
700
701        if ( attribute != null )
702        {
703            // This Attribute already exist, we add the values
704            // into it
705            attribute.add( values );
706        }
707        else
708        {
709            // We have to create a new Attribute and set the values.
710            // The upId, which is set to null, will be setup by the
711            // createAttribute method
712            createAttribute( null, attributeType, values );
713        }
714
715        return this;
716    }
717
718
719    /**
720     * {@inheritDoc}
721     */
722    @Override
723    public Entry add( AttributeType attributeType, Value... values ) throws LdapException
724    {
725        if ( attributeType == null )
726        {
727            String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED );
728            LOG.error( message );
729            throw new IllegalArgumentException( message );
730        }
731
732        Attribute attribute = attributes.get( attributeType.getOid() );
733
734        if ( attribute != null )
735        {
736            // This Attribute already exist, we add the values
737            // into it
738            attribute.add( values );
739        }
740        else
741        {
742            // We have to create a new Attribute and set the values.
743            // The upId, which is set to null, will be setup by the
744            // createAttribute method
745            createAttribute( null, attributeType, values );
746        }
747
748        return this;
749    }
750
751
752    /**
753     * {@inheritDoc}
754     */
755    @Override
756    public Entry add( String upId, AttributeType attributeType, byte[]... values ) throws LdapException
757    {
758        // ObjectClass with binary values are not allowed
759        if ( attributeType.equals( objectClassAttributeType ) )
760        {
761            String message = I18n.err( I18n.ERR_13227_NON_STRING_VALUE_NOT_ALLOWED );
762            LOG.error( message );
763            throw new UnsupportedOperationException( message );
764        }
765
766        Attribute attribute = attributes.get( attributeType.getOid() );
767
768        String id = getUpId( upId, attributeType );
769
770        if ( attribute != null )
771        {
772            // This Attribute already exist, we add the values
773            // into it
774            attribute.add( values );
775            attribute.setUpId( id, attributeType );
776        }
777        else
778        {
779            // We have to create a new Attribute and set the values
780            // and the upId
781            createAttribute( id, attributeType, values );
782        }
783
784        return this;
785    }
786
787
788    /**
789     * {@inheritDoc}
790     */
791    @Override
792    public Entry add( String upId, AttributeType attributeType, Value... values ) throws LdapException
793    {
794        if ( attributeType == null )
795        {
796            String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED );
797            LOG.error( message );
798            throw new IllegalArgumentException( message );
799        }
800
801        String id = getUpId( upId, attributeType );
802
803        Attribute attribute = attributes.get( attributeType.getOid() );
804
805        if ( attribute != null )
806        {
807            // This Attribute already exist, we add the values
808            // into it
809            attribute.add( values );
810            attribute.setUpId( id, attributeType );
811        }
812        else
813        {
814            createAttribute( id, attributeType, values );
815        }
816
817        return this;
818    }
819
820
821    /**
822     * {@inheritDoc}
823     */
824    @Override
825    public Entry add( String upId, AttributeType attributeType, String... values ) throws LdapException
826    {
827        if ( attributeType == null )
828        {
829            String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED );
830            LOG.error( message );
831            throw new IllegalArgumentException( message );
832        }
833
834        String id = getUpId( upId, attributeType );
835
836        Attribute attribute = attributes.get( attributeType.getOid() );
837
838        if ( attribute != null )
839        {
840            // This Attribute already exist, we add the values
841            // into it
842            attribute.add( values );
843            attribute.setUpId( id, attributeType );
844        }
845        else
846        {
847            // We have to create a new Attribute and set the values
848            // and the upId
849            createAttribute( id, attributeType, values );
850        }
851
852        return this;
853    }
854
855
856    /**
857     * {@inheritDoc}
858     */
859    @Override
860    public Entry add( Attribute... attributes ) throws LdapException
861    {
862        // Loop on all the added attributes
863        for ( Attribute attribute : attributes )
864        {
865            AttributeType attributeType = attribute.getAttributeType();
866
867            if ( attributeType != null )
868            {
869                String oid = attributeType.getOid();
870
871                if ( this.attributes.containsKey( oid ) )
872                {
873                    // We already have an attribute with the same AttributeType
874                    // Just add the new values into it.
875                    Attribute existingAttribute = this.attributes.get( oid );
876
877                    for ( Value value : attribute )
878                    {
879                        existingAttribute.add( value );
880                    }
881
882                    // And update the upId
883                    existingAttribute.setUpId( attribute.getUpId() );
884                }
885                else
886                {
887                    // The attributeType does not exist, add it
888                    this.attributes.put( oid, attribute );
889                }
890            }
891            else
892            {
893                // If the attribute already exist, we will add the new values.
894                if ( contains( attribute ) )
895                {
896                    Attribute existingAttribute = get( attribute.getId() );
897
898                    // Loop on all the values, and add them to the existing attribute
899                    for ( Value value : attribute )
900                    {
901                        existingAttribute.add( value );
902                    }
903                }
904                else
905                {
906                    // Stores the attribute into the entry
907                    this.attributes.put( attribute.getId(), attribute );
908                }
909            }
910        }
911
912        return this;
913    }
914
915
916    /**
917     * {@inheritDoc}
918     */
919    @Override
920    public Entry add( String upId, byte[]... values ) throws LdapException
921    {
922        if ( Strings.isEmpty( upId ) )
923        {
924            String message = I18n.err( I18n.ERR_13204_NULL_ATTRIBUTE_ID );
925            LOG.error( message );
926            throw new IllegalArgumentException( message );
927        }
928
929        // First, transform the upID to a valid ID
930        String id = getId( upId );
931
932        if ( schemaManager != null )
933        {
934            add( upId, schemaManager.lookupAttributeTypeRegistry( id ), values );
935        }
936        else
937        {
938            // Now, check to see if we already have such an attribute
939            Attribute attribute = attributes.get( id );
940
941            if ( attribute != null )
942            {
943                // This Attribute already exist, we add the values
944                // into it. (If the values already exists, they will
945                // not be added, but this is done in the add() method)
946                attribute.add( values );
947                attribute.setUpId( upId );
948            }
949            else
950            {
951                // We have to create a new Attribute and set the values
952                // and the upId
953                attributes.put( id, new DefaultAttribute( upId, values ) );
954            }
955        }
956
957        return this;
958    }
959
960
961    /**
962     * {@inheritDoc}
963     */
964    @Override
965    public Entry add( String upId, String... values ) throws LdapException
966    {
967        if ( Strings.isEmpty( upId ) )
968        {
969            String message = I18n.err( I18n.ERR_13204_NULL_ATTRIBUTE_ID );
970            LOG.error( message );
971            throw new IllegalArgumentException( message );
972        }
973
974        // First, transform the upID to a valid ID
975        String id = getId( upId );
976
977        if ( schemaManager != null )
978        {
979            add( upId, schemaManager.lookupAttributeTypeRegistry( upId ), values );
980        }
981        else
982        {
983            // Now, check to see if we already have such an attribute
984            Attribute attribute = attributes.get( id );
985
986            if ( attribute != null )
987            {
988                // This Attribute already exist, we add the values
989                // into it. (If the values already exists, they will
990                // not be added, but this is done in the add() method)
991                attribute.add( values );
992                attribute.setUpId( upId );
993            }
994            else
995            {
996                // We have to create a new Attribute and set the values
997                // and the upId
998                attributes.put( id, new DefaultAttribute( upId, values ) );
999            }
1000        }
1001
1002        return this;
1003    }
1004
1005
1006    /**
1007     * {@inheritDoc}
1008     */
1009    @Override
1010    public Entry add( String upId, Value... values ) throws LdapException
1011    {
1012        if ( Strings.isEmpty( upId ) )
1013        {
1014            String message = I18n.err( I18n.ERR_13204_NULL_ATTRIBUTE_ID );
1015            LOG.error( message );
1016            throw new IllegalArgumentException( message );
1017        }
1018
1019        // First, transform the upID to a valid ID
1020        String id = getId( upId );
1021
1022        if ( schemaManager != null )
1023        {
1024            add( upId, schemaManager.lookupAttributeTypeRegistry( upId ), values );
1025        }
1026        else
1027        {
1028            // Now, check to see if we already have such an attribute
1029            Attribute attribute = attributes.get( id );
1030
1031            if ( attribute != null )
1032            {
1033                // This Attribute already exist, we add the values
1034                // into it. (If the values already exists, they will
1035                // not be added, but this is done in the add() method)
1036                attribute.add( values );
1037                attribute.setUpId( upId );
1038            }
1039            else
1040            {
1041                // We have to create a new Attribute and set the values
1042                // and the upId
1043                attributes.put( id, new DefaultAttribute( upId, values ) );
1044            }
1045        }
1046
1047        return this;
1048    }
1049
1050
1051    /**
1052     * Clone an entry. All the element are duplicated, so a modification on
1053     * the original object won't affect the cloned object, as a modification
1054     * on the cloned object has no impact on the original object
1055     */
1056    @Override
1057    public Entry clone()
1058    {
1059        // First, clone the structure
1060        DefaultEntry clone = ( DefaultEntry ) shallowClone();
1061
1062        // now clone all the attributes
1063        clone.attributes.clear();
1064
1065        if ( schemaManager != null )
1066        {
1067            for ( Attribute attribute : attributes.values() )
1068            {
1069                String oid = attribute.getAttributeType().getOid();
1070                clone.attributes.put( oid, attribute.clone() );
1071            }
1072        }
1073        else
1074        {
1075            for ( Attribute attribute : attributes.values() )
1076            {
1077                clone.attributes.put( attribute.getId(), attribute.clone() );
1078            }
1079
1080        }
1081
1082        // We are done !
1083        return clone;
1084
1085        /*
1086        // First, clone the structure
1087        //DefaultEntry clone = ( DefaultEntry ) shallowClone();
1088        try
1089        {
1090            DefaultEntry clone = ( DefaultEntry ) super.clone();
1091            clone.attributes = new HashMap<>( attributes.size() );
1092
1093            // now clone all the attributes
1094            //clone.attributes.clear();
1095
1096            if ( schemaManager != null )
1097            {
1098                for ( Attribute attribute : attributes.values() )
1099                {
1100                    String oid = attribute.getAttributeType().getOid();
1101                    clone.attributes.put( oid, attribute.clone() );
1102                }
1103            }
1104            else
1105            {
1106                for ( Attribute attribute : attributes.values() )
1107                {
1108                    clone.attributes.put( attribute.getId(), attribute.clone() );
1109                }
1110            }
1111
1112            // We are done !
1113            return clone;
1114        }
1115        catch ( CloneNotSupportedException cnse )
1116        {
1117            return this;
1118        }
1119        */
1120    }
1121
1122
1123    /**
1124     * Shallow clone an entry. We don't clone the Attributes
1125     */
1126    @SuppressWarnings("unchecked")
1127    @Override
1128    public Entry shallowClone()
1129    {
1130        try
1131        {
1132            // First, clone the structure
1133            DefaultEntry clone = ( DefaultEntry ) super.clone();
1134
1135            // An Entry has a Dn and many attributes.
1136            // note that Dn is immutable now
1137            clone.dn = dn;
1138
1139            // then clone the ClientAttribute Map.
1140            clone.attributes = ( Map<String, Attribute> ) ( ( ( HashMap<String, Attribute> ) attributes )
1141                .clone() );
1142
1143            // We are done !
1144            return clone;
1145        }
1146        catch ( CloneNotSupportedException cnse )
1147        {
1148            return null;
1149        }
1150    }
1151
1152
1153    /**
1154     * {@inheritDoc}
1155     */
1156    @Override
1157    public boolean contains( Attribute... attributes )
1158    {
1159        if ( schemaManager == null )
1160        {
1161            for ( Attribute attribute : attributes )
1162            {
1163                if ( attribute == null )
1164                {
1165                    return this.attributes.size() == 0;
1166                }
1167
1168                if ( !this.attributes.containsKey( attribute.getId() ) )
1169                {
1170                    return false;
1171                }
1172            }
1173        }
1174        else
1175        {
1176            for ( Attribute entryAttribute : attributes )
1177            {
1178                if ( entryAttribute == null )
1179                {
1180                    return this.attributes.size() == 0;
1181                }
1182
1183                AttributeType attributeType = entryAttribute.getAttributeType();
1184
1185                if ( ( attributeType == null ) || !this.attributes.containsKey( attributeType.getOid() ) )
1186                {
1187                    return false;
1188                }
1189            }
1190        }
1191
1192        return true;
1193    }
1194
1195
1196    /**
1197     * {@inheritDoc}
1198     */
1199    @Override
1200    public boolean containsAttribute( String... attributes )
1201    {
1202        if ( schemaManager == null )
1203        {
1204            for ( String attribute : attributes )
1205            {
1206                String id = getId( attribute );
1207
1208                if ( !this.attributes.containsKey( id ) )
1209                {
1210                    return false;
1211                }
1212            }
1213
1214            return true;
1215        }
1216        else
1217        {
1218            for ( String attribute : attributes )
1219            {
1220                try
1221                {
1222                    if ( !containsAttribute( schemaManager.lookupAttributeTypeRegistry( attribute ) ) )
1223                    {
1224                        return false;
1225                    }
1226                }
1227                catch ( LdapException ne )
1228                {
1229                    return false;
1230                }
1231            }
1232
1233            return true;
1234        }
1235    }
1236
1237
1238    /**
1239     * {@inheritDoc}
1240     */
1241    @Override
1242    public boolean containsAttribute( AttributeType attributeType )
1243    {
1244        if ( attributeType == null )
1245        {
1246            return false;
1247        }
1248
1249        return attributes.containsKey( attributeType.getOid() );
1250    }
1251
1252
1253    /**
1254     * {@inheritDoc}
1255     */
1256    @Override
1257    public boolean contains( AttributeType attributeType, byte[]... values )
1258    {
1259        if ( attributeType == null )
1260        {
1261            return false;
1262        }
1263
1264        Attribute attribute = attributes.get( attributeType.getOid() );
1265
1266        if ( attribute != null )
1267        {
1268            return attribute.contains( values );
1269        }
1270        else
1271        {
1272            return false;
1273        }
1274    }
1275
1276
1277    /**
1278     * {@inheritDoc}
1279     */
1280    @Override
1281    public boolean contains( AttributeType attributeType, String... values )
1282    {
1283        if ( attributeType == null )
1284        {
1285            return false;
1286        }
1287
1288        Attribute attribute = attributes.get( attributeType.getOid() );
1289
1290        if ( attribute != null )
1291        {
1292            return attribute.contains( values );
1293        }
1294        else
1295        {
1296            return false;
1297        }
1298    }
1299
1300
1301    /**
1302     * {@inheritDoc}
1303     */
1304    @Override
1305    public boolean contains( AttributeType attributeType, Value... values )
1306    {
1307        if ( attributeType == null )
1308        {
1309            return false;
1310        }
1311
1312        Attribute attribute = attributes.get( attributeType.getOid() );
1313
1314        if ( attribute != null )
1315        {
1316            return attribute.contains( values );
1317        }
1318        else
1319        {
1320            return false;
1321        }
1322    }
1323
1324
1325    /**
1326     * {@inheritDoc}
1327     */
1328    @Override
1329    public boolean contains( String upId, byte[]... values )
1330    {
1331        if ( Strings.isEmpty( upId ) )
1332        {
1333            return false;
1334        }
1335
1336        String id = getId( upId );
1337
1338        if ( schemaManager != null )
1339        {
1340            try
1341            {
1342                return contains( schemaManager.lookupAttributeTypeRegistry( id ), values );
1343            }
1344            catch ( LdapException le )
1345            {
1346                return false;
1347            }
1348        }
1349
1350        Attribute attribute = attributes.get( id );
1351
1352        if ( attribute == null )
1353        {
1354            return false;
1355        }
1356
1357        return attribute.contains( values );
1358    }
1359
1360
1361    /**
1362     * {@inheritDoc}
1363     */
1364    @Override
1365    public boolean contains( String upId, String... values )
1366    {
1367        if ( Strings.isEmpty( upId ) )
1368        {
1369            return false;
1370        }
1371
1372        String id = getId( upId );
1373
1374        if ( schemaManager != null )
1375        {
1376            try
1377            {
1378                return contains( schemaManager.lookupAttributeTypeRegistry( id ), values );
1379            }
1380            catch ( LdapException le )
1381            {
1382                return false;
1383            }
1384        }
1385
1386        Attribute attribute = attributes.get( id );
1387
1388        if ( attribute == null )
1389        {
1390            return false;
1391        }
1392
1393        return attribute.contains( values );
1394    }
1395
1396
1397    /**
1398     * {@inheritDoc}
1399     */
1400    @Override
1401    public boolean contains( String upId, Value... values )
1402    {
1403        if ( Strings.isEmpty( upId ) )
1404        {
1405            return false;
1406        }
1407
1408        String id = getId( upId );
1409
1410        if ( schemaManager != null )
1411        {
1412            try
1413            {
1414                return contains( schemaManager.lookupAttributeTypeRegistry( id ), values );
1415            }
1416            catch ( LdapException le )
1417            {
1418                return false;
1419            }
1420        }
1421
1422        Attribute attribute = attributes.get( id );
1423
1424        if ( attribute == null )
1425        {
1426            return false;
1427        }
1428
1429        return attribute.contains( values );
1430    }
1431
1432
1433    /**
1434     * {@inheritDoc}
1435     */
1436    @Override
1437    public Attribute get( String alias )
1438    {
1439        try
1440        {
1441            String id = getId( alias );
1442
1443            if ( schemaManager != null )
1444            {
1445                try
1446                {
1447                    AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( id );
1448
1449                    return attributes.get( attributeType.getOid() );
1450                }
1451                catch ( LdapException ne )
1452                {
1453                    String message = ne.getLocalizedMessage();
1454                    LOG.error( message );
1455                    return null;
1456                }
1457            }
1458            else
1459            {
1460                return attributes.get( id );
1461            }
1462        }
1463        catch ( IllegalArgumentException iea )
1464        {
1465            LOG.error( I18n.err( I18n.ERR_13217_FAILED_LOOKUP_AT, alias ) );
1466            return null;
1467        }
1468    }
1469
1470
1471    /**
1472     * {@inheritDoc}
1473     */
1474    @Override
1475    public Attribute get( AttributeType attributeType )
1476    {
1477        if ( attributeType != null )
1478        {
1479            return attributes.get( attributeType.getOid() );
1480        }
1481        else
1482        {
1483            return null;
1484        }
1485    }
1486
1487
1488    /**
1489     * {@inheritDoc}
1490     */
1491    @Override
1492    public Collection<Attribute> getAttributes()
1493    {
1494        return Collections.unmodifiableMap( attributes ).values();
1495    }
1496
1497
1498    /**
1499     * {@inheritDoc}
1500     */
1501    @Override
1502    public Attribute put( String upId, byte[]... values )
1503    {
1504        if ( Strings.isEmpty( upId ) )
1505        {
1506            String message = I18n.err( I18n.ERR_13204_NULL_ATTRIBUTE_ID );
1507            LOG.error( message );
1508            throw new IllegalArgumentException( message );
1509        }
1510
1511        if ( schemaManager == null )
1512        {
1513            // Get the normalized form of the ID
1514            String id = getId( upId );
1515
1516            // Create a new attribute
1517            Attribute clientAttribute = new DefaultAttribute( upId, values );
1518
1519            // Replace the previous one, and return it back
1520            return attributes.put( id, clientAttribute );
1521        }
1522        else
1523        {
1524            try
1525            {
1526                return put( upId, getAttributeType( upId ), values );
1527            }
1528            catch ( LdapException ne )
1529            {
1530                String message = I18n.err( I18n.ERR_13212_ERROR_ADDING_VALUE, upId, ne.getLocalizedMessage() );
1531                LOG.error( message );
1532                throw new IllegalArgumentException( message, ne );
1533            }
1534        }
1535    }
1536
1537
1538    /**
1539     * {@inheritDoc}
1540     */
1541    @Override
1542    public Attribute put( String upId, String... values )
1543    {
1544        if ( Strings.isEmpty( upId ) )
1545        {
1546            String message = I18n.err( I18n.ERR_13204_NULL_ATTRIBUTE_ID );
1547            LOG.error( message );
1548            throw new IllegalArgumentException( message );
1549        }
1550
1551        if ( schemaManager == null )
1552        {
1553            // Get the normalized form of the ID
1554            String id = getId( upId );
1555
1556            // Create a new attribute
1557            Attribute clientAttribute = new DefaultAttribute( upId, values );
1558
1559            // Replace the previous one, and return it back
1560            return attributes.put( id, clientAttribute );
1561        }
1562        else
1563        {
1564            try
1565            {
1566                return put( upId, getAttributeType( upId ), values );
1567            }
1568            catch ( LdapException ne )
1569            {
1570                String message = I18n.err( I18n.ERR_13212_ERROR_ADDING_VALUE, upId, ne.getLocalizedMessage() );
1571                LOG.error( message );
1572                throw new IllegalArgumentException( message, ne );
1573            }
1574        }
1575    }
1576
1577
1578    /**
1579     * {@inheritDoc}
1580     */
1581    @Override
1582    public Attribute put( String upId, Value... values )
1583    {
1584        if ( Strings.isEmpty( upId ) )
1585        {
1586            String message = I18n.err( I18n.ERR_13204_NULL_ATTRIBUTE_ID );
1587            LOG.error( message );
1588            throw new IllegalArgumentException( message );
1589        }
1590
1591        if ( schemaManager == null )
1592        {
1593            // Get the normalized form of the ID
1594            String id = getId( upId );
1595
1596            // Create a new attribute
1597            Attribute clientAttribute = new DefaultAttribute( upId, values );
1598
1599            // Replace the previous one, and return it back
1600            return attributes.put( id, clientAttribute );
1601        }
1602        else
1603        {
1604            try
1605            {
1606                return put( upId, getAttributeType( upId ), values );
1607            }
1608            catch ( LdapException ne )
1609            {
1610                String message = I18n.err( I18n.ERR_13212_ERROR_ADDING_VALUE, upId, ne.getLocalizedMessage() );
1611                LOG.error( message );
1612                throw new IllegalArgumentException( message, ne );
1613            }
1614        }
1615    }
1616
1617
1618    /**
1619     * {@inheritDoc}
1620     */
1621    @Override
1622    public List<Attribute> put( Attribute... attributes ) throws LdapException
1623    {
1624        // First, get the existing attributes
1625        List<Attribute> previous = new ArrayList<>();
1626
1627        if ( schemaManager == null )
1628        {
1629            for ( Attribute attribute : attributes )
1630            {
1631                String id = attribute.getId();
1632
1633                if ( containsAttribute( id ) )
1634                {
1635                    // Store the attribute and remove it from the list
1636                    previous.add( get( id ) );
1637                    this.attributes.remove( id );
1638                }
1639
1640                // add the new one
1641                this.attributes.put( id, attribute );
1642            }
1643        }
1644        else
1645        {
1646            for ( Attribute attribute : attributes )
1647            {
1648                if ( attribute == null )
1649                {
1650                    String message = I18n.err( I18n.ERR_13228_AT_LIST_NULL_ELEMENTS );
1651                    LOG.error( message );
1652                    throw new IllegalArgumentException( message );
1653                }
1654
1655                if ( attribute.getAttributeType() == null )
1656                {
1657                    AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( attribute.getId() );
1658                    attribute.apply( attributeType );
1659                }
1660
1661                Attribute removed = this.attributes.put( attribute.getAttributeType().getOid(), attribute );
1662
1663                if ( removed != null )
1664                {
1665                    previous.add( removed );
1666                }
1667            }
1668        }
1669
1670        // return the previous attributes
1671        return previous;
1672    }
1673
1674
1675    /**
1676     * {@inheritDoc}
1677     */
1678    @Override
1679    public Attribute put( AttributeType attributeType, byte[]... values ) throws LdapException
1680    {
1681        return put( null, attributeType, values );
1682    }
1683
1684
1685    /**
1686     * {@inheritDoc}
1687     */
1688    @Override
1689    public Attribute put( AttributeType attributeType, String... values ) throws LdapException
1690    {
1691        return put( null, attributeType, values );
1692    }
1693
1694
1695    /**
1696     * {@inheritDoc}
1697     */
1698    @Override
1699    public Attribute put( AttributeType attributeType, Value... values ) throws LdapException
1700    {
1701        return put( null, attributeType, values );
1702    }
1703
1704
1705    /**
1706     * {@inheritDoc}
1707     */
1708    @Override
1709    public Attribute put( String upId, AttributeType attributeType, byte[]... values ) throws LdapException
1710    {
1711        if ( attributeType == null )
1712        {
1713            try
1714            {
1715                attributeType = getAttributeType( upId );
1716            }
1717            catch ( Exception e )
1718            {
1719                String message = I18n.err( I18n.ERR_13231_NO_VALID_AT_FOR_THIS_ID );
1720                LOG.error( message );
1721                throw new IllegalArgumentException( message, e );
1722            }
1723        }
1724        else
1725        {
1726            if ( !Strings.isEmpty( upId ) )
1727            {
1728                AttributeType tempAT = getAttributeType( upId );
1729
1730                if ( !tempAT.equals( attributeType ) )
1731                {
1732                    String message = I18n.err( I18n.ERR_13229_ID_INCOMPATIBLE_WITH_AT, upId, attributeType );
1733                    LOG.error( message );
1734                    throw new IllegalArgumentException( message );
1735                }
1736            }
1737            else
1738            {
1739                upId = getUpId( upId, attributeType );
1740            }
1741        }
1742
1743        if ( attributeType.equals( objectClassAttributeType ) )
1744        {
1745            String message = I18n.err( I18n.ERR_13227_NON_STRING_VALUE_NOT_ALLOWED );
1746            LOG.error( message );
1747            throw new UnsupportedOperationException( message );
1748        }
1749
1750        Attribute attribute = new DefaultAttribute( upId, attributeType, values );
1751
1752        return attributes.put( attributeType.getOid(), attribute );
1753    }
1754
1755
1756    /**
1757     * {@inheritDoc}
1758     */
1759    @Override
1760    public Attribute put( String upId, AttributeType attributeType, String... values ) throws LdapException
1761    {
1762        if ( attributeType == null )
1763        {
1764            try
1765            {
1766                attributeType = getAttributeType( upId );
1767            }
1768            catch ( Exception e )
1769            {
1770                String message = I18n.err( I18n.ERR_13231_NO_VALID_AT_FOR_THIS_ID );
1771                LOG.error( message );
1772                throw new IllegalArgumentException( message, e );
1773            }
1774        }
1775        else
1776        {
1777            if ( !Strings.isEmpty( upId ) )
1778            {
1779                AttributeType tempAT = getAttributeType( upId );
1780
1781                if ( !tempAT.equals( attributeType ) )
1782                {
1783                    String message = I18n.err( I18n.ERR_13229_ID_INCOMPATIBLE_WITH_AT, upId, attributeType );
1784                    LOG.error( message );
1785                    throw new IllegalArgumentException( message );
1786                }
1787            }
1788            else
1789            {
1790                upId = getUpId( upId, attributeType );
1791            }
1792        }
1793
1794        Attribute attribute = new DefaultAttribute( upId, attributeType, values );
1795
1796        return attributes.put( attributeType.getOid(), attribute );
1797    }
1798
1799
1800    /**
1801     * {@inheritDoc}
1802     */
1803    @Override
1804    public Attribute put( String upId, AttributeType attributeType, Value... values ) throws LdapException
1805    {
1806        if ( attributeType == null )
1807        {
1808            try
1809            {
1810                attributeType = getAttributeType( upId );
1811            }
1812            catch ( Exception e )
1813            {
1814                String message = I18n.err( I18n.ERR_13231_NO_VALID_AT_FOR_THIS_ID );
1815                LOG.error( message );
1816                throw new IllegalArgumentException( message, e );
1817            }
1818        }
1819        else
1820        {
1821            if ( !Strings.isEmpty( upId ) )
1822            {
1823                AttributeType tempAT = getAttributeType( upId );
1824
1825                if ( !tempAT.equals( attributeType ) )
1826                {
1827                    String message = I18n.err( I18n.ERR_13229_ID_INCOMPATIBLE_WITH_AT, upId, attributeType );
1828                    LOG.error( message );
1829                    throw new IllegalArgumentException( message );
1830                }
1831            }
1832            else
1833            {
1834                upId = getUpId( upId, attributeType );
1835            }
1836        }
1837
1838        Attribute attribute = new DefaultAttribute( upId, attributeType, values );
1839
1840        return attributes.put( attributeType.getOid(), attribute );
1841    }
1842
1843
1844    /**
1845     * {@inheritDoc}
1846     */
1847    @Override
1848    public List<Attribute> remove( Attribute... attributes ) throws LdapException
1849    {
1850        List<Attribute> removedAttributes = new ArrayList<>();
1851
1852        if ( schemaManager == null )
1853        {
1854            for ( Attribute attribute : attributes )
1855            {
1856                if ( containsAttribute( attribute.getId() ) )
1857                {
1858                    this.attributes.remove( attribute.getId() );
1859                    removedAttributes.add( attribute );
1860                }
1861            }
1862        }
1863        else
1864        {
1865            for ( Attribute attribute : attributes )
1866            {
1867                AttributeType attributeType = attribute.getAttributeType();
1868
1869                if ( attributeType == null )
1870                {
1871                    String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED );
1872                    LOG.error( message );
1873                    throw new IllegalArgumentException( message );
1874                }
1875
1876                if ( this.attributes.containsKey( attributeType.getOid() ) )
1877                {
1878                    this.attributes.remove( attributeType.getOid() );
1879                    removedAttributes.add( attribute );
1880                }
1881            }
1882        }
1883
1884        return removedAttributes;
1885    }
1886
1887
1888    /**
1889     * {@inheritDoc}
1890     */
1891    @Override
1892    public boolean remove( AttributeType attributeType, byte[]... values ) throws LdapException
1893    {
1894        if ( attributeType == null )
1895        {
1896            return false;
1897        }
1898
1899        try
1900        {
1901            Attribute attribute = attributes.get( attributeType.getOid() );
1902
1903            if ( attribute == null )
1904            {
1905                // Can't remove values from a not existing attribute !
1906                return false;
1907            }
1908
1909            int nbOldValues = attribute.size();
1910
1911            // Remove the values
1912            attribute.remove( values );
1913
1914            if ( attribute.size() == 0 )
1915            {
1916                // No mare values, remove the attribute
1917                attributes.remove( attributeType.getOid() );
1918
1919                return true;
1920            }
1921
1922            return nbOldValues != attribute.size();
1923        }
1924        catch ( IllegalArgumentException iae )
1925        {
1926            LOG.error( I18n.err( I18n.ERR_13205_CANNOT_REMOVE_VAL_MISSING_ATTR, attributeType ) );
1927            return false;
1928        }
1929    }
1930
1931
1932    /**
1933     * {@inheritDoc}
1934     */
1935    @Override
1936    public boolean remove( AttributeType attributeType, String... values ) throws LdapException
1937    {
1938        if ( attributeType == null )
1939        {
1940            return false;
1941        }
1942
1943        try
1944        {
1945            Attribute attribute = attributes.get( attributeType.getOid() );
1946
1947            if ( attribute == null )
1948            {
1949                // Can't remove values from a not existing attribute !
1950                return false;
1951            }
1952
1953            int nbOldValues = attribute.size();
1954
1955            // Remove the values
1956            attribute.remove( values );
1957
1958            if ( attribute.size() == 0 )
1959            {
1960                // No mare values, remove the attribute
1961                attributes.remove( attributeType.getOid() );
1962
1963                return true;
1964            }
1965
1966            return nbOldValues != attribute.size();
1967        }
1968        catch ( IllegalArgumentException iae )
1969        {
1970            LOG.error( I18n.err( I18n.ERR_13205_CANNOT_REMOVE_VAL_MISSING_ATTR, attributeType ) );
1971            return false;
1972        }
1973    }
1974
1975
1976    /**
1977     * {@inheritDoc}
1978     */
1979    @Override
1980    public boolean remove( AttributeType attributeType, Value... values ) throws LdapException
1981    {
1982        if ( attributeType == null )
1983        {
1984            return false;
1985        }
1986
1987        try
1988        {
1989            Attribute attribute = attributes.get( attributeType.getOid() );
1990
1991            if ( attribute == null )
1992            {
1993                // Can't remove values from a not existing attribute !
1994                return false;
1995            }
1996
1997            int nbOldValues = attribute.size();
1998
1999            // Remove the values
2000            attribute.remove( values );
2001
2002            if ( attribute.size() == 0 )
2003            {
2004                // No mare values, remove the attribute
2005                attributes.remove( attributeType.getOid() );
2006
2007                return true;
2008            }
2009
2010            return nbOldValues != attribute.size();
2011        }
2012        catch ( IllegalArgumentException iae )
2013        {
2014            LOG.error( I18n.err( I18n.ERR_13205_CANNOT_REMOVE_VAL_MISSING_ATTR, attributeType ) );
2015            return false;
2016        }
2017    }
2018
2019
2020    /**
2021     * <p>
2022     * Removes the attribute with the specified AttributeTypes.
2023     * </p>
2024     * <p>
2025     * The removed attribute are returned by this method.
2026     * </p>
2027     * <p>
2028     * If there is no attribute with the specified AttributeTypes,
2029     * the return value is <code>null</code>.
2030     * </p>
2031     *
2032     * @param attributes the AttributeTypes to be removed
2033     */
2034    @Override
2035    public void removeAttributes( AttributeType... attributes )
2036    {
2037        if ( ( attributes == null ) || ( attributes.length == 0 ) || ( schemaManager == null ) )
2038        {
2039            return;
2040        }
2041
2042        for ( AttributeType attributeType : attributes )
2043        {
2044            if ( attributeType == null )
2045            {
2046                continue;
2047            }
2048
2049            this.attributes.remove( attributeType.getOid() );
2050        }
2051    }
2052
2053
2054    /**
2055     * {@inheritDoc}
2056     */
2057    @Override
2058    public void removeAttributes( String... attributes )
2059    {
2060        if ( attributes.length == 0 )
2061        {
2062            return;
2063        }
2064
2065        if ( schemaManager == null )
2066        {
2067            for ( String attribute : attributes )
2068            {
2069                Attribute attr = get( attribute );
2070
2071                if ( attr != null )
2072                {
2073                    this.attributes.remove( attr.getId() );
2074                }
2075                else
2076                {
2077                    if ( LOG.isWarnEnabled() )
2078                    {
2079                        LOG.warn( I18n.err( I18n.ERR_13218_AT_DOES_NOT_EXIST, attribute ) );
2080                    }
2081                }
2082            }
2083        }
2084        else
2085        {
2086            for ( String attribute : attributes )
2087            {
2088                AttributeType attributeType = null;
2089
2090                try
2091                {
2092                    attributeType = schemaManager.lookupAttributeTypeRegistry( attribute );
2093                }
2094                catch ( LdapException ne )
2095                {
2096                    if ( LOG.isWarnEnabled() )
2097                    {
2098                        LOG.warn( I18n.msg( I18n.MSG_13203_MISSING_ATTRIBUTE_IN_ENTRY, attribute ) );
2099                    }
2100
2101                    continue;
2102                }
2103
2104                this.attributes.remove( attributeType.getOid() );
2105            }
2106        }
2107    }
2108
2109
2110    /**
2111     * <p>
2112     * Removes the specified binary values from an attribute.
2113     * </p>
2114     * <p>
2115     * If at least one value is removed, this method returns <code>true</code>.
2116     * </p>
2117     * <p>
2118     * If there is no more value after having removed the values, the attribute
2119     * will be removed too.
2120     * </p>
2121     * <p>
2122     * If the attribute does not exist, nothing is done and the method returns
2123     * <code>false</code>
2124     * </p>
2125     *
2126     * @param upId The attribute ID
2127     * @param values the values to be removed
2128     * @return <code>true</code> if at least a value is removed, <code>false</code>
2129     * if not all the values have been removed or if the attribute does not exist.
2130     */
2131    @Override
2132    public boolean remove( String upId, byte[]... values ) throws LdapException
2133    {
2134        if ( Strings.isEmpty( upId ) )
2135        {
2136            if ( LOG.isInfoEnabled() )
2137            {
2138                LOG.info( I18n.err( I18n.ERR_13204_NULL_ATTRIBUTE_ID ) );
2139            }
2140
2141            return false;
2142        }
2143
2144        if ( schemaManager == null )
2145        {
2146            String id = getId( upId );
2147
2148            Attribute attribute = get( id );
2149
2150            if ( attribute == null )
2151            {
2152                // Can't remove values from a not existing attribute !
2153                return false;
2154            }
2155
2156            int nbOldValues = attribute.size();
2157
2158            // Remove the values
2159            attribute.remove( values );
2160
2161            if ( attribute.size() == 0 )
2162            {
2163                // No mare values, remove the attribute
2164                attributes.remove( id );
2165
2166                return true;
2167            }
2168
2169            return nbOldValues != attribute.size();
2170        }
2171        else
2172        {
2173            try
2174            {
2175                AttributeType attributeType = getAttributeType( upId );
2176
2177                return remove( attributeType, values );
2178            }
2179            catch ( LdapException ne )
2180            {
2181                LOG.error( I18n.err( I18n.ERR_13205_CANNOT_REMOVE_VAL_MISSING_ATTR, upId ) );
2182                return false;
2183            }
2184            catch ( IllegalArgumentException iae )
2185            {
2186                LOG.error( I18n.err( I18n.ERR_13206_CANNOT_REMOVE_VAL_BAD_ATTR, upId ) );
2187                return false;
2188            }
2189        }
2190
2191    }
2192
2193
2194    /**
2195     * <p>
2196     * Removes the specified String values from an attribute.
2197     * </p>
2198     * <p>
2199     * If at least one value is removed, this method returns <code>true</code>.
2200     * </p>
2201     * <p>
2202     * If there is no more value after having removed the values, the attribute
2203     * will be removed too.
2204     * </p>
2205     * <p>
2206     * If the attribute does not exist, nothing is done and the method returns
2207     * <code>false</code>
2208     * </p>
2209     *
2210     * @param upId The attribute ID
2211     * @param values the attributes to be removed
2212     * @return <code>true</code> if at least a value is removed, <code>false</code>
2213     * if not all the values have been removed or if the attribute does not exist.
2214     */
2215    @Override
2216    public boolean remove( String upId, String... values ) throws LdapException
2217    {
2218        if ( Strings.isEmpty( upId ) )
2219        {
2220            if ( LOG.isInfoEnabled() )
2221            {
2222                LOG.info( I18n.err( I18n.ERR_13204_NULL_ATTRIBUTE_ID ) );
2223            }
2224
2225            return false;
2226        }
2227
2228        if ( schemaManager == null )
2229        {
2230            String id = getId( upId );
2231
2232            Attribute attribute = get( id );
2233
2234            if ( attribute == null )
2235            {
2236                // Can't remove values from a not existing attribute !
2237                return false;
2238            }
2239
2240            int nbOldValues = attribute.size();
2241
2242            // Remove the values
2243            attribute.remove( values );
2244
2245            if ( attribute.size() == 0 )
2246            {
2247                // No mare values, remove the attribute
2248                attributes.remove( id );
2249
2250                return true;
2251            }
2252
2253            return nbOldValues != attribute.size();
2254        }
2255        else
2256        {
2257            try
2258            {
2259                AttributeType attributeType = getAttributeType( upId );
2260
2261                return remove( attributeType, values );
2262            }
2263            catch ( LdapException ne )
2264            {
2265                LOG.error( I18n.err( I18n.ERR_13205_CANNOT_REMOVE_VAL_MISSING_ATTR, upId ) );
2266                return false;
2267            }
2268            catch ( IllegalArgumentException iae )
2269            {
2270                LOG.error( I18n.err( I18n.ERR_13206_CANNOT_REMOVE_VAL_BAD_ATTR, upId ) );
2271                return false;
2272            }
2273        }
2274    }
2275
2276
2277    /**
2278     * <p>
2279     * Removes the specified values from an attribute.
2280     * </p>
2281     * <p>
2282     * If at least one value is removed, this method returns <code>true</code>.
2283     * </p>
2284     * <p>
2285     * If there is no more value after having removed the values, the attribute
2286     * will be removed too.
2287     * </p>
2288     * <p>
2289     * If the attribute does not exist, nothing is done and the method returns
2290     * <code>false</code>
2291     * </p>
2292     *
2293     * @param upId The attribute ID
2294     * @param values the attributes to be removed
2295     * @return <code>true</code> if at least a value is removed, <code>false</code>
2296     * if not all the values have been removed or if the attribute does not exist.
2297     */
2298    @Override
2299    public boolean remove( String upId, Value... values ) throws LdapException
2300    {
2301        if ( Strings.isEmpty( upId ) )
2302        {
2303            if ( LOG.isInfoEnabled() )
2304            {
2305                LOG.info( I18n.err( I18n.ERR_13204_NULL_ATTRIBUTE_ID ) );
2306            }
2307
2308            return false;
2309        }
2310
2311        if ( schemaManager == null )
2312        {
2313            String id = getId( upId );
2314
2315            Attribute attribute = get( id );
2316
2317            if ( attribute == null )
2318            {
2319                // Can't remove values from a not existing attribute !
2320                return false;
2321            }
2322
2323            int nbOldValues = attribute.size();
2324
2325            // Remove the values
2326            attribute.remove( values );
2327
2328            if ( attribute.size() == 0 )
2329            {
2330                // No mare values, remove the attribute
2331                attributes.remove( id );
2332
2333                return true;
2334            }
2335
2336            return nbOldValues != attribute.size();
2337        }
2338        else
2339        {
2340            try
2341            {
2342                AttributeType attributeType = getAttributeType( upId );
2343
2344                return remove( attributeType, values );
2345            }
2346            catch ( LdapException ne )
2347            {
2348                LOG.error( I18n.err( I18n.ERR_13205_CANNOT_REMOVE_VAL_MISSING_ATTR, upId ) );
2349                return false;
2350            }
2351            catch ( IllegalArgumentException iae )
2352            {
2353                LOG.error( I18n.err( I18n.ERR_13206_CANNOT_REMOVE_VAL_BAD_ATTR, upId ) );
2354                return false;
2355            }
2356        }
2357    }
2358
2359
2360    /**
2361     * Get this entry's Dn.
2362     *
2363     * @return The entry's Dn
2364     */
2365    @Override
2366    public Dn getDn()
2367    {
2368        return dn;
2369    }
2370
2371
2372    /**
2373     * {@inheritDoc}
2374     */
2375    @Override
2376    public void setDn( Dn dn )
2377    {
2378        this.dn = dn;
2379
2380        // Rehash the object
2381        rehash();
2382    }
2383
2384
2385    /**
2386     * {@inheritDoc}
2387     */
2388    @Override
2389    public void setDn( String dn ) throws LdapInvalidDnException
2390    {
2391        setDn( new Dn( dn ) );
2392    }
2393
2394
2395    /**
2396     * Remove all the attributes for this entry. The Dn is not reset
2397     */
2398    @Override
2399    public void clear()
2400    {
2401        attributes.clear();
2402    }
2403
2404
2405    /**
2406     * Returns an enumeration containing the zero or more attributes in the
2407     * collection. The behavior of the enumeration is not specified if the
2408     * attribute collection is changed.
2409     *
2410     * @return an enumeration of all contained attributes
2411     */
2412    @Override
2413    public Iterator<Attribute> iterator()
2414    {
2415        return attributes.values().iterator();
2416    }
2417
2418
2419    /**
2420     * Returns the number of attributes.
2421     *
2422     * @return the number of attributes
2423     */
2424    @Override
2425    public int size()
2426    {
2427        return attributes.size();
2428    }
2429
2430
2431    /**
2432     * This is the place where we serialize entries, and all theirs
2433     * elements.
2434     * <br>
2435     * The structure used to store the entry is the following :
2436     * <ul>
2437     *   <li>
2438     *     <b>[Dn]</b> : If it's null, stores an empty Dn
2439     *   </li>
2440     *   <li>
2441     *     <b>[attributes number]</b> : the number of attributes.
2442     *   </li>
2443     *   <li>
2444     *     <b>[attribute]*</b> : each attribute, if we have some
2445     *   </li>
2446     * </ul>
2447     *
2448     * {@inheritDoc}
2449     */
2450    @Override
2451    public void writeExternal( ObjectOutput out ) throws IOException
2452    {
2453        // First, the Dn
2454        if ( dn == null )
2455        {
2456            // Write an empty Dn
2457            Dn.EMPTY_DN.writeExternal( out );
2458        }
2459        else
2460        {
2461            // Write the Dn
2462            dn.writeExternal( out );
2463        }
2464
2465        // Then the attributes.
2466        // Store the attributes' nulber first
2467        out.writeInt( attributes.size() );
2468
2469        // Iterate through the keys.
2470        for ( Attribute attribute : attributes.values() )
2471        {
2472            // Store the attribute
2473            attribute.writeExternal( out );
2474        }
2475
2476        out.flush();
2477    }
2478
2479
2480    /**
2481     * {@inheritDoc}
2482     */
2483    @Override
2484    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
2485    {
2486        // Read the Dn
2487        dn = new Dn( schemaManager );
2488        dn.readExternal( in );
2489
2490        // Read the number of attributes
2491        int nbAttributes = in.readInt();
2492
2493        // Read the attributes
2494        for ( int i = 0; i < nbAttributes; i++ )
2495        {
2496            // Read each attribute
2497            Attribute attribute = new DefaultAttribute();
2498            attribute.readExternal( in );
2499
2500            if ( schemaManager != null )
2501            {
2502                try
2503                {
2504                    AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( attribute.getId() );
2505                    attribute.apply( attributeType );
2506
2507                    attributes.put( attributeType.getOid(), attribute );
2508                }
2509                catch ( LdapException le )
2510                {
2511                    String message = le.getLocalizedMessage();
2512                    LOG.error( message );
2513                    throw new IOException( message, le );
2514                }
2515            }
2516            else
2517            {
2518                attributes.put( attribute.getId(), attribute );
2519            }
2520        }
2521    }
2522
2523
2524    /**
2525     * Get the hash code of this ClientEntry. The Attributes will be sorted
2526     * before the comparison can be done.
2527     *
2528     * @see java.lang.Object#hashCode()
2529     * @return the instance's hash code
2530     */
2531    @Override
2532    public int hashCode()
2533    {
2534        if ( h == 0 )
2535        {
2536            rehash();
2537        }
2538
2539        return h;
2540    }
2541
2542
2543    /**
2544     * {@inheritDoc}
2545     */
2546    @Override
2547    public boolean hasObjectClass( String... objectClasses )
2548    {
2549        if ( ( objectClasses == null ) || ( objectClasses.length == 0 ) || ( objectClasses[0] == null ) )
2550        {
2551            return false;
2552        }
2553
2554        for ( String objectClass : objectClasses )
2555        {
2556            if ( schemaManager != null )
2557            {
2558                if ( !contains( objectClassAttributeType, objectClass ) )
2559                {
2560                    return false;
2561                }
2562            }
2563            else
2564            {
2565                if ( !contains( "objectclass", objectClass ) )
2566                {
2567                    return false;
2568                }
2569            }
2570        }
2571
2572        return true;
2573    }
2574
2575
2576    /**
2577     * {@inheritDoc}
2578     */
2579    @Override
2580    public boolean hasObjectClass( Attribute... objectClasses )
2581    {
2582        if ( ( objectClasses == null ) || ( objectClasses.length == 0 ) || ( objectClasses[0] == null ) )
2583        {
2584            return false;
2585        }
2586
2587        for ( Attribute objectClass : objectClasses )
2588        {
2589            // We have to check that we are checking the ObjectClass attributeType
2590            if ( !objectClass.getAttributeType().equals( objectClassAttributeType ) )
2591            {
2592                return false;
2593            }
2594
2595            Attribute attribute = attributes.get( objectClassAttributeType.getOid() );
2596
2597            if ( attribute == null )
2598            {
2599                // The entry does not have an ObjectClass attribute
2600                return false;
2601            }
2602
2603            for ( Value value : objectClass )
2604            {
2605                // Loop on all the values, and check if they are present
2606                if ( !attribute.contains( value.getString() ) )
2607                {
2608                    return false;
2609                }
2610            }
2611        }
2612
2613        return true;
2614    }
2615
2616
2617    /**
2618     * {@inheritDoc}
2619     */
2620    @Override
2621    public boolean isSchemaAware()
2622    {
2623        return schemaManager != null;
2624    }
2625
2626
2627    /**
2628     * @see Object#equals(Object)
2629     */
2630    @Override
2631    public boolean equals( Object o )
2632    {
2633        // Short circuit
2634        if ( this == o )
2635        {
2636            return true;
2637        }
2638
2639        if ( !( o instanceof Entry ) )
2640        {
2641            return false;
2642        }
2643
2644        Entry other = ( Entry ) o;
2645
2646        // Both Dn must be equal
2647        if ( dn == null )
2648        {
2649            if ( other.getDn() != null )
2650            {
2651                return false;
2652            }
2653        }
2654        else
2655        {
2656            if ( !dn.equals( other.getDn() ) )
2657            {
2658                return false;
2659            }
2660        }
2661
2662        // They must have the same number of attributes
2663        if ( size() != other.size() )
2664        {
2665            return false;
2666        }
2667
2668        // Each attribute must be equal
2669        for ( Attribute attribute : other )
2670        {
2671            if ( !attribute.equals( this.get( attribute.getId() ) ) )
2672            {
2673                return false;
2674            }
2675        }
2676
2677        return true;
2678    }
2679
2680
2681    /**
2682     * @see Object#toString()
2683     */
2684    @Override
2685    public String toString()
2686    {
2687        return toString( "" );
2688    }
2689
2690
2691    /**
2692     * {@inheritDoc}
2693     */
2694    @Override
2695    public String toString( String tabs )
2696    {
2697        StringBuilder sb = new StringBuilder();
2698
2699        sb.append( tabs ).append( "Entry\n" );
2700        sb.append( tabs ).append( "    dn" );
2701
2702        if ( dn.isSchemaAware() )
2703        {
2704            sb.append( "[n]" );
2705        }
2706
2707        sb.append( ": " );
2708        sb.append( dn.getName() );
2709        sb.append( '\n' );
2710
2711        // First dump the ObjectClass attribute
2712        if ( schemaManager != null )
2713        {
2714            // First dump the ObjectClass attribute
2715            if ( containsAttribute( objectClassAttributeType.getOid() ) )
2716            {
2717                Attribute objectClass = get( objectClassAttributeType );
2718
2719                sb.append( objectClass.toString( tabs + "    " ) );
2720            }
2721        }
2722        else
2723        {
2724            Attribute objectClass = get( "objectclass" );
2725
2726            if ( objectClass != null )
2727            {
2728                sb.append( objectClass.toString( tabs + "    " ) );
2729            }
2730        }
2731
2732        sb.append( '\n' );
2733
2734        if ( attributes.size() != 0 )
2735        {
2736            for ( Attribute attribute : attributes.values() )
2737            {
2738                String id = attribute.getId();
2739
2740                if ( schemaManager != null )
2741                {
2742                    AttributeType attributeType = schemaManager.getAttributeType( id );
2743
2744                    if ( attributeType == null )
2745                    {
2746                        sb.append( tabs ).append( "id: " ).append( id );
2747                    }
2748                    else if ( !attributeType.equals( objectClassAttributeType ) )
2749                    {
2750                        sb.append( attribute.toString( tabs + "    " ) );
2751                        sb.append( '\n' );
2752                    }
2753                }
2754                else
2755                {
2756                    if ( !id.equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT )
2757                        && !id.equals( SchemaConstants.OBJECT_CLASS_AT_OID ) )
2758                    {
2759                        sb.append( attribute.toString( tabs + "    " ) );
2760                        sb.append( '\n' );
2761                    }
2762                }
2763            }
2764        }
2765
2766        return sb.toString();
2767    }
2768}