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