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.getAttributeTypeOrNull(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("No %s attribute type is defined in the server schema.", attrTypeName);
2651      }
2652      return false;
2653    }
2654    return userAttributes.containsKey(attrType)
2655        || operationalAttributes.containsKey(attrType);
2656  }
2657
2658  /**
2659   * Whether the object class name exists in the objectClass of this entry.
2660   *
2661   * @param objectClassName
2662   *          the name of the object class to look for
2663   * @return true if the object class name exists in the objectClass of this
2664   *         entry, false otherwise
2665   */
2666  private boolean containsObjectClassByName(String objectClassName)
2667  {
2668    for (String ocName : objectClasses.values())
2669    {
2670      if (objectClassName.equalsIgnoreCase(ocName))
2671      {
2672        return true;
2673      }
2674    }
2675    return false;
2676  }
2677
2678  /**
2679   * Retrieves the set of referral URLs that are included in this
2680   * referral entry.  This should only be called if
2681   * <CODE>isReferral()</CODE> returns <CODE>true</CODE>.
2682   *
2683   * @return  The set of referral URLs that are included in this entry
2684   *          if it is a referral, or <CODE>null</CODE> if it is not a
2685   *          referral.
2686   */
2687  public Set<String> getReferralURLs()
2688  {
2689    AttributeType referralType = DirectoryServer.getAttributeTypeOrNull(ATTR_REFERRAL_URL);
2690    if (referralType == null)
2691    {
2692      // This should not happen -- The server doesn't have a ref attribute type defined.
2693      logger.trace("No %s attribute type is defined in the server schema.", ATTR_REFERRAL_URL);
2694      return null;
2695    }
2696
2697    List<Attribute> refAttrs = userAttributes.get(referralType);
2698    if (refAttrs == null)
2699    {
2700      refAttrs = operationalAttributes.get(referralType);
2701      if (refAttrs == null)
2702      {
2703        return null;
2704      }
2705    }
2706
2707    Set<String> referralURLs = new LinkedHashSet<>();
2708    for (Attribute a : refAttrs)
2709    {
2710      for (ByteString v : a)
2711      {
2712        referralURLs.add(v.toString());
2713      }
2714    }
2715
2716    return referralURLs;
2717  }
2718
2719
2720
2721  /**
2722   * Indicates whether this entry meets the criteria to consider it an
2723   * alias (e.g., it contains the "aliasObject" objectclass and a
2724   * "alias" attribute).
2725   *
2726   * @return  <CODE>true</CODE> if this entry meets the criteria to
2727   *          consider it an alias, or <CODE>false</CODE> if not.
2728   */
2729  public boolean isAlias()
2730  {
2731    return hasObjectClassOrAttribute(OC_ALIAS, ATTR_ALIAS_DN);
2732  }
2733
2734
2735
2736  /**
2737   * Retrieves the DN of the entry referenced by this alias entry.
2738   * This should only be called if <CODE>isAlias()</CODE> returns
2739   * <CODE>true</CODE>.
2740   *
2741   * @return  The DN of the entry referenced by this alias entry, or
2742   *          <CODE>null</CODE> if it is not an alias.
2743   *
2744   * @throws  DirectoryException  If there is an aliasedObjectName
2745   *                              attribute but its value cannot be
2746   *                              parsed as a DN.
2747   */
2748  public DN getAliasedDN() throws DirectoryException
2749  {
2750    AttributeType aliasType = DirectoryServer.getAttributeTypeOrNull(ATTR_REFERRAL_URL);
2751    if (aliasType == null)
2752    {
2753      // This should not happen -- The server doesn't have an aliasedObjectName attribute type defined.
2754      logger.trace("No %s attribute type is defined in the server schema.", ATTR_ALIAS_DN);
2755      return null;
2756    }
2757
2758    List<Attribute> aliasAttrs = userAttributes.get(aliasType);
2759    if (aliasAttrs == null)
2760    {
2761      aliasAttrs = operationalAttributes.get(aliasType);
2762      if (aliasAttrs == null)
2763      {
2764        return null;
2765      }
2766    }
2767
2768    if (!aliasAttrs.isEmpty())
2769    {
2770      // There should only be a single alias attribute in an entry,
2771      // and we'll skip the check for others for performance reasons.
2772      // We would just end up taking the first one anyway. The same
2773      // is true with the set of values, since it should be a
2774      // single-valued attribute.
2775      Attribute aliasAttr = aliasAttrs.get(0);
2776      if (!aliasAttr.isEmpty())
2777      {
2778        return DN.valueOf(aliasAttr.iterator().next().toString());
2779      }
2780    }
2781    return null;
2782  }
2783
2784
2785
2786  /**
2787   * Indicates whether this entry meets the criteria to consider it an
2788   * LDAP subentry (i.e., it contains the "ldapSubentry" objectclass).
2789   *
2790   * @return  <CODE>true</CODE> if this entry meets the criteria to
2791   *          consider it an LDAP subentry, or <CODE>false</CODE> if
2792   *          not.
2793   */
2794  public boolean isLDAPSubentry()
2795  {
2796    return hasObjectClass(OC_LDAP_SUBENTRY_LC);
2797  }
2798
2799  /**
2800   * Returns whether the current entry has a specific object class.
2801   *
2802   * @param objectClassLowerCase
2803   *          the lowercase name of the object class to look for
2804   * @return true if the current entry has the object class, false otherwise
2805   */
2806  private boolean hasObjectClass(String objectClassLowerCase)
2807  {
2808    ObjectClass oc = DirectoryServer.getObjectClass(objectClassLowerCase);
2809    if (oc == null)
2810    {
2811      // This should not happen
2812      // The server doesn't have this object class defined.
2813      if (logger.isTraceEnabled())
2814      {
2815        logger.trace(
2816            "No %s objectclass is defined in the server schema.",
2817            objectClassLowerCase);
2818      }
2819      return containsObjectClassByName(objectClassLowerCase);
2820    }
2821
2822    // Make the determination based on whether this entry has this objectclass.
2823    return objectClasses.containsKey(oc);
2824  }
2825
2826
2827
2828  /**
2829   * Indicates whether this entry meets the criteria to consider it
2830   * an RFC 3672 LDAP subentry (i.e., it contains the "subentry"
2831   * objectclass).
2832   *
2833   * @return  <CODE>true</CODE> if this entry meets the criteria to
2834   *          consider it an RFC 3672 LDAP subentry, or <CODE>false
2835   *          </CODE> if not.
2836   */
2837  public boolean isSubentry()
2838  {
2839    return hasObjectClass(OC_SUBENTRY);
2840  }
2841
2842
2843
2844  /**
2845   * Indicates whether the entry meets the criteria to consider it an
2846   * RFC 3671 LDAP collective attributes subentry (i.e., it contains
2847   * the "collectiveAttributeSubentry" objectclass).
2848   *
2849   * @return  <CODE>true</CODE> if this entry meets the criteria to
2850   *          consider it an RFC 3671 LDAP collective attributes
2851   *          subentry, or <CODE>false</CODE> if not.
2852   */
2853  public boolean isCollectiveAttributeSubentry()
2854  {
2855    return hasObjectClass(OC_COLLECTIVE_ATTR_SUBENTRY_LC);
2856  }
2857
2858
2859
2860  /**
2861   * Indicates whether the entry meets the criteria to consider it an
2862   * inherited collective attributes subentry (i.e., it contains
2863   * the "inheritedCollectiveAttributeSubentry" objectclass).
2864   *
2865   * @return  <CODE>true</CODE> if this entry meets the criteria to
2866   *          consider it an inherited collective attributes
2867   *          subentry, or <CODE>false</CODE> if not.
2868   */
2869  public boolean isInheritedCollectiveAttributeSubentry()
2870  {
2871    return hasObjectClass(OC_INHERITED_COLLECTIVE_ATTR_SUBENTRY_LC);
2872  }
2873
2874
2875
2876  /**
2877   * Indicates whether the entry meets the criteria to consider it an inherited
2878   * from DN collective attributes subentry (i.e., it contains the
2879   * "inheritedFromDNCollectiveAttributeSubentry" objectclass).
2880   *
2881   * @return <CODE>true</CODE> if this entry meets the criteria to consider it
2882   *         an inherited from DN collective attributes subentry, or
2883   *         <CODE>false</CODE> if not.
2884   */
2885  public boolean isInheritedFromDNCollectiveAttributeSubentry()
2886  {
2887    return hasObjectClass(OC_INHERITED_FROM_DN_COLLECTIVE_ATTR_SUBENTRY_LC);
2888  }
2889
2890
2891
2892  /**
2893   * Indicates whether the entry meets the criteria to consider it
2894   * an inherited from RDN collective attributes subentry (i.e.,
2895   * it contains the "inheritedFromRDNCollectiveAttributeSubentry"
2896   * objectclass).
2897   *
2898   * @return  <CODE>true</CODE> if this entry meets the criteria to
2899   *          consider it an inherited from RDN collective attributes
2900   *          subentry, or <CODE>false</CODE> if not.
2901   */
2902  public boolean isInheritedFromRDNCollectiveAttributeSubentry()
2903  {
2904    return hasObjectClass(OC_INHERITED_FROM_RDN_COLLECTIVE_ATTR_SUBENTRY_LC);
2905  }
2906
2907
2908
2909  /**
2910   * Indicates whether the entry meets the criteria to consider it a
2911   * LDAP password policy subentry (i.e., it contains the "pwdPolicy"
2912   * objectclass of LDAP Password Policy Internet-Draft).
2913   *
2914   * @return  <CODE>true</CODE> if this entry meets the criteria to
2915   *          consider it a LDAP Password Policy Internet-Draft
2916   *          subentry, or <CODE>false</CODE> if not.
2917   */
2918  public boolean isPasswordPolicySubentry()
2919  {
2920    return hasObjectClass(OC_PWD_POLICY_SUBENTRY_LC);
2921  }
2922
2923
2924
2925  /**
2926   * Indicates whether this entry falls within the range of the
2927   * provided search base DN and scope.
2928   *
2929   * @param  baseDN  The base DN for which to make the determination.
2930   * @param  scope   The search scope for which to make the
2931   *                 determination.
2932   *
2933   * @return  <CODE>true</CODE> if this entry is within the given
2934   *          base and scope, or <CODE>false</CODE> if it is not.
2935   */
2936  public boolean matchesBaseAndScope(DN baseDN, SearchScope scope)
2937  {
2938    return dn.matchesBaseAndScope(baseDN, scope);
2939  }
2940
2941
2942
2943  /**
2944   * Performs any necessary collective attribute processing for this
2945   * entry.  This should only be called at the time the entry is
2946   * decoded or created within the backend.
2947   */
2948  private void processCollectiveAttributes()
2949  {
2950    if (isSubentry() || isLDAPSubentry())
2951    {
2952      return;
2953    }
2954
2955    SubentryManager manager =
2956            DirectoryServer.getSubentryManager();
2957    if(manager == null)
2958    {
2959      //Subentry manager may not have been initialized by
2960      //a component that doesn't require it.
2961      return;
2962    }
2963    // Get applicable collective subentries.
2964    List<SubEntry> collectiveAttrSubentries =
2965            manager.getCollectiveSubentries(this);
2966
2967    if (collectiveAttrSubentries == null || collectiveAttrSubentries.isEmpty())
2968    {
2969      // Nothing to see here, move along.
2970      return;
2971    }
2972
2973    // Get collective attribute exclusions.
2974    AttributeType exclusionsType = DirectoryServer.getAttributeTypeOrNull(ATTR_COLLECTIVE_EXCLUSIONS_LC);
2975    List<Attribute> exclusionsAttrList = operationalAttributes.get(exclusionsType);
2976    Set<String> exclusionsNameSet = new HashSet<>();
2977    if (exclusionsAttrList != null && !exclusionsAttrList.isEmpty())
2978    {
2979      for (Attribute attr : exclusionsAttrList)
2980      {
2981        for (ByteString attrValue : attr)
2982        {
2983          String exclusionsName = attrValue.toString().toLowerCase();
2984          if (VALUE_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL_LC.equals(exclusionsName)
2985              || OID_COLLECTIVE_EXCLUSIONS_EXCLUDE_ALL.equals(exclusionsName))
2986          {
2987            return;
2988          }
2989          exclusionsNameSet.add(exclusionsName);
2990        }
2991      }
2992    }
2993
2994    // Process collective attributes.
2995    for (SubEntry subEntry : collectiveAttrSubentries)
2996    {
2997      if (subEntry.isCollective() || subEntry.isInheritedCollective())
2998      {
2999        Entry inheritFromEntry = null;
3000        if (subEntry.isInheritedCollective())
3001        {
3002          if (subEntry.isInheritedFromDNCollective() &&
3003              hasAttribute(subEntry.getInheritFromDNType()))
3004          {
3005            try
3006            {
3007              DN inheritFromDN = null;
3008              for (Attribute attr : getAttribute(
3009                   subEntry.getInheritFromDNType()))
3010              {
3011                for (ByteString value : attr)
3012                {
3013                  inheritFromDN = DN.decode(value);
3014                  // Respect subentry root scope.
3015                  if (!inheritFromDN.isDescendantOf(
3016                       subEntry.getDN().parent()))
3017                  {
3018                    inheritFromDN = null;
3019                  }
3020                  break;
3021                }
3022              }
3023              if (inheritFromDN == null)
3024              {
3025                continue;
3026              }
3027
3028              // TODO : ACI check; needs re-factoring to happen.
3029              inheritFromEntry = DirectoryServer.getEntry(inheritFromDN);
3030            }
3031            catch (DirectoryException de)
3032            {
3033              logger.traceException(de);
3034            }
3035          }
3036          else if (subEntry.isInheritedFromRDNCollective() &&
3037                   hasAttribute(subEntry.getInheritFromRDNAttrType()))
3038          {
3039            DN inheritFromDN = subEntry.getInheritFromBaseDN();
3040            if (inheritFromDN != null)
3041            {
3042              try
3043              {
3044                for (Attribute attr : getAttribute(
3045                   subEntry.getInheritFromRDNAttrType()))
3046                {
3047                  inheritFromDN = subEntry.getInheritFromBaseDN();
3048                  for (ByteString value : attr)
3049                  {
3050                    inheritFromDN = inheritFromDN.child(
3051                        RDN.create(subEntry.getInheritFromRDNType(),
3052                        value));
3053                    break;
3054                  }
3055                }
3056
3057                // TODO : ACI check; needs re-factoring to happen.
3058                inheritFromEntry = DirectoryServer.getEntry(inheritFromDN);
3059              }
3060              catch (DirectoryException de)
3061              {
3062                logger.traceException(de);
3063              }
3064            }
3065            else
3066            {
3067              continue;
3068            }
3069          }
3070        }
3071        List<Attribute> collectiveAttrList = subEntry.getCollectiveAttributes();
3072        for (Attribute collectiveAttr : collectiveAttrList)
3073        {
3074          AttributeType attributeType = collectiveAttr.getAttributeType();
3075          if (exclusionsNameSet.contains(
3076                  attributeType.getNormalizedPrimaryNameOrOID()))
3077          {
3078            continue;
3079          }
3080          if (subEntry.isInheritedCollective())
3081          {
3082            if (inheritFromEntry != null)
3083            {
3084              collectiveAttr = inheritFromEntry.getExactAttribute(
3085                      collectiveAttr.getAttributeType(),
3086                      collectiveAttr.getOptions());
3087              if (collectiveAttr == null || collectiveAttr.isEmpty())
3088              {
3089                continue;
3090              }
3091              collectiveAttr = new CollectiveVirtualAttribute(collectiveAttr);
3092            }
3093            else
3094            {
3095              continue;
3096            }
3097          }
3098          List<Attribute> attrList = userAttributes.get(attributeType);
3099          if (attrList == null || attrList.isEmpty())
3100          {
3101            attrList = operationalAttributes.get(attributeType);
3102            if (attrList == null || attrList.isEmpty())
3103            {
3104              // There aren't any conflicts, so we can just add the attribute to the entry.
3105              putAttributes(attributeType, newLinkedList(collectiveAttr));
3106            }
3107            else
3108            {
3109              // There is a conflict with an existing operational attribute.
3110              resolveCollectiveConflict(subEntry.getConflictBehavior(),
3111                  collectiveAttr, attrList, operationalAttributes, attributeType);
3112            }
3113          }
3114          else
3115          {
3116            // There is a conflict with an existing user attribute.
3117            resolveCollectiveConflict(subEntry.getConflictBehavior(),
3118                collectiveAttr, attrList, userAttributes, attributeType);
3119          }
3120        }
3121      }
3122    }
3123  }
3124
3125  private ByteString normalize(MatchingRule matchingRule, ByteString value)
3126      throws DirectoryException
3127  {
3128    try
3129    {
3130      return matchingRule.normalizeAttributeValue(value);
3131    }
3132    catch (DecodeException e)
3133    {
3134      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
3135          e.getMessageObject(), e);
3136    }
3137  }
3138
3139  /**
3140   * Resolves a conflict arising with a collective attribute.
3141   *
3142   * @param conflictBehavior
3143   *          the behavior of the conflict
3144   * @param collectiveAttr
3145   *          the attribute in conflict
3146   * @param attrList
3147   *          the List of attribute where to resolve the conflict
3148   * @param attributes
3149   *          the Map of attributes where to solve the conflict
3150   * @param attributeType
3151   *          the attribute type used with the Map
3152   */
3153  private void resolveCollectiveConflict(
3154      CollectiveConflictBehavior conflictBehavior, Attribute collectiveAttr,
3155      List<Attribute> attrList, Map<AttributeType, List<Attribute>> attributes,
3156      AttributeType attributeType)
3157  {
3158    if (attrList.get(0).isVirtual())
3159    {
3160      // The existing attribute is already virtual,
3161      // so we've got a different conflict, but we'll let the first win.
3162      // FIXME -- Should we handle this differently?
3163      return;
3164    }
3165
3166    // The conflict is with a real attribute. See what the
3167    // conflict behavior is and figure out how to handle it.
3168    switch (conflictBehavior)
3169    {
3170    case REAL_OVERRIDES_VIRTUAL:
3171      // We don't need to update the entry because the real attribute will take
3172      // precedence.
3173      break;
3174
3175    case VIRTUAL_OVERRIDES_REAL:
3176      // We need to move the real attribute to the suppressed list
3177      // and replace it with the virtual attribute.
3178      suppressedAttributes.put(attributeType, attrList);
3179      attributes.put(attributeType, newLinkedList(collectiveAttr));
3180      break;
3181
3182    case MERGE_REAL_AND_VIRTUAL:
3183      // We need to add the virtual attribute to the
3184      // list and keep the existing real attribute(s).
3185      attrList.add(collectiveAttr);
3186      break;
3187    }
3188  }
3189
3190
3191
3192  /**
3193   * Performs any necessary virtual attribute processing for this
3194   * entry.  This should only be called at the time the entry is
3195   * decoded or created within the backend.
3196   */
3197  public void processVirtualAttributes()
3198  {
3199    for (VirtualAttributeRule rule : DirectoryServer.getVirtualAttributes(this))
3200    {
3201      AttributeType attributeType = rule.getAttributeType();
3202      List<Attribute> attrList = userAttributes.get(attributeType);
3203      if (attrList == null || attrList.isEmpty())
3204      {
3205        attrList = operationalAttributes.get(attributeType);
3206        if (attrList == null || attrList.isEmpty())
3207        {
3208          // There aren't any conflicts, so we can just add the attribute to the entry.
3209          Attribute attr = new VirtualAttribute(attributeType, this, rule);
3210          putAttributes(attributeType, newLinkedList(attr));
3211        }
3212        else
3213        {
3214          // There is a conflict with an existing operational attribute.
3215          resolveVirtualConflict(rule, attrList, operationalAttributes, attributeType);
3216        }
3217      }
3218      else
3219      {
3220        // There is a conflict with an existing user attribute.
3221        resolveVirtualConflict(rule, attrList, userAttributes, attributeType);
3222      }
3223    }
3224
3225    // Collective attributes.
3226    processCollectiveAttributes();
3227  }
3228
3229  /**
3230   * Resolves a conflict arising with a virtual attribute.
3231   *
3232   * @param rule
3233   *          the VirtualAttributeRule in conflict
3234   * @param attrList
3235   *          the List of attribute where to resolve the conflict
3236   * @param attributes
3237   *          the Map of attribute where to resolve the conflict
3238   * @param attributeType
3239   *          the attribute type used with the Map
3240   */
3241  private void resolveVirtualConflict(VirtualAttributeRule rule,
3242      List<Attribute> attrList, Map<AttributeType, List<Attribute>> attributes,
3243      AttributeType attributeType)
3244  {
3245    if (attrList.get(0).isVirtual())
3246    {
3247      // The existing attribute is already virtual, so we've got
3248      // a different conflict, but we'll let the first win.
3249      // FIXME -- Should we handle this differently?
3250      return;
3251    }
3252
3253    // The conflict is with a real attribute. See what the
3254    // conflict behavior is and figure out how to handle it.
3255    switch (rule.getConflictBehavior())
3256    {
3257    case REAL_OVERRIDES_VIRTUAL:
3258      // We don't need to update the entry because the real
3259      // attribute will take precedence.
3260      break;
3261
3262    case VIRTUAL_OVERRIDES_REAL:
3263      // We need to move the real attribute to the suppressed
3264      // list and replace it with the virtual attribute.
3265      suppressedAttributes.put(attributeType, attrList);
3266      Attribute attr = new VirtualAttribute(attributeType, this, rule);
3267      attributes.put(attributeType, newLinkedList(attr));
3268      break;
3269
3270    case MERGE_REAL_AND_VIRTUAL:
3271      // We need to add the virtual attribute to the list and
3272      // keep the existing real attribute(s).
3273      attrList.add(new VirtualAttribute(attributeType, this, rule));
3274      break;
3275    }
3276  }
3277
3278
3279  /**
3280   * Encodes this entry into a form that is suitable for long-term
3281   * persistent storage.  The encoding will have a version number so
3282   * that if the way we store entries changes in the future we will
3283   * still be able to read entries encoded in an older format.
3284   *
3285   * @param  buffer  The buffer to encode into.
3286   * @param  config  The configuration that may be used to control how
3287   *                 the entry is encoded.
3288   *
3289   * @throws  DirectoryException  If a problem occurs while attempting
3290   *                              to encode the entry.
3291   */
3292  public void encode(ByteStringBuilder buffer,
3293                     EntryEncodeConfig config)
3294         throws DirectoryException
3295  {
3296    encodeV3(buffer, config);
3297  }
3298
3299  /**
3300   * Encodes this entry using the V3 encoding.
3301   *
3302   * @param  buffer  The buffer to encode into.
3303   * @param  config  The configuration that should be used to encode
3304   *                 the entry.
3305   *
3306   * @throws  DirectoryException  If a problem occurs while attempting
3307   *                              to encode the entry.
3308   */
3309  private void encodeV3(ByteStringBuilder buffer,
3310                        EntryEncodeConfig config)
3311         throws DirectoryException
3312  {
3313    // The version number will be one byte.
3314    buffer.appendByte(0x03);
3315
3316    // Get the encoded representation of the config.
3317    config.encode(buffer);
3318
3319    // If we should include the DN, then it will be encoded as a
3320    // one-to-five byte length followed by the UTF-8 byte
3321    // representation.
3322    if (! config.excludeDN())
3323    {
3324      // TODO: Can we encode the DN directly into buffer?
3325      byte[] dnBytes  = getBytes(dn.toString());
3326      buffer.appendBERLength(dnBytes.length);
3327      buffer.appendBytes(dnBytes);
3328    }
3329
3330
3331    // Encode the object classes in the appropriate manner.
3332    if (config.compressObjectClassSets())
3333    {
3334      config.getCompressedSchema().encodeObjectClasses(buffer, objectClasses);
3335    }
3336    else
3337    {
3338      // Encode number of OCs and 0 terminated names.
3339      buffer.appendBERLength(objectClasses.size());
3340      for (String ocName : objectClasses.values())
3341      {
3342        buffer.appendUtf8(ocName);
3343        buffer.appendByte(0x00);
3344      }
3345    }
3346
3347
3348    // Encode the user attributes in the appropriate manner.
3349    encodeAttributes(buffer, userAttributes, config);
3350
3351
3352    // The operational attributes will be encoded in the same way as
3353    // the user attributes.
3354    encodeAttributes(buffer, operationalAttributes, config);
3355  }
3356
3357  /**
3358   * Encode the given attributes of an entry.
3359   *
3360   * @param  buffer  The buffer to encode into.
3361   * @param  attributes The attributes to encode.
3362   * @param  config  The configuration that may be used to control how
3363   *                 the entry is encoded.
3364   *
3365   * @throws  DirectoryException  If a problem occurs while attempting
3366   *                              to encode the entry.
3367   */
3368  private void encodeAttributes(ByteStringBuilder buffer,
3369                    Map<AttributeType,List<Attribute>> attributes,
3370                                EntryEncodeConfig config)
3371      throws DirectoryException
3372  {
3373    int numAttributes = 0;
3374
3375    // First count how many attributes are there to encode.
3376    for (List<Attribute> attrList : attributes.values())
3377    {
3378      Attribute a;
3379      for (int i = 0; i < attrList.size(); i++)
3380      {
3381        a = attrList.get(i);
3382        if (a.isVirtual() || a.isEmpty())
3383        {
3384          continue;
3385        }
3386
3387        numAttributes++;
3388      }
3389    }
3390
3391    // Encoded one-to-five byte number of attributes
3392    buffer.appendBERLength(numAttributes);
3393
3394    if (config.compressAttributeDescriptions())
3395    {
3396      for (List<Attribute> attrList : attributes.values())
3397      {
3398        for (Attribute a : attrList)
3399        {
3400          if (a.isVirtual() || a.isEmpty())
3401          {
3402            continue;
3403          }
3404
3405          config.getCompressedSchema().encodeAttribute(buffer, a);
3406        }
3407      }
3408    }
3409    else
3410    {
3411      // The attributes will be encoded as a sequence of:
3412      // - A UTF-8 byte representation of the attribute name.
3413      // - A zero delimiter
3414      // - A one-to-five byte number of values for the attribute
3415      // - A sequence of:
3416      //   - A one-to-five byte length for the value
3417      //   - A UTF-8 byte representation for the value
3418      for (List<Attribute> attrList : attributes.values())
3419      {
3420        for (Attribute a : attrList)
3421        {
3422          byte[] nameBytes = getBytes(a.getNameWithOptions());
3423          buffer.appendBytes(nameBytes);
3424          buffer.appendByte(0x00);
3425
3426          buffer.appendBERLength(a.size());
3427          for(ByteString v : a)
3428          {
3429            buffer.appendBERLength(v.length());
3430            buffer.appendBytes(v);
3431          }
3432        }
3433      }
3434    }
3435  }
3436
3437
3438  /**
3439   * Decodes the provided byte array as an entry.
3440   *
3441   * @param  entryBuffer  The byte array containing the data to be
3442   *                      decoded.
3443   *
3444   * @return  The decoded entry.
3445   *
3446   * @throws  DirectoryException  If the provided byte array cannot be
3447   *                              decoded as an entry.
3448   */
3449  public static Entry decode(ByteSequenceReader entryBuffer)
3450         throws DirectoryException
3451  {
3452    return decode(entryBuffer,
3453                  DirectoryServer.getDefaultCompressedSchema());
3454  }
3455
3456
3457
3458    /**
3459   * Decodes the provided byte array as an entry using the V3
3460   * encoding.
3461   *
3462   * @param  entryBuffer       The byte buffer containing the data to
3463   *                           be decoded.
3464   * @param  compressedSchema  The compressed schema manager to use
3465   *                           when decoding tokenized schema
3466   *                           elements.
3467   *
3468   * @return  The decoded entry.
3469   *
3470   * @throws  DirectoryException  If the provided byte array cannot be
3471   *                              decoded as an entry.
3472   */
3473  public static Entry decode(ByteSequenceReader entryBuffer,
3474                             CompressedSchema compressedSchema)
3475         throws DirectoryException
3476  {
3477    try
3478    {
3479      // The first byte must be the entry version.  If it's not one
3480      // we recognize, then that's an error.
3481      Byte version = entryBuffer.readByte();
3482      if (version != 0x03 && version != 0x02 && version != 0x01)
3483      {
3484        LocalizableMessage message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get(
3485            byteToHex(version));
3486        throw new DirectoryException(
3487                       DirectoryServer.getServerErrorResultCode(),
3488                       message);
3489      }
3490
3491      EntryEncodeConfig config;
3492      if(version != 0x01)
3493      {
3494        // Next is the length of the encoded configuration.
3495        int configLength = entryBuffer.readBERLength();
3496
3497        // Next is the encoded configuration itself.
3498        config =
3499            EntryEncodeConfig.decode(entryBuffer, configLength,
3500                compressedSchema);
3501      }
3502      else
3503      {
3504        config = EntryEncodeConfig.DEFAULT_CONFIG;
3505      }
3506
3507      // If we should have included the DN in the entry, then it's
3508      // next.
3509      DN dn;
3510      if (config.excludeDN())
3511      {
3512        dn = DN.NULL_DN;
3513      }
3514      else
3515      {
3516        // Next is the length of the DN.  It may be a single byte or
3517        // multiple bytes.
3518        int dnLength = entryBuffer.readBERLength();
3519
3520
3521        // Next is the DN itself.
3522        ByteSequence dnBytes = entryBuffer.readByteSequence(dnLength);
3523        dn = DN.decode(dnBytes.toByteString());
3524      }
3525
3526
3527      // Next is the set of encoded object classes.  The encoding will
3528      // depend on the configuration.
3529      Map<ObjectClass,String> objectClasses =
3530          decodeObjectClasses(version, entryBuffer, config);
3531
3532
3533      // Now, we should iterate through the user and operational attributes and
3534      // decode each one.
3535      Map<AttributeType, List<Attribute>> userAttributes =
3536          decodeAttributes(version, entryBuffer, config);
3537      Map<AttributeType, List<Attribute>> operationalAttributes =
3538          decodeAttributes(version, entryBuffer, config);
3539
3540
3541      // We've got everything that we need, so create and return the entry.
3542      return new Entry(dn, objectClasses, userAttributes,
3543          operationalAttributes);
3544    }
3545    catch (DirectoryException de)
3546    {
3547      throw de;
3548    }
3549    catch (Exception e)
3550    {
3551      logger.traceException(e);
3552
3553      LocalizableMessage message =
3554          ERR_ENTRY_DECODE_EXCEPTION.get(getExceptionMessage(e));
3555      throw new DirectoryException(
3556                     DirectoryServer.getServerErrorResultCode(),
3557                     message, e);
3558    }
3559  }
3560
3561
3562  /**
3563   * Decode the object classes of an encoded entry.
3564   *
3565   * @param  ver The version of the entry encoding.
3566   * @param  entryBuffer The byte sequence containing the encoded
3567   *                     entry.
3568   * @param  config  The configuration that may be used to control how
3569   *                 the entry is encoded.
3570   *
3571   * @return  A map of the decoded object classes.
3572   * @throws  DirectoryException  If a problem occurs while attempting
3573   *                              to encode the entry.
3574   */
3575  private static Map<ObjectClass,String> decodeObjectClasses(
3576      byte ver, ByteSequenceReader entryBuffer,
3577      EntryEncodeConfig config) throws DirectoryException
3578  {
3579    // Next is the set of encoded object classes.  The encoding will
3580    // depend on the configuration.
3581    if (config.compressObjectClassSets())
3582    {
3583      return config.getCompressedSchema().decodeObjectClasses(entryBuffer);
3584    }
3585
3586    Map<ObjectClass, String> objectClasses;
3587    {
3588      if(ver < 0x03)
3589      {
3590        // Next is the length of the object classes. It may be a
3591        // single byte or multiple bytes.
3592        int ocLength = entryBuffer.readBERLength();
3593
3594        // The set of object classes will be encoded as a single
3595        // string with the object class names separated by zeros.
3596        objectClasses = new LinkedHashMap<>();
3597        int startPos = entryBuffer.position();
3598        for (int i=0; i < ocLength; i++)
3599        {
3600          if (entryBuffer.readByte() == 0x00)
3601          {
3602            int endPos = entryBuffer.position() - 1;
3603            addObjectClass(objectClasses, entryBuffer, startPos, endPos);
3604
3605            entryBuffer.skip(1);
3606            startPos = entryBuffer.position();
3607          }
3608        }
3609        int endPos = entryBuffer.position();
3610        addObjectClass(objectClasses, entryBuffer, startPos, endPos);
3611      }
3612      else
3613      {
3614        // Next is the number of zero terminated object classes.
3615        int numOC = entryBuffer.readBERLength();
3616        objectClasses = new LinkedHashMap<>(numOC);
3617        for(int i = 0; i < numOC; i++)
3618        {
3619          int startPos = entryBuffer.position();
3620          while(entryBuffer.readByte() != 0x00)
3621          {}
3622          int endPos = entryBuffer.position() - 1;
3623          addObjectClass(objectClasses, entryBuffer, startPos, endPos);
3624          entryBuffer.skip(1);
3625        }
3626      }
3627    }
3628
3629    return objectClasses;
3630  }
3631
3632  /**
3633   * Adds the objectClass contained in the buffer to the map of object class.
3634   *
3635   * @param objectClasses
3636   *          the Map where to add the objectClass
3637   * @param entryBuffer
3638   *          the buffer containing the objectClass name
3639   * @param startPos
3640   *          the starting position in the buffer
3641   * @param endPos
3642   *          the ending position in the buffer
3643   */
3644  private static void addObjectClass(Map<ObjectClass, String> objectClasses,
3645      ByteSequenceReader entryBuffer, int startPos, int endPos)
3646  {
3647    entryBuffer.position(startPos);
3648    final String ocName = entryBuffer.readStringUtf8(endPos - startPos);
3649    final String lowerName = toLowerCase(ocName);
3650    final ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
3651    objectClasses.put(oc, ocName);
3652  }
3653
3654  /**
3655   * Decode the attributes of an encoded entry.
3656   *
3657   * @param  ver The version of the entry encoding.
3658   * @param  entryBuffer The byte sequence containing the encoded
3659   *                     entry.
3660   * @param  config  The configuration that may be used to control how
3661   *                 the entry is encoded.
3662   *
3663   * @return  A map of the decoded object classes.
3664   * @throws  DirectoryException  If a problem occurs while attempting
3665   *                              to encode the entry.
3666   */
3667  private static Map<AttributeType, List<Attribute>>
3668  decodeAttributes(Byte ver, ByteSequenceReader entryBuffer,
3669                   EntryEncodeConfig config) throws DirectoryException
3670  {
3671    // Next is the total number of attributes.  It may be a
3672    // single byte or multiple bytes.
3673    int attrs = entryBuffer.readBERLength();
3674
3675
3676    // Now, we should iterate through the attributes and decode each one.
3677    Map<AttributeType, List<Attribute>> attributes = new LinkedHashMap<>(attrs);
3678    if (config.compressAttributeDescriptions())
3679    {
3680      for (int i=0; i < attrs; i++)
3681      {
3682        if(ver < 0x03)
3683        {
3684          // Version 2 includes a total attribute length
3685          entryBuffer.readBERLength();
3686        }
3687        // Decode the attribute.
3688        Attribute a = config.getCompressedSchema().decodeAttribute(entryBuffer);
3689        List<Attribute> attrList = attributes.get(a.getAttributeType());
3690        if (attrList == null)
3691        {
3692          attrList = new ArrayList<>(1);
3693          attributes.put(a.getAttributeType(), attrList);
3694        }
3695        attrList.add(a);
3696      }
3697    }
3698    else
3699    {
3700      AttributeBuilder builder = new AttributeBuilder();
3701      int startPos;
3702      int endPos;
3703      for (int i=0; i < attrs; i++)
3704      {
3705
3706        // First, we have the zero-terminated attribute name.
3707        startPos = entryBuffer.position();
3708        while (entryBuffer.readByte() != 0x00)
3709        {}
3710        endPos = entryBuffer.position()-1;
3711        entryBuffer.position(startPos);
3712        String name = entryBuffer.readStringUtf8(endPos - startPos);
3713        entryBuffer.skip(1);
3714
3715        AttributeType attributeType;
3716        int semicolonPos = name.indexOf(';');
3717        if (semicolonPos > 0)
3718        {
3719          builder.setAttributeType(name.substring(0, semicolonPos));
3720          attributeType = builder.getAttributeType();
3721
3722          int nextPos = name.indexOf(';', semicolonPos+1);
3723          while (nextPos > 0)
3724          {
3725            String option = name.substring(semicolonPos+1, nextPos);
3726            if (option.length() > 0)
3727            {
3728              builder.setOption(option);
3729            }
3730
3731            semicolonPos = nextPos;
3732            nextPos = name.indexOf(';', semicolonPos+1);
3733          }
3734
3735          String option = name.substring(semicolonPos+1);
3736          if (option.length() > 0)
3737          {
3738            builder.setOption(option);
3739          }
3740        }
3741        else
3742        {
3743          builder.setAttributeType(name);
3744          attributeType = builder.getAttributeType();
3745        }
3746
3747
3748        // Next, we have the number of values.
3749        int numValues = entryBuffer.readBERLength();
3750
3751        // Next, we have the sequence of length-value pairs.
3752        for (int j=0; j < numValues; j++)
3753        {
3754          int valueLength = entryBuffer.readBERLength();
3755
3756          ByteString valueBytes =
3757              entryBuffer.readByteSequence(valueLength).toByteString();
3758          builder.add(valueBytes);
3759        }
3760
3761
3762        // Create the attribute and add it to the set of attributes.
3763        Attribute a = builder.toAttribute();
3764        List<Attribute> attrList = attributes.get(attributeType);
3765        if (attrList == null)
3766        {
3767          attrList = new ArrayList<>(1);
3768          attributes.put(attributeType, attrList);
3769        }
3770        attrList.add(a);
3771      }
3772    }
3773
3774    return attributes;
3775  }
3776
3777
3778
3779  /**
3780   * Retrieves a list of the lines for this entry in LDIF form.  Long
3781   * lines will not be wrapped automatically.
3782   *
3783   * @return  A list of the lines for this entry in LDIF form.
3784   */
3785  public List<StringBuilder> toLDIF()
3786  {
3787    List<StringBuilder> ldifLines = new LinkedList<>();
3788
3789    // First, append the DN.
3790    StringBuilder dnLine = new StringBuilder("dn");
3791    appendLDIFSeparatorAndValue(dnLine, ByteString.valueOfUtf8(dn.toString()));
3792    ldifLines.add(dnLine);
3793
3794    // Next, add the set of objectclasses.
3795    for (String s : objectClasses.values())
3796    {
3797      StringBuilder ocLine = new StringBuilder("objectClass: ").append(s);
3798      ldifLines.add(ocLine);
3799    }
3800
3801    // Finally, add the set of user and operational attributes.
3802    addLinesForAttributes(ldifLines, userAttributes);
3803    addLinesForAttributes(ldifLines, operationalAttributes);
3804
3805    return ldifLines;
3806  }
3807
3808
3809  /**
3810   * Add LDIF lines for each passed in attributes.
3811   *
3812   * @param ldifLines
3813   *          the List where to add the LDIF lines
3814   * @param attributes
3815   *          the List of attributes to convert into LDIf lines
3816   */
3817  private void addLinesForAttributes(List<StringBuilder> ldifLines,
3818      Map<AttributeType, List<Attribute>> attributes)
3819  {
3820    for (List<Attribute> attrList : attributes.values())
3821    {
3822      for (Attribute a : attrList)
3823      {
3824        StringBuilder attrName = new StringBuilder(a.getName());
3825        for (String o : a.getOptions())
3826        {
3827          attrName.append(";");
3828          attrName.append(o);
3829        }
3830
3831        for (ByteString v : a)
3832        {
3833          StringBuilder attrLine = new StringBuilder(attrName);
3834          appendLDIFSeparatorAndValue(attrLine, v);
3835          ldifLines.add(attrLine);
3836        }
3837      }
3838    }
3839  }
3840
3841
3842  /**
3843   * Writes this entry in LDIF form according to the provided
3844   * configuration.
3845   *
3846   * @param  exportConfig  The configuration that specifies how the
3847   *                       entry should be written.
3848   *
3849   * @return  <CODE>true</CODE> if the entry is actually written, or
3850   *          <CODE>false</CODE> if it is not for some reason.
3851   *
3852   * @throws  IOException  If a problem occurs while writing the
3853   *                       information.
3854   *
3855   * @throws  LDIFException  If a problem occurs while trying to
3856   *                         determine whether to write the entry.
3857   */
3858  public boolean toLDIF(LDIFExportConfig exportConfig)
3859         throws IOException, LDIFException
3860  {
3861    // See if this entry should be included in the export at all.
3862    try
3863    {
3864      if (! exportConfig.includeEntry(this))
3865      {
3866        if (logger.isTraceEnabled())
3867        {
3868          logger.trace("Skipping entry %s because of the export configuration.", dn);
3869        }
3870        return false;
3871      }
3872    }
3873    catch (Exception e)
3874    {
3875      logger.traceException(e);
3876      throw new LDIFException(ERR_LDIF_COULD_NOT_EVALUATE_FILTERS_FOR_EXPORT.get(dn, e), e);
3877    }
3878
3879
3880    // Invoke LDIF export plugins on the entry if appropriate.
3881    if (exportConfig.invokeExportPlugins())
3882    {
3883      PluginConfigManager pluginConfigManager =
3884           DirectoryServer.getPluginConfigManager();
3885      PluginResult.ImportLDIF pluginResult =
3886           pluginConfigManager.invokeLDIFExportPlugins(exportConfig,
3887                                                    this);
3888      if (! pluginResult.continueProcessing())
3889      {
3890        return false;
3891      }
3892    }
3893
3894
3895    // Get the information necessary to write the LDIF.
3896    BufferedWriter writer     = exportConfig.getWriter();
3897    int            wrapColumn = exportConfig.getWrapColumn();
3898    boolean        wrapLines  = wrapColumn > 1;
3899
3900
3901    // First, write the DN.  It will always be included.
3902    StringBuilder dnLine = new StringBuilder("dn");
3903    appendLDIFSeparatorAndValue(dnLine, ByteString.valueOfUtf8(dn.toString()));
3904    LDIFWriter.writeLDIFLine(dnLine, writer, wrapLines, wrapColumn);
3905
3906
3907    // Next, the set of objectclasses.
3908    final boolean typesOnly = exportConfig.typesOnly();
3909    if (exportConfig.includeObjectClasses())
3910    {
3911      if (typesOnly)
3912      {
3913        StringBuilder ocLine = new StringBuilder("objectClass:");
3914        LDIFWriter.writeLDIFLine(ocLine, writer, wrapLines, wrapColumn);
3915      }
3916      else
3917      {
3918        for (String s : objectClasses.values())
3919        {
3920          StringBuilder ocLine = new StringBuilder("objectClass: ").append(s);
3921          LDIFWriter.writeLDIFLine(ocLine, writer, wrapLines, wrapColumn);
3922        }
3923      }
3924    }
3925    else
3926    {
3927      if (logger.isTraceEnabled())
3928      {
3929        logger.trace("Skipping objectclasses for entry %s because of the export configuration.", dn);
3930      }
3931    }
3932
3933
3934    // Now the set of user attributes.
3935    writeLDIFLines(userAttributes, typesOnly, "user", exportConfig, writer,
3936        wrapColumn, wrapLines);
3937
3938
3939    // Next, the set of operational attributes.
3940    if (exportConfig.includeOperationalAttributes())
3941    {
3942      writeLDIFLines(operationalAttributes, typesOnly, "operational",
3943          exportConfig, writer, wrapColumn, wrapLines);
3944    }
3945    else
3946    {
3947      if (logger.isTraceEnabled())
3948      {
3949        logger.trace(
3950            "Skipping all operational attributes for entry %s " +
3951            "because of the export configuration.", dn);
3952      }
3953    }
3954
3955
3956    // If we are not supposed to include virtual attributes, then
3957    // write any attributes that may normally be suppressed by a
3958    // virtual attribute.
3959    if (! exportConfig.includeVirtualAttributes())
3960    {
3961      for (AttributeType t : suppressedAttributes.keySet())
3962      {
3963        if (exportConfig.includeAttribute(t))
3964        {
3965          for (Attribute a : suppressedAttributes.get(t))
3966          {
3967            writeLDIFLine(a, typesOnly, writer, wrapLines, wrapColumn);
3968          }
3969        }
3970      }
3971    }
3972
3973
3974    // Make sure there is a blank line after the entry.
3975    writer.newLine();
3976
3977
3978    return true;
3979  }
3980
3981
3982  /**
3983   * Writes the provided List of attributes to LDIF using the provided
3984   * information.
3985   *
3986   * @param attributes
3987   *          the List of attributes to write as LDIF
3988   * @param typesOnly
3989   *          if true, only writes the type information, else writes the type
3990   *          information and values for the attribute.
3991   * @param attributeType
3992   *          the type of attribute being written to LDIF
3993   * @param exportConfig
3994   *          configures the export to LDIF
3995   * @param writer
3996   *          The writer to which the data should be written. It must not be
3997   *          <CODE>null</CODE>.
3998   * @param wrapLines
3999   *          Indicates whether to wrap long lines.
4000   * @param wrapColumn
4001   *          The column at which long lines should be wrapped.
4002   * @throws IOException
4003   *           If a problem occurs while writing the information.
4004   */
4005  private void writeLDIFLines(Map<AttributeType, List<Attribute>> attributes,
4006      final boolean typesOnly, String attributeType,
4007      LDIFExportConfig exportConfig, BufferedWriter writer, int wrapColumn,
4008      boolean wrapLines) throws IOException
4009  {
4010    for (AttributeType attrType : attributes.keySet())
4011    {
4012      if (exportConfig.includeAttribute(attrType))
4013      {
4014        List<Attribute> attrList = attributes.get(attrType);
4015        for (Attribute a : attrList)
4016        {
4017          if (a.isVirtual() && !exportConfig.includeVirtualAttributes())
4018          {
4019            continue;
4020          }
4021
4022          writeLDIFLine(a, typesOnly, writer, wrapLines, wrapColumn);
4023        }
4024      }
4025      else
4026      {
4027        if (logger.isTraceEnabled())
4028        {
4029          logger.trace("Skipping %s attribute %s for entry %s "
4030              + "because of the export configuration.", attributeType, attrType.getNameOrOID(), dn);
4031        }
4032      }
4033    }
4034  }
4035
4036
4037  /**
4038   * Writes the provided attribute to LDIF using the provided information.
4039   *
4040   * @param attribute
4041   *          the attribute to write to LDIF
4042   * @param typesOnly
4043   *          if true, only writes the type information, else writes the type
4044   *          information and values for the attribute.
4045   * @param writer
4046   *          The writer to which the data should be written. It must not be
4047   *          <CODE>null</CODE>.
4048   * @param wrapLines
4049   *          Indicates whether to wrap long lines.
4050   * @param wrapColumn
4051   *          The column at which long lines should be wrapped.
4052   * @throws IOException
4053   *           If a problem occurs while writing the information.
4054   */
4055  private void writeLDIFLine(Attribute attribute, final boolean typesOnly,
4056      BufferedWriter writer, boolean wrapLines, int wrapColumn)
4057      throws IOException
4058  {
4059    StringBuilder attrName = new StringBuilder(attribute.getName());
4060    for (String o : attribute.getOptions())
4061    {
4062      attrName.append(";");
4063      attrName.append(o);
4064    }
4065
4066    if (typesOnly)
4067    {
4068      attrName.append(":");
4069
4070      LDIFWriter.writeLDIFLine(attrName, writer, wrapLines, wrapColumn);
4071    }
4072    else
4073    {
4074      for (ByteString v : attribute)
4075      {
4076        StringBuilder attrLine = new StringBuilder(attrName);
4077        appendLDIFSeparatorAndValue(attrLine, v);
4078        LDIFWriter.writeLDIFLine(attrLine, writer, wrapLines, wrapColumn);
4079      }
4080    }
4081  }
4082
4083
4084
4085  /**
4086   * Retrieves the name of the protocol associated with this protocol
4087   * element.
4088   *
4089   * @return  The name of the protocol associated with this protocol
4090   *          element.
4091   */
4092  @Override
4093  public String getProtocolElementName()
4094  {
4095    return "Entry";
4096  }
4097
4098
4099
4100  /**
4101   * Retrieves a hash code for this entry.
4102   *
4103   * @return  The hash code for this entry.
4104   */
4105  @Override
4106  public int hashCode()
4107  {
4108    int hashCode = dn.hashCode();
4109    for (ObjectClass oc : objectClasses.keySet())
4110    {
4111      hashCode += oc.hashCode();
4112    }
4113
4114    hashCode += hashCode(userAttributes.values());
4115    hashCode += hashCode(operationalAttributes.values());
4116    return hashCode;
4117  }
4118
4119  /**
4120   * Computes the hashCode for the list of attributes list.
4121   *
4122   * @param attributesLists
4123   *          the attributes for which to commpute the hashCode
4124   * @return the hashCode for the list of attributes list.
4125   */
4126  private int hashCode(Collection<List<Attribute>> attributesLists)
4127  {
4128    int result = 0;
4129    for (List<Attribute> attributes : attributesLists)
4130    {
4131      for (Attribute a : attributes)
4132      {
4133        result += a.hashCode();
4134      }
4135    }
4136    return result;
4137  }
4138
4139
4140
4141  /**
4142   * Indicates whether the provided object is equal to this entry.  In
4143   * order for the object to be considered equal, it must be an entry
4144   * with the same DN, set of object classes, and set of user and
4145   * operational attributes.
4146   *
4147   * @param  o  The object for which to make the determination.
4148   *
4149   * @return  {@code true} if the provided object may be considered
4150   *          equal to this entry, or {@code false} if not.
4151   */
4152  @Override
4153  public boolean equals(Object o)
4154  {
4155    if (this == o)
4156    {
4157      return true;
4158    }
4159    if (o == null)
4160    {
4161      return false;
4162    }
4163    if (! (o instanceof Entry))
4164    {
4165      return false;
4166    }
4167
4168    Entry e = (Entry) o;
4169    return dn.equals(e.dn)
4170        && objectClasses.keySet().equals(e.objectClasses.keySet())
4171        && equals(userAttributes, e.userAttributes)
4172        && equals(operationalAttributes, e.operationalAttributes);
4173  }
4174
4175  /**
4176   * Returns whether the 2 Maps are equal.
4177   *
4178   * @param attributes1
4179   *          the first Map of attributes
4180   * @param attributes2
4181   *          the second Map of attributes
4182   * @return true if the 2 Maps are equal, false otherwise
4183   */
4184  private boolean equals(Map<AttributeType, List<Attribute>> attributes1,
4185      Map<AttributeType, List<Attribute>> attributes2)
4186  {
4187    for (AttributeType at : attributes1.keySet())
4188    {
4189      List<Attribute> list1 = attributes1.get(at);
4190      List<Attribute> list2 = attributes2.get(at);
4191      if (list2 == null || list1.size() != list2.size())
4192      {
4193        return false;
4194      }
4195      for (Attribute a : list1)
4196      {
4197        if (!list2.contains(a))
4198        {
4199          return false;
4200        }
4201      }
4202    }
4203    return true;
4204  }
4205
4206
4207
4208  /**
4209   * Retrieves a string representation of this protocol element.
4210   *
4211   * @return  A string representation of this protocol element.
4212   */
4213  @Override
4214  public String toString()
4215  {
4216    return toLDIFString();
4217  }
4218
4219
4220
4221  /**
4222   * Appends a string representation of this protocol element to the
4223   * provided buffer.
4224   *
4225   * @param  buffer  The buffer into which the string representation
4226   *                 should be written.
4227   */
4228  @Override
4229  public void toString(StringBuilder buffer)
4230  {
4231    buffer.append(this);
4232  }
4233
4234
4235
4236  /**
4237   * Appends a string representation of this protocol element to the
4238   * provided buffer.
4239   *
4240   * @param  buffer  The buffer into which the string representation
4241   *                 should be written.
4242   * @param  indent  The number of spaces that should be used to
4243   *                 indent the resulting string representation.
4244   */
4245  @Override
4246  public void toString(StringBuilder buffer, int indent)
4247  {
4248    StringBuilder indentBuf = new StringBuilder(indent);
4249    for (int i=0 ; i < indent; i++)
4250    {
4251      indentBuf.append(' ');
4252    }
4253
4254    for (StringBuilder b : toLDIF())
4255    {
4256      buffer.append(indentBuf);
4257      buffer.append(b);
4258      buffer.append(EOL);
4259    }
4260  }
4261
4262
4263
4264  /**
4265   * Retrieves a string representation of this entry in LDIF form.
4266   *
4267   * @return  A string representation of this entry in LDIF form.
4268   */
4269  public String toLDIFString()
4270  {
4271    StringBuilder buffer = new StringBuilder();
4272
4273    for (StringBuilder ldifLine : toLDIF())
4274    {
4275      buffer.append(ldifLine);
4276      buffer.append(EOL);
4277    }
4278
4279    return buffer.toString();
4280  }
4281
4282
4283
4284  /**
4285   * Appends a single-line representation of this entry to the
4286   * provided buffer.
4287   *
4288   * @param  buffer  The buffer to which the information should be
4289   *                 written.
4290   */
4291  public void toSingleLineString(StringBuilder buffer)
4292  {
4293    buffer.append("Entry(dn=\"");
4294    dn.toString(buffer);
4295    buffer.append("\",objectClasses={");
4296
4297    Iterator<String> iterator = objectClasses.values().iterator();
4298    if (iterator.hasNext())
4299    {
4300      buffer.append(iterator.next());
4301
4302      while (iterator.hasNext())
4303      {
4304        buffer.append(",");
4305        buffer.append(iterator.next());
4306      }
4307    }
4308
4309    buffer.append("},userAttrs={");
4310    appendAttributes(buffer, userAttributes.values());
4311    buffer.append("},operationalAttrs={");
4312    appendAttributes(buffer, operationalAttributes.values());
4313    buffer.append("})");
4314  }
4315
4316  /**
4317   * Appends the attributes to the StringBuilder.
4318   *
4319   * @param buffer
4320   *          the StringBuilder where to append
4321   * @param attributesLists
4322   *          the attributesLists to append
4323   */
4324  private void appendAttributes(StringBuilder buffer,
4325      Collection<List<Attribute>> attributesLists)
4326  {
4327    boolean firstAttr = true;
4328    for (List<Attribute> attributes : attributesLists)
4329    {
4330      for (Attribute a : attributes)
4331      {
4332        if (firstAttr)
4333        {
4334          firstAttr = false;
4335        }
4336        else
4337        {
4338          buffer.append(",");
4339        }
4340
4341        buffer.append(a.getName());
4342
4343        if (a.hasOptions())
4344        {
4345          for (String optionString : a.getOptions())
4346          {
4347            buffer.append(";");
4348            buffer.append(optionString);
4349          }
4350        }
4351
4352        buffer.append("={");
4353        Iterator<ByteString> valueIterator = a.iterator();
4354        if (valueIterator.hasNext())
4355        {
4356          buffer.append(valueIterator.next());
4357
4358          while (valueIterator.hasNext())
4359          {
4360            buffer.append(",");
4361            buffer.append(valueIterator.next());
4362          }
4363        }
4364
4365        buffer.append("}");
4366      }
4367    }
4368  }
4369
4370
4371
4372  /**
4373   * Retrieves the requested attribute element for the specified
4374   * attribute type and options or <code>null</code> if this entry
4375   * does not contain an attribute with the specified attribute type
4376   * and options.
4377   *
4378   * @param attributeType
4379   *          The attribute type to retrieve.
4380   * @param options
4381   *          The set of attribute options.
4382   * @return The requested attribute element for the specified
4383   *         attribute type and options, or <code>null</code> if the
4384   *         specified attribute type is not present in this entry
4385   *         with the provided set of options.
4386   */
4387  public Attribute getExactAttribute(AttributeType attributeType,
4388      Set<String> options)
4389  {
4390    List<Attribute> attributes = getAttributes(attributeType);
4391    if (attributes != null)
4392    {
4393      for (Attribute attribute : attributes)
4394      {
4395        if (attribute.optionsEqual(options))
4396        {
4397          return attribute;
4398        }
4399      }
4400    }
4401    return null;
4402  }
4403
4404
4405
4406  /**
4407   * Adds the provided attribute to this entry. If an attribute with
4408   * the provided type and options already exists, then it will be
4409   * either merged or replaced depending on the value of
4410   * <code>replace</code>.
4411   *
4412   * @param attribute
4413   *          The attribute to add/replace in this entry.
4414   * @param duplicateValues
4415   *          A list to which any duplicate values will be added.
4416   * @param replace
4417   *          <code>true</code> if the attribute should replace any
4418   *          existing attribute.
4419   */
4420  private void setAttribute(Attribute attribute,
4421      List<ByteString> duplicateValues, boolean replace)
4422  {
4423    attachment = null;
4424
4425    AttributeType attributeType = attribute.getAttributeType();
4426
4427    if (attribute.getAttributeType().isObjectClass())
4428    {
4429      // We will not do any validation of the object classes - this is
4430      // left to the caller.
4431      if (replace)
4432      {
4433        objectClasses.clear();
4434      }
4435
4436      MatchingRule rule =
4437          attribute.getAttributeType().getEqualityMatchingRule();
4438      for (ByteString v : attribute)
4439      {
4440        String name = v.toString();
4441        String lowerName = toLowerName(rule, v);
4442
4443        // Create a default object class if necessary.
4444        ObjectClass oc =
4445          DirectoryServer.getObjectClass(lowerName, true);
4446
4447        if (replace)
4448        {
4449          objectClasses.put(oc, name);
4450        }
4451        else
4452        {
4453          if (objectClasses.containsKey(oc))
4454          {
4455            duplicateValues.add(v);
4456          }
4457          else
4458          {
4459            objectClasses.put(oc, name);
4460          }
4461        }
4462      }
4463
4464      return;
4465    }
4466
4467    List<Attribute> attributes = getAttributes(attributeType);
4468    if (attributes == null)
4469    {
4470      // Do nothing if we are deleting a non-existing attribute.
4471      if (replace && attribute.isEmpty())
4472      {
4473        return;
4474      }
4475
4476      // We are adding the first attribute with this attribute type.
4477      putAttributes(attributeType, newArrayList(attribute));
4478      return;
4479    }
4480
4481    // There are already attributes with the same attribute type.
4482    Set<String> options = attribute.getOptions();
4483    for (int i = 0; i < attributes.size(); i++)
4484    {
4485      Attribute a = attributes.get(i);
4486      if (a.optionsEqual(options))
4487      {
4488        if (replace)
4489        {
4490          if (!attribute.isEmpty())
4491          {
4492            attributes.set(i, attribute);
4493          }
4494          else
4495          {
4496            attributes.remove(i);
4497
4498            if (attributes.isEmpty())
4499            {
4500              removeAttributes(attributeType);
4501            }
4502          }
4503        }
4504        else
4505        {
4506          AttributeBuilder builder = new AttributeBuilder(a);
4507          for (ByteString v : attribute)
4508          {
4509            if (!builder.add(v))
4510            {
4511              duplicateValues.add(v);
4512            }
4513          }
4514          attributes.set(i, builder.toAttribute());
4515        }
4516        return;
4517      }
4518    }
4519
4520    // There were no attributes with the same options.
4521    if (replace && attribute.isEmpty())
4522    {
4523      // Do nothing.
4524      return;
4525    }
4526
4527    attributes.add(attribute);
4528  }
4529
4530
4531
4532  /**
4533   * Returns an entry containing only those attributes of this entry
4534   * which match the provided criteria.
4535   *
4536   * @param attrNameList
4537   *          The list of attributes to include, may include wild
4538   *          cards.
4539   * @param omitValues
4540   *          Indicates whether to omit attribute values when
4541   *          processing.
4542   * @param omitReal
4543   *          Indicates whether to exclude real attributes.
4544   * @param omitVirtual
4545   *          Indicates whether to exclude virtual attributes.
4546   * @return An entry containing only those attributes of this entry
4547   *         which match the provided criteria.
4548   */
4549  public Entry filterEntry(Set<String> attrNameList,
4550      boolean omitValues, boolean omitReal, boolean omitVirtual)
4551  {
4552    final AttributeType ocType = DirectoryServer.getObjectClassAttributeType();
4553
4554    Map<ObjectClass, String> objectClassesCopy;
4555    Map<AttributeType, List<Attribute>> userAttrsCopy;
4556    Map<AttributeType, List<Attribute>> operationalAttrsCopy;
4557
4558    if (attrNameList == null || attrNameList.isEmpty())
4559    {
4560      // Common case: return filtered user attributes.
4561      userAttrsCopy = new LinkedHashMap<>(userAttributes.size());
4562      operationalAttrsCopy = new LinkedHashMap<>(0);
4563
4564      if (omitReal)
4565      {
4566        objectClassesCopy = new LinkedHashMap<>(0);
4567      }
4568      else if (omitValues)
4569      {
4570        objectClassesCopy = new LinkedHashMap<>(0);
4571
4572        // Add empty object class attribute.
4573        userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType)));
4574      }
4575      else
4576      {
4577        objectClassesCopy = new LinkedHashMap<>(objectClasses);
4578
4579        // First, add the objectclass attribute.
4580        Attribute ocAttr = getObjectClassAttribute();
4581        if (ocAttr != null)
4582        {
4583          userAttrsCopy.put(ocType, newArrayList(ocAttr));
4584        }
4585      }
4586
4587      // Copy all user attributes.
4588      deepCopy(userAttributes, userAttrsCopy, omitValues, true,
4589          omitReal, omitVirtual, true);
4590    }
4591    else
4592    {
4593      // Incrementally build table of attributes.
4594      if (omitReal || omitValues)
4595      {
4596        objectClassesCopy = new LinkedHashMap<>(0);
4597      }
4598      else
4599      {
4600        objectClassesCopy = new LinkedHashMap<>(objectClasses.size());
4601      }
4602
4603      userAttrsCopy = new LinkedHashMap<>(userAttributes.size());
4604      operationalAttrsCopy = new LinkedHashMap<>(operationalAttributes.size());
4605
4606      for (String attrName : attrNameList)
4607      {
4608        if ("*".equals(attrName))
4609        {
4610          // This is a special placeholder indicating that all user
4611          // attributes should be returned.
4612          if (!omitReal)
4613          {
4614            if (omitValues)
4615            {
4616              // Add empty object class attribute.
4617              userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType)));
4618            }
4619            else
4620            {
4621              // Add the objectclass attribute.
4622              objectClassesCopy.putAll(objectClasses);
4623              Attribute ocAttr = getObjectClassAttribute();
4624              if (ocAttr != null)
4625              {
4626                userAttrsCopy.put(ocType, newArrayList(ocAttr));
4627              }
4628            }
4629          }
4630
4631          // Copy all user attributes.
4632          deepCopy(userAttributes, userAttrsCopy, omitValues, true,
4633              omitReal, omitVirtual, true);
4634          continue;
4635        }
4636        else if ("+".equals(attrName))
4637        {
4638          // This is a special placeholder indicating that all
4639          // operational attributes should be returned.
4640          deepCopy(operationalAttributes, operationalAttrsCopy,
4641              omitValues, true, omitReal, omitVirtual, true);
4642          continue;
4643        }
4644
4645        String lowerName;
4646        Set<String> options;
4647        int semicolonPos = attrName.indexOf(';');
4648        if (semicolonPos > 0)
4649        {
4650          String tmpName = attrName.substring(0, semicolonPos);
4651          lowerName = toLowerCase(tmpName);
4652          int nextPos = attrName.indexOf(';', semicolonPos+1);
4653          options = new HashSet<>();
4654          while (nextPos > 0)
4655          {
4656            options.add(attrName.substring(semicolonPos+1, nextPos));
4657
4658            semicolonPos = nextPos;
4659            nextPos = attrName.indexOf(';', semicolonPos+1);
4660          }
4661          options.add(attrName.substring(semicolonPos+1));
4662          attrName = tmpName;
4663        }
4664        else
4665        {
4666          lowerName = toLowerCase(attrName);
4667          options = null;
4668        }
4669
4670        AttributeType attrType = DirectoryServer.getAttributeTypeOrNull(lowerName);
4671        if (attrType == null)
4672        {
4673          // Unrecognized attribute type - do best effort search.
4674          for (Map.Entry<AttributeType, List<Attribute>> e :
4675            userAttributes.entrySet())
4676          {
4677            AttributeType t = e.getKey();
4678            if (t.hasNameOrOID(lowerName))
4679            {
4680              mergeAttributeLists(e.getValue(), userAttrsCopy, t,
4681                  attrName, options, omitValues, omitReal, omitVirtual);
4682              continue;
4683            }
4684          }
4685
4686          for (Map.Entry<AttributeType, List<Attribute>> e :
4687            operationalAttributes.entrySet())
4688          {
4689            AttributeType t = e.getKey();
4690            if (t.hasNameOrOID(lowerName))
4691            {
4692              mergeAttributeLists(e.getValue(), operationalAttrsCopy,
4693                  t, attrName, options, omitValues, omitReal, omitVirtual);
4694              continue;
4695            }
4696          }
4697        }
4698        else
4699        {
4700          // Recognized attribute type.
4701          if (attrType.isObjectClass()) {
4702            if (!omitReal)
4703            {
4704              if (omitValues)
4705              {
4706                userAttrsCopy.put(ocType, newArrayList(Attributes.empty(ocType, attrName)));
4707              }
4708              else
4709              {
4710                Attribute ocAttr = getObjectClassAttribute();
4711                if (ocAttr != null)
4712                {
4713                  if (!attrName.equals(ocAttr.getName()))
4714                  {
4715                    // User requested non-default object class type name.
4716                    AttributeBuilder builder = new AttributeBuilder(ocAttr);
4717                    builder.setAttributeType(ocType, attrName);
4718                    ocAttr = builder.toAttribute();
4719                  }
4720
4721                  userAttrsCopy.put(ocType, newArrayList(ocAttr));
4722                }
4723              }
4724            }
4725          }
4726          else
4727          {
4728            List<Attribute> attrList = getUserAttribute(attrType);
4729            if (attrList != null)
4730            {
4731              mergeAttributeLists(attrList, userAttrsCopy, attrType,
4732                  attrName, options, omitValues, omitReal, omitVirtual);
4733            }
4734            else
4735            {
4736              attrList = getOperationalAttribute(attrType);
4737              if (attrList != null)
4738              {
4739                mergeAttributeLists(attrList, operationalAttrsCopy,
4740                    attrType, attrName, options, omitValues, omitReal,
4741                    omitVirtual);
4742              }
4743            }
4744          }
4745        }
4746      }
4747    }
4748
4749    return new Entry(dn, objectClassesCopy, userAttrsCopy,
4750                     operationalAttrsCopy);
4751  }
4752
4753  /**
4754   * Copies the provided list of attributes into the destination
4755   * attribute map according to the provided criteria.
4756   *
4757   * @param sourceList
4758   *          The list containing the attributes to be copied.
4759   * @param destMap
4760   *          The map where the attributes should be copied to.
4761   * @param attrType
4762   *          The attribute type.
4763   * @param attrName
4764   *          The user-provided attribute name.
4765   * @param options
4766   *          The user-provided attribute options.
4767   * @param omitValues
4768   *          Indicates whether to exclude attribute values.
4769   * @param omitReal
4770   *          Indicates whether to exclude real attributes.
4771   * @param omitVirtual
4772   *          Indicates whether to exclude virtual attributes.
4773   */
4774  private void mergeAttributeLists(List<Attribute> sourceList,
4775      Map<AttributeType, List<Attribute>> destMap,
4776      AttributeType attrType, String attrName, Set<String> options,
4777      boolean omitValues, boolean omitReal, boolean omitVirtual)
4778  {
4779    if (sourceList == null)
4780    {
4781      return;
4782    }
4783
4784    for (Attribute attribute : sourceList)
4785    {
4786      if (attribute.isEmpty()
4787          || (omitReal && attribute.isReal())
4788          || (omitVirtual && attribute.isVirtual())
4789          || !attribute.hasAllOptions(options))
4790      {
4791        continue;
4792      }
4793      else
4794      {
4795        // If a non-default attribute name was provided or if the
4796        // attribute has options then we will need to rebuild the
4797        // attribute so that it contains the user-requested names and options.
4798        AttributeType subAttrType = attribute.getAttributeType();
4799
4800        if ((attrName != null && !attrName.equals(attribute.getName()))
4801            || (options != null && !options.isEmpty()))
4802        {
4803          AttributeBuilder builder = new AttributeBuilder();
4804
4805          // We want to use the user-provided name only if this attribute has
4806          // the same type as the requested type. This might not be the case for
4807          // sub-types e.g. requesting "name" and getting back "cn" - we don't
4808          // want to rename "name" to "cn".
4809          if (attrName == null || !subAttrType.equals(attrType))
4810          {
4811            builder.setAttributeType(subAttrType, attribute.getName());
4812          }
4813          else
4814          {
4815            builder.setAttributeType(subAttrType, attrName);
4816          }
4817
4818          if (options != null)
4819          {
4820            builder.setOptions(options);
4821          }
4822
4823          // Now add in remaining options from original attribute
4824          // (this will not overwrite options already present).
4825          builder.setOptions(attribute.getOptions());
4826
4827          if (!omitValues)
4828          {
4829            builder.addAll(attribute);
4830          }
4831
4832          attribute = builder.toAttribute();
4833        }
4834        else if (omitValues)
4835        {
4836          attribute = Attributes.empty(attribute);
4837        }
4838
4839        // Now put the attribute into the destination map.
4840        // Be careful of duplicates.
4841        List<Attribute> attrList = destMap.get(subAttrType);
4842
4843        if (attrList == null)
4844        {
4845          // Assume that they'll all go in the one list. This isn't
4846          // always the case, for example if the list contains sub-types.
4847          attrList = new ArrayList<>(sourceList.size());
4848          attrList.add(attribute);
4849          destMap.put(subAttrType, attrList);
4850        }
4851        else
4852        {
4853          // The attribute may have already been put in the list.
4854          //
4855          // This may occur in two cases:
4856          //
4857          // 1) The attribute is identified by more than one attribute
4858          //    type description in the attribute list (e.g. in a wildcard).
4859          //
4860          // 2) The attribute has both a real and virtual component.
4861          //
4862          boolean found = false;
4863          for (int i = 0; i < attrList.size(); i++)
4864          {
4865            Attribute otherAttribute = attrList.get(i);
4866            if (otherAttribute.optionsEqual(attribute.getOptions()))
4867            {
4868              // Assume that wildcards appear first in an attribute
4869              // list with more specific attribute names afterwards:
4870              // let the attribute name and options from the later
4871              // attribute take preference.
4872              attrList.set(i, Attributes.merge(attribute, otherAttribute));
4873              found = true;
4874            }
4875          }
4876
4877          if (!found)
4878          {
4879            attrList.add(attribute);
4880          }
4881        }
4882      }
4883    }
4884  }
4885}