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