001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.types;
018
019import java.io.BufferedWriter;
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.LinkedHashMap;
028import java.util.LinkedHashSet;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033
034import org.forgerock.i18n.LocalizableMessage;
035import org.forgerock.i18n.LocalizableMessageBuilder;
036import org.forgerock.i18n.LocalizedIllegalArgumentException;
037import org.forgerock.i18n.slf4j.LocalizedLogger;
038import org.forgerock.opendj.ldap.AVA;
039import org.forgerock.opendj.ldap.AttributeDescription;
040import org.forgerock.opendj.ldap.ByteSequence;
041import org.forgerock.opendj.ldap.ByteSequenceReader;
042import org.forgerock.opendj.ldap.ByteString;
043import org.forgerock.opendj.ldap.ByteStringBuilder;
044import org.forgerock.opendj.ldap.DN;
045import org.forgerock.opendj.ldap.DecodeException;
046import org.forgerock.opendj.ldap.RDN;
047import org.forgerock.opendj.ldap.ResultCode;
048import org.forgerock.opendj.ldap.SearchScope;
049import org.forgerock.opendj.ldap.schema.AttributeType;
050import org.forgerock.opendj.ldap.schema.MatchingRule;
051import org.forgerock.opendj.ldap.schema.ObjectClassType;
052import org.opends.server.api.CompressedSchema;
053import org.opends.server.api.ProtocolElement;
054import org.opends.server.api.plugin.PluginResult;
055import org.opends.server.core.DirectoryServer;
056import org.opends.server.core.PluginConfigManager;
057import org.opends.server.core.SubentryManager;
058import org.opends.server.types.SubEntry.CollectiveConflictBehavior;
059import org.opends.server.util.LDIFException;
060import org.opends.server.util.LDIFWriter;
061
062import static org.forgerock.opendj.ldap.ResultCode.*;
063import static org.opends.messages.CoreMessages.*;
064import static org.opends.messages.UtilityMessages.*;
065import static org.opends.server.config.ConfigConstants.*;
066import static org.opends.server.util.CollectionUtils.*;
067import static org.opends.server.util.LDIFWriter.*;
068import static org.opends.server.util.ServerConstants.*;
069import static org.opends.server.util.StaticUtils.*;
070
071/**
072 * This class defines a data structure for a Directory Server entry.
073 * It includes a DN and a set of attributes.
074 * <BR><BR>
075 * The entry also contains a volatile attachment object, which should
076 * be used to associate the entry with a special type of object that
077 * is based on its contents.  For example, if the entry holds access
078 * control information, then the attachment might be an object that
079 * contains a representation of that access control definition in a
080 * more useful form.  This is only useful if the entry is to be
081 * cached, since the attachment may be accessed if the entry is
082 * retrieved from the cache, but if the entry is retrieved from the
083 * backend repository it cannot be guaranteed to contain any
084 * attachment (and in most cases will not).  This attachment is
085 * volatile in that it is not always guaranteed to be present, it may
086 * be removed or overwritten at any time, and it will be invalidated
087 * and removed if the entry is altered in any way.
088 */
089@org.opends.server.types.PublicAPI(
090     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
091     mayInstantiate=true,
092     mayExtend=false,
093     mayInvoke=true)
094public class Entry
095       implements ProtocolElement
096{
097  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
098
099  /** The set of operational attributes for this entry. */
100  private Map<AttributeType,List<Attribute>> operationalAttributes;
101
102  /** The set of user attributes for this entry. */
103  private Map<AttributeType,List<Attribute>> userAttributes;
104
105  /**
106   * The set of suppressed real attributes for this entry. It contains real
107   * attributes that have been overridden by virtual attributes.
108   */
109  private final Map<AttributeType, List<Attribute>> suppressedAttributes = new LinkedHashMap<>();
110
111  /** The set of objectclasses for this entry. */
112  private Map<ObjectClass,String> objectClasses;
113
114  private Attribute objectClassAttribute;
115
116  /** The DN for this entry. */
117  private DN dn;
118
119  /**
120   * A generic attachment that may be used to associate this entry with some
121   * other object.
122   */
123  private transient Object attachment;
124
125  /** The schema used to govern this entry. */
126  private final Schema schema;
127
128
129
130  /**
131   * Creates a new entry with the provided information.
132   *
133   * @param  dn                     The distinguished name for this
134   *                                entry.
135   * @param  objectClasses          The set of objectclasses for this
136   *                                entry as a mapping between the
137   *                                objectclass and the name to use to
138   *                                reference it.
139   * @param  userAttributes         The set of user attributes for
140   *                                this entry as a mapping between
141   *                                the attribute type and the list of
142   *                                attributes with that type.
143   * @param  operationalAttributes  The set of operational attributes
144   *                                for this entry as a mapping
145   *                                between the attribute type and the
146   *                                list of attributes with that type.
147   */
148  public Entry(DN dn, Map<ObjectClass,String> objectClasses,
149               Map<AttributeType,List<Attribute>> userAttributes,
150               Map<AttributeType,List<Attribute>> operationalAttributes)
151  {
152    schema = DirectoryServer.getSchema();
153
154    setDN(dn);
155
156    this.objectClasses = newMapIfNull(objectClasses);
157    this.userAttributes = newMapIfNull(userAttributes);
158    this.operationalAttributes = newMapIfNull(operationalAttributes);
159  }
160
161  /**
162   * Returns a new Map if the passed in Map is null.
163   *
164   * @param <K>
165   *          the type of the key
166   * @param <V>
167   *          the type of the value
168   * @param map
169   *          the map to test
170   * @return a new Map if the passed in Map is null.
171   */
172  private <K, V> Map<K, V> newMapIfNull(Map<K, V> map)
173  {
174    if (map != null)
175    {
176      return map;
177    }
178    return new HashMap<>();
179  }
180
181
182
183  /**
184   * Retrieves the distinguished name for this entry.
185   *
186   * @return  The distinguished name for this entry.
187   */
188  public DN getName()
189  {
190    return dn;
191  }
192
193
194
195  /**
196   * Specifies the distinguished name for this entry.
197   *
198   * @param  dn  The distinguished name for this entry.
199   */
200  public void setDN(DN dn)
201  {
202    if (dn == null)
203    {
204      this.dn = DN.rootDN();
205    }
206    else
207    {
208      this.dn = dn;
209    }
210
211    attachment = null;
212  }
213
214
215
216  /**
217   * Retrieves the set of objectclasses defined for this entry.  The
218   * caller should be allowed to modify the contents of this list, but
219   * if it does then it should also invalidate the attachment.
220   *
221   * @return  The set of objectclasses defined for this entry.
222   */
223  public Map<ObjectClass,String> getObjectClasses()
224  {
225    return objectClasses;
226  }
227
228
229
230  /**
231   * Indicates whether this entry has the specified objectclass.
232   *
233   * @param  objectClass  The objectclass for which to make the
234   *                      determination.
235   *
236   * @return  <CODE>true</CODE> if this entry has the specified
237   *          objectclass, or <CODE>false</CODE> if not.
238   */
239  public boolean hasObjectClass(ObjectClass objectClass)
240  {
241    return objectClasses.containsKey(objectClass);
242  }
243
244
245
246  /**
247   * Retrieves the structural objectclass for this entry.
248   *
249   * @return  The structural objectclass for this entry, or
250   *          <CODE>null</CODE> if there is none for some reason.  If
251   *          there are multiple structural classes in the entry, then
252   *          the first will be returned.
253   */
254  public ObjectClass getStructuralObjectClass()
255  {
256    ObjectClass structuralClass = null;
257
258    for (ObjectClass oc : objectClasses.keySet())
259    {
260      if (oc.getObjectClassType() == ObjectClassType.STRUCTURAL)
261      {
262        if (structuralClass == null)
263        {
264          structuralClass = oc;
265        }
266        else if (oc.isDescendantOf(structuralClass))
267        {
268          structuralClass = oc;
269        }
270      }
271    }
272
273    return structuralClass;
274  }
275
276
277
278  /**
279   * Adds the provided objectClass to this entry.
280   *
281   * @param  oc The objectClass to add to this entry.
282   *
283   * @throws  DirectoryException  If a problem occurs while attempting
284   *                              to add the objectclass to this
285   *                              entry.
286   */
287  public void addObjectClass(ObjectClass oc)
288         throws DirectoryException
289  {
290    attachment = null;
291
292    if (objectClasses.containsKey(oc))
293    {
294      LocalizableMessage message = ERR_ENTRY_ADD_DUPLICATE_OC.get(oc.getNameOrOID(), dn);
295      throw new DirectoryException(OBJECTCLASS_VIOLATION, message);
296    }
297
298    objectClasses.put(oc, oc.getNameOrOID());
299  }
300
301
302
303  /**
304   * Retrieves the entire set of attributes for this entry.  This will
305   * include both user and operational attributes.  The caller must
306   * not modify the contents of this list.  Also note that this method
307   * is less efficient than calling either (or both)
308   * <CODE>getUserAttributes</CODE> or
309   * <CODE>getOperationalAttributes</CODE>, so it should only be used
310   * when calls to those methods are not appropriate.
311   *
312   * @return  The entire set of attributes for this entry.
313   */
314  public List<Attribute> getAttributes()
315  {
316    // Estimate the size.
317    int size = userAttributes.size() + operationalAttributes.size();
318
319    final List<Attribute> attributes = new ArrayList<>(size);
320    for (List<Attribute> attrs : userAttributes.values())
321    {
322      attributes.addAll(attrs);
323    }
324    for (List<Attribute> attrs : operationalAttributes.values())
325    {
326      attributes.addAll(attrs);
327    }
328    return attributes;
329  }
330
331  /**
332   * Retrieves the entire set of user (i.e., non-operational)
333   * attributes for this entry.  The caller should be allowed to
334   * modify the contents of this list, but if it does then it should
335   * also invalidate the attachment.
336   *
337   * @return  The entire set of user attributes for this entry.
338   */
339  public Map<AttributeType,List<Attribute>> getUserAttributes()
340  {
341    return userAttributes;
342  }
343
344
345
346  /**
347   * Retrieves the entire set of operational attributes for this
348   * entry.  The caller should be allowed to modify the contents of
349   * this list, but if it does then it should also invalidate the
350   * attachment.
351   *
352   * @return  The entire set of operational attributes for this entry.
353   */
354  public Map<AttributeType,List<Attribute>> getOperationalAttributes()
355  {
356    return operationalAttributes;
357  }
358
359
360
361  /**
362   * Retrieves an attribute holding the objectclass information for
363   * this entry.  The returned attribute must not be altered.
364   *
365   * @return  An attribute holding the objectclass information for
366   *          this entry, or <CODE>null</CODE> if it does not have any
367   *          objectclass information.
368   */
369  public Attribute getObjectClassAttribute()
370  {
371    if (objectClasses == null || objectClasses.isEmpty())
372    {
373      return null;
374    }
375
376    if(objectClassAttribute == null)
377    {
378      AttributeType ocType = DirectoryServer.getObjectClassAttributeType();
379      AttributeBuilder builder = new AttributeBuilder(ocType, ATTR_OBJECTCLASS);
380      builder.addAllStrings(objectClasses.values());
381      objectClassAttribute = builder.toAttribute();
382    }
383
384    return objectClassAttribute;
385  }
386
387
388
389  /**
390   * Indicates whether this entry contains the specified attribute.
391   * Any subordinate attribute of the specified attribute will also be
392   * used in the determination.
393   *
394   * @param attributeType
395   *          The attribute type for which to make the determination.
396   * @return <CODE>true</CODE> if this entry contains the specified
397   *         attribute, or <CODE>false</CODE> if not.
398   */
399  public boolean hasAttribute(AttributeType attributeType)
400  {
401    return hasAttribute(AttributeDescription.create(attributeType), true);
402  }
403
404
405  /**
406   * Indicates whether this entry contains the specified attribute.
407   *
408   * @param  attributeType       The attribute type for which to
409   *                             make the determination.
410   * @param  includeSubordinates Whether to include any subordinate
411   *                             attributes of the attribute type
412   *                             being retrieved.
413   *
414   * @return  <CODE>true</CODE> if this entry contains the specified
415   *          attribute, or <CODE>false</CODE> if not.
416   */
417  public boolean hasAttribute(AttributeType attributeType,
418                              boolean includeSubordinates)
419  {
420    return hasAttribute(AttributeDescription.create(attributeType), includeSubordinates);
421  }
422
423
424
425  /**
426   * Indicates whether this entry contains the specified attribute
427   * with all of the options in the provided set. Any subordinate
428   * attribute of the specified attribute will also be used in the
429   * determination.
430   *
431   * @param attributeDescription
432   *          The attribute description for which to make the determination.
433   * @return <CODE>true</CODE> if this entry contains the specified
434   *         attribute, or <CODE>false</CODE> if not.
435   */
436  public boolean hasAttribute(AttributeDescription attributeDescription)
437  {
438    return hasAttribute(attributeDescription, true);
439  }
440
441  /**
442   * Indicates whether this entry contains the specified attribute with all of the options in the
443   * provided set.
444   *
445   * @param attributeDescription
446   *          The attribute description for which to make the determination.
447   * @param includeSubordinates
448   *          Whether to include any subordinate attributes of the attribute type being retrieved.
449   * @return <CODE>true</CODE> if this entry contains the specified attribute, or <CODE>false</CODE>
450   *         if not.
451   */
452  public boolean hasAttribute(AttributeDescription attributeDescription, boolean includeSubordinates)
453  {
454    AttributeType attributeType = attributeDescription.getAttributeType();
455
456    // Handle object class.
457    if (attributeType.isObjectClass())
458    {
459      return !objectClasses.isEmpty() && !attributeDescription.hasOptions();
460    }
461
462    if (!includeSubordinates)
463    {
464      // It's possible that there could be an attribute without any
465      // values, which we should treat as not having the requested attribute.
466      Attribute attribute = getExactAttribute(attributeDescription);
467      return attribute != null && !attribute.isEmpty();
468    }
469
470    // Check all matching attributes.
471    List<Attribute> attributes = getAttributes(attributeType);
472    if (attributes != null)
473    {
474      for (Attribute attribute : attributes)
475      {
476        // It's possible that there could be an attribute without any
477        // values, which we should treat as not having the requested attribute.
478        if (!attribute.isEmpty() && attribute.getAttributeDescription().isSubTypeOf(attributeDescription))
479        {
480          return true;
481        }
482      }
483    }
484
485    // Check sub-types.
486    for (AttributeType subType : schema.getSubTypes(attributeType))
487    {
488      attributes = getAttributes(subType);
489      if (attributes != null)
490      {
491        for (Attribute attribute : attributes)
492        {
493          // It's possible that there could be an attribute without any values,
494          // which we should treat as not having the requested attribute.
495          if (!attribute.isEmpty() && attribute.getAttributeDescription().isSubTypeOf(attributeDescription))
496          {
497            return true;
498          }
499        }
500      }
501    }
502
503    return false;
504  }
505
506  /**
507   * Returns the attributes Map corresponding to the operational status of the
508   * supplied attribute type.
509   *
510   * @param attrType
511   *          the attribute type
512   * @return the user of operational attributes Map
513   */
514  private Map<AttributeType, List<Attribute>> getUserOrOperationalAttributes(
515      AttributeType attrType)
516  {
517    if (attrType.isOperational())
518    {
519      return operationalAttributes;
520    }
521    return userAttributes;
522  }
523
524  /**
525   * Return the List of attributes for the passed in attribute type.
526   *
527   * @param attrType
528   *          the attribute type
529   * @return the List of user or operational attributes
530   */
531  private List<Attribute> getAttributes(AttributeType attrType)
532  {
533    return getUserOrOperationalAttributes(attrType).get(attrType);
534  }
535
536  /**
537   * Puts the supplied List of attributes for the passed in attribute type into
538   * the map of attributes.
539   *
540   * @param attrType
541   *          the attribute type
542   * @param attributes
543   *          the List of user or operational attributes to put
544   */
545  private void putAttributes(AttributeType attrType, List<Attribute> attributes)
546  {
547    getUserOrOperationalAttributes(attrType).put(attrType, attributes);
548  }
549
550  /**
551   * Removes the List of attributes for the passed in attribute type from the
552   * map of attributes.
553   *
554   * @param attrType
555   *          the attribute type
556   */
557  private void removeAttributes(AttributeType attrType)
558  {
559    getUserOrOperationalAttributes(attrType).remove(attrType);
560  }
561
562  /**
563   * Retrieves the requested attribute element(s) for the specified
564   * attribute type. The list returned may include multiple elements
565   * if the same attribute exists in the entry multiple times with
566   * different sets of options. It may also include any subordinate
567   * attributes of the attribute being retrieved.
568   *
569   * @param attributeType
570   *          The attribute type to retrieve.
571   * @return The requested attribute element(s) for the specified
572   *         attribute type, or an empty list if the specified
573   *         attribute type is not present in this entry.
574   */
575  public List<Attribute> getAttribute(AttributeType attributeType)
576  {
577    return getAttribute(attributeType, true);
578  }
579
580
581  /**
582   * Retrieves the requested attribute element(s) for the specified
583   * attribute type.  The list returned may include multiple elements
584   * if the same attribute exists in the entry multiple times with
585   * different sets of options.
586   *
587   * @param  attributeType       The attribute type to retrieve.
588   * @param  includeSubordinates Whether to include any subordinate
589   *                             attributes of the attribute type
590   *                             being retrieved.
591   *
592   * @return  The requested attribute element(s) for the specified
593   *          attribute type, or an empty list if the specified
594   *          attribute type is not present in this entry.
595   */
596  public List<Attribute> getAttribute(AttributeType attributeType,
597                                      boolean includeSubordinates)
598  {
599    if (includeSubordinates && !attributeType.isObjectClass())
600    {
601      List<Attribute> attributes = new LinkedList<>();
602      addAllIfNotNull(attributes, userAttributes.get(attributeType));
603      addAllIfNotNull(attributes, operationalAttributes.get(attributeType));
604
605      for (AttributeType at : schema.getSubTypes(attributeType))
606      {
607        addAllIfNotNull(attributes, userAttributes.get(at));
608        addAllIfNotNull(attributes, operationalAttributes.get(at));
609      }
610
611      return attributes;
612    }
613
614    List<Attribute> attributes = userAttributes.get(attributeType);
615    if (attributes != null)
616    {
617      return attributes;
618    }
619    attributes = operationalAttributes.get(attributeType);
620    if (attributes != null)
621    {
622      return attributes;
623    }
624    if (attributeType.isObjectClass() && !objectClasses.isEmpty())
625    {
626      return newArrayList(getObjectClassAttribute());
627    }
628    return Collections.emptyList();
629  }
630
631  /**
632   * Add to the destination all the elements from a non null source .
633   *
634   * @param dest
635   *          the destination where to add
636   * @param source
637   *          the source with the elements to be added
638   */
639  private void addAllIfNotNull(List<Attribute> dest, List<Attribute> source)
640  {
641    if (source != null)
642    {
643      dest.addAll(source);
644    }
645  }
646
647
648
649  /**
650   * Retrieves the requested attribute element(s) for the attribute
651   * with the specified name or OID.  The list returned may include
652   * multiple elements if the same attribute exists in the entry
653   * multiple times with different sets of options. It may also
654   * include any subordinate attributes of the attribute being
655   * retrieved.
656   * <BR><BR>
657   * Note that this method should only be used in cases in which the
658   * Directory Server schema has no reference of an attribute type
659   * with the specified name.  It is not as accurate or efficient as
660   * the version of this method that takes an
661   * <CODE>AttributeType</CODE> argument.
662   *
663   * @param  lowerName  The name or OID of the attribute to return,
664   *                    formatted in all lowercase characters.
665   *
666   * @return  The requested attribute element(s) for the specified
667   *          attribute type, or an empty list if the specified
668   *          attribute type is not present in this entry.
669   */
670  public List<Attribute> getAttribute(String lowerName)
671  {
672    for (AttributeType attr : userAttributes.keySet())
673    {
674      if (attr.hasNameOrOID(lowerName))
675      {
676        return getAttribute(attr, true);
677      }
678    }
679
680    for (AttributeType attr : operationalAttributes.keySet())
681    {
682      if (attr.hasNameOrOID(lowerName))
683      {
684        return getAttribute(attr, true);
685      }
686    }
687
688    if (lowerName.equals(OBJECTCLASS_ATTRIBUTE_TYPE_NAME)
689        && !objectClasses.isEmpty())
690    {
691      return newLinkedList(getObjectClassAttribute());
692    }
693    return Collections.emptyList();
694  }
695
696  /**
697   * Retrieves the requested attribute element(s) for the specified
698   * attribute description.  The list returned may include multiple elements
699   * if the same attribute exists in the entry multiple times with
700   * different sets of options. It may also include any subordinate
701   * attributes of the attribute being retrieved.
702   *
703   * @param  attributeDescription The attribute description to retrieve.
704   * @return  The requested attribute element(s) for the specified
705   *          attribute type, or an empty list if the specified
706   *          attribute type is not present in this entry with the
707   *          provided set of options.
708   */
709  public List<Attribute> getAttribute(AttributeDescription attributeDescription)
710  {
711    AttributeType attributeType = attributeDescription.getAttributeType();
712    List<Attribute> attributes = new LinkedList<>();
713    if (!attributeType.isObjectClass())
714    {
715      addAllIfNotNull(attributes, userAttributes.get(attributeType));
716      addAllIfNotNull(attributes, operationalAttributes.get(attributeType));
717
718      for (AttributeType at : schema.getSubTypes(attributeType))
719      {
720        addAllIfNotNull(attributes, userAttributes.get(at));
721        addAllIfNotNull(attributes, operationalAttributes.get(at));
722      }
723    }
724    else
725    {
726      List<Attribute> attrs = userAttributes.get(attributeType);
727      if (attrs == null)
728      {
729        attrs = operationalAttributes.get(attributeType);
730        if (attrs == null)
731        {
732          if (attributeType.isObjectClass()
733              && !objectClasses.isEmpty()
734              && !attributeDescription.hasOptions())
735          {
736            attributes.add(getObjectClassAttribute());
737            return attributes;
738          }
739          return Collections.emptyList();
740        }
741      }
742      attributes.addAll(attrs);
743    }
744
745    onlyKeepAttributesWithAllOptions(attributes, attributeDescription);
746
747    return attributes;
748  }
749
750  /**
751   * Returns a parser for the named attribute contained in this entry.
752   * <p>
753   * The attribute description will be decoded using the schema associated
754   * with this entry (usually the default schema).
755   *
756   * @param attributeDescription
757   *            The name of the attribute to be parsed.
758   * @return A parser for the named attribute.
759   * @throws LocalizedIllegalArgumentException
760   *             If {@code attributeDescription} could not be decoded using
761   *             the schema associated with this entry.
762   * @throws NullPointerException
763   *             If {@code attributeDescription} was {@code null}.
764   */
765  public AttributeParser parseAttribute(String attributeDescription)
766      throws LocalizedIllegalArgumentException, NullPointerException
767  {
768    final List<Attribute> attribute = getAttribute(attributeDescription);
769    return AttributeParser.parseAttribute(!attribute.isEmpty() ? attribute.get(0) : null);
770  }
771
772
773
774  /**
775   * Indicates whether this entry contains the specified user
776   * attribute.
777   *
778   * @param attributeType
779   *          The attribute type for which to make the determination.
780   * @return <CODE>true</CODE> if this entry contains the specified
781   *         user attribute, or <CODE>false</CODE> if not.
782   */
783  public boolean hasUserAttribute(AttributeType attributeType)
784  {
785    return hasAttribute(userAttributes, attributeType);
786  }
787
788
789
790  /**
791   * Retrieves the requested user attribute element(s) for the
792   * specified attribute type.  The list returned may include multiple
793   * elements if the same attribute exists in the entry multiple times
794   * with different sets of options.
795   *
796   * @param  attributeType  The attribute type to retrieve.
797   *
798   * @return  The requested attribute element(s) for the specified
799   *          attribute type, or an empty list if there is no such
800   *          user attribute.
801   */
802  public List<Attribute> getUserAttribute(AttributeType attributeType)
803  {
804    return getAttribute(attributeType, userAttributes);
805  }
806
807  /**
808   * Returns the List of attributes for a given attribute type.
809   *
810   * @param attributeType
811   *          the attribute type to be looked for
812   * @param attrs
813   *          the attributes Map where to find the attributes
814   * @return the List of attributes
815   */
816  private List<Attribute> getAttribute(AttributeType attributeType,
817      Map<AttributeType, List<Attribute>> attrs)
818  {
819    List<Attribute> attributes = new LinkedList<>();
820    addAllIfNotNull(attributes, attrs.get(attributeType));
821    for (AttributeType at : schema.getSubTypes(attributeType))
822    {
823      addAllIfNotNull(attributes, attrs.get(at));
824    }
825    return attributes;
826  }
827
828
829
830  /**
831   * Returns the List of attributes for a given attribute type having all the
832   * required options.
833   *
834   * @param attributeType
835   *          the attribute type to be looked for
836   * @param options
837   *          the options that must all be present
838   * @param attrs
839   *          the attributes Map where to find the attributes
840   * @return the filtered List of attributes
841   */
842  private List<Attribute> getAttribute(AttributeDescription attributeDescription,
843      Map<AttributeType, List<Attribute>> attrs)
844  {
845    AttributeType attributeType = attributeDescription.getAttributeType();
846    List<Attribute> attributes = new LinkedList<>();
847    addAllIfNotNull(attributes, attrs.get(attributeType));
848
849    for (AttributeType at : schema.getSubTypes(attributeType))
850    {
851      addAllIfNotNull(attributes, attrs.get(at));
852    }
853
854    onlyKeepAttributesWithAllOptions(attributes, attributeDescription);
855    return attributes;
856  }
857
858  /**
859   * Removes all the attributes that do not have all the supplied options.
860   *
861   * @param attributes
862   *          the attributes to filter.
863   * @param attributeDescription
864   *          contains the options to look for
865   */
866  private void onlyKeepAttributesWithAllOptions(List<Attribute> attributes, AttributeDescription attributeDescription)
867  {
868    Iterator<Attribute> iterator = attributes.iterator();
869    while (iterator.hasNext())
870    {
871      Attribute a = iterator.next();
872      if (!a.getAttributeDescription().isSubTypeOf(attributeDescription))
873      {
874        iterator.remove();
875      }
876    }
877  }
878
879  /**
880   * Indicates whether this entry contains the specified operational
881   * attribute.
882   *
883   * @param  attributeType  The attribute type for which to make the
884   *                        determination.
885   *
886   * @return  <CODE>true</CODE> if this entry contains the specified
887   *          operational attribute, or <CODE>false</CODE> if not.
888   */
889  public boolean hasOperationalAttribute(AttributeType attributeType)
890  {
891    return hasAttribute(operationalAttributes, attributeType);
892  }
893
894  private boolean hasAttribute(Map<AttributeType, List<Attribute>> attributes, AttributeType attributeType)
895  {
896    if (attributes.containsKey(attributeType))
897    {
898      return true;
899    }
900
901    for (AttributeType at : schema.getSubTypes(attributeType))
902    {
903      if (attributes.containsKey(at))
904      {
905        return true;
906      }
907    }
908
909    return false;
910  }
911
912  /**
913   * Retrieves the requested operational attribute element(s) for the
914   * specified attribute type.  The list returned may include multiple
915   * elements if the same attribute exists in the entry multiple times
916   * with different sets of options.
917   *
918   * @param  attributeType  The attribute type to retrieve.
919   *
920   * @return  The requested attribute element(s) for the specified
921   *          attribute type, or an empty list if there is no such
922   *          operational attribute.
923   */
924  public List<Attribute> getOperationalAttribute(AttributeType attributeType)
925  {
926    return getAttribute(attributeType, operationalAttributes);
927  }
928
929
930
931
932  /**
933   * Retrieves the requested operational attribute element(s) for the
934   * specified attribute type.  The list returned may include multiple
935   * elements if the same attribute exists in the entry multiple times
936   * with different sets of options.
937   *
938   * @param  attributeDescription  The attribute description to retrieve.
939   *
940   * @return  The requested attribute element(s) for the specified
941   *          attribute type, or an empty list if there is no such
942   *          operational attribute with the specified set of options.
943   */
944  public List<Attribute> getOperationalAttribute(AttributeDescription attributeDescription)
945  {
946    return getAttribute(attributeDescription, operationalAttributes);
947  }
948
949
950
951  /**
952   * Puts the provided attribute in this entry.  If an attribute
953   * already exists with the provided type, it will be overwritten.
954   * Otherwise, a new attribute will be added.  Note that no
955   * validation will be performed.
956   *
957   * @param  attributeType  The attribute type for the set of
958   *                        attributes to add.
959   * @param  attributeList  The set of attributes to add for the given
960   *                        type.
961   */
962  public void putAttribute(AttributeType attributeType,
963                           List<Attribute> attributeList)
964  {
965    attachment = null;
966
967
968    // See if there is already a set of attributes with the specified
969    // type.  If so, then overwrite it.
970    List<Attribute> attrList = userAttributes.get(attributeType);
971    if (attrList != null)
972    {
973      userAttributes.put(attributeType, attributeList);
974      return;
975    }
976
977    attrList = operationalAttributes.get(attributeType);
978    if (attrList != null)
979    {
980      operationalAttributes.put(attributeType, attributeList);
981      return;
982    }
983
984    putAttributes(attributeType, attributeList);
985  }
986
987
988
989  /**
990   * Ensures that this entry contains the provided attribute and its
991   * values. If an attribute with the provided type already exists,
992   * then its attribute values will be merged.
993   * <p>
994   * This method handles object class additions but will not perform
995   * any object class validation. In particular, it will create
996   * default object classes when an object class is unknown.
997   * <p>
998   * This method implements LDAP modification add semantics, with the
999   * exception that it allows empty attributes to be added.
1000   *
1001   * @param attribute
1002   *          The attribute to add or merge with this entry.
1003   * @param duplicateValues
1004   *          A list to which any duplicate values will be added.
1005   */
1006  public void addAttribute(Attribute attribute, List<ByteString> duplicateValues)
1007  {
1008    setAttribute(attribute, duplicateValues, false /* merge */);
1009  }
1010
1011
1012
1013  /**
1014   * Puts the provided attribute into this entry. If an attribute with
1015   * the provided type and options already exists, then it will be
1016   * replaced. If the provided attribute is empty then any existing
1017   * attribute will be completely removed.
1018   * <p>
1019   * This method handles object class replacements but will not
1020   * perform any object class validation. In particular, it will
1021   * create default object classes when an object class is unknown.
1022   * <p>
1023   * This method implements LDAP modification replace semantics.
1024   *
1025   * @param attribute
1026   *          The attribute to replace in this entry.
1027   */
1028  public void replaceAttribute(Attribute attribute)
1029  {
1030    // There can never be duplicate values for a replace.
1031    setAttribute(attribute, null, true /* replace */);
1032  }
1033
1034
1035
1036  /**
1037   * Increments an attribute in this entry by the amount specified in
1038   * the provided attribute.
1039   *
1040   * @param attribute
1041   *          The attribute identifying the attribute to be increment
1042   *          and the amount it is to be incremented by. The attribute
1043   *          must contain a single value.
1044   * @throws DirectoryException
1045   *           If a problem occurs while attempting to increment the
1046   *           provided attribute. This may occur if the provided
1047   *           attribute was not single valued or if it could not be
1048   *           parsed as an integer of if the existing attribute
1049   *           values could not be parsed as integers.
1050   */
1051  public void incrementAttribute(Attribute attribute) throws DirectoryException
1052  {
1053    Attribute a = getExactAttribute(attribute.getAttributeDescription());
1054    if (a == null)
1055    {
1056      LocalizableMessage message = ERR_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE.get(
1057            attribute.getName());
1058      throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE, message);
1059    }
1060
1061    // Decode the increment.
1062    Iterator<ByteString> i = attribute.iterator();
1063    if (!i.hasNext())
1064    {
1065      LocalizableMessage message = ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT.get(
1066          attribute.getName());
1067      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1068    }
1069
1070    String incrementValue = i.next().toString();
1071    long increment;
1072    try
1073    {
1074      increment = Long.parseLong(incrementValue);
1075    }
1076    catch (NumberFormatException e)
1077    {
1078      LocalizableMessage message = ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT.get(
1079          attribute.getName());
1080      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1081    }
1082
1083    if (i.hasNext())
1084    {
1085      LocalizableMessage message = ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT.get(
1086          attribute.getName());
1087      throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
1088    }
1089
1090    // Increment each attribute value by the specified amount.
1091    AttributeBuilder builder = new AttributeBuilder(a, true);
1092
1093    for (ByteString v : a)
1094    {
1095      long currentValue;
1096      try
1097      {
1098        currentValue = Long.parseLong(v.toString());
1099      }
1100      catch (NumberFormatException e)
1101      {
1102        LocalizableMessage message = ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT.get(
1103            attribute.getName());
1104        throw new DirectoryException(
1105            ResultCode.CONSTRAINT_VIOLATION, message);
1106      }
1107
1108      long newValue = currentValue + increment;
1109      builder.add(String.valueOf(newValue));
1110    }
1111
1112    replaceAttribute(builder.toAttribute());
1113  }
1114
1115
1116
1117  /**
1118   * Removes all instances of the specified attribute type from this
1119   * entry, including any instances with options. If the provided
1120   * attribute type is the objectclass type, then all objectclass
1121   * values will be removed (but must be replaced for the entry to be
1122   * valid). If the specified attribute type is not present in this
1123   * entry, then this method will have no effect.
1124   *
1125   * @param attributeType
1126   *          The attribute type for the attribute to remove from this
1127   *          entry.
1128   * @return <CODE>true</CODE> if the attribute was found and
1129   *         removed, or <CODE>false</CODE> if it was not present in
1130   *         the entry.
1131   */
1132  public boolean removeAttribute(AttributeType attributeType)
1133  {
1134    attachment = null;
1135
1136    if (attributeType.isObjectClass())
1137    {
1138      objectClasses.clear();
1139      return true;
1140    }
1141    return userAttributes.remove(attributeType) != null
1142        || operationalAttributes.remove(attributeType) != null;
1143  }
1144
1145
1146
1147  /**
1148   * Ensures that this entry does not contain the provided attribute
1149   * values. If the provided attribute is empty, then all values of
1150   * the associated attribute type will be removed. Otherwise, only
1151   * the specified values will be removed.
1152   * <p>
1153   * This method handles object class deletions.
1154   * <p>
1155   * This method implements LDAP modification delete semantics.
1156   *
1157   * @param attribute
1158   *          The attribute containing the information to use to
1159   *          perform the removal.
1160   * @param missingValues
1161   *          A list to which any values contained in the provided
1162   *          attribute but not present in the entry will be added.
1163   * @return <CODE>true</CODE> if the attribute type was present and
1164   *         the specified values that were present were removed, or
1165   *         <CODE>false</CODE> if the attribute type was not
1166   *         present in the entry. If the attribute type was present
1167   *         but only contained some of the values in the provided
1168   *         attribute, then this method will return <CODE>true</CODE>
1169   *         but will add those values to the provided list.
1170   */
1171  public boolean removeAttribute(Attribute attribute,
1172      List<ByteString> missingValues)
1173  {
1174    attachment = null;
1175
1176    AttributeDescription attrDesc = attribute.getAttributeDescription();
1177    AttributeType attrType = attrDesc.getAttributeType();
1178    if (attrType.isObjectClass())
1179    {
1180      if (attribute.isEmpty())
1181      {
1182        objectClasses.clear();
1183        return true;
1184      }
1185
1186      boolean allSuccessful = true;
1187
1188      MatchingRule rule = attrType.getEqualityMatchingRule();
1189      for (ByteString v : attribute)
1190      {
1191        String ocName = toLowerName(rule, v);
1192
1193        boolean matchFound = false;
1194        for (ObjectClass oc : objectClasses.keySet())
1195        {
1196          if (oc.hasNameOrOID(ocName))
1197          {
1198            matchFound = true;
1199            objectClasses.remove(oc);
1200            break;
1201          }
1202        }
1203
1204        if (!matchFound)
1205        {
1206          allSuccessful = false;
1207          missingValues.add(v);
1208        }
1209      }
1210
1211      return allSuccessful;
1212    }
1213
1214    List<Attribute> attributes = getAttributes(attrType);
1215    if (attributes == null)
1216    {
1217      // There are no attributes with the same attribute type.
1218      for (ByteString v : attribute)
1219      {
1220        missingValues.add(v);
1221      }
1222      return false;
1223    }
1224
1225    // There are already attributes with the same attribute type.
1226    for (int i = 0; i < attributes.size(); i++)
1227    {
1228      Attribute a = attributes.get(i);
1229      if (a.getAttributeDescription().equals(attrDesc))
1230      {
1231        if (attribute.isEmpty())
1232        {
1233          // Remove the entire attribute.
1234          attributes.remove(i);
1235        }
1236        else
1237        {
1238          // Remove Specified values.
1239          AttributeBuilder builder = new AttributeBuilder(a);
1240          for (ByteString v : attribute)
1241          {
1242            if (!builder.remove(v))
1243            {
1244              missingValues.add(v);
1245            }
1246          }
1247
1248          // Remove / replace the attribute as necessary.
1249          if (!builder.isEmpty())
1250          {
1251            attributes.set(i, builder.toAttribute());
1252          }
1253          else
1254          {
1255            attributes.remove(i);
1256          }
1257        }
1258
1259        // If the attribute list is now empty remove it.
1260        if (attributes.isEmpty())
1261        {
1262          removeAttributes(attrType);
1263        }
1264
1265        return true;
1266      }
1267    }
1268
1269    // No matching attribute found.
1270    return false;
1271  }
1272
1273  private String toLowerName(MatchingRule rule, ByteString value)
1274  {
1275    try
1276    {
1277      return normalize(rule, value).toString();
1278    }
1279    catch (Exception e)
1280    {
1281      logger.traceException(e);
1282      return toLowerCase(value.toString());
1283    }
1284  }
1285
1286  /**
1287   * Indicates whether this entry contains the specified attribute value.
1288   *
1289   * @param attributeDescription
1290   *          The attribute description for the attribute.
1291   * @param value
1292   *          The value for the attribute.
1293   * @return {@code true} if this entry contains the specified attribute value, {@code false}
1294   *         otherwise.
1295   */
1296  public boolean hasValue(AttributeDescription attributeDescription, ByteString value)
1297  {
1298    Attribute attr = getExactAttribute(attributeDescription);
1299    return attr != null && attr.contains(value);
1300  }
1301
1302  /**
1303   * Indicates whether this entry contains the specified attribute value.
1304   *
1305   * @param attributeType
1306   *          The attribute type for the attribute.
1307   * @param value
1308   *          The value for the attribute.
1309   * @return {@code true} if this entry contains the specified attribute value, {@code false}
1310   *         otherwise.
1311   */
1312  public boolean hasValue(AttributeType attributeType, ByteString value)
1313  {
1314    for (Attribute a : getAttribute(attributeType))
1315    {
1316      if (!a.hasOptions() && a.contains(value))
1317      {
1318        return true;
1319      }
1320    }
1321    return false;
1322  }
1323
1324
1325
1326  /**
1327   * Applies the provided modification to this entry.  No schema
1328   * checking will be performed.
1329   *
1330   * @param  mod  The modification to apply to this entry.
1331   * @param  relaxConstraints indicates if the modification
1332   *                          constraints are relaxed to match
1333   *                          the ones of a set (add existing
1334   *                          value and delete absent value do not fail)
1335   *
1336   * @throws  DirectoryException  If a problem occurs while
1337   *                              attempting to apply the
1338   *                              modification. Note
1339   *                              that even if a problem occurs, then
1340   *                              the entry may have been altered in some way.
1341   */
1342  public void applyModification(Modification mod, boolean relaxConstraints)
1343         throws DirectoryException
1344  {
1345    Attribute     a = mod.getAttribute();
1346    AttributeType t = a.getAttributeDescription().getAttributeType();
1347
1348    if (t.isObjectClass())
1349    {
1350      applyModificationToObjectclass(mod, relaxConstraints);
1351    }
1352    else
1353    {
1354      applyModificationToNonObjectclass(mod, relaxConstraints);
1355    }
1356  }
1357
1358  private void applyModificationToObjectclass(Modification mod, boolean relaxConstraints) throws DirectoryException
1359  {
1360    Attribute a = mod.getAttribute();
1361
1362    Map<ObjectClass, String> ocs = new LinkedHashMap<>();
1363    for (ByteString v : a)
1364    {
1365      String ocName = v.toString();
1366      String lowerName = toLowerCase(ocName);
1367      ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
1368      ocs.put(oc, ocName);
1369    }
1370
1371    switch (mod.getModificationType().asEnum())
1372    {
1373    case ADD:
1374      for (ObjectClass oc : ocs.keySet())
1375      {
1376        if (objectClasses.containsKey(oc))
1377        {
1378          if (!relaxConstraints)
1379          {
1380            LocalizableMessage message = ERR_ENTRY_DUPLICATE_VALUES.get(a.getName());
1381            throw new DirectoryException(ATTRIBUTE_OR_VALUE_EXISTS, message);
1382          }
1383        }
1384        else
1385        {
1386          objectClasses.put(oc, ocs.get(oc));
1387        }
1388      }
1389      objectClassAttribute = null;
1390      break;
1391
1392    case DELETE:
1393      for (ObjectClass oc : ocs.keySet())
1394      {
1395        if (objectClasses.remove(oc) == null && !relaxConstraints)
1396        {
1397          LocalizableMessage message = ERR_ENTRY_NO_SUCH_VALUE.get(a.getName());
1398          throw new DirectoryException(NO_SUCH_ATTRIBUTE, message);
1399        }
1400      }
1401      objectClassAttribute = null;
1402      break;
1403
1404    case REPLACE:
1405      objectClasses = ocs;
1406      objectClassAttribute = null;
1407      break;
1408
1409    case INCREMENT:
1410      LocalizableMessage message = ERR_ENTRY_OC_INCREMENT_NOT_SUPPORTED.get();
1411      throw new DirectoryException(CONSTRAINT_VIOLATION, message);
1412
1413    default:
1414      message = ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE.get(mod.getModificationType());
1415      throw new DirectoryException(UNWILLING_TO_PERFORM, message);
1416    }
1417  }
1418
1419  private void applyModificationToNonObjectclass(Modification mod, boolean relaxConstraints) throws DirectoryException
1420  {
1421    Attribute a = mod.getAttribute();
1422    switch (mod.getModificationType().asEnum())
1423    {
1424    case ADD:
1425      List<ByteString> duplicateValues = new LinkedList<>();
1426      addAttribute(a, duplicateValues);
1427      if (!duplicateValues.isEmpty() && !relaxConstraints)
1428      {
1429        LocalizableMessage message = ERR_ENTRY_DUPLICATE_VALUES.get(a.getName());
1430        throw new DirectoryException(ATTRIBUTE_OR_VALUE_EXISTS, message);
1431      }
1432      break;
1433
1434    case DELETE:
1435      List<ByteString> missingValues = new LinkedList<>();
1436      removeAttribute(a, missingValues);
1437      if (!missingValues.isEmpty() && !relaxConstraints)
1438      {
1439        LocalizableMessage message = ERR_ENTRY_NO_SUCH_VALUE.get(a.getName());
1440        throw new DirectoryException(NO_SUCH_ATTRIBUTE, message);
1441      }
1442      break;
1443
1444    case REPLACE:
1445      replaceAttribute(a);
1446      break;
1447
1448    case INCREMENT:
1449      incrementAttribute(a);
1450      break;
1451
1452    default:
1453      LocalizableMessage message = ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE.get(mod.getModificationType());
1454      throw new DirectoryException(UNWILLING_TO_PERFORM, message);
1455    }
1456  }
1457
1458  /**
1459   * Applies the provided modification to this entry.  No schema
1460   * checking will be performed.
1461   *
1462   * @param  mod  The modification to apply to this entry.
1463   *
1464   * @throws  DirectoryException  If a problem occurs while attempting
1465   *                              to apply the modification.  Note
1466   *                              that even if a problem occurs, then
1467   *                              the entry may have been altered in some way.
1468   */
1469  public void applyModification(Modification mod) throws DirectoryException
1470  {
1471    applyModification(mod, false);
1472  }
1473
1474  /**
1475   * Applies all of the provided modifications to this entry.
1476   *
1477   * @param  mods  The modifications to apply to this entry.
1478   *
1479   * @throws  DirectoryException  If a problem occurs while attempting
1480   *                              to apply the modifications.  Note
1481   *                              that even if a problem occurs, then
1482   *                              the entry may have been altered in some way.
1483   */
1484  public void applyModifications(List<Modification> mods)
1485         throws DirectoryException
1486  {
1487    for (Modification m : mods)
1488    {
1489      applyModification(m);
1490    }
1491  }
1492
1493
1494
1495  /**
1496   * Indicates whether this entry conforms to the server's schema
1497   * requirements.  The checks performed by this method include:
1498   *
1499   * <UL>
1500   *   <LI>Make sure that all required attributes are present, either
1501   *       in the list of user or operational attributes.</LI>
1502   *   <LI>Make sure that all user attributes are allowed by at least
1503   *       one of the objectclasses.  The operational attributes will
1504   *       not be checked in this manner.</LI>
1505   *   <LI>Make sure that all single-valued attributes contained in
1506   *       the entry have only a single value.</LI>
1507   *   <LI>Make sure that the entry contains a single structural
1508   *       objectclass.</LI>
1509   *   <LI>Make sure that the entry complies with any defined name
1510   *       forms, DIT content rules, and DIT structure rules.</LI>
1511   * </UL>
1512   *
1513   * @param  parentEntry             The entry that is the immediate
1514   *                                 parent of this entry, which may
1515   *                                 be checked for DIT structure rule
1516   *                                 conformance.  This may be
1517   *                                 {@code null} if there is no
1518   *                                 parent or if it is unavailable
1519   *                                to the caller.
1520   * @param  parentProvided          Indicates whether the caller
1521   *                                 attempted to provide the parent.
1522   *                                 If not, then the parent entry
1523   *                                 will be loaded on demand if it is
1524   *                                 required.
1525   * @param  validateNameForms       Indicates whether to validate the
1526   *                                 entry against name form
1527   *                                 definitions.  This should only be
1528   *                                 {@code true} for add and modify
1529   *                                 DN operations, as well as for
1530   *                                 for imports.
1531   * @param  validateStructureRules  Indicates whether to validate the
1532   *                                 entry against DIT structure rule
1533   *                                 definitions.  This should only
1534   *                                 be {@code true} for add and
1535   *                                 modify DN operations.
1536   * @param  invalidReason           The buffer to which an
1537   *                                 explanation will be appended if
1538   *                                 this entry does not conform to
1539   *                                 the server's schema
1540   *                                 configuration.
1541   *
1542   * @return  {@code true} if this entry conforms to the server's
1543   *          schema requirements, or {@code false} if it does not.
1544   */
1545  public boolean conformsToSchema(Entry parentEntry,
1546                                  boolean parentProvided,
1547                                  boolean validateNameForms,
1548                                  boolean validateStructureRules,
1549                                  LocalizableMessageBuilder invalidReason)
1550  {
1551    // Get the structural objectclass for the entry.  If there isn't
1552    // one, or if there's more than one, then see if that's OK.
1553    AcceptRejectWarn structuralPolicy =
1554         DirectoryServer.getSingleStructuralObjectClassPolicy();
1555    ObjectClass structuralClass = null;
1556    boolean multipleOCErrorLogged = false;
1557    for (ObjectClass oc : objectClasses.keySet())
1558    {
1559      if (oc.getObjectClassType() == ObjectClassType.STRUCTURAL)
1560      {
1561        if (structuralClass == null || oc.isDescendantOf(structuralClass))
1562        {
1563          structuralClass = oc;
1564        }
1565        else if (! structuralClass.isDescendantOf(oc))
1566        {
1567          LocalizableMessage message =
1568                  ERR_ENTRY_SCHEMA_MULTIPLE_STRUCTURAL_CLASSES.get(
1569                    dn,
1570                    structuralClass.getNameOrOID(),
1571                    oc.getNameOrOID());
1572
1573          if (structuralPolicy == AcceptRejectWarn.REJECT)
1574          {
1575            invalidReason.append(message);
1576            return false;
1577          }
1578          else if (structuralPolicy == AcceptRejectWarn.WARN
1579              && !multipleOCErrorLogged)
1580          {
1581            logger.error(message);
1582            multipleOCErrorLogged = true;
1583          }
1584        }
1585      }
1586    }
1587
1588    NameForm         nameForm         = null;
1589    DITContentRule   ditContentRule   = null;
1590    DITStructureRule ditStructureRule = null;
1591    if (structuralClass == null)
1592    {
1593      LocalizableMessage message = ERR_ENTRY_SCHEMA_NO_STRUCTURAL_CLASS.get(dn);
1594      if (structuralPolicy == AcceptRejectWarn.REJECT)
1595      {
1596        invalidReason.append(message);
1597        return false;
1598      }
1599      else if (structuralPolicy == AcceptRejectWarn.WARN)
1600      {
1601        logger.error(message);
1602      }
1603
1604      if (! checkAttributesAndObjectClasses(null,
1605              structuralPolicy, invalidReason))
1606      {
1607          return false;
1608      }
1609
1610    }
1611    else
1612    {
1613      ditContentRule = DirectoryServer.getDITContentRule(structuralClass);
1614      if (ditContentRule != null && ditContentRule.isObsolete())
1615      {
1616        ditContentRule = null;
1617      }
1618
1619      if (! checkAttributesAndObjectClasses(ditContentRule,
1620                 structuralPolicy, invalidReason))
1621      {
1622        return false;
1623      }
1624
1625      if (validateNameForms)
1626      {
1627        /**
1628         * There may be multiple nameforms registered with this
1629         * structural objectclass.However, we need to select only one
1630         * of the nameforms and its corresponding DITstructure rule.
1631         * We will iterate over all the nameforms and see if atleast
1632         * one is acceptable before rejecting the entry.
1633         * DITStructureRules corresponding to other non-acceptable
1634         * nameforms are not applied.
1635         */
1636        List<NameForm> listForms = DirectoryServer.getNameForm(structuralClass);
1637        if(listForms != null)
1638        {
1639          boolean matchFound = false;
1640          boolean obsolete = true;
1641          for(int index=0; index <listForms.size(); index++)
1642          {
1643            NameForm nf = listForms.get(index);
1644            if(!nf.isObsolete())
1645            {
1646              obsolete = false;
1647              matchFound = checkNameForm(nf, structuralPolicy, invalidReason);
1648
1649              if(matchFound)
1650              {
1651                nameForm = nf;
1652                break;
1653              }
1654
1655              if(index != listForms.size()-1)
1656              {
1657                invalidReason.append(",");
1658              }
1659            }
1660          }
1661          if(! obsolete && !matchFound)
1662          {
1663            // We couldn't match this entry against any of the nameforms.
1664            return false;
1665          }
1666        }
1667
1668
1669        if (validateStructureRules && nameForm != null)
1670        {
1671          ditStructureRule = DirectoryServer.getDITStructureRule(nameForm);
1672          if (ditStructureRule != null && ditStructureRule.isObsolete())
1673          {
1674            ditStructureRule = null;
1675          }
1676        }
1677      }
1678    }
1679
1680
1681    // If there is a DIT content rule for this entry, then make sure
1682    // that the entry is in compliance with it.
1683    if (ditContentRule != null
1684       && !checkDITContentRule(ditContentRule, structuralPolicy, invalidReason))
1685    {
1686      return false;
1687    }
1688
1689    return checkDITStructureRule(ditStructureRule, structuralClass,
1690        parentEntry, parentProvided, validateStructureRules, structuralPolicy,
1691        invalidReason);
1692  }
1693
1694
1695
1696  /**
1697   * Checks the attributes and object classes contained in this entry
1698   * to determine whether they conform to the server schema
1699   * requirements.
1700   *
1701   * @param  ditContentRule    The DIT content rule for this entry, if
1702   *                           any.
1703   * @param  structuralPolicy  The policy that should be used for
1704   *                           structural object class compliance.
1705   * @param  invalidReason     A buffer into which an invalid reason
1706   *                           may be added.
1707   *
1708   * @return {@code true} if this entry passes all of the checks, or
1709   *         {@code false} if there are any failures.
1710   */
1711  private boolean checkAttributesAndObjectClasses(
1712                       DITContentRule ditContentRule,
1713                       AcceptRejectWarn structuralPolicy,
1714                       LocalizableMessageBuilder invalidReason)
1715  {
1716    // Make sure that we recognize all of the objectclasses, that all
1717    // auxiliary classes are allowed by the DIT content rule, and that
1718    // all attributes required by the object classes are present.
1719    for (ObjectClass o : objectClasses.keySet())
1720    {
1721      if (DirectoryServer.getObjectClass(o.getOID()) == null)
1722      {
1723        LocalizableMessage message = ERR_ENTRY_SCHEMA_UNKNOWN_OC.get(dn, o.getNameOrOID());
1724        invalidReason.append(message);
1725        return false;
1726      }
1727
1728      if (o.getObjectClassType() == ObjectClassType.AUXILIARY
1729          && ditContentRule != null && !ditContentRule.getAuxiliaryClasses().contains(o))
1730      {
1731        LocalizableMessage message =
1732                ERR_ENTRY_SCHEMA_DISALLOWED_AUXILIARY_CLASS.get(
1733                  dn,
1734                  o.getNameOrOID(),
1735                  ditContentRule.getNameOrOID());
1736        if (structuralPolicy == AcceptRejectWarn.REJECT)
1737        {
1738          invalidReason.append(message);
1739          return false;
1740        }
1741        else if (structuralPolicy == AcceptRejectWarn.WARN)
1742        {
1743          logger.error(message);
1744        }
1745      }
1746
1747      for (AttributeType t : o.getRequiredAttributes())
1748      {
1749        if (!userAttributes.containsKey(t)
1750            && !operationalAttributes.containsKey(t)
1751            && !t.isObjectClass())
1752        {
1753          LocalizableMessage message =
1754                  ERR_ENTRY_SCHEMA_MISSING_REQUIRED_ATTR_FOR_OC.get(
1755                    dn,
1756                    t.getNameOrOID(),
1757                    o.getNameOrOID());
1758          invalidReason.append(message);
1759          return false;
1760        }
1761      }
1762    }
1763
1764
1765    // Make sure all the user attributes are allowed, have at least
1766    // one value, and if they are single-valued that they have exactly
1767    // one value.
1768    for (AttributeType t : userAttributes.keySet())
1769    {
1770      boolean found = false;
1771      for (ObjectClass o : objectClasses.keySet())
1772      {
1773        if (o.isRequiredOrOptional(t))
1774        {
1775          found = true;
1776          break;
1777        }
1778      }
1779
1780      if (!found && ditContentRule != null
1781          && ditContentRule.isRequiredOrOptional(t))
1782      {
1783        found = true;
1784      }
1785
1786      if (! found)
1787      {
1788        LocalizableMessage message =
1789                ERR_ENTRY_SCHEMA_DISALLOWED_USER_ATTR_FOR_OC.get( dn, t.getNameOrOID());
1790        invalidReason.append(message);
1791        return false;
1792      }
1793
1794      List<Attribute> attrList = userAttributes.get(t);
1795      if (attrList != null)
1796      {
1797        for (Attribute a : attrList)
1798        {
1799          if (a.isEmpty())
1800          {
1801            invalidReason.append(ERR_ENTRY_SCHEMA_ATTR_NO_VALUES.get(dn, t.getNameOrOID()));
1802            return false;
1803          }
1804          else if (t.isSingleValue() && a.size() != 1)
1805          {
1806            invalidReason.append(ERR_ENTRY_SCHEMA_ATTR_SINGLE_VALUED.get(dn, t.getNameOrOID()));
1807            return false;
1808          }
1809        }
1810      }
1811    }
1812
1813
1814    // Iterate through all of the operational attributes and make sure
1815    // that all of the single-valued attributes only have one value.
1816    for (AttributeType t : operationalAttributes.keySet())
1817    {
1818      if (t.isSingleValue())
1819      {
1820        List<Attribute> attrList = operationalAttributes.get(t);
1821        if (attrList != null)
1822        {
1823          for (Attribute a : attrList)
1824          {
1825            if (a.size() > 1)
1826            {
1827              invalidReason.append(ERR_ENTRY_SCHEMA_ATTR_SINGLE_VALUED.get(dn, t.getNameOrOID()));
1828              return false;
1829            }
1830          }
1831        }
1832      }
1833    }
1834
1835
1836    // If we've gotten here, then things are OK.
1837    return true;
1838  }
1839
1840
1841
1842  /**
1843   * Performs any processing needed for name form validation.
1844   *
1845   * @param  nameForm          The name form to validate against this
1846   *                           entry.
1847   * @param  structuralPolicy  The policy that should be used for
1848   *                           structural object class compliance.
1849   * @param  invalidReason     A buffer into which an invalid reason
1850   *                           may be added.
1851   *
1852   * @return {@code true} if this entry passes all of the checks, or
1853   *         {@code false} if there are any failures.
1854   */
1855  private boolean checkNameForm(NameForm nameForm,
1856                       AcceptRejectWarn structuralPolicy,
1857                       LocalizableMessageBuilder invalidReason)
1858  {
1859    RDN rdn = dn.rdn();
1860    if (rdn != null)
1861    {
1862        // Make sure that all the required attributes are present.
1863        for (AttributeType t : nameForm.getRequiredAttributes())
1864        {
1865          if (! rdn.hasAttributeType(t))
1866          {
1867            LocalizableMessage message =
1868                    ERR_ENTRY_SCHEMA_RDN_MISSING_REQUIRED_ATTR.get(
1869                      dn,
1870                      t.getNameOrOID(),
1871                      nameForm.getNameOrOID());
1872
1873            if (structuralPolicy == AcceptRejectWarn.REJECT)
1874            {
1875              invalidReason.append(message);
1876              return false;
1877            }
1878            else if (structuralPolicy == AcceptRejectWarn.WARN)
1879            {
1880              logger.error(message);
1881            }
1882          }
1883        }
1884
1885          // Make sure that all attributes in the RDN are allowed.
1886          for (AVA ava : rdn)
1887          {
1888            AttributeType t = ava.getAttributeType();
1889            if (! nameForm.isRequiredOrOptional(t))
1890            {
1891              LocalizableMessage message =
1892                      ERR_ENTRY_SCHEMA_RDN_DISALLOWED_ATTR.get(
1893                        dn,
1894                        t.getNameOrOID(),
1895                        nameForm.getNameOrOID());
1896
1897              if (structuralPolicy == AcceptRejectWarn.REJECT)
1898              {
1899                invalidReason.append(message);
1900                return false;
1901              }
1902              else if (structuralPolicy == AcceptRejectWarn.WARN)
1903              {
1904                logger.error(message);
1905              }
1906            }
1907          }
1908    }
1909
1910    // If we've gotten here, then things are OK.
1911    return true;
1912  }
1913
1914
1915
1916  /**
1917   * Performs any processing needed for DIT content rule validation.
1918   *
1919   * @param  ditContentRule    The DIT content rule to validate
1920   *                           against this entry.
1921   * @param  structuralPolicy  The policy that should be used for
1922   *                           structural object class compliance.
1923   * @param  invalidReason     A buffer into which an invalid reason
1924   *                           may be added.
1925   *
1926   * @return {@code true} if this entry passes all of the checks, or
1927   *         {@code false} if there are any failures.
1928   */
1929  private boolean checkDITContentRule(DITContentRule ditContentRule,
1930                       AcceptRejectWarn structuralPolicy,
1931                       LocalizableMessageBuilder invalidReason)
1932  {
1933    // Make sure that all of the required attributes are present.
1934    for (AttributeType t : ditContentRule.getRequiredAttributes())
1935    {
1936      if (!userAttributes.containsKey(t)
1937          && !operationalAttributes.containsKey(t)
1938          && !t.isObjectClass())
1939      {
1940        LocalizableMessage message =
1941                ERR_ENTRY_SCHEMA_MISSING_REQUIRED_ATTR_FOR_DCR.get(
1942                  dn,
1943                  t.getNameOrOID(),
1944                  ditContentRule.getNameOrOID());
1945
1946        if (structuralPolicy == AcceptRejectWarn.REJECT)
1947        {
1948          invalidReason.append(message);
1949          return false;
1950        }
1951        else if (structuralPolicy == AcceptRejectWarn.WARN)
1952        {
1953          logger.error(message);
1954        }
1955      }
1956    }
1957
1958    // Make sure that none of the prohibited attributes are present.
1959    for (AttributeType t : ditContentRule.getProhibitedAttributes())
1960    {
1961      if (userAttributes.containsKey(t) ||
1962          operationalAttributes.containsKey(t))
1963      {
1964        LocalizableMessage message =
1965                ERR_ENTRY_SCHEMA_PROHIBITED_ATTR_FOR_DCR.get(
1966                  dn,
1967                  t.getNameOrOID(),
1968                  ditContentRule.getNameOrOID());
1969
1970        if (structuralPolicy == AcceptRejectWarn.REJECT)
1971        {
1972          invalidReason.append(message);
1973          return false;
1974        }
1975        else if (structuralPolicy == AcceptRejectWarn.WARN)
1976        {
1977          logger.error(message);
1978        }
1979      }
1980    }
1981
1982    // If we've gotten here, then things are OK.
1983    return true;
1984  }
1985
1986
1987
1988  /**
1989   * Performs any processing needed for DIT structure rule validation.
1990   *
1991   * @param  ditStructureRule        The DIT structure rule for this
1992   *                                 entry.
1993   * @param  structuralClass         The structural object class for
1994   *                                 this entry.
1995   * @param  parentEntry             The parent entry, if available
1996   *                                 and applicable.
1997   * @param  parentProvided          Indicates whether the parent
1998   *                                 entry was provided.
1999   * @param  validateStructureRules  Indicates whether to check to see
2000   *                                 if this entry violates a DIT
2001   *                                 structure rule for its parent.
2002   * @param  structuralPolicy        The policy that should be used
2003   *                                 for structural object class
2004   *                                 compliance.
2005   * @param  invalidReason           A buffer into which an invalid
2006   *                                 reason may be added.
2007   *
2008   * @return {@code true} if this entry passes all of the checks, or
2009   *         {@code false} if there are any failures.
2010   */
2011  private boolean checkDITStructureRule(
2012                       DITStructureRule ditStructureRule,
2013                       ObjectClass structuralClass,
2014                       Entry parentEntry, boolean parentProvided,
2015                       boolean validateStructureRules,
2016                       AcceptRejectWarn structuralPolicy,
2017                       LocalizableMessageBuilder invalidReason)
2018  {
2019    // If there is a DIT structure rule for this entry, then make sure
2020    // that the entry is in compliance with it.
2021    if (ditStructureRule != null && ditStructureRule.hasSuperiorRules())
2022    {
2023      if (parentProvided)
2024      {
2025        if (parentEntry != null)
2026        {
2027          boolean dsrValid =
2028               validateDITStructureRule(ditStructureRule,
2029                                        structuralClass, parentEntry,
2030                                        structuralPolicy,
2031                                        invalidReason);
2032          if (! dsrValid)
2033          {
2034            return false;
2035          }
2036        }
2037      }
2038      else
2039      {
2040        // Get the DN of the parent entry if possible.
2041        DN parentDN = DirectoryServer.getParentDNInSuffix(dn);
2042        if (parentDN != null)
2043        {
2044          try
2045          {
2046            parentEntry = DirectoryServer.getEntry(parentDN);
2047            if (parentEntry == null)
2048            {
2049              LocalizableMessage message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_ENTRY.get(dn, parentDN);
2050
2051              if (structuralPolicy == AcceptRejectWarn.REJECT)
2052              {
2053                invalidReason.append(message);
2054                return false;
2055              }
2056              else if (structuralPolicy == AcceptRejectWarn.WARN)
2057              {
2058                logger.error(message);
2059              }
2060            }
2061            else
2062            {
2063              boolean dsrValid =
2064                   validateDITStructureRule(ditStructureRule,
2065                                            structuralClass,
2066                                            parentEntry,
2067                                            structuralPolicy,
2068                                            invalidReason);
2069              if (! dsrValid)
2070              {
2071                return false;
2072              }
2073            }
2074          }
2075          catch (Exception e)
2076          {
2077            logger.traceException(e);
2078
2079            LocalizableMessage message =
2080                 ERR_ENTRY_SCHEMA_COULD_NOT_CHECK_DSR.get(
2081                         dn,
2082                         ditStructureRule.getNameOrRuleID(),
2083                         getExceptionMessage(e));
2084
2085            if (structuralPolicy == AcceptRejectWarn.REJECT)
2086            {
2087              invalidReason.append(message);
2088              return false;
2089            }
2090            else if (structuralPolicy == AcceptRejectWarn.WARN)
2091            {
2092              logger.error(message);
2093            }
2094          }
2095        }
2096      }
2097    }
2098    else if (validateStructureRules)
2099    {
2100      // There is no DIT structure rule for this entry, but there may
2101      // be one for the parent entry.  If there is such a rule for the
2102      // parent entry, then this entry will not be valid.
2103      boolean parentExists = false;
2104      ObjectClass parentStructuralClass = null;
2105      if (parentEntry != null)
2106      {
2107        parentExists = true;
2108        parentStructuralClass = parentEntry.getStructuralObjectClass();
2109      }
2110      else if (! parentProvided)
2111      {
2112        DN parentDN = DirectoryServer.getParentDNInSuffix(getName());
2113        if (parentDN != null)
2114        {
2115          try
2116          {
2117            parentEntry = DirectoryServer.getEntry(parentDN);
2118            if (parentEntry == null)
2119            {
2120              LocalizableMessage message =
2121                   ERR_ENTRY_SCHEMA_DSR_NO_PARENT_ENTRY.get(
2122                       dn, parentDN);
2123
2124              if (structuralPolicy == AcceptRejectWarn.REJECT)
2125              {
2126                invalidReason.append(message);
2127                return false;
2128              }
2129              else if (structuralPolicy == AcceptRejectWarn.WARN)
2130              {
2131                logger.error(message);
2132              }
2133            }
2134            else
2135            {
2136              parentExists = true;
2137              parentStructuralClass = parentEntry.getStructuralObjectClass();
2138            }
2139          }
2140          catch (Exception e)
2141          {
2142            logger.traceException(e);
2143
2144            LocalizableMessage message =
2145                 ERR_ENTRY_SCHEMA_COULD_NOT_CHECK_PARENT_DSR.get(
2146                     dn, getExceptionMessage(e));
2147
2148            if (structuralPolicy == AcceptRejectWarn.REJECT)
2149            {
2150              invalidReason.append(message);
2151              return false;
2152            }
2153            else if (structuralPolicy == AcceptRejectWarn.WARN)
2154            {
2155              logger.error(message);
2156            }
2157          }
2158        }
2159      }
2160
2161      if (parentExists)
2162      {
2163        if (parentStructuralClass == null)
2164        {
2165          LocalizableMessage message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC.get(
2166              dn, parentEntry.getName());
2167
2168          if (structuralPolicy == AcceptRejectWarn.REJECT)
2169          {
2170            invalidReason.append(message);
2171            return false;
2172          }
2173          else if (structuralPolicy == AcceptRejectWarn.WARN)
2174          {
2175            logger.error(message);
2176          }
2177        }
2178        else
2179        {
2180          List<NameForm> allNFs =
2181               DirectoryServer.getNameForm(parentStructuralClass);
2182          if(allNFs != null)
2183          {
2184            for(NameForm parentNF : allNFs)
2185            {
2186              if (parentNF != null && !parentNF.isObsolete())
2187              {
2188                DITStructureRule parentDSR =
2189                     DirectoryServer.getDITStructureRule(parentNF);
2190                if (parentDSR != null && !parentDSR.isObsolete())
2191                {
2192                  LocalizableMessage message =
2193                       ERR_ENTRY_SCHEMA_VIOLATES_PARENT_DSR.get(dn, parentEntry.getName());
2194
2195                  if (structuralPolicy == AcceptRejectWarn.REJECT)
2196                  {
2197                    invalidReason.append(message);
2198                    return false;
2199                  }
2200                  else if (structuralPolicy == AcceptRejectWarn.WARN)
2201                  {
2202                    logger.error(message);
2203                  }
2204                }
2205              }
2206            }
2207          }
2208        }
2209      }
2210    }
2211
2212    // If we've gotten here, then things are OK.
2213    return true;
2214  }
2215
2216
2217
2218  /**
2219   * Determines whether this entry is in conformance to the provided
2220   * DIT structure rule.
2221   *
2222   * @param  dsr               The DIT structure rule to use in the
2223   *                           determination.
2224   * @param  structuralClass   The structural objectclass for this
2225   *                           entry to use in the determination.
2226   * @param  parentEntry       The reference to the parent entry to
2227   *                           check.
2228   * @param  structuralPolicy  The policy that should be used around
2229   *                           enforcement of DIT structure rules.
2230   * @param  invalidReason     The buffer to which the invalid reason
2231   *                           should be appended if a problem is
2232   *                           found.
2233   *
2234   * @return  <CODE>true</CODE> if this entry conforms to the provided
2235   *          DIT structure rule, or <CODE>false</CODE> if not.
2236   */
2237  private boolean validateDITStructureRule(DITStructureRule dsr,
2238                       ObjectClass structuralClass, Entry parentEntry,
2239                       AcceptRejectWarn structuralPolicy,
2240                       LocalizableMessageBuilder invalidReason)
2241  {
2242    ObjectClass oc = parentEntry.getStructuralObjectClass();
2243    if (oc == null)
2244    {
2245      LocalizableMessage message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC.get(
2246          dn, parentEntry.getName());
2247
2248      if (structuralPolicy == AcceptRejectWarn.REJECT)
2249      {
2250        invalidReason.append(message);
2251        return false;
2252      }
2253      else if (structuralPolicy == AcceptRejectWarn.WARN)
2254      {
2255        logger.error(message);
2256      }
2257    }
2258
2259    boolean matchFound = false;
2260    for (DITStructureRule dsr2 : dsr.getSuperiorRules())
2261    {
2262      if (dsr2.getStructuralClass().equals(oc))
2263      {
2264        matchFound = true;
2265      }
2266    }
2267
2268    if (! matchFound)
2269    {
2270      LocalizableMessage message =
2271              ERR_ENTRY_SCHEMA_DSR_DISALLOWED_SUPERIOR_OC.get(
2272                dn,
2273                dsr.getNameOrRuleID(),
2274                structuralClass.getNameOrOID(),
2275                oc.getNameOrOID());
2276
2277      if (structuralPolicy == AcceptRejectWarn.REJECT)
2278      {
2279        invalidReason.append(message);
2280        return false;
2281      }
2282      else if (structuralPolicy == AcceptRejectWarn.WARN)
2283      {
2284        logger.error(message);
2285      }
2286    }
2287
2288    return true;
2289  }
2290
2291
2292
2293  /**
2294   * Retrieves the attachment for this entry.
2295   *
2296   * @return  The attachment for this entry, or <CODE>null</CODE> if
2297   *          there is none.
2298   */
2299  public Object getAttachment()
2300  {
2301    return attachment;
2302  }
2303
2304
2305
2306  /**
2307   * Specifies the attachment for this entry.  This will replace any
2308   * existing attachment that might be defined.
2309   *
2310   * @param  attachment  The attachment for this entry, or
2311   *                     <CODE>null</CODE> if there should not be an
2312   *                     attachment.
2313   */
2314  public void setAttachment(Object attachment)
2315  {
2316    this.attachment = attachment;
2317  }
2318
2319
2320
2321  /**
2322   * Creates a duplicate of this entry that may be altered without
2323   * impacting the information in this entry.
2324   *
2325   * @param  processVirtual  Indicates whether virtual attribute
2326   *                         processing should be performed for the
2327   *                         entry.
2328   *
2329   * @return  A duplicate of this entry that may be altered without
2330   *          impacting the information in this entry.
2331   */
2332  public Entry duplicate(boolean processVirtual)
2333  {
2334    Map<ObjectClass, String> objectClassesCopy = new HashMap<>(objectClasses);
2335
2336    Map<AttributeType, List<Attribute>> userAttrsCopy = new HashMap<>(userAttributes.size());
2337    deepCopy(userAttributes, userAttrsCopy, false, false, false,
2338        true, false);
2339
2340    Map<AttributeType, List<Attribute>> operationalAttrsCopy =
2341         new HashMap<>(operationalAttributes.size());
2342    deepCopy(operationalAttributes, operationalAttrsCopy, false,
2343        false, false, true, false);
2344
2345    // Put back all the suppressed attributes where they belonged to.
2346    // Then hopefully processVirtualAttributes() will rebuild the suppressed
2347    // attribute list correctly.
2348    for (AttributeType t : suppressedAttributes.keySet())
2349    {
2350      List<Attribute> attrList = suppressedAttributes.get(t);
2351      if (t.isOperational())
2352      {
2353        operationalAttrsCopy.put(t, attrList);
2354      }
2355      else
2356      {
2357        userAttrsCopy.put(t, attrList);
2358      }
2359    }
2360
2361    Entry e = new Entry(dn, objectClassesCopy, userAttrsCopy,
2362                        operationalAttrsCopy);
2363    if (processVirtual)
2364    {
2365      e.processVirtualAttributes();
2366    }
2367    return e;
2368  }
2369
2370
2371
2372  /**
2373   * Performs a deep copy from the source map to the target map.
2374   * In this case, the attributes in the list will be duplicates
2375   * rather than re-using the same reference.
2376   *
2377   * @param source
2378   *          The source map from which to obtain the information.
2379   * @param target
2380   *          The target map into which to place the copied
2381   *          information.
2382   * @param omitValues
2383   *          Indicates whether to omit attribute values when
2384   *          processing.
2385   * @param omitEmpty
2386   *          Indicates whether to omit empty attributes when
2387   *          processing.
2388   * @param omitReal
2389   *          Indicates whether to exclude real attributes.
2390   * @param omitVirtual
2391   *          Indicates whether to exclude virtual attributes.
2392   * @param mergeDuplicates
2393   *          Indicates whether duplicate attributes should be merged.
2394   */
2395  private void deepCopy(Map<AttributeType,List<Attribute>> source,
2396                        Map<AttributeType,List<Attribute>> target,
2397                        boolean omitValues,
2398                        boolean omitEmpty,
2399                        boolean omitReal,
2400                        boolean omitVirtual,
2401                        boolean mergeDuplicates)
2402  {
2403    for (Map.Entry<AttributeType, List<Attribute>> mapEntry :
2404      source.entrySet())
2405    {
2406      AttributeType t = mapEntry.getKey();
2407      List<Attribute> sourceList = mapEntry.getValue();
2408      List<Attribute> targetList = new ArrayList<>(sourceList.size());
2409
2410      for (Attribute a : sourceList)
2411      {
2412        if ((omitReal && a.isReal())
2413            || (omitVirtual && a.isVirtual())
2414            || (omitEmpty && a.isEmpty()))
2415        {
2416          continue;
2417        }
2418
2419        if (omitValues)
2420        {
2421          a = Attributes.empty(a);
2422        }
2423
2424        if (!targetList.isEmpty() && mergeDuplicates)
2425        {
2426          // Ensure that there is only one attribute with the same type and options.
2427          // This is not very efficient but will occur very rarely.
2428          boolean found = false;
2429          for (int i = 0; i < targetList.size(); i++)
2430          {
2431            Attribute otherAttribute = targetList.get(i);
2432            if (otherAttribute.getAttributeDescription().equals(a.getAttributeDescription()))
2433            {
2434              targetList.set(i, Attributes.merge(a, otherAttribute));
2435              found = true;
2436            }
2437          }
2438
2439          if (!found)
2440          {
2441            targetList.add(a);
2442          }
2443        }
2444        else
2445        {
2446          targetList.add(a);
2447        }
2448      }
2449
2450      if (!targetList.isEmpty())
2451      {
2452        target.put(t, targetList);
2453      }
2454    }
2455  }
2456
2457
2458
2459  /**
2460   * Indicates whether this entry meets the criteria to consider it a referral
2461   * (e.g., it contains the "referral" objectclass and a "ref" attribute).
2462   *
2463   * @return  <CODE>true</CODE> if this entry meets the criteria to
2464   *          consider it a referral, or <CODE>false</CODE> if not.
2465   */
2466  public boolean isReferral()
2467  {
2468    return hasObjectClassOrAttribute(OC_REFERRAL, ATTR_REFERRAL_URL);
2469  }
2470
2471  /**
2472   * Returns whether the current entry has a specific object class or attribute.
2473   *
2474   * @param objectClassName
2475   *          the name of the object class to look for
2476   * @param attrTypeName
2477   *          the attribute type name of the object class to look for
2478   * @return true if the current entry has the object class or the attribute,
2479   *         false otherwise
2480   */
2481  private boolean hasObjectClassOrAttribute(String objectClassName,
2482      String attrTypeName)
2483  {
2484    ObjectClass oc = DirectoryServer.getObjectClass(objectClassName);
2485    if (oc == null)
2486    {
2487      // This should not happen
2488      // The server doesn't have this objectclass defined.
2489      if (logger.isTraceEnabled())
2490      {
2491        logger.trace(
2492            "No %s objectclass is defined in the server schema.",
2493            objectClassName);
2494      }
2495      return containsObjectClassByName(objectClassName);
2496    }
2497    if (!objectClasses.containsKey(oc))
2498    {
2499      return false;
2500    }
2501
2502
2503    AttributeType attrType = DirectoryServer.getAttributeType(attrTypeName);
2504    if (attrType.isPlaceHolder())
2505    {
2506      // This should not happen
2507      // The server doesn't have this attribute type defined.
2508      if (logger.isTraceEnabled())
2509      {
2510        logger.trace("No %s attribute type is defined in the server schema.", attrTypeName);
2511      }
2512      return false;
2513    }
2514    return userAttributes.containsKey(attrType)
2515        || operationalAttributes.containsKey(attrType);
2516  }
2517
2518  /**
2519   * Whether the object class name exists in the objectClass of this entry.
2520   *
2521   * @param objectClassName
2522   *          the name of the object class to look for
2523   * @return true if the object class name exists in the objectClass of this
2524   *         entry, false otherwise
2525   */
2526  private boolean containsObjectClassByName(String objectClassName)
2527  {
2528    for (String ocName : objectClasses.values())
2529    {
2530      if (objectClassName.equalsIgnoreCase(ocName))
2531      {
2532        return true;
2533      }
2534    }
2535    return false;
2536  }
2537
2538  /**
2539   * Retrieves the set of referral URLs that are included in this
2540   * referral entry.  This should only be called if
2541   * <CODE>isReferral()</CODE> returns <CODE>true</CODE>.
2542   *
2543   * @return  The set of referral URLs that are included in this entry
2544   *          if it is a referral, or <CODE>null</CODE> if it is not a
2545   *          referral.
2546   */
2547  public Set<String> getReferralURLs()
2548  {
2549    AttributeType referralType = DirectoryServer.getAttributeType(ATTR_REFERRAL_URL);
2550    if (referralType.isPlaceHolder())
2551    {
2552      // This should not happen -- The server doesn't have a ref attribute type defined.
2553      logger.trace("No %s attribute type is defined in the server schema.", ATTR_REFERRAL_URL);
2554      return null;
2555    }
2556
2557    List<Attribute> refAttrs = userAttributes.get(referralType);
2558    if (refAttrs == null)
2559    {
2560      refAttrs = operationalAttributes.get(referralType);
2561      if (refAttrs == null)
2562      {
2563        return null;
2564      }
2565    }
2566
2567    Set<String> referralURLs = new LinkedHashSet<>();
2568    for (Attribute a : refAttrs)
2569    {
2570      for (ByteString v : a)
2571      {
2572        referralURLs.add(v.toString());
2573      }
2574    }
2575
2576    return referralURLs;
2577  }
2578
2579
2580
2581  /**
2582   * Indicates whether this entry meets the criteria to consider it an
2583   * alias (e.g., it contains the "aliasObject" objectclass and a
2584   * "alias" attribute).
2585   *
2586   * @return  <CODE>true</CODE> if this entry meets the criteria to
2587   *          consider it an alias, or <CODE>false</CODE> if not.
2588   */
2589  public boolean isAlias()
2590  {
2591    return hasObjectClassOrAttribute(OC_ALIAS, ATTR_ALIAS_DN);
2592  }
2593
2594
2595
2596  /**
2597   * Retrieves the DN of the entry referenced by this alias entry.
2598   * This should only be called if <CODE>isAlias()</CODE> returns
2599   * <CODE>true</CODE>.
2600   *
2601   * @return  The DN of the entry referenced by this alias entry, or
2602   *          <CODE>null</CODE> if it is not an alias.
2603   *
2604   * @throws  DirectoryException  If there is an aliasedObjectName
2605   *                              attribute but its value cannot be
2606   *                              parsed as a DN.
2607   */
2608  public DN getAliasedDN() throws DirectoryException
2609  {
2610    AttributeType aliasType = DirectoryServer.getAttributeType(ATTR_REFERRAL_URL);
2611    if (aliasType.isPlaceHolder())
2612    {
2613      // This should not happen -- The server doesn't have an aliasedObjectName attribute type defined.
2614      logger.trace("No %s attribute type is defined in the server schema.", ATTR_ALIAS_DN);
2615      return null;
2616    }
2617
2618    List<Attribute> aliasAttrs = userAttributes.get(aliasType);
2619    if (aliasAttrs == null)
2620    {
2621      aliasAttrs = operationalAttributes.get(aliasType);
2622      if (aliasAttrs == null)
2623      {
2624        return null;
2625      }
2626    }
2627
2628    if (!aliasAttrs.isEmpty())
2629    {
2630      // There should only be a single alias attribute in an entry,
2631      // and we'll skip the check for others for performance reasons.
2632      // We would just end up taking the first one anyway. The same
2633      // is true with the set of values, since it should be a
2634      // single-valued attribute.
2635      Attribute aliasAttr = aliasAttrs.get(0);
2636      if (!aliasAttr.isEmpty())
2637      {
2638        return DN.valueOf(aliasAttr.iterator().next().toString());
2639      }
2640    }
2641    return null;
2642  }
2643
2644
2645
2646  /**
2647   * Indicates whether this entry meets the criteria to consider it an
2648   * LDAP subentry (i.e., it contains the "ldapSubentry" objectclass).
2649   *
2650   * @return  <CODE>true</CODE> if this entry meets the criteria to
2651   *          consider it an LDAP subentry, or <CODE>false</CODE> if
2652   *          not.
2653   */
2654  public boolean isLDAPSubentry()
2655  {
2656    return hasObjectClass(OC_LDAP_SUBENTRY_LC);
2657  }
2658
2659  /**
2660   * Returns whether the current entry has a specific object class.
2661   *
2662   * @param objectClassLowerCase
2663   *          the lowercase name of the object class to look for
2664   * @return true if the current entry has the object class, false otherwise
2665   */
2666  private boolean hasObjectClass(String objectClassLowerCase)
2667  {
2668    ObjectClass oc = DirectoryServer.getObjectClass(objectClassLowerCase);
2669    if (oc == null)
2670    {
2671      // This should not happen
2672      // The server doesn't have this object class defined.
2673      if (logger.isTraceEnabled())
2674      {
2675        logger.trace(
2676            "No %s objectclass is defined in the server schema.",
2677            objectClassLowerCase);
2678      }
2679      return containsObjectClassByName(objectClassLowerCase);
2680    }
2681
2682    // Make the determination based on whether this entry has this objectclass.
2683    return objectClasses.containsKey(oc);
2684  }
2685
2686
2687
2688  /**
2689   * Indicates whether this entry meets the criteria to consider it
2690   * an RFC 3672 LDAP subentry (i.e., it contains the "subentry"
2691   * objectclass).
2692   *
2693   * @return  <CODE>true</CODE> if this entry meets the criteria to
2694   *          consider it an RFC 3672 LDAP subentry, or <CODE>false
2695   *          </CODE> if not.
2696   */
2697  public boolean isSubentry()
2698  {
2699    return hasObjectClass(OC_SUBENTRY);
2700  }
2701
2702
2703
2704  /**
2705   * Indicates whether the entry meets the criteria to consider it an
2706   * RFC 3671 LDAP collective attributes subentry (i.e., it contains
2707   * the "collectiveAttributeSubentry" objectclass).
2708   *
2709   * @return  <CODE>true</CODE> if this entry meets the criteria to
2710   *          consider it an RFC 3671 LDAP collective attributes
2711   *          subentry, or <CODE>false</CODE> if not.
2712   */
2713  public boolean isCollectiveAttributeSubentry()
2714  {
2715    return hasObjectClass(OC_COLLECTIVE_ATTR_SUBENTRY_LC);
2716  }
2717
2718
2719
2720  /**
2721   * Indicates whether the entry meets the criteria to consider it an
2722   * inherited collective attributes subentry (i.e., it contains
2723   * the "inheritedCollectiveAttributeSubentry" objectclass).
2724   *
2725   * @return  <CODE>true</CODE> if this entry meets the criteria to
2726   *          consider it an inherited collective attributes
2727   *          subentry, or <CODE>false</CODE> if not.
2728   */
2729  public boolean isInheritedCollectiveAttributeSubentry()
2730  {
2731    return hasObjectClass(OC_INHERITED_COLLECTIVE_ATTR_SUBENTRY_LC);
2732  }
2733
2734
2735
2736  /**
2737   * Indicates whether the entry meets the criteria to consider it an inherited
2738   * from DN collective attributes subentry (i.e., it contains the
2739   * "inheritedFromDNCollectiveAttributeSubentry" objectclass).
2740   *
2741   * @return <CODE>true</CODE> if this entry meets the criteria to consider it
2742   *         an inherited from DN collective attributes subentry, or
2743   *         <CODE>false</CODE> if not.
2744   */
2745  public boolean isInheritedFromDNCollectiveAttributeSubentry()
2746  {
2747    return hasObjectClass(OC_INHERITED_FROM_DN_COLLECTIVE_ATTR_SUBENTRY_LC);
2748  }
2749
2750
2751
2752  /**
2753   * Indicates whether the entry meets the criteria to consider it
2754   * an inherited from RDN collective attributes subentry (i.e.,
2755   * it contains the "inheritedFromRDNCollectiveAttributeSubentry"
2756   * objectclass).
2757   *
2758   * @return  <CODE>true</CODE> if this entry meets the criteria to
2759   *          consider it an inherited from RDN collective attributes
2760   *          subentry, or <CODE>false</CODE> if not.
2761   */
2762  public boolean isInheritedFromRDNCollectiveAttributeSubentry()
2763  {
2764    return hasObjectClass(OC_INHERITED_FROM_RDN_COLLECTIVE_ATTR_SUBENTRY_LC);
2765  }
2766
2767
2768
2769  /**
2770   * Indicates whether the entry meets the criteria to consider it a
2771   * LDAP password policy subentry (i.e., it contains the "pwdPolicy"
2772   * objectclass of LDAP Password Policy Internet-Draft).
2773   *
2774   * @return  <CODE>true</CODE> if this entry meets the criteria to
2775   *          consider it a LDAP Password Policy Internet-Draft
2776   *          subentry, or <CODE>false</CODE> if not.
2777   */
2778  public boolean isPasswordPolicySubentry()
2779  {
2780    return hasObjectClass(OC_PWD_POLICY_SUBENTRY_LC);
2781  }
2782
2783
2784
2785  /**
2786   * Indicates whether this entry falls within the range of the
2787   * provided search base DN and scope.
2788   *
2789   * @param  baseDN  The base DN for which to make the determination.
2790   * @param  scope   The search scope for which to make the
2791   *                 determination.
2792   *
2793   * @return  <CODE>true</CODE> if this entry is within the given
2794   *          base and scope, or <CODE>false</CODE> if it is not.
2795   */
2796  public boolean matchesBaseAndScope(DN baseDN, SearchScope scope)
2797  {
2798    return dn.isInScopeOf(baseDN, scope);
2799  }
2800
2801
2802
2803  /**
2804   * Performs any necessary collective attribute processing for this
2805   * entry.  This should only be called at the time the entry is
2806   * decoded or created within the backend.
2807   */
2808  private void processCollectiveAttributes()
2809  {
2810    if (isSubentry() || isLDAPSubentry())
2811    {
2812      return;
2813    }
2814
2815    SubentryManager manager =
2816            DirectoryServer.getSubentryManager();
2817    if(manager == null)
2818    {
2819      //Subentry manager may not have been initialized by
2820      //a component that doesn't require it.
2821      return;
2822    }
2823    // Get applicable collective subentries.
2824    List<SubEntry> collectiveAttrSubentries =
2825            manager.getCollectiveSubentries(this);
2826
2827    if (collectiveAttrSubentries == null || collectiveAttrSubentries.isEmpty())
2828    {
2829      // Nothing to see here, move along.
2830      return;
2831    }
2832
2833    // Get collective attribute exclusions.
2834    AttributeType exclusionsType = DirectoryServer.getAttributeType(ATTR_COLLECTIVE_EXCLUSIONS_LC);
2835    List<Attribute> exclusionsAttrList = operationalAttributes.get(exclusionsType);
2836    Set<String> exclusionsNameSet = new HashSet<>();
2837    if (exclusionsAttrList != null && !exclusionsAttrList.isEmpty())
2838    {
2839      for (Attribute attr : exclusionsAttrList)
2840      {
2841        for (ByteString attrValue : attr)
2842        {
2843          String exclusionsName = attrValue.toString().toLowerCase();
2844          if (VALUE_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL_LC.equals(exclusionsName)
2845              || OID_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL.equals(exclusionsName))
2846          {
2847            return;
2848          }
2849          exclusionsNameSet.add(exclusionsName);
2850        }
2851      }
2852    }
2853
2854    // Process collective attributes.
2855    for (SubEntry subEntry : collectiveAttrSubentries)
2856    {
2857      if (subEntry.isCollective() || subEntry.isInheritedCollective())
2858      {
2859        Entry inheritFromEntry = null;
2860        if (subEntry.isInheritedCollective())
2861        {
2862          if (subEntry.isInheritedFromDNCollective() &&
2863              hasAttribute(subEntry.getInheritFromDNType()))
2864          {
2865            try
2866            {
2867              DN inheritFromDN = null;
2868              for (Attribute attr : getAttribute(subEntry.getInheritFromDNType()))
2869              {
2870                for (ByteString value : attr)
2871                {
2872                  inheritFromDN = DN.valueOf(value);
2873                  // Respect subentry root scope.
2874                  if (!inheritFromDN.isSubordinateOrEqualTo(
2875                       subEntry.getDN().parent()))
2876                  {
2877                    inheritFromDN = null;
2878                  }
2879                  break;
2880                }
2881              }
2882              if (inheritFromDN == null)
2883              {
2884                continue;
2885              }
2886
2887              // TODO : ACI check; needs re-factoring to happen.
2888              inheritFromEntry = DirectoryServer.getEntry(inheritFromDN);
2889            }
2890            catch (DirectoryException de)
2891            {
2892              logger.traceException(de);
2893            }
2894          }
2895          else if (subEntry.isInheritedFromRDNCollective() &&
2896                   hasAttribute(subEntry.getInheritFromRDNAttrType()))
2897          {
2898            DN inheritFromDN = subEntry.getInheritFromBaseDN();
2899            if (inheritFromDN != null)
2900            {
2901              try
2902              {
2903                for (Attribute attr : getAttribute(subEntry.getInheritFromRDNAttrType()))
2904                {
2905                  inheritFromDN = subEntry.getInheritFromBaseDN();
2906                  for (ByteString value : attr)
2907                  {
2908                    inheritFromDN = inheritFromDN.child(
2909                        new RDN(subEntry.getInheritFromRDNType(), value));
2910                    break;
2911                  }
2912                }
2913
2914                // TODO : ACI check; needs re-factoring to happen.
2915                inheritFromEntry = DirectoryServer.getEntry(inheritFromDN);
2916              }
2917              catch (DirectoryException de)
2918              {
2919                logger.traceException(de);
2920              }
2921            }
2922            else
2923            {
2924              continue;
2925            }
2926          }
2927        }
2928        List<Attribute> collectiveAttrList = subEntry.getCollectiveAttributes();
2929        for (Attribute collectiveAttr : collectiveAttrList)
2930        {
2931          AttributeType attributeType = collectiveAttr.getAttributeDescription().getAttributeType();
2932          if (exclusionsNameSet.contains(attributeType.getNormalizedNameOrOID()))
2933          {
2934            continue;
2935          }
2936          if (subEntry.isInheritedCollective())
2937          {
2938            if (inheritFromEntry != null)
2939            {
2940              collectiveAttr = inheritFromEntry.getExactAttribute(collectiveAttr.getAttributeDescription());
2941              if (collectiveAttr == null || collectiveAttr.isEmpty())
2942              {
2943                continue;
2944              }
2945              collectiveAttr = new CollectiveVirtualAttribute(collectiveAttr);
2946            }
2947            else
2948            {
2949              continue;
2950            }
2951          }
2952          List<Attribute> attrList = userAttributes.get(attributeType);
2953          if (attrList == null || attrList.isEmpty())
2954          {
2955            attrList = operationalAttributes.get(attributeType);
2956            if (attrList == null || attrList.isEmpty())
2957            {
2958              // There aren't any conflicts, so we can just add the attribute to the entry.
2959              putAttributes(attributeType, newLinkedList(collectiveAttr));
2960            }
2961            else
2962            {
2963              // There is a conflict with an existing operational attribute.
2964              resolveCollectiveConflict(subEntry.getConflictBehavior(),
2965                  collectiveAttr, attrList, operationalAttributes, attributeType);
2966            }
2967          }
2968          else
2969          {
2970            // There is a conflict with an existing user attribute.
2971            resolveCollectiveConflict(subEntry.getConflictBehavior(),
2972                collectiveAttr, attrList, userAttributes, attributeType);
2973          }
2974        }
2975      }
2976    }
2977  }
2978
2979  private ByteString normalize(MatchingRule matchingRule, ByteString value)
2980      throws DirectoryException
2981  {
2982    try
2983    {
2984      return matchingRule.normalizeAttributeValue(value);
2985    }
2986    catch (DecodeException e)
2987    {
2988      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
2989          e.getMessageObject(), e);
2990    }
2991  }
2992
2993  /**
2994   * Resolves a conflict arising with a collective attribute.
2995   *
2996   * @param conflictBehavior
2997   *          the behavior of the conflict
2998   * @param collectiveAttr
2999   *          the attribute in conflict
3000   * @param attrList
3001   *          the List of attribute where to resolve the conflict
3002   * @param attributes
3003   *          the Map of attributes where to solve the conflict
3004   * @param attributeType
3005   *          the attribute type used with the Map
3006   */
3007  private void resolveCollectiveConflict(
3008      CollectiveConflictBehavior conflictBehavior, Attribute collectiveAttr,
3009      List<Attribute> attrList, Map<AttributeType, List<Attribute>> attributes,
3010      AttributeType attributeType)
3011  {
3012    if (attrList.get(0).isVirtual())
3013    {
3014      // The existing attribute is already virtual,
3015      // so we've got a different conflict, but we'll let the first win.
3016      // FIXME -- Should we handle this differently?
3017      return;
3018    }
3019
3020    // The conflict is with a real attribute. See what the
3021    // conflict behavior is and figure out how to handle it.
3022    switch (conflictBehavior)
3023    {
3024    case REAL_OVERRIDES_VIRTUAL:
3025      // We don't need to update the entry because the real attribute will take
3026      // precedence.
3027      break;
3028
3029    case VIRTUAL_OVERRIDES_REAL:
3030      // We need to move the real attribute to the suppressed list
3031      // and replace it with the virtual attribute.
3032      suppressedAttributes.put(attributeType, attrList);
3033      attributes.put(attributeType, newLinkedList(collectiveAttr));
3034      break;
3035
3036    case MERGE_REAL_AND_VIRTUAL:
3037      // We need to add the virtual attribute to the
3038      // list and keep the existing real attribute(s).
3039      attrList.add(collectiveAttr);
3040      break;
3041    }
3042  }
3043
3044
3045
3046  /**
3047   * Performs any necessary virtual attribute processing for this
3048   * entry.  This should only be called at the time the entry is
3049   * decoded or created within the backend.
3050   */
3051  public void processVirtualAttributes()
3052  {
3053    for (VirtualAttributeRule rule : DirectoryServer.getVirtualAttributes(this))
3054    {
3055      AttributeType attributeType = rule.getAttributeType();
3056      List<Attribute> attrList = userAttributes.get(attributeType);
3057      if (attrList == null || attrList.isEmpty())
3058      {
3059        attrList = operationalAttributes.get(attributeType);
3060        if (attrList == null || attrList.isEmpty())
3061        {
3062          // There aren't any conflicts, so we can just add the attribute to the entry.
3063          Attribute attr = new VirtualAttribute(attributeType, this, rule);
3064          putAttributes(attributeType, newLinkedList(attr));
3065        }
3066        else
3067        {
3068          // There is a conflict with an existing operational attribute.
3069          resolveVirtualConflict(rule, attrList, operationalAttributes, attributeType);
3070        }
3071      }
3072      else
3073      {
3074        // There is a conflict with an existing user attribute.
3075        resolveVirtualConflict(rule, attrList, userAttributes, attributeType);
3076      }
3077    }
3078
3079    // Collective attributes.
3080    processCollectiveAttributes();
3081  }
3082
3083  /**
3084   * Resolves a conflict arising with a virtual attribute.
3085   *
3086   * @param rule
3087   *          the VirtualAttributeRule in conflict
3088   * @param attrList
3089   *          the List of attribute where to resolve the conflict
3090   * @param attributes
3091   *          the Map of attribute where to resolve the conflict
3092   * @param attributeType
3093   *          the attribute type used with the Map
3094   */
3095  private void resolveVirtualConflict(VirtualAttributeRule rule,
3096      List<Attribute> attrList, Map<AttributeType, List<Attribute>> attributes,
3097      AttributeType attributeType)
3098  {
3099    if (attrList.get(0).isVirtual())
3100    {
3101      // The existing attribute is already virtual, so we've got
3102      // a different conflict, but we'll let the first win.
3103      // FIXME -- Should we handle this differently?
3104      return;
3105    }
3106
3107    // The conflict is with a real attribute. See what the
3108    // conflict behavior is and figure out how to handle it.
3109    switch (rule.getConflictBehavior())
3110    {
3111    case REAL_OVERRIDES_VIRTUAL:
3112      // We don't need to update the entry because the real
3113      // attribute will take precedence.
3114      break;
3115
3116    case VIRTUAL_OVERRIDES_REAL:
3117      // We need to move the real attribute to the suppressed
3118      // list and replace it with the virtual attribute.
3119      suppressedAttributes.put(attributeType, attrList);
3120      Attribute attr = new VirtualAttribute(attributeType, this, rule);
3121      attributes.put(attributeType, newLinkedList(attr));
3122      break;
3123
3124    case MERGE_REAL_AND_VIRTUAL:
3125      // We need to add the virtual attribute to the list and
3126      // keep the existing real attribute(s).
3127      attrList.add(new VirtualAttribute(attributeType, this, rule));
3128      break;
3129    }
3130  }
3131
3132
3133  /**
3134   * Encodes this entry into a form that is suitable for long-term
3135   * persistent storage.  The encoding will have a version number so
3136   * that if the way we store entries changes in the future we will
3137   * still be able to read entries encoded in an older format.
3138   *
3139   * @param  buffer  The buffer to encode into.
3140   * @param  config  The configuration that may be used to control how
3141   *                 the entry is encoded.
3142   *
3143   * @throws  DirectoryException  If a problem occurs while attempting
3144   *                              to encode the entry.
3145   */
3146  public void encode(ByteStringBuilder buffer,
3147                     EntryEncodeConfig config)
3148         throws DirectoryException
3149  {
3150    encodeV3(buffer, config);
3151  }
3152
3153  /**
3154   * Encodes this entry using the V3 encoding.
3155   *
3156   * @param  buffer  The buffer to encode into.
3157   * @param  config  The configuration that should be used to encode
3158   *                 the entry.
3159   *
3160   * @throws  DirectoryException  If a problem occurs while attempting
3161   *                              to encode the entry.
3162   */
3163  private void encodeV3(ByteStringBuilder buffer,
3164                        EntryEncodeConfig config)
3165         throws DirectoryException
3166  {
3167    // The version number will be one byte.
3168    buffer.appendByte(0x03);
3169
3170    // Get the encoded representation of the config.
3171    config.encode(buffer);
3172
3173    // If we should include the DN, then it will be encoded as a
3174    // one-to-five byte length followed by the UTF-8 byte
3175    // representation.
3176    if (! config.excludeDN())
3177    {
3178      // TODO: Can we encode the DN directly into buffer?
3179      byte[] dnBytes  = getBytes(dn.toString());
3180      buffer.appendBERLength(dnBytes.length);
3181      buffer.appendBytes(dnBytes);
3182    }
3183
3184
3185    // Encode the object classes in the appropriate manner.
3186    if (config.compressObjectClassSets())
3187    {
3188      config.getCompressedSchema().encodeObjectClasses(buffer, objectClasses);
3189    }
3190    else
3191    {
3192      // Encode number of OCs and 0 terminated names.
3193      buffer.appendBERLength(objectClasses.size());
3194      for (String ocName : objectClasses.values())
3195      {
3196        buffer.appendUtf8(ocName);
3197        buffer.appendByte(0x00);
3198      }
3199    }
3200
3201
3202    // Encode the user attributes in the appropriate manner.
3203    encodeAttributes(buffer, userAttributes, config);
3204
3205
3206    // The operational attributes will be encoded in the same way as
3207    // the user attributes.
3208    encodeAttributes(buffer, operationalAttributes, config);
3209  }
3210
3211  /**
3212   * Encode the given attributes of an entry.
3213   *
3214   * @param  buffer  The buffer to encode into.
3215   * @param  attributes The attributes to encode.
3216   * @param  config  The configuration that may be used to control how
3217   *                 the entry is encoded.
3218   *
3219   * @throws  DirectoryException  If a problem occurs while attempting
3220   *                              to encode the entry.
3221   */
3222  private void encodeAttributes(ByteStringBuilder buffer,
3223                    Map<AttributeType,List<Attribute>> attributes,
3224                                EntryEncodeConfig config)
3225      throws DirectoryException
3226  {
3227    int numAttributes = 0;
3228
3229    // First count how many attributes are there to encode.
3230    for (List<Attribute> attrList : attributes.values())
3231    {
3232      Attribute a;
3233      for (int i = 0; i < attrList.size(); i++)
3234      {
3235        a = attrList.get(i);
3236        if (a.isVirtual() || a.isEmpty())
3237        {
3238          continue;
3239        }
3240
3241        numAttributes++;
3242      }
3243    }
3244
3245    // Encoded one-to-five byte number of attributes
3246    buffer.appendBERLength(numAttributes);
3247
3248    if (config.compressAttributeDescriptions())
3249    {
3250      for (List<Attribute> attrList : attributes.values())
3251      {
3252        for (Attribute a : attrList)
3253        {
3254          if (a.isVirtual() || a.isEmpty())
3255          {
3256            continue;
3257          }
3258
3259          config.getCompressedSchema().encodeAttribute(buffer, a);
3260        }
3261      }
3262    }
3263    else
3264    {
3265      // The attributes will be encoded as a sequence of:
3266      // - A UTF-8 byte representation of the attribute name.
3267      // - A zero delimiter
3268      // - A one-to-five byte number of values for the attribute
3269      // - A sequence of:
3270      //   - A one-to-five byte length for the value
3271      //   - A UTF-8 byte representation for the value
3272      for (List<Attribute> attrList : attributes.values())
3273      {
3274        for (Attribute a : attrList)
3275        {
3276          byte[] nameBytes = getBytes(a.getNameWithOptions());
3277          buffer.appendBytes(nameBytes);
3278          buffer.appendByte(0x00);
3279
3280          buffer.appendBERLength(a.size());
3281          for(ByteString v : a)
3282          {
3283            buffer.appendBERLength(v.length());
3284            buffer.appendBytes(v);
3285          }
3286        }
3287      }
3288    }
3289  }
3290
3291
3292  /**
3293   * Decodes the provided byte array as an entry.
3294   *
3295   * @param  entryBuffer  The byte array containing the data to be
3296   *                      decoded.
3297   *
3298   * @return  The decoded entry.
3299   *
3300   * @throws  DirectoryException  If the provided byte array cannot be
3301   *                              decoded as an entry.
3302   */
3303  public static Entry decode(ByteSequenceReader entryBuffer)
3304         throws DirectoryException
3305  {
3306    return decode(entryBuffer,
3307                  DirectoryServer.getDefaultCompressedSchema());
3308  }
3309
3310
3311
3312    /**
3313   * Decodes the provided byte array as an entry using the V3
3314   * encoding.
3315   *
3316   * @param  entryBuffer       The byte buffer containing the data to
3317   *                           be decoded.
3318   * @param  compressedSchema  The compressed schema manager to use
3319   *                           when decoding tokenized schema
3320   *                           elements.
3321   *
3322   * @return  The decoded entry.
3323   *
3324   * @throws  DirectoryException  If the provided byte array cannot be
3325   *                              decoded as an entry.
3326   */
3327  public static Entry decode(ByteSequenceReader entryBuffer,
3328                             CompressedSchema compressedSchema)
3329         throws DirectoryException
3330  {
3331    try
3332    {
3333      // The first byte must be the entry version.  If it's not one
3334      // we recognize, then that's an error.
3335      Byte version = entryBuffer.readByte();
3336      if (version != 0x03 && version != 0x02 && version != 0x01)
3337      {
3338        LocalizableMessage message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get(
3339            byteToHex(version));
3340        throw new DirectoryException(
3341                       DirectoryServer.getServerErrorResultCode(),
3342                       message);
3343      }
3344
3345      EntryEncodeConfig config;
3346      if(version != 0x01)
3347      {
3348        // Next is the length of the encoded configuration.
3349        int configLength = entryBuffer.readBERLength();
3350
3351        // Next is the encoded configuration itself.
3352        config =
3353            EntryEncodeConfig.decode(entryBuffer, configLength,
3354                compressedSchema);
3355      }
3356      else
3357      {
3358        config = EntryEncodeConfig.DEFAULT_CONFIG;
3359      }
3360
3361      // If we should have included the DN in the entry, then it's
3362      // next.
3363      DN dn;
3364      if (config.excludeDN())
3365      {
3366        dn = DN.rootDN();
3367      }
3368      else
3369      {
3370        // Next is the length of the DN.  It may be a single byte or
3371        // multiple bytes.
3372        int dnLength = entryBuffer.readBERLength();
3373
3374
3375        // Next is the DN itself.
3376        ByteSequence dnBytes = entryBuffer.readByteSequence(dnLength);
3377        dn = DN.valueOf(dnBytes.toByteString());
3378      }
3379
3380
3381      // Next is the set of encoded object classes.  The encoding will
3382      // depend on the configuration.
3383      Map<ObjectClass,String> objectClasses =
3384          decodeObjectClasses(version, entryBuffer, config);
3385
3386
3387      // Now, we should iterate through the user and operational attributes and
3388      // decode each one.
3389      Map<AttributeType, List<Attribute>> userAttributes =
3390          decodeAttributes(version, entryBuffer, config);
3391      Map<AttributeType, List<Attribute>> operationalAttributes =
3392          decodeAttributes(version, entryBuffer, config);
3393
3394
3395      // We've got everything that we need, so create and return the entry.
3396      return new Entry(dn, objectClasses, userAttributes,
3397          operationalAttributes);
3398    }
3399    catch (DirectoryException de)
3400    {
3401      throw de;
3402    }
3403    catch (Exception e)
3404    {
3405      logger.traceException(e);
3406
3407      LocalizableMessage message =
3408          ERR_ENTRY_DECODE_EXCEPTION.get(getExceptionMessage(e));
3409      throw new DirectoryException(
3410                     DirectoryServer.getServerErrorResultCode(),
3411                     message, e);
3412    }
3413  }
3414
3415
3416  /**
3417   * Decode the object classes of an encoded entry.
3418   *
3419   * @param  ver The version of the entry encoding.
3420   * @param  entryBuffer The byte sequence containing the encoded
3421   *                     entry.
3422   * @param  config  The configuration that may be used to control how
3423   *                 the entry is encoded.
3424   *
3425   * @return  A map of the decoded object classes.
3426   * @throws  DirectoryException  If a problem occurs while attempting
3427   *                              to encode the entry.
3428   */
3429  private static Map<ObjectClass,String> decodeObjectClasses(
3430      byte ver, ByteSequenceReader entryBuffer,
3431      EntryEncodeConfig config) throws DirectoryException
3432  {
3433    // Next is the set of encoded object classes.  The encoding will
3434    // depend on the configuration.
3435    if (config.compressObjectClassSets())
3436    {
3437      return config.getCompressedSchema().decodeObjectClasses(entryBuffer);
3438    }
3439
3440    Map<ObjectClass, String> objectClasses;
3441    {
3442      if(ver < 0x03)
3443      {
3444        // Next is the length of the object classes. It may be a
3445        // single byte or multiple bytes.
3446        int ocLength = entryBuffer.readBERLength();
3447
3448        // The set of object classes will be encoded as a single
3449        // string with the object class names separated by zeros.
3450        objectClasses = new LinkedHashMap<>();
3451        int startPos = entryBuffer.position();
3452        for (int i=0; i < ocLength; i++)
3453        {
3454          if (entryBuffer.readByte() == 0x00)
3455          {
3456            int endPos = entryBuffer.position() - 1;
3457            addObjectClass(objectClasses, entryBuffer, startPos, endPos);
3458
3459            entryBuffer.skip(1);
3460            startPos = entryBuffer.position();
3461          }
3462        }
3463        int endPos = entryBuffer.position();
3464        addObjectClass(objectClasses, entryBuffer, startPos, endPos);
3465      }
3466      else
3467      {
3468        // Next is the number of zero terminated object classes.
3469        int numOC = entryBuffer.readBERLength();
3470        objectClasses = new LinkedHashMap<>(numOC);
3471        for(int i = 0; i < numOC; i++)
3472        {
3473          int startPos = entryBuffer.position();
3474          while(entryBuffer.readByte() != 0x00)
3475          {}
3476          int endPos = entryBuffer.position() - 1;
3477          addObjectClass(objectClasses, entryBuffer, startPos, endPos);
3478          entryBuffer.skip(1);
3479        }
3480      }
3481    }
3482
3483    return objectClasses;
3484  }
3485
3486  /**
3487   * Adds the objectClass contained in the buffer to the map of object class.
3488   *
3489   * @param objectClasses
3490   *          the Map where to add the objectClass
3491   * @param entryBuffer
3492   *          the buffer containing the objectClass name
3493   * @param startPos
3494   *          the starting position in the buffer
3495   * @param endPos
3496   *          the ending position in the buffer
3497   */
3498  private static void addObjectClass(Map<ObjectClass, String> objectClasses,
3499      ByteSequenceReader entryBuffer, int startPos, int endPos)
3500  {
3501    entryBuffer.position(startPos);
3502    final String ocName = entryBuffer.readStringUtf8(endPos - startPos);
3503    final String lowerName = toLowerCase(ocName);
3504    final ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
3505    objectClasses.put(oc, ocName);
3506  }
3507
3508  /**
3509   * Decode the attributes of an encoded entry.
3510   *
3511   * @param  ver The version of the entry encoding.
3512   * @param  entryBuffer The byte sequence containing the encoded
3513   *                     entry.
3514   * @param  config  The configuration that may be used to control how
3515   *                 the entry is encoded.
3516   *
3517   * @return  A map of the decoded object classes.
3518   * @throws  DirectoryException  If a problem occurs while attempting
3519   *                              to encode the entry.
3520   */
3521  private static Map<AttributeType, List<Attribute>>
3522  decodeAttributes(Byte ver, ByteSequenceReader entryBuffer,
3523                   EntryEncodeConfig config) throws DirectoryException
3524  {
3525    // Next is the total number of attributes.  It may be a
3526    // single byte or multiple bytes.
3527    int attrs = entryBuffer.readBERLength();
3528
3529
3530    // Now, we should iterate through the attributes and decode each one.
3531    Map<AttributeType, List<Attribute>> attributes = new LinkedHashMap<>(attrs);
3532    if (config.compressAttributeDescriptions())
3533    {
3534      for (int i=0; i < attrs; i++)
3535      {
3536        if(ver < 0x03)
3537        {
3538          // Version 2 includes a total attribute length
3539          entryBuffer.readBERLength();
3540        }
3541        // Decode the attribute.
3542        Attribute a = config.getCompressedSchema().decodeAttribute(entryBuffer);
3543        List<Attribute> attrList = attributes.get(a.getAttributeDescription().getAttributeType());
3544        if (attrList == null)
3545        {
3546          attrList = new ArrayList<>(1);
3547          attributes.put(a.getAttributeDescription().getAttributeType(), attrList);
3548        }
3549        attrList.add(a);
3550      }
3551    }
3552    else
3553    {
3554      AttributeBuilder builder = new AttributeBuilder();
3555      int startPos;
3556      int endPos;
3557      for (int i=0; i < attrs; i++)
3558      {
3559
3560        // First, we have the zero-terminated attribute name.
3561        startPos = entryBuffer.position();
3562        while (entryBuffer.readByte() != 0x00)
3563        {}
3564        endPos = entryBuffer.position()-1;
3565        entryBuffer.position(startPos);
3566        String name = entryBuffer.readStringUtf8(endPos - startPos);
3567        entryBuffer.skip(1);
3568
3569        AttributeType attributeType;
3570        int semicolonPos = name.indexOf(';');
3571        if (semicolonPos > 0)
3572        {
3573          builder.setAttributeType(name.substring(0, semicolonPos));
3574          attributeType = builder.getAttributeType();
3575
3576          int nextPos = name.indexOf(';', semicolonPos+1);
3577          while (nextPos > 0)
3578          {
3579            String option = name.substring(semicolonPos+1, nextPos);
3580            if (option.length() > 0)
3581            {
3582              builder.setOption(option);
3583            }
3584
3585            semicolonPos = nextPos;
3586            nextPos = name.indexOf(';', semicolonPos+1);
3587          }
3588
3589          String option = name.substring(semicolonPos+1);
3590          if (option.length() > 0)
3591          {
3592            builder.setOption(option);
3593          }
3594        }
3595        else
3596        {
3597          builder.setAttributeType(name);
3598          attributeType = builder.getAttributeType();
3599        }
3600
3601
3602        // Next, we have the number of values.
3603        int numValues = entryBuffer.readBERLength();
3604
3605        // Next, we have the sequence of length-value pairs.
3606        for (int j=0; j < numValues; j++)
3607        {
3608          int valueLength = entryBuffer.readBERLength();
3609
3610          ByteString valueBytes =
3611              entryBuffer.readByteSequence(valueLength).toByteString();
3612          builder.add(valueBytes);
3613        }
3614
3615
3616        // Create the attribute and add it to the set of attributes.
3617        Attribute a = builder.toAttribute();
3618        List<Attribute> attrList = attributes.get(attributeType);
3619        if (attrList == null)
3620        {
3621          attrList = new ArrayList<>(1);
3622          attributes.put(attributeType, attrList);
3623        }
3624        attrList.add(a);
3625      }
3626    }
3627
3628    return attributes;
3629  }
3630
3631
3632
3633  /**
3634   * Retrieves a list of the lines for this entry in LDIF form.  Long
3635   * lines will not be wrapped automatically.
3636   *
3637   * @return  A list of the lines for this entry in LDIF form.
3638   */
3639  public List<StringBuilder> toLDIF()
3640  {
3641    List<StringBuilder> ldifLines = new LinkedList<>();
3642
3643    // First, append the DN.
3644    StringBuilder dnLine = new StringBuilder("dn");
3645    appendLDIFSeparatorAndValue(dnLine, ByteString.valueOfUtf8(dn.toString()));
3646    ldifLines.add(dnLine);
3647
3648    // Next, add the set of objectclasses.
3649    for (String s : objectClasses.values())
3650    {
3651      StringBuilder ocLine = new StringBuilder("objectClass: ").append(s);
3652      ldifLines.add(ocLine);
3653    }
3654
3655    // Finally, add the set of user and operational attributes.
3656    addLinesForAttributes(ldifLines, userAttributes);
3657    addLinesForAttributes(ldifLines, operationalAttributes);
3658
3659    return ldifLines;
3660  }
3661
3662
3663  /**
3664   * Add LDIF lines for each passed in attributes.
3665   *
3666   * @param ldifLines
3667   *          the List where to add the LDIF lines
3668   * @param attributes
3669   *          the List of attributes to convert into LDIf lines
3670   */
3671  private void addLinesForAttributes(List<StringBuilder> ldifLines,
3672      Map<AttributeType, List<Attribute>> attributes)
3673  {
3674    for (List<Attribute> attrList : attributes.values())
3675    {
3676      for (Attribute a : attrList)
3677      {
3678        String attrName = a.getNameWithOptions();
3679        for (ByteString v : a)
3680        {
3681          StringBuilder attrLine = new StringBuilder(attrName);
3682          appendLDIFSeparatorAndValue(attrLine, v);
3683          ldifLines.add(attrLine);
3684        }
3685      }
3686    }
3687  }
3688
3689
3690  /**
3691   * Writes this entry in LDIF form according to the provided
3692   * configuration.
3693   *
3694   * @param  exportConfig  The configuration that specifies how the
3695   *                       entry should be written.
3696   *
3697   * @return  <CODE>true</CODE> if the entry is actually written, or
3698   *          <CODE>false</CODE> if it is not for some reason.
3699   *
3700   * @throws  IOException  If a problem occurs while writing the
3701   *                       information.
3702   *
3703   * @throws  LDIFException  If a problem occurs while trying to
3704   *                         determine whether to write the entry.
3705   */
3706  public boolean toLDIF(LDIFExportConfig exportConfig)
3707         throws IOException, LDIFException
3708  {
3709    // See if this entry should be included in the export at all.
3710    try
3711    {
3712      if (! exportConfig.includeEntry(this))
3713      {
3714        if (logger.isTraceEnabled())
3715        {
3716          logger.trace("Skipping entry %s because of the export configuration.", dn);
3717        }
3718        return false;
3719      }
3720    }
3721    catch (Exception e)
3722    {
3723      logger.traceException(e);
3724      throw new LDIFException(ERR_LDIF_COULD_NOT_EVALUATE_FILTERS_FOR_EXPORT.get(dn, e), e);
3725    }
3726
3727
3728    // Invoke LDIF export plugins on the entry if appropriate.
3729    if (exportConfig.invokeExportPlugins())
3730    {
3731      PluginConfigManager pluginConfigManager =
3732           DirectoryServer.getPluginConfigManager();
3733      PluginResult.ImportLDIF pluginResult =
3734           pluginConfigManager.invokeLDIFExportPlugins(exportConfig,
3735                                                    this);
3736      if (! pluginResult.continueProcessing())
3737      {
3738        return false;
3739      }
3740    }
3741
3742
3743    // Get the information necessary to write the LDIF.
3744    BufferedWriter writer     = exportConfig.getWriter();
3745    int            wrapColumn = exportConfig.getWrapColumn();
3746    boolean        wrapLines  = wrapColumn > 1;
3747
3748
3749    // First, write the DN.  It will always be included.
3750    StringBuilder dnLine = new StringBuilder("dn");
3751    appendLDIFSeparatorAndValue(dnLine, ByteString.valueOfUtf8(dn.toString()));
3752    LDIFWriter.writeLDIFLine(dnLine, writer, wrapLines, wrapColumn);
3753
3754
3755    // Next, the set of objectclasses.
3756    final boolean typesOnly = exportConfig.typesOnly();
3757    if (exportConfig.includeObjectClasses())
3758    {
3759      if (typesOnly)
3760      {
3761        StringBuilder ocLine = new StringBuilder("objectClass:");
3762        LDIFWriter.writeLDIFLine(ocLine, writer, wrapLines, wrapColumn);
3763      }
3764      else
3765      {
3766        for (String s : objectClasses.values())
3767        {
3768          StringBuilder ocLine = new StringBuilder("objectClass: ").append(s);
3769          LDIFWriter.writeLDIFLine(ocLine, writer, wrapLines, wrapColumn);
3770        }
3771      }
3772    }
3773    else
3774    {
3775      if (logger.isTraceEnabled())
3776      {
3777        logger.trace("Skipping objectclasses for entry %s because of the export configuration.", dn);
3778      }
3779    }
3780
3781
3782    // Now the set of user attributes.
3783    writeLDIFLines(userAttributes, typesOnly, "user", exportConfig, writer,
3784        wrapColumn, wrapLines);
3785
3786
3787    // Next, the set of operational attributes.
3788    if (exportConfig.includeOperationalAttributes())
3789    {
3790      writeLDIFLines(operationalAttributes, typesOnly, "operational",
3791          exportConfig, writer, wrapColumn, wrapLines);
3792    }
3793    else
3794    {
3795      if (logger.isTraceEnabled())
3796      {
3797        logger.trace(
3798            "Skipping all operational attributes for entry %s " +
3799            "because of the export configuration.", dn);
3800      }
3801    }
3802
3803
3804    // If we are not supposed to include virtual attributes, then
3805    // write any attributes that may normally be suppressed by a
3806    // virtual attribute.
3807    if (! exportConfig.includeVirtualAttributes())
3808    {
3809      for (AttributeType t : suppressedAttributes.keySet())
3810      {
3811        if (exportConfig.includeAttribute(t))
3812        {
3813          for (Attribute a : suppressedAttributes.get(t))
3814          {
3815            writeLDIFLine(a, typesOnly, writer, wrapLines, wrapColumn);
3816          }
3817        }
3818      }
3819    }
3820
3821
3822    // Make sure there is a blank line after the entry.
3823    writer.newLine();
3824
3825
3826    return true;
3827  }
3828
3829
3830  /**
3831   * Writes the provided List of attributes to LDIF using the provided
3832   * information.
3833   *
3834   * @param attributes
3835   *          the List of attributes to write as LDIF
3836   * @param typesOnly
3837   *          if true, only writes the type information, else writes the type
3838   *          information and values for the attribute.
3839   * @param attributeType
3840   *          the type of attribute being written to LDIF
3841   * @param exportConfig
3842   *          configures the export to LDIF
3843   * @param writer
3844   *          The writer to which the data should be written. It must not be
3845   *          <CODE>null</CODE>.
3846   * @param wrapLines
3847   *          Indicates whether to wrap long lines.
3848   * @param wrapColumn
3849   *          The column at which long lines should be wrapped.
3850   * @throws IOException
3851   *           If a problem occurs while writing the information.
3852   */
3853  private void writeLDIFLines(Map<AttributeType, List<Attribute>> attributes,
3854      final boolean typesOnly, String attributeType,
3855      LDIFExportConfig exportConfig, BufferedWriter writer, int wrapColumn,
3856      boolean wrapLines) throws IOException
3857  {
3858    for (AttributeType attrType : attributes.keySet())
3859    {
3860      if (exportConfig.includeAttribute(attrType))
3861      {
3862        List<Attribute> attrList = attributes.get(attrType);
3863        for (Attribute a : attrList)
3864        {
3865          if (a.isVirtual() && !exportConfig.includeVirtualAttributes())
3866          {
3867            continue;
3868          }
3869
3870          writeLDIFLine(a, typesOnly, writer, wrapLines, wrapColumn);
3871        }
3872      }
3873      else
3874      {
3875        if (logger.isTraceEnabled())
3876        {
3877          logger.trace("Skipping %s attribute %s for entry %s "
3878              + "because of the export configuration.", attributeType, attrType.getNameOrOID(), dn);
3879        }
3880      }
3881    }
3882  }
3883
3884
3885  /**
3886   * Writes the provided attribute to LDIF using the provided information.
3887   *
3888   * @param attribute
3889   *          the attribute to write to LDIF
3890   * @param typesOnly
3891   *          if true, only writes the type information, else writes the type
3892   *          information and values for the attribute.
3893   * @param writer
3894   *          The writer to which the data should be written. It must not be
3895   *          <CODE>null</CODE>.
3896   * @param wrapLines
3897   *          Indicates whether to wrap long lines.
3898   * @param wrapColumn
3899   *          The column at which long lines should be wrapped.
3900   * @throws IOException
3901   *           If a problem occurs while writing the information.
3902   */
3903  private void writeLDIFLine(Attribute attribute, final boolean typesOnly,
3904      BufferedWriter writer, boolean wrapLines, int wrapColumn)
3905      throws IOException
3906  {
3907    String attrName = attribute.getNameWithOptions();
3908    if (typesOnly)
3909    {
3910      StringBuilder attrLine = new StringBuilder(attrName);
3911      attrLine.append(":");
3912
3913      LDIFWriter.writeLDIFLine(attrLine, writer, wrapLines, wrapColumn);
3914    }
3915    else
3916    {
3917      for (ByteString v : attribute)
3918      {
3919        StringBuilder attrLine = new StringBuilder(attrName);
3920        appendLDIFSeparatorAndValue(attrLine, v);
3921        LDIFWriter.writeLDIFLine(attrLine, writer, wrapLines, wrapColumn);
3922      }
3923    }
3924  }
3925
3926
3927
3928  /**
3929   * Retrieves the name of the protocol associated with this protocol
3930   * element.
3931   *
3932   * @return  The name of the protocol associated with this protocol
3933   *          element.
3934   */
3935  @Override
3936  public String getProtocolElementName()
3937  {
3938    return "Entry";
3939  }
3940
3941
3942
3943  /**
3944   * Retrieves a hash code for this entry.
3945   *
3946   * @return  The hash code for this entry.
3947   */
3948  @Override
3949  public int hashCode()
3950  {
3951    int hashCode = dn.hashCode();
3952    for (ObjectClass oc : objectClasses.keySet())
3953    {
3954      hashCode += oc.hashCode();
3955    }
3956
3957    hashCode += hashCode(userAttributes.values());
3958    hashCode += hashCode(operationalAttributes.values());
3959    return hashCode;
3960  }
3961
3962  /**
3963   * Computes the hashCode for the list of attributes list.
3964   *
3965   * @param attributesLists
3966   *          the attributes for which to commpute the hashCode
3967   * @return the hashCode for the list of attributes list.
3968   */
3969  private int hashCode(Collection<List<Attribute>> attributesLists)
3970  {
3971    int result = 0;
3972    for (List<Attribute> attributes : attributesLists)
3973    {
3974      for (Attribute a : attributes)
3975      {
3976        result += a.hashCode();
3977      }
3978    }
3979    return result;
3980  }
3981
3982
3983
3984  /**
3985   * Indicates whether the provided object is equal to this entry.  In
3986   * order for the object to be considered equal, it must be an entry
3987   * with the same DN, set of object classes, and set of user and
3988   * operational attributes.
3989   *
3990   * @param  o  The object for which to make the determination.
3991   *
3992   * @return  {@code true} if the provided object may be considered
3993   *          equal to this entry, or {@code false} if not.
3994   */
3995  @Override
3996  public boolean equals(Object o)
3997  {
3998    if (this == o)
3999    {
4000      return true;
4001    }
4002    if (o == null)
4003    {
4004      return false;
4005    }
4006    if (! (o instanceof Entry))
4007    {
4008      return false;
4009    }
4010
4011    Entry e = (Entry) o;
4012    return dn.equals(e.dn)
4013        && objectClasses.keySet().equals(e.objectClasses.keySet())
4014        && equals(userAttributes, e.userAttributes)
4015        && equals(operationalAttributes, e.operationalAttributes);
4016  }
4017
4018  /**
4019   * Returns whether the 2 Maps are equal.
4020   *
4021   * @param attributes1
4022   *          the first Map of attributes
4023   * @param attributes2
4024   *          the second Map of attributes
4025   * @return true if the 2 Maps are equal, false otherwise
4026   */
4027  private boolean equals(Map<AttributeType, List<Attribute>> attributes1,
4028      Map<AttributeType, List<Attribute>> attributes2)
4029  {
4030    for (AttributeType at : attributes1.keySet())
4031    {
4032      List<Attribute> list1 = attributes1.get(at);
4033      List<Attribute> list2 = attributes2.get(at);
4034      if (list2 == null || list1.size() != list2.size())
4035      {
4036        return false;
4037      }
4038      for (Attribute a : list1)
4039      {
4040        if (!list2.contains(a))
4041        {
4042          return false;
4043        }
4044      }
4045    }
4046    return true;
4047  }
4048
4049
4050
4051  /**
4052   * Retrieves a string representation of this protocol element.
4053   *
4054   * @return  A string representation of this protocol element.
4055   */
4056  @Override
4057  public String toString()
4058  {
4059    return toLDIFString();
4060  }
4061
4062
4063
4064  /**
4065   * Appends a string representation of this protocol element to the
4066   * provided buffer.
4067   *
4068   * @param  buffer  The buffer into which the string representation
4069   *                 should be written.
4070   */
4071  @Override
4072  public void toString(StringBuilder buffer)
4073  {
4074    buffer.append(this);
4075  }
4076
4077
4078
4079  /**
4080   * Appends a string representation of this protocol element to the
4081   * provided buffer.
4082   *
4083   * @param  buffer  The buffer into which the string representation
4084   *                 should be written.
4085   * @param  indent  The number of spaces that should be used to
4086   *                 indent the resulting string representation.
4087   */
4088  @Override
4089  public void toString(StringBuilder buffer, int indent)
4090  {
4091    StringBuilder indentBuf = new StringBuilder(indent);
4092    for (int i=0 ; i < indent; i++)
4093    {
4094      indentBuf.append(' ');
4095    }
4096
4097    for (StringBuilder b : toLDIF())
4098    {
4099      buffer.append(indentBuf);
4100      buffer.append(b);
4101      buffer.append(EOL);
4102    }
4103  }
4104
4105
4106
4107  /**
4108   * Retrieves a string representation of this entry in LDIF form.
4109   *
4110   * @return  A string representation of this entry in LDIF form.
4111   */
4112  public String toLDIFString()
4113  {
4114    StringBuilder buffer = new StringBuilder();
4115
4116    for (StringBuilder ldifLine : toLDIF())
4117    {
4118      buffer.append(ldifLine);
4119      buffer.append(EOL);
4120    }
4121
4122    return buffer.toString();
4123  }
4124
4125
4126
4127  /**
4128   * Appends a single-line representation of this entry to the
4129   * provided buffer.
4130   *
4131   * @param  buffer  The buffer to which the information should be
4132   *                 written.
4133   */
4134  public void toSingleLineString(StringBuilder buffer)
4135  {
4136    buffer.append("Entry(dn=\"");
4137    buffer.append(dn);
4138    buffer.append("\",objectClasses={");
4139
4140    Iterator<String> iterator = objectClasses.values().iterator();
4141    if (iterator.hasNext())
4142    {
4143      buffer.append(iterator.next());
4144
4145      while (iterator.hasNext())
4146      {
4147        buffer.append(",");
4148        buffer.append(iterator.next());
4149      }
4150    }
4151
4152    buffer.append("},userAttrs={");
4153    appendAttributes(buffer, userAttributes.values());
4154    buffer.append("},operationalAttrs={");
4155    appendAttributes(buffer, operationalAttributes.values());
4156    buffer.append("})");
4157  }
4158
4159  /**
4160   * Appends the attributes to the StringBuilder.
4161   *
4162   * @param buffer
4163   *          the StringBuilder where to append
4164   * @param attributesLists
4165   *          the attributesLists to append
4166   */
4167  private void appendAttributes(StringBuilder buffer,
4168      Collection<List<Attribute>> attributesLists)
4169  {
4170    boolean firstAttr = true;
4171    for (List<Attribute> attributes : attributesLists)
4172    {
4173      for (Attribute a : attributes)
4174      {
4175        if (firstAttr)
4176        {
4177          firstAttr = false;
4178        }
4179        else
4180        {
4181          buffer.append(",");
4182        }
4183
4184        buffer.append(a.getNameWithOptions());
4185
4186        buffer.append("={");
4187        Iterator<ByteString> valueIterator = a.iterator();
4188        if (valueIterator.hasNext())
4189        {
4190          buffer.append(valueIterator.next());
4191
4192          while (valueIterator.hasNext())
4193          {
4194            buffer.append(",");
4195            buffer.append(valueIterator.next());
4196          }
4197        }
4198
4199        buffer.append("}");
4200      }
4201    }
4202  }
4203
4204
4205
4206  /**
4207   * Retrieves the requested attribute element for the specified
4208   * attribute type and options or <code>null</code> if this entry
4209   * does not contain an attribute with the specified attribute type
4210   * and options.
4211   *
4212   * @param attributeDescription
4213   *          The attribute description to retrieve.
4214   * @return The requested attribute element for the specified
4215   *         attribute type and options, or <code>null</code> if the
4216   *         specified attribute type is not present in this entry
4217   *         with the provided set of options.
4218   */
4219  public Attribute getExactAttribute(AttributeDescription attributeDescription)
4220  {
4221    List<Attribute> attributes = getAttributes(attributeDescription.getAttributeType());
4222    if (attributes != null)
4223    {
4224      for (Attribute attribute : attributes)
4225      {
4226        if (attribute.getAttributeDescription().equals(attributeDescription))
4227        {
4228          return attribute;
4229        }
4230      }
4231    }
4232    return null;
4233  }
4234
4235
4236
4237  /**
4238   * Adds the provided attribute to this entry. If an attribute with
4239   * the provided type and options already exists, then it will be
4240   * either merged or replaced depending on the value of
4241   * <code>replace</code>.
4242   *
4243   * @param attribute
4244   *          The attribute to add/replace in this entry.
4245   * @param duplicateValues
4246   *          A list to which any duplicate values will be added.
4247   * @param replace
4248   *          <code>true</code> if the attribute should replace any
4249   *          existing attribute.
4250   */
4251  private void setAttribute(Attribute attribute,
4252      List<ByteString> duplicateValues, boolean replace)
4253  {
4254    attachment = null;
4255
4256    AttributeDescription attrDesc = attribute.getAttributeDescription();
4257    AttributeType attrType = attrDesc.getAttributeType();
4258    if (attrType.isObjectClass())
4259    {
4260      // We will not do any validation of the object classes - this is
4261      // left to the caller.
4262      if (replace)
4263      {
4264        objectClasses.clear();
4265      }
4266
4267      MatchingRule rule = attrType.getEqualityMatchingRule();
4268      for (ByteString v : attribute)
4269      {
4270        String name = v.toString();
4271        String lowerName = toLowerName(rule, v);
4272
4273        // Create a default object class if necessary.
4274        ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
4275
4276        if (replace)
4277        {
4278          objectClasses.put(oc, name);
4279        }
4280        else
4281        {
4282          if (objectClasses.containsKey(oc))
4283          {
4284            duplicateValues.add(v);
4285          }
4286          else
4287          {
4288            objectClasses.put(oc, name);
4289          }
4290        }
4291      }
4292
4293      return;
4294    }
4295
4296    List<Attribute> attributes = getAttributes(attrType);
4297    if (attributes == null)
4298    {
4299      // Do nothing if we are deleting a non-existing attribute.
4300      if (replace && attribute.isEmpty())
4301      {
4302        return;
4303      }
4304
4305      // We are adding the first attribute with this attribute type.
4306      putAttributes(attrType, newArrayList(attribute));
4307      return;
4308    }
4309
4310    // There are already attributes with the same attribute type.
4311    for (int i = 0; i < attributes.size(); i++)
4312    {
4313      Attribute a = attributes.get(i);
4314      if (a.getAttributeDescription().equals(attrDesc))
4315      {
4316        if (replace)
4317        {
4318          if (!attribute.isEmpty())
4319          {
4320            attributes.set(i, attribute);
4321          }
4322          else
4323          {
4324            attributes.remove(i);
4325
4326            if (attributes.isEmpty())
4327            {
4328              removeAttributes(attrType);
4329            }
4330          }
4331        }
4332        else
4333        {
4334          AttributeBuilder builder = new AttributeBuilder(a);
4335          for (ByteString v : attribute)
4336          {
4337            if (!builder.add(v))
4338            {
4339              duplicateValues.add(v);
4340            }
4341          }
4342          attributes.set(i, builder.toAttribute());
4343        }
4344        return;
4345      }
4346    }
4347
4348    // There were no attributes with the same options.
4349    if (replace && attribute.isEmpty())
4350    {
4351      // Do nothing.
4352      return;
4353    }
4354
4355    attributes.add(attribute);
4356  }
4357
4358
4359
4360  /**
4361   * Returns an entry containing only those attributes of this entry
4362   * which match the provided criteria.
4363   *
4364   * @param attrNameList
4365   *          The list of attributes to include, may include wild
4366   *          cards.
4367   * @param omitValues
4368   *          Indicates whether to omit attribute values when
4369   *          processing.
4370   * @param omitReal
4371   *          Indicates whether to exclude real attributes.
4372   * @param omitVirtual
4373   *          Indicates whether to exclude virtual attributes.
4374   * @return An entry containing only those attributes of this entry
4375   *         which match the provided criteria.
4376   */
4377  public Entry filterEntry(Set<String> attrNameList,
4378      boolean omitValues, boolean omitReal, boolean omitVirtual)
4379  {
4380    final AttributeType ocType = DirectoryServer.getObjectClassAttributeType();
4381
4382    Map<ObjectClass, String> objectClassesCopy;
4383    Map<AttributeType, List<Attribute>> userAttrsCopy;
4384    Map<AttributeType, List<Attribute>> operationalAttrsCopy;
4385
4386    if (attrNameList == null || attrNameList.isEmpty())
4387    {
4388      // Common case: return filtered user attributes.
4389      userAttrsCopy = new LinkedHashMap<>(userAttributes.size());
4390      operationalAttrsCopy = new LinkedHashMap<>(0);
4391
4392      if (omitReal)
4393      {
4394        objectClassesCopy = new LinkedHashMap<>(0);
4395      }
4396      else if (omitValues)
4397      {
4398        objectClassesCopy = new LinkedHashMap<>(0);
4399
4400        // Add empty object class attribute.
4401        userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType)));
4402      }
4403      else
4404      {
4405        objectClassesCopy = new LinkedHashMap<>(objectClasses);
4406
4407        // First, add the objectclass attribute.
4408        Attribute ocAttr = getObjectClassAttribute();
4409        if (ocAttr != null)
4410        {
4411          userAttrsCopy.put(ocType, newArrayList(ocAttr));
4412        }
4413      }
4414
4415      // Copy all user attributes.
4416      deepCopy(userAttributes, userAttrsCopy, omitValues, true,
4417          omitReal, omitVirtual, true);
4418    }
4419    else
4420    {
4421      // Incrementally build table of attributes.
4422      if (omitReal || omitValues)
4423      {
4424        objectClassesCopy = new LinkedHashMap<>(0);
4425      }
4426      else
4427      {
4428        objectClassesCopy = new LinkedHashMap<>(objectClasses.size());
4429      }
4430
4431      userAttrsCopy = new LinkedHashMap<>(userAttributes.size());
4432      operationalAttrsCopy = new LinkedHashMap<>(operationalAttributes.size());
4433
4434      for (String attrName : attrNameList)
4435      {
4436        if ("*".equals(attrName))
4437        {
4438          // This is a special placeholder indicating that all user
4439          // attributes should be returned.
4440          if (!omitReal)
4441          {
4442            if (omitValues)
4443            {
4444              // Add empty object class attribute.
4445              userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType)));
4446            }
4447            else
4448            {
4449              // Add the objectclass attribute.
4450              objectClassesCopy.putAll(objectClasses);
4451              Attribute ocAttr = getObjectClassAttribute();
4452              if (ocAttr != null)
4453              {
4454                userAttrsCopy.put(ocType, newArrayList(ocAttr));
4455              }
4456            }
4457          }
4458
4459          // Copy all user attributes.
4460          deepCopy(userAttributes, userAttrsCopy, omitValues, true,
4461              omitReal, omitVirtual, true);
4462          continue;
4463        }
4464        else if ("+".equals(attrName))
4465        {
4466          // This is a special placeholder indicating that all
4467          // operational attributes should be returned.
4468          deepCopy(operationalAttributes, operationalAttrsCopy,
4469              omitValues, true, omitReal, omitVirtual, true);
4470          continue;
4471        }
4472
4473        String name;
4474        Set<String> options;
4475        int semicolonPos = attrName.indexOf(';');
4476        if (semicolonPos > 0)
4477        {
4478          String tmpName = attrName.substring(0, semicolonPos);
4479          name = tmpName;
4480          int nextPos = attrName.indexOf(';', semicolonPos+1);
4481          options = new HashSet<>();
4482          while (nextPos > 0)
4483          {
4484            options.add(attrName.substring(semicolonPos+1, nextPos));
4485
4486            semicolonPos = nextPos;
4487            nextPos = attrName.indexOf(';', semicolonPos+1);
4488          }
4489          options.add(attrName.substring(semicolonPos+1));
4490          attrName = tmpName;
4491        }
4492        else
4493        {
4494          name = attrName;
4495          options = null;
4496        }
4497
4498        AttributeType attrType = DirectoryServer.getAttributeType(name);
4499        if (attrType.isPlaceHolder())
4500        {
4501          // Unrecognized attribute type - do best effort search.
4502          for (Map.Entry<AttributeType, List<Attribute>> e :
4503            userAttributes.entrySet())
4504          {
4505            AttributeType t = e.getKey();
4506            if (t.hasNameOrOID(name))
4507            {
4508              mergeAttributeLists(e.getValue(), userAttrsCopy, t,
4509                  attrName, options, omitValues, omitReal, omitVirtual);
4510              continue;
4511            }
4512          }
4513
4514          for (Map.Entry<AttributeType, List<Attribute>> e :
4515            operationalAttributes.entrySet())
4516          {
4517            AttributeType t = e.getKey();
4518            if (t.hasNameOrOID(name))
4519            {
4520              mergeAttributeLists(e.getValue(), operationalAttrsCopy,
4521                  t, attrName, options, omitValues, omitReal, omitVirtual);
4522              continue;
4523            }
4524          }
4525        }
4526        else
4527        {
4528          // Recognized attribute type.
4529          if (attrType.isObjectClass()) {
4530            if (!omitReal)
4531            {
4532              if (omitValues)
4533              {
4534                userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType, attrName)));
4535              }
4536              else
4537              {
4538                Attribute ocAttr = getObjectClassAttribute();
4539                if (ocAttr != null)
4540                {
4541                  if (!attrName.equals(ocAttr.getName()))
4542                  {
4543                    // User requested non-default object class type name.
4544                    AttributeBuilder builder = new AttributeBuilder(ocAttr);
4545                    builder.setAttributeType(ocType, attrName);
4546                    ocAttr = builder.toAttribute();
4547                  }
4548
4549                  userAttrsCopy.put(ocType, newArrayList(ocAttr));
4550                }
4551              }
4552            }
4553          }
4554          else
4555          {
4556            List<Attribute> attrList = getUserAttribute(attrType);
4557            if (!attrList.isEmpty())
4558            {
4559              mergeAttributeLists(attrList, userAttrsCopy, attrType,
4560                  attrName, options, omitValues, omitReal, omitVirtual);
4561            }
4562            else
4563            {
4564              attrList = getOperationalAttribute(attrType);
4565              if (!attrList.isEmpty())
4566              {
4567                mergeAttributeLists(attrList, operationalAttrsCopy,
4568                    attrType, attrName, options, omitValues, omitReal,
4569                    omitVirtual);
4570              }
4571            }
4572          }
4573        }
4574      }
4575    }
4576
4577    return new Entry(dn, objectClassesCopy, userAttrsCopy,
4578                     operationalAttrsCopy);
4579  }
4580
4581  /**
4582   * Copies the provided list of attributes into the destination
4583   * attribute map according to the provided criteria.
4584   *
4585   * @param sourceList
4586   *          The list containing the attributes to be copied.
4587   * @param destMap
4588   *          The map where the attributes should be copied to.
4589   * @param attrType
4590   *          The attribute type.
4591   * @param attrName
4592   *          The user-provided attribute name.
4593   * @param options
4594   *          The user-provided attribute options.
4595   * @param omitValues
4596   *          Indicates whether to exclude attribute values.
4597   * @param omitReal
4598   *          Indicates whether to exclude real attributes.
4599   * @param omitVirtual
4600   *          Indicates whether to exclude virtual attributes.
4601   */
4602  private void mergeAttributeLists(List<Attribute> sourceList,
4603      Map<AttributeType, List<Attribute>> destMap,
4604      AttributeType attrType, String attrName, Set<String> options,
4605      boolean omitValues, boolean omitReal, boolean omitVirtual)
4606  {
4607    AttributeDescription attrDesc = AttributeDescription.create(attrType, options);
4608    mergeAttributeLists(sourceList, destMap, attrDesc, attrName, omitValues, omitReal, omitVirtual);
4609  }
4610
4611  private void mergeAttributeLists(List<Attribute> sourceList,
4612      Map<AttributeType, List<Attribute>> destMap,
4613      AttributeDescription attrDesc, String attrName,
4614      boolean omitValues, boolean omitReal, boolean omitVirtual)
4615  {
4616    if (sourceList == null)
4617    {
4618      return;
4619    }
4620
4621    for (Attribute attribute : sourceList)
4622    {
4623      if (attribute.isEmpty()
4624          || (omitReal && attribute.isReal())
4625          || (omitVirtual && attribute.isVirtual())
4626          || !attribute.getAttributeDescription().isSubTypeOf(attrDesc))
4627      {
4628        continue;
4629      }
4630      else
4631      {
4632        // If a non-default attribute name was provided or if the
4633        // attribute has options then we will need to rebuild the
4634        // attribute so that it contains the user-requested names and options.
4635        AttributeDescription subAttrDesc = attribute.getAttributeDescription();
4636        AttributeType subAttrType = subAttrDesc.getAttributeType();
4637
4638        if ((attrName != null && !attrName.equals(attribute.getName()))
4639            || attrDesc.hasOptions())
4640        {
4641          AttributeBuilder builder = new AttributeBuilder();
4642
4643          // We want to use the user-provided name only if this attribute has
4644          // the same type as the requested type. This might not be the case for
4645          // sub-types e.g. requesting "name" and getting back "cn" - we don't
4646          // want to rename "name" to "cn".
4647          if (attrName == null || !subAttrType.equals(attrDesc.getAttributeType()))
4648          {
4649            builder.setAttributeType(subAttrType, attribute.getName());
4650          }
4651          else
4652          {
4653            builder.setAttributeType(subAttrType, attrName);
4654          }
4655
4656          builder.setOptions(attrDesc.getOptions());
4657
4658          // Now add in remaining options from original attribute
4659          // (this will not overwrite options already present).
4660          builder.setOptions(attribute.getAttributeDescription().getOptions());
4661
4662          if (!omitValues)
4663          {
4664            builder.addAll(attribute);
4665          }
4666
4667          attribute = builder.toAttribute();
4668        }
4669        else if (omitValues)
4670        {
4671          attribute = Attributes.empty(attribute);
4672        }
4673
4674        // Now put the attribute into the destination map.
4675        // Be careful of duplicates.
4676        List<Attribute> attrList = destMap.get(subAttrType);
4677
4678        if (attrList == null)
4679        {
4680          // Assume that they'll all go in the one list. This isn't
4681          // always the case, for example if the list contains sub-types.
4682          attrList = new ArrayList<>(sourceList.size());
4683          attrList.add(attribute);
4684          destMap.put(subAttrType, attrList);
4685        }
4686        else
4687        {
4688          // The attribute may have already been put in the list.
4689          //
4690          // This may occur in two cases:
4691          //
4692          // 1) The attribute is identified by more than one attribute
4693          //    type description in the attribute list (e.g. in a wildcard).
4694          //
4695          // 2) The attribute has both a real and virtual component.
4696          //
4697          boolean found = false;
4698          for (int i = 0; i < attrList.size(); i++)
4699          {
4700            Attribute otherAttribute = attrList.get(i);
4701            if (otherAttribute.getAttributeDescription().equals(subAttrDesc))
4702            {
4703              // Assume that wildcards appear first in an attribute
4704              // list with more specific attribute names afterwards:
4705              // let the attribute name and options from the later
4706              // attribute take preference.
4707              attrList.set(i, Attributes.merge(attribute, otherAttribute));
4708              found = true;
4709            }
4710          }
4711
4712          if (!found)
4713          {
4714            attrList.add(attribute);
4715          }
4716        }
4717      }
4718    }
4719  }
4720}