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