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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.types;
028    import org.opends.messages.Message;
029    import org.opends.messages.MessageBuilder;
030    
031    
032    import java.io.BufferedWriter;
033    import java.io.IOException;
034    import java.util.ArrayList;
035    import java.util.Collection;
036    import java.util.HashMap;
037    import java.util.HashSet;
038    import java.util.Iterator;
039    import java.util.LinkedHashMap;
040    import java.util.LinkedHashSet;
041    import java.util.LinkedList;
042    import java.util.List;
043    import java.util.Map;
044    import java.util.Set;
045    import java.util.concurrent.locks.Lock;
046    
047    import org.opends.server.api.AttributeValueDecoder;
048    import org.opends.server.api.CompressedSchema;
049    import org.opends.server.api.ProtocolElement;
050    import org.opends.server.api.plugin.PluginResult;
051    import org.opends.server.core.DirectoryServer;
052    import org.opends.server.core.PluginConfigManager;
053    import org.opends.server.protocols.asn1.ASN1Element;
054    import org.opends.server.protocols.asn1.ASN1OctetString;
055    import org.opends.server.util.LDIFException;
056    
057    import static org.opends.server.config.ConfigConstants.*;
058    import static org.opends.server.loggers.debug.DebugLogger.*;
059    import org.opends.server.loggers.debug.DebugTracer;
060    import static org.opends.server.loggers.ErrorLogger.*;
061    import static org.opends.messages.CoreMessages.*;
062    import static org.opends.messages.UtilityMessages.*;
063    import static org.opends.server.util.LDIFWriter.*;
064    import static org.opends.server.util.ServerConstants.*;
065    import static org.opends.server.util.StaticUtils.*;
066    
067    
068    
069    /**
070     * This class defines a data structure for a Directory Server entry.
071     * It includes a DN and a set of attributes.
072     * <BR><BR>
073     * The entry also contains a volatile attachment object, which should
074     * be used to associate the entry with a special type of object that
075     * is based on its contents.  For example, if the entry holds access
076     * control information, then the attachment might be an object that
077     * contains a representation of that access control definition in a
078     * more useful form.  This is only useful if the entry is to be
079     * cached, since the attachment may be accessed if the entry is
080     * retrieved from the cache, but if the entry is retrieved from the
081     * backend repository it cannot be guaranteed to contain any
082     * attachment (and in most cases will not).  This attachment is
083     * volatile in that it is not always guaranteed to be present, it may
084     * be removed or overwritten at any time, and it will be invalidated
085     * and removed if the entry is altered in any way.
086     */
087    @org.opends.server.types.PublicAPI(
088         stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
089         mayInstantiate=true,
090         mayExtend=false,
091         mayInvoke=true)
092    public class Entry
093           implements ProtocolElement
094    {
095      /**
096       * The tracer object for the debug logger.
097       */
098      private static final DebugTracer TRACER = getTracer();
099    
100      // Indicates whether virtual attribute processing has been performed
101      // for this entry.
102      private boolean virtualAttributeProcessingPerformed;
103    
104      // The set of operational attributes for this entry.
105      private Map<AttributeType,List<Attribute>> operationalAttributes;
106    
107      // The set of user attributes for this entry.
108      private Map<AttributeType,List<Attribute>> userAttributes;
109    
110      // The set of suppressed real attributes for this entry.
111      private Map<AttributeType,List<Attribute>> suppressedAttributes;
112    
113      // The set of objectclasses for this entry.
114      private Map<ObjectClass,String> objectClasses;
115    
116      // The DN for this entry.
117      private DN dn;
118    
119      // A generic attachment that may be used to associate this entry
120      // with some other object.
121      private transient Object attachment;
122    
123      // The schema used to govern this entry.
124      private Schema schema;
125    
126    
127    
128      /**
129       * Creates a new entry with the provided information.
130       *
131       * @param  dn                     The distinguished name for this
132       *                                entry.
133       * @param  objectClasses          The set of objectclasses for this
134       *                                entry as a mapping between the
135       *                                objectclass and the name to use to
136       *                                reference it.
137       * @param  userAttributes         The set of user attributes for
138       *                                this entry as a mapping between
139       *                                the attribute type and the list of
140       *                                attributes with that type.
141       * @param  operationalAttributes  The set of operational attributes
142       *                                for this entry as a mapping
143       *                                between the attribute type and the
144       *                                list of attributes with that type.
145       */
146      public Entry(DN dn, Map<ObjectClass,String> objectClasses,
147                   Map<AttributeType,List<Attribute>> userAttributes,
148                   Map<AttributeType,List<Attribute>>
149                        operationalAttributes)
150      {
151        attachment                          = null;
152        schema                              = DirectoryServer.getSchema();
153        virtualAttributeProcessingPerformed = false;
154    
155    
156        suppressedAttributes =
157             new LinkedHashMap<AttributeType,List<Attribute>>();
158    
159    
160        if (dn == null)
161        {
162          this.dn = DN.nullDN();
163        }
164        else
165        {
166          this.dn = dn;
167        }
168    
169        if (objectClasses == null)
170        {
171          this.objectClasses = new HashMap<ObjectClass,String>();
172        }
173        else
174        {
175          this.objectClasses = objectClasses;
176        }
177    
178        if (userAttributes == null)
179        {
180          this.userAttributes =
181               new HashMap<AttributeType,List<Attribute>>();
182        }
183        else
184        {
185          this.userAttributes = userAttributes;
186        }
187    
188        if (operationalAttributes == null)
189        {
190          this.operationalAttributes =
191               new HashMap<AttributeType,List<Attribute>>();
192        }
193        else
194        {
195          this.operationalAttributes = operationalAttributes;
196        }
197      }
198    
199    
200    
201      /**
202       * Retrieves the distinguished name for this entry.
203       *
204       * @return  The distinguished name for this entry.
205       */
206      public DN getDN()
207      {
208        return dn;
209      }
210    
211    
212    
213      /**
214       * Specifies the distinguished name for this entry.
215       *
216       * @param  dn  The distinguished name for this entry.
217       */
218      public void setDN(DN dn)
219      {
220        if (dn == null)
221        {
222          this.dn = DN.nullDN();
223        }
224        else
225        {
226          this.dn = dn;
227        }
228    
229        attachment = null;
230      }
231    
232    
233    
234      /**
235       * Retrieves the set of objectclasses defined for this entry.  The
236       * caller should be allowed to modify the contents of this list, but
237       * if it does then it should also invalidate the attachment.
238       *
239       * @return  The set of objectclasses defined for this entry.
240       */
241      public Map<ObjectClass,String> getObjectClasses()
242      {
243        return objectClasses;
244      }
245    
246    
247    
248      /**
249       * Indicates whether this entry has the specified objectclass.
250       *
251       * @param  objectClass  The objectclass for which to make the
252       *                      determination.
253       *
254       * @return  <CODE>true</CODE> if this entry has the specified
255       *          objectclass, or <CODE>false</CODE> if not.
256       */
257      public boolean hasObjectClass(ObjectClass objectClass)
258      {
259        return objectClasses.containsKey(objectClass);
260      }
261    
262    
263    
264      /**
265       * Retrieves the structural objectclass for this entry.
266       *
267       * @return  The structural objectclass for this entry, or
268       *          <CODE>null</CODE> if there is none for some reason.  If
269       *          there are multiple structural classes in the entry, then
270       *          the first will be returned.
271       */
272      public ObjectClass getStructuralObjectClass()
273      {
274        ObjectClass structuralClass = null;
275    
276        for (ObjectClass oc : objectClasses.keySet())
277        {
278          if (oc.getObjectClassType() == ObjectClassType.STRUCTURAL)
279          {
280            if (structuralClass == null)
281            {
282              structuralClass = oc;
283            }
284            else
285            {
286              if (oc.isDescendantOf(structuralClass))
287              {
288                structuralClass = oc;
289              }
290            }
291          }
292        }
293    
294        return structuralClass;
295      }
296    
297    
298    
299      /**
300       * Specifies the set of objectclasses for this entry.
301       *
302       * @param  objectClassNames  The values containing the names or OIDs
303       *                           of the objectClasses for this entry.
304       *
305       * @throws  DirectoryException  If a problem occurs while attempting
306       *                              to set the objectclasses for this
307       *                              entry.
308       */
309      public void setObjectClasses(
310                       Collection<AttributeValue> objectClassNames)
311             throws DirectoryException
312      {
313        attachment = null;
314    
315        // Iterate through all the provided objectclass names and make
316        // sure that they are names of valid objectclasses.
317        LinkedHashMap<ObjectClass,String> ocMap =
318             new LinkedHashMap<ObjectClass,String>();
319        for (AttributeValue v : objectClassNames)
320        {
321          String name = v.getStringValue();
322    
323          String lowerName;
324          try
325          {
326            lowerName = v.getNormalizedStringValue();
327          }
328          catch (Exception e)
329          {
330            if (debugEnabled())
331            {
332              TRACER.debugCaught(DebugLogLevel.ERROR, e);
333            }
334    
335            lowerName = toLowerCase(v.getStringValue());
336          }
337    
338          ObjectClass oc = DirectoryServer.getObjectClass(lowerName);
339          if (oc == null)
340          {
341            Message message =
342                ERR_ENTRY_ADD_UNKNOWN_OC.get(name, String.valueOf(dn));
343            throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
344                                         message);
345          }
346    
347          ocMap.put(oc, name);
348        }
349    
350    
351        // If we've gotten here, then everything is fine so put the new
352        // set of objectclasses.
353        objectClasses = ocMap;
354      }
355    
356    
357    
358      /**
359       * Adds the objectClass with the given name to this entry.
360       *
361       * @param  objectClassName  The value containing the name or OID of
362       *                          the objectClass to add to this entry.
363       *
364       * @throws  DirectoryException  If a problem occurs while attempting
365       *                              to add the objectclass to this
366       *                              entry.
367       */
368      public void addObjectClass(AttributeValue objectClassName)
369             throws DirectoryException
370      {
371        attachment = null;
372    
373        String name = objectClassName.getStringValue();
374    
375        String lowerName;
376        try
377        {
378          lowerName = objectClassName.getNormalizedStringValue();
379        }
380        catch (Exception e)
381        {
382          if (debugEnabled())
383          {
384            TRACER.debugCaught(DebugLogLevel.ERROR, e);
385          }
386    
387          lowerName = toLowerCase(name);
388        }
389    
390        ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true);
391        if (objectClasses.containsKey(oc))
392        {
393          Message message =
394              ERR_ENTRY_ADD_DUPLICATE_OC.get(name, String.valueOf(dn));
395          throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
396                                       message);
397        }
398    
399        objectClasses.put(oc, name);
400      }
401    
402    
403    
404      /**
405       * Adds the provided objectClass to this entry.
406       *
407       * @param  oc The objectClass to add to this entry.
408       *
409       * @throws  DirectoryException  If a problem occurs while attempting
410       *                              to add the objectclass to this
411       *                              entry.
412       */
413      public void addObjectClass(ObjectClass oc)
414             throws DirectoryException
415      {
416        attachment = null;
417    
418        if (objectClasses.containsKey(oc))
419        {
420          Message message = ERR_ENTRY_ADD_DUPLICATE_OC.get(
421              oc.getNameOrOID(), String.valueOf(dn));
422          throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
423                                       message);
424        }
425    
426        objectClasses.put(oc, oc.getNameOrOID());
427      }
428    
429    
430    
431      /**
432       * Adds the objectclasses corresponding to the provided set of names
433       * to this entry.
434       *
435       * @param  objectClassNames  The values containing the names or OIDs
436       *                           of the objectClasses to add to this
437       *                           entry.
438       *
439       * @throws  DirectoryException  If a problem occurs while attempting
440       *                              to add the set of objectclasses to
441       *                              this entry.
442       */
443      public void addObjectClasses(
444                       Collection<AttributeValue> objectClassNames)
445             throws DirectoryException
446      {
447        attachment = null;
448    
449    
450        // Iterate through all the provided objectclass names and make
451        // sure that they are names of valid objectclasses not already
452        // assigned to the entry.
453        LinkedHashMap<ObjectClass,String> tmpOCMap =
454             new LinkedHashMap<ObjectClass,String>();
455        for (AttributeValue v : objectClassNames)
456        {
457          String name = v.getStringValue();
458    
459          String lowerName;
460          try
461          {
462            lowerName = v.getNormalizedStringValue();
463          }
464          catch (Exception e)
465          {
466            if (debugEnabled())
467            {
468              TRACER.debugCaught(DebugLogLevel.ERROR, e);
469            }
470    
471            lowerName = toLowerCase(v.getStringValue());
472          }
473    
474          ObjectClass oc = DirectoryServer.getObjectClass(lowerName);
475          if (oc == null)
476          {
477            Message message =
478                ERR_ENTRY_ADD_UNKNOWN_OC.get(name, String.valueOf(dn));
479            throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
480                                         message);
481          }
482    
483          if (objectClasses.containsKey(oc))
484          {
485            Message message =
486                ERR_ENTRY_ADD_DUPLICATE_OC.get(name, String.valueOf(dn));
487            throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
488                                         message);
489          }
490    
491          if (oc.isObsolete())
492          {
493            Message message =
494                ERR_ENTRY_ADD_OBSOLETE_OC.get(name, String.valueOf(dn));
495            throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION,
496                                         message);
497          }
498    
499          tmpOCMap.put(oc, name);
500        }
501    
502    
503        // If we've gotten here, then everything is OK, so add the new
504        // classes.
505        for (ObjectClass oc : tmpOCMap.keySet())
506        {
507          String name = tmpOCMap.get(oc);
508    
509          objectClasses.put(oc, name);
510        }
511      }
512    
513    
514    
515      /**
516       * Retrieves the entire set of attributes for this entry.  This will
517       * include both user and operational attributes.  The caller must
518       * not modify the contents of this list.  Also note that this method
519       * is less efficient than calling either (or both)
520       * <CODE>getUserAttributes</CODE> or
521       * <CODE>getOperationalAttributes</CODE>, so it should only be used
522       * when calls to those methods are not appropriate.
523       *
524       * @return  The entire set of attributes for this entry.
525       */
526      public List<Attribute> getAttributes()
527      {
528        ArrayList<Attribute> attributes = new ArrayList<Attribute>();
529    
530        for (List<Attribute> list : userAttributes.values())
531        {
532          for (Attribute a : list)
533          {
534            attributes.add(a);
535          }
536        }
537    
538        for (List<Attribute> list : operationalAttributes.values())
539        {
540          for (Attribute a : list)
541          {
542            attributes.add(a);
543          }
544        }
545    
546        return attributes;
547      }
548    
549    
550    
551      /**
552       * Retrieves the entire set of user (i.e., non-operational)
553       * attributes for this entry.  The caller should be allowed to
554       * modify the contents of this list, but if it does then it should
555       * also invalidate the attachment.
556       *
557       * @return  The entire set of user attributes for this entry.
558       */
559      public Map<AttributeType,List<Attribute>> getUserAttributes()
560      {
561        return userAttributes;
562      }
563    
564    
565    
566      /**
567       * Retrieves the entire set of operational attributes for this
568       * entry.  The caller should be allowed to modify the contents of
569       * this list, but if it does then it should also invalidate the
570       * attachment.
571       *
572       * @return  The entire set of operational attributes for this entry.
573       */
574      public Map<AttributeType,List<Attribute>> getOperationalAttributes()
575      {
576        return operationalAttributes;
577      }
578    
579    
580    
581      /**
582       * Retrieves an attribute holding the objectclass information for
583       * this entry.  The returned attribute must not be altered.
584       *
585       * @return  An attribute holding the objectclass information for
586       *          this entry, or <CODE>null</CODE> if it does not have any
587       *          objectclass information.
588       */
589      public Attribute getObjectClassAttribute()
590      {
591        if ((objectClasses == null) || objectClasses.isEmpty())
592        {
593          return null;
594        }
595    
596        AttributeType ocType =
597             DirectoryServer.getObjectClassAttributeType();
598    
599        LinkedHashSet<AttributeValue> ocValues =
600             new LinkedHashSet<AttributeValue>(objectClasses.size());
601        for (String s : objectClasses.values())
602        {
603          ocValues.add(new AttributeValue(ocType,
604                                          new ASN1OctetString(s)));
605        }
606    
607        return new Attribute(ocType, ATTR_OBJECTCLASS, ocValues);
608      }
609    
610    
611      /**
612       * Indicates whether this entry contains the specified attribute.
613       * Any subordinate attribute of the specified attribute will also
614       * be used in the determination.
615       *
616       *
617       * @param  attributeType       The attribute type for which to
618       *                             make the determination.
619       *
620       * @return  <CODE>true</CODE> if this entry contains the specified
621       *          attribute, or <CODE>false</CODE> if not.
622       */
623      public boolean hasAttribute(AttributeType attributeType)
624      {
625        return hasAttribute(attributeType, true);
626      }
627    
628    
629      /**
630       * Indicates whether this entry contains the specified attribute.
631       *
632       * @param  attributeType       The attribute type for which to
633       *                             make the determination.
634       * @param  includeSubordinates Whether to include any subordinate
635       *                             attributes of the attribute type
636       *                             being retrieved.
637       *
638       * @return  <CODE>true</CODE> if this entry contains the specified
639       *          attribute, or <CODE>false</CODE> if not.
640       */
641      public boolean hasAttribute(AttributeType attributeType,
642                                  boolean includeSubordinates)
643      {
644        if (userAttributes.containsKey(attributeType) ||
645            operationalAttributes.containsKey(attributeType))
646        {
647          return true;
648        }
649    
650        if (includeSubordinates &&
651            attributeType.mayHaveSubordinateTypes())
652        {
653          for (AttributeType at : schema.getSubTypes(attributeType))
654          {
655            if (userAttributes.containsKey(at) ||
656                operationalAttributes.containsKey(at))
657            {
658              return true;
659            }
660          }
661        }
662    
663        return (attributeType.isObjectClassType() &&
664                (! objectClasses.isEmpty()));
665      }
666    
667    
668      /**
669       * Indicates whether this entry contains the specified attribute
670       * with all of the options in the provided set. Any subordinate
671       * attribute of the specified attribute will also be used in
672       * the determination.
673       *
674       * @param  attributeType       The attribute type for which to
675       *                             make the determination.
676       * @param  attributeOptions    The set of options to use in the
677       *                             determination.
678       *
679       * @return  <CODE>true</CODE> if this entry contains the specified
680       *          attribute, or <CODE>false</CODE> if not.
681       */
682      public boolean hasAttribute(AttributeType attributeType,
683                                  Set<String> attributeOptions)
684      {
685        return hasAttribute(attributeType, true, attributeOptions);
686      }
687    
688    
689      /**
690       * Indicates whether this entry contains the specified attribute
691       * with all of the options in the provided set.
692       *
693       * @param  attributeType       The attribute type for which to
694       *                             make the determination.
695       * @param  includeSubordinates Whether to include any subordinate
696       *                             attributes of the attribute type
697       *                             being retrieved.
698       * @param  attributeOptions    The set of options to use in the
699       *                             determination.
700       *
701       * @return  <CODE>true</CODE> if this entry contains the specified
702       *          attribute, or <CODE>false</CODE> if not.
703       */
704      public boolean hasAttribute(AttributeType attributeType,
705                                  boolean includeSubordinates,
706                                  Set<String> attributeOptions)
707      {
708        List<Attribute> attributes;
709        if (includeSubordinates &&
710            attributeType.mayHaveSubordinateTypes())
711        {
712          attributes = new LinkedList<Attribute>();
713          List<Attribute> attrs = userAttributes.get(attributeType);
714          if (attrs != null)
715          {
716            attributes.addAll(attrs);
717          }
718    
719          attrs = operationalAttributes.get(attributeType);
720          if (attrs != null)
721          {
722            attributes.addAll(attrs);
723          }
724    
725          for (AttributeType at : schema.getSubTypes(attributeType))
726          {
727            attrs = userAttributes.get(at);
728            if (attrs != null)
729            {
730              attributes.addAll(attrs);
731            }
732    
733            attrs = operationalAttributes.get(at);
734            if (attrs != null)
735            {
736              attributes.addAll(attrs);
737            }
738          }
739        }
740        else
741        {
742          attributes = userAttributes.get(attributeType);
743          if (attributes == null)
744          {
745            attributes = operationalAttributes.get(attributeType);
746            if (attributes == null)
747            {
748              if (attributeType.isObjectClassType() &&
749                  (! objectClasses.isEmpty()))
750              {
751                return ((attributeOptions == null) ||
752                        attributeOptions.isEmpty());
753              }
754              else
755              {
756                return false;
757              }
758            }
759          }
760        }
761    
762        // It's possible that there could be an attribute without any
763        // values, which we should treat as not having the requested
764        // attribute.
765        for (Attribute a : attributes)
766        {
767          if (a.hasValue() && a.hasOptions(attributeOptions))
768          {
769            return true;
770          }
771        }
772    
773        return false;
774      }
775    
776      /**
777       * Retrieves the requested attribute element(s) for the specified
778       * attribute type.  The list returned may include multiple elements
779       * if the same attribute exists in the entry multiple times with
780       * different sets of options. It may also include any subordinate
781       * attributes of the attribute being retrieved.
782       *
783       * @param  attributeType  The attribute type to retrieve.
784       *
785       * @return  The requested attribute element(s) for the specified
786       *          attribute type, or <CODE>null</CODE> if the specified
787       *          attribute type is not present in this entry.
788       */
789      public List<Attribute> getAttribute(AttributeType attributeType)
790      {
791        return getAttribute(attributeType, true);
792      }
793    
794    
795      /**
796       * Retrieves the requested attribute element(s) for the specified
797       * attribute type.  The list returned may include multiple elements
798       * if the same attribute exists in the entry multiple times with
799       * different sets of options.
800       *
801       * @param  attributeType       The attribute type to retrieve.
802       * @param  includeSubordinates Whether to include any subordinate
803       *                             attributes of the attribute type
804       *                             being retrieved.
805       *
806       * @return  The requested attribute element(s) for the specified
807       *          attribute type, or <CODE>null</CODE> if the specified
808       *          attribute type is not present in this entry.
809       */
810      public List<Attribute> getAttribute(AttributeType attributeType,
811                                          boolean includeSubordinates)
812      {
813        if (includeSubordinates &&
814            attributeType.mayHaveSubordinateTypes())
815        {
816          List<Attribute> attributes = new LinkedList<Attribute>();
817    
818          List<Attribute> attrs = userAttributes.get(attributeType);
819          if (attrs != null)
820          {
821            attributes.addAll(attrs);
822          }
823    
824          attrs = operationalAttributes.get(attributeType);
825          if (attrs != null)
826          {
827            attributes.addAll(attrs);
828          }
829    
830          for (AttributeType at : schema.getSubTypes(attributeType))
831          {
832            attrs = userAttributes.get(at);
833            if (attrs != null)
834            {
835              attributes.addAll(attrs);
836            }
837    
838            attrs = operationalAttributes.get(at);
839            if (attrs != null)
840            {
841              attributes.addAll(attrs);
842            }
843          }
844    
845          if (attributes.isEmpty())
846          {
847            return null;
848          }
849          else
850          {
851            return attributes;
852          }
853        }
854        else
855        {
856          List<Attribute> attributes = userAttributes.get(attributeType);
857    
858          if (attributes == null)
859          {
860            attributes = operationalAttributes.get(attributeType);
861            if (attributes == null)
862            {
863              if (attributeType.isObjectClassType() &&
864                  (! objectClasses.isEmpty()))
865              {
866                attributes = new ArrayList<Attribute>(1);
867                attributes.add(getObjectClassAttribute());
868                return attributes;
869              }
870              else
871              {
872                return null;
873              }
874            }
875            else
876            {
877              return attributes;
878            }
879          }
880          else
881          {
882            return attributes;
883          }
884        }
885      }
886    
887    
888    
889      /**
890       * Retrieves the requested attribute element(s) for the attribute
891       * with the specified name or OID.  The list returned may include
892       * multiple elements if the same attribute exists in the entry
893       * multiple times with different sets of options. It may also
894       * include any subordinate attributes of the attribute being
895       * retrieved.
896       * <BR><BR>
897       * Note that this method should only be used in cases in which the
898       * Directory Server schema has no reference of an attribute type
899       * with the specified name.  It is not as accurate or efficient as
900       * the version of this method that takes an
901       * <CODE>AttributeType</CODE> argument.
902       *
903       * @param  lowerName  The name or OID of the attribute to return,
904       *                    formatted in all lowercase characters.
905       *
906       * @return  The requested attribute element(s) for the specified
907       *          attribute type, or <CODE>null</CODE> if the specified
908       *          attribute type is not present in this entry.
909       */
910      public List<Attribute> getAttribute(String lowerName)
911      {
912        for (AttributeType attr : userAttributes.keySet())
913        {
914          if (attr.hasNameOrOID(lowerName))
915          {
916            return getAttribute(attr, true);
917          }
918        }
919    
920        for (AttributeType attr : operationalAttributes.keySet())
921        {
922          if (attr.hasNameOrOID(lowerName))
923          {
924            return getAttribute(attr, true);
925          }
926        }
927    
928        if (lowerName.equals(OBJECTCLASS_ATTRIBUTE_TYPE_NAME) &&
929            (! objectClasses.isEmpty()))
930        {
931          LinkedList<Attribute> attrList = new LinkedList<Attribute>();
932          attrList.add(getObjectClassAttribute());
933          return attrList;
934        }
935    
936        return null;
937      }
938    
939      /**
940       * Retrieves the requested attribute element(s) for the specified
941       * attribute type.  The list returned may include multiple elements
942       * if the same attribute exists in the entry multiple times with
943       * different sets of options. It may also include any subordinate
944       * attributes of the attribute being retrieved.
945       *
946       * @param  attributeType       The attribute type to retrieve.
947       * @param  options             The set of attribute options to
948       *                             include in matching elements.
949       *
950       * @return  The requested attribute element(s) for the specified
951       *          attribute type, or <CODE>null</CODE> if the specified
952       *          attribute type is not present in this entry with the
953       *          provided set of options.
954       */
955      public List<Attribute> getAttribute(AttributeType attributeType,
956                                          Set<String> options)
957      {
958        return getAttribute(attributeType, true, options);
959      }
960    
961      /**
962       * Retrieves the requested attribute element(s) for the specified
963       * attribute type.  The list returned may include multiple elements
964       * if the same attribute exists in the entry multiple times with
965       * different sets of options.
966       *
967       * @param  attributeType       The attribute type to retrieve.
968       * @param  includeSubordinates Whether to include any subordinate
969       *                             attributes of the attribute type
970       *                             being retrieved.
971       * @param  options             The set of attribute options to
972       *                             include in matching elements.
973       *
974       * @return  The requested attribute element(s) for the specified
975       *          attribute type, or <CODE>null</CODE> if the specified
976       *          attribute type is not present in this entry with the
977       *          provided set of options.
978       */
979      public List<Attribute> getAttribute(AttributeType attributeType,
980                                          boolean includeSubordinates,
981                                          Set<String> options)
982      {
983        List<Attribute> attributes = new LinkedList<Attribute>();
984        if (includeSubordinates &&
985            attributeType.mayHaveSubordinateTypes())
986        {
987          List<Attribute> attrs = userAttributes.get(attributeType);
988          if (attrs != null)
989          {
990            attributes.addAll(attrs);
991          }
992    
993          attrs = operationalAttributes.get(attributeType);
994          if (attrs != null)
995          {
996            attributes.addAll(attrs);
997          }
998    
999          for (AttributeType at : schema.getSubTypes(attributeType))
1000          {
1001            attrs = userAttributes.get(at);
1002            if (attrs != null)
1003            {
1004              attributes.addAll(attrs);
1005            }
1006    
1007            attrs = operationalAttributes.get(at);
1008            if (attrs != null)
1009            {
1010              attributes.addAll(attrs);
1011            }
1012          }
1013        }
1014        else
1015        {
1016          List<Attribute> attrs = userAttributes.get(attributeType);
1017          if (attrs == null)
1018          {
1019            attrs = operationalAttributes.get(attributeType);
1020            if (attrs == null)
1021            {
1022              if (attributeType.isObjectClassType() &&
1023                  (! objectClasses.isEmpty()) &&
1024                  ((options == null) || options.isEmpty()))
1025              {
1026                attributes.add(getObjectClassAttribute());
1027                return attributes;
1028              }
1029              else
1030              {
1031                return null;
1032              }
1033            }
1034            else
1035            {
1036              attributes.addAll(attrs);
1037            }
1038          }
1039          else
1040          {
1041            attributes.addAll(attrs);
1042          }
1043        }
1044    
1045    
1046        Iterator<Attribute> iterator = attributes.iterator();
1047        while (iterator.hasNext())
1048        {
1049          Attribute a = iterator.next();
1050          if (! a.hasOptions(options))
1051          {
1052            iterator.remove();
1053          }
1054        }
1055    
1056        if (attributes.isEmpty())
1057        {
1058          return null;
1059        }
1060        else
1061        {
1062          return attributes;
1063        }
1064      }
1065    
1066    
1067    
1068      /**
1069       * Retrieves the requested attribute element(s) for the attribute
1070       * with the specified name or OID and set of options.  The list
1071       * returned may include multiple elements if the same attribute
1072       * exists in the entry multiple times with different sets of
1073       * matching options.
1074       * <BR><BR>
1075       * Note that this method should only be used in cases in which the
1076       * Directory Server schema has no reference of an attribute type
1077       * with the specified name.  It is not as accurate or efficient as
1078       * the version of this method that takes an
1079       * <CODE>AttributeType</CODE> argument.
1080       *
1081       * @param  lowerName  The name or OID of the attribute to return,
1082       *                    formatted in all lowercase characters.
1083       * @param  options    The set of attribute options to include in
1084       *                    matching elements.
1085       *
1086       * @return  The requested attribute element(s) for the specified
1087       *          attribute type, or <CODE>null</CODE> if the specified
1088       *          attribute type is not present in this entry.
1089       */
1090      public List<Attribute> getAttribute(String lowerName,
1091                                          Set<String> options)
1092      {
1093        for (AttributeType attr : userAttributes.keySet())
1094        {
1095          if (attr.hasNameOrOID(lowerName))
1096          {
1097            return getAttribute(attr, options);
1098          }
1099        }
1100    
1101        for (AttributeType attr : operationalAttributes.keySet())
1102        {
1103          if (attr.hasNameOrOID(lowerName))
1104          {
1105            return getAttribute(attr, options);
1106          }
1107        }
1108    
1109        if (lowerName.equals(OBJECTCLASS_ATTRIBUTE_TYPE_NAME) &&
1110            ((options == null) || options.isEmpty()))
1111        {
1112          LinkedList<Attribute> attributes = new LinkedList<Attribute>();
1113          attributes.add(getObjectClassAttribute());
1114          return attributes;
1115        }
1116        else
1117        {
1118          return null;
1119        }
1120      }
1121    
1122    
1123    
1124      /**
1125       * Retrieves the requested attribute type from the entry and decodes
1126       * a single value as an object of type T.
1127       * <p>
1128       * If the requested attribute type is not present then
1129       * <code>null</code> is returned. If more than one attribute value
1130       * is present, then the first value found will be decoded and
1131       * returned.
1132       * <p>
1133       * The attribute value is decoded using the specified
1134       * {@link org.opends.server.api.AttributeValueDecoder}.
1135       *
1136       * @param <T>
1137       *          Decode the attribute value to an object of this type.
1138       * @param attributeType
1139       *          The attribute type to retrieve.
1140       * @param decoder
1141       *          The attribute value decoder.
1142       * @return The decoded attribute value or <code>null</code> if no
1143       *         attribute value having the specified attribute type was
1144       *         found.
1145       * @throws DirectoryException
1146       *           If the requested attribute value could not be decoded
1147       *           successfully.
1148       */
1149      public final <T> T getAttributeValue(AttributeType attributeType,
1150          AttributeValueDecoder<T> decoder) throws DirectoryException
1151      {
1152        List<Attribute> attributes = getAttribute(attributeType, true);
1153        AttributeValueIterable values =
1154             new AttributeValueIterable(attributes);
1155        Iterator<AttributeValue> iterator = values.iterator();
1156    
1157        if (iterator.hasNext())
1158        {
1159          return decoder.decode(iterator.next());
1160        }
1161        else
1162        {
1163          return null;
1164        }
1165      }
1166    
1167    
1168    
1169      /**
1170       * Retrieves the requested attribute type from the entry and decodes
1171       * any values as objects of type T and then places them in the
1172       * specified collection.
1173       * <p>
1174       * If the requested attribute type is not present then no decoded
1175       * values will be added to the container.
1176       * <p>
1177       * The attribute value is decoded using the specified
1178       * {@link org.opends.server.api.AttributeValueDecoder}.
1179       *
1180       * @param <T>
1181       *          Decode the attribute values to objects of this type.
1182       * @param attributeType
1183       *          The attribute type to retrieve.
1184       * @param decoder
1185       *          The attribute value decoder.
1186       * @param collection
1187       *          The collection to which decoded values should be added.
1188       * @return The collection containing the decoded attribute value.
1189       * @throws DirectoryException
1190       *           If one or more of the requested attribute values could
1191       *           not be decoded successfully.
1192       */
1193      public final <T> Collection<T> getAttributeValues(
1194          AttributeType attributeType,
1195          AttributeValueDecoder<? extends T> decoder,
1196          Collection<T> collection)
1197          throws DirectoryException
1198      {
1199        List<Attribute> attributes = getAttribute(attributeType, true);
1200        AttributeValueIterable values =
1201             new AttributeValueIterable(attributes);
1202    
1203        for (AttributeValue value : values)
1204        {
1205          collection.add(decoder.decode(value));
1206        }
1207    
1208        return collection;
1209      }
1210    
1211    
1212    
1213      /**
1214       * Indicates whether this entry contains the specified user
1215       * attribute.
1216       *
1217       * @param attributeType
1218       *          The attribute type for which to make the determination.
1219       * @return <CODE>true</CODE> if this entry contains the specified
1220       *         user attribute, or <CODE>false</CODE> if not.
1221       */
1222      public boolean hasUserAttribute(AttributeType attributeType)
1223      {
1224        if (userAttributes.containsKey(attributeType))
1225        {
1226          return true;
1227        }
1228    
1229        if (attributeType.mayHaveSubordinateTypes())
1230        {
1231          for (AttributeType at : schema.getSubTypes(attributeType))
1232          {
1233            if (userAttributes.containsKey(at))
1234            {
1235              return true;
1236            }
1237          }
1238        }
1239    
1240        return false;
1241      }
1242    
1243    
1244    
1245      /**
1246       * Retrieves the requested user attribute element(s) for the
1247       * specified attribute type.  The list returned may include multiple
1248       * elements if the same attribute exists in the entry multiple times
1249       * with different sets of options.
1250       *
1251       * @param  attributeType  The attribute type to retrieve.
1252       *
1253       * @return  The requested attribute element(s) for the specified
1254       *          attribute type, or <CODE>null</CODE> if there is no such
1255       *          user attribute.
1256       */
1257      public List<Attribute> getUserAttribute(AttributeType attributeType)
1258      {
1259        if (attributeType.mayHaveSubordinateTypes())
1260        {
1261          LinkedList<Attribute> attributes = new LinkedList<Attribute>();
1262    
1263          List<Attribute> attrs = userAttributes.get(attributeType);
1264          if (attrs != null)
1265          {
1266            attributes.addAll(attrs);
1267          }
1268    
1269          for (AttributeType at : schema.getSubTypes(attributeType))
1270          {
1271            attrs = userAttributes.get(at);
1272            if (attrs != null)
1273            {
1274              attributes.addAll(attrs);
1275            }
1276          }
1277    
1278          if (attributes.isEmpty())
1279          {
1280            return null;
1281          }
1282          else
1283          {
1284            return attributes;
1285          }
1286        }
1287        else
1288        {
1289          return userAttributes.get(attributeType);
1290        }
1291      }
1292    
1293    
1294    
1295      /**
1296       * Retrieves the requested user attribute element(s) for the
1297       * specified attribute type.  The list returned may include multiple
1298       * elements if the same attribute exists in the entry multiple times
1299       * with different sets of options.
1300       *
1301       * @param  attributeType  The attribute type to retrieve.
1302       * @param  options        The set of attribute options to include in
1303       *                        matching elements.
1304       *
1305       * @return  The requested attribute element(s) for the specified
1306       *          attribute type, or <CODE>null</CODE> if there is no such
1307       *          user attribute with the specified set of options.
1308       */
1309      public List<Attribute> getUserAttribute(AttributeType attributeType,
1310                                              Set<String> options)
1311      {
1312        LinkedList<Attribute> attributes = new LinkedList<Attribute>();
1313        List<Attribute> attrs = userAttributes.get(attributeType);
1314        if (attrs != null)
1315        {
1316          attributes.addAll(attrs);
1317        }
1318    
1319        if (attributeType.mayHaveSubordinateTypes())
1320        {
1321          for (AttributeType at : schema.getSubTypes(attributeType))
1322          {
1323            attrs = userAttributes.get(at);
1324            if (attrs != null)
1325            {
1326              attributes.addAll(attrs);
1327            }
1328          }
1329        }
1330    
1331        Iterator<Attribute> iterator = attributes.iterator();
1332        while (iterator.hasNext())
1333        {
1334          Attribute a = iterator.next();
1335          if (! a.hasOptions(options))
1336          {
1337            iterator.remove();
1338          }
1339        }
1340    
1341        if (attributes.isEmpty())
1342        {
1343          return null;
1344        }
1345        else
1346        {
1347          return attributes;
1348        }
1349      }
1350    
1351    
1352    
1353      /**
1354       * Retrieves a duplicate of the user attribute list for the
1355       * specified type.
1356       *
1357       * @param  attributeType  The attribute type for which to retrieve a
1358       *                        duplicate attribute list.
1359       *
1360       * @return  A duplicate of the requested attribute list, or
1361       *          <CODE>null</CODE> if there is no such user attribute.
1362       */
1363      public List<Attribute> duplicateUserAttribute(
1364                                 AttributeType attributeType)
1365      {
1366        LinkedList<Attribute> attributes = new LinkedList<Attribute>();
1367    
1368        List<Attribute> attrs = userAttributes.get(attributeType);
1369        if (attrs != null)
1370        {
1371          for (Attribute a : attrs)
1372          {
1373            attributes.add(a.duplicate());
1374          }
1375        }
1376    
1377        if (attributeType.mayHaveSubordinateTypes())
1378        {
1379          for (AttributeType at : schema.getSubTypes(attributeType))
1380          {
1381            attrs = userAttributes.get(at);
1382            if (attrs != null)
1383            {
1384              for (Attribute a : attrs)
1385              {
1386                attributes.add(a.duplicate());
1387              }
1388            }
1389          }
1390        }
1391    
1392        if (attributes.isEmpty())
1393        {
1394          return null;
1395        }
1396        else
1397        {
1398          return attributes;
1399        }
1400      }
1401    
1402    
1403    
1404      /**
1405       * Makes a copy of attributes matching the specified options.
1406       *
1407       * @param  attrList       The attributes to be copied.
1408       * @param  options        The set of attribute options to include in
1409       *                        matching elements.
1410       * @param  omitValues     <CODE>true</CODE> if the values are to be
1411       *                        omitted.
1412       *
1413       * @return  A copy of the attributes matching the specified options,
1414       *          or <CODE>null</CODE> if there is no such attribute with
1415       *          the specified set of options.
1416       */
1417      private static List<Attribute> duplicateAttribute(
1418           List<Attribute> attrList,
1419           Set<String> options,
1420           boolean omitValues)
1421      {
1422        if (attrList == null)
1423        {
1424          return null;
1425        }
1426    
1427        ArrayList<Attribute> duplicateList =
1428             new ArrayList<Attribute>(attrList.size());
1429        for (Attribute a : attrList)
1430        {
1431          if (a.hasOptions(options))
1432          {
1433            duplicateList.add(a.duplicate(omitValues));
1434          }
1435        }
1436    
1437        if (duplicateList.isEmpty())
1438        {
1439          return null;
1440        }
1441        else
1442        {
1443          return duplicateList;
1444        }
1445      }
1446    
1447    
1448    
1449      /**
1450       * Retrieves a copy of the requested user attribute element(s) for
1451       * the specified attribute type.  The list returned may include
1452       * multiple elements if the same attribute exists in the entry
1453       * multiple times with different sets of options.
1454       *
1455       * @param  attributeType  The attribute type to retrieve.
1456       * @param  options        The set of attribute options to include in
1457       *                        matching elements.
1458       * @param  omitValues     <CODE>true</CODE> if the values are to be
1459       *                        omitted.
1460       *
1461       * @return  A copy of the requested attribute element(s) for the
1462       *          specified attribute type, or <CODE>null</CODE> if there
1463       *          is no such user attribute with the specified set of
1464       *          options.
1465       */
1466      public List<Attribute> duplicateUserAttribute(
1467           AttributeType attributeType,
1468           Set<String> options,
1469           boolean omitValues)
1470      {
1471        List<Attribute> currentList = getUserAttribute(attributeType);
1472        return duplicateAttribute(currentList, options, omitValues);
1473      }
1474    
1475    
1476    
1477      /**
1478       * Retrieves a copy of the requested operational attribute
1479       * element(s) for the specified attribute type.  The list returned
1480       * may include multiple elements if the same attribute exists in
1481       * the entry multiple times with different sets of options.
1482       *
1483       * @param  attributeType  The attribute type to retrieve.
1484       * @param  options        The set of attribute options to include in
1485       *                        matching elements.
1486       * @param  omitValues     <CODE>true</CODE> if the values are to be
1487       *                        omitted.
1488       *
1489       * @return  A copy of the requested attribute element(s) for the
1490       *          specified attribute type, or <CODE>null</CODE> if there
1491       *          is no such user attribute with the specified set of
1492       *          options.
1493       */
1494      public List<Attribute> duplicateOperationalAttribute(
1495           AttributeType attributeType,
1496           Set<String> options,
1497           boolean omitValues)
1498      {
1499        List<Attribute> currentList =
1500             getOperationalAttribute(attributeType);
1501        return duplicateAttribute(currentList, options, omitValues);
1502      }
1503    
1504    
1505      /**
1506       * Indicates whether this entry contains the specified operational
1507       * attribute.
1508       *
1509       * @param  attributeType  The attribute type for which to make the
1510       *                        determination.
1511       *
1512       * @return  <CODE>true</CODE> if this entry contains the specified
1513       *          operational attribute, or <CODE>false</CODE> if not.
1514       */
1515      public boolean hasOperationalAttribute(AttributeType attributeType)
1516      {
1517        if (operationalAttributes.containsKey(attributeType))
1518        {
1519          return true;
1520        }
1521    
1522        if (attributeType.mayHaveSubordinateTypes())
1523        {
1524          for (AttributeType at : schema.getSubTypes(attributeType))
1525          {
1526            if (operationalAttributes.containsKey(at))
1527            {
1528              return true;
1529            }
1530          }
1531        }
1532    
1533        return false;
1534      }
1535    
1536    
1537    
1538      /**
1539       * Retrieves the requested operational attribute element(s) for the
1540       * specified attribute type.  The list returned may include multiple
1541       * elements if the same attribute exists in the entry multiple times
1542       * with different sets of options.
1543       *
1544       * @param  attributeType  The attribute type to retrieve.
1545       *
1546       * @return  The requested attribute element(s) for the specified
1547       *          attribute type, or <CODE>null</CODE> if there is no such
1548       *          operational attribute.
1549       */
1550      public List<Attribute> getOperationalAttribute(
1551                                  AttributeType attributeType)
1552      {
1553        if (attributeType.mayHaveSubordinateTypes())
1554        {
1555          LinkedList<Attribute> attributes = new LinkedList<Attribute>();
1556    
1557          List<Attribute> attrs =
1558               operationalAttributes.get(attributeType);
1559          if (attrs != null)
1560          {
1561            attributes.addAll(attrs);
1562          }
1563    
1564          for (AttributeType at : schema.getSubTypes(attributeType))
1565          {
1566            attrs = operationalAttributes.get(at);
1567            if (attrs != null)
1568            {
1569              attributes.addAll(attrs);
1570            }
1571          }
1572    
1573          if (attributes.isEmpty())
1574          {
1575            return null;
1576          }
1577          else
1578          {
1579            return attributes;
1580          }
1581        }
1582        else
1583        {
1584          return operationalAttributes.get(attributeType);
1585        }
1586      }
1587    
1588    
1589    
1590      /**
1591       * Retrieves the requested operational attribute element(s) for the
1592       * specified attribute type.  The list returned may include multiple
1593       * elements if the same attribute exists in the entry multiple times
1594       * with different sets of options.
1595       *
1596       * @param  attributeType  The attribute type to retrieve.
1597       * @param  options        The set of attribute options to include in
1598       *                        matching elements.
1599       *
1600       * @return  The requested attribute element(s) for the specified
1601       *          attribute type, or <CODE>null</CODE> if there is no such
1602       *          operational attribute with the specified set of options.
1603       */
1604      public List<Attribute> getOperationalAttribute(
1605                                  AttributeType attributeType,
1606                                  Set<String> options)
1607      {
1608        LinkedList<Attribute> attributes = new LinkedList<Attribute>();
1609        List<Attribute> attrs = operationalAttributes.get(attributeType);
1610        if (attrs != null)
1611        {
1612          attributes.addAll(attrs);
1613        }
1614    
1615        if (attributeType.mayHaveSubordinateTypes())
1616        {
1617          for (AttributeType at : schema.getSubTypes(attributeType))
1618          {
1619            attrs = operationalAttributes.get(at);
1620            if (attrs != null)
1621            {
1622              attributes.addAll(attrs);
1623            }
1624          }
1625        }
1626    
1627        Iterator<Attribute> iterator = attributes.iterator();
1628        while (iterator.hasNext())
1629        {
1630          Attribute a = iterator.next();
1631          if (! a.hasOptions(options))
1632          {
1633            iterator.remove();
1634          }
1635        }
1636    
1637        if (attributes.isEmpty())
1638        {
1639          return null;
1640        }
1641        else
1642        {
1643          return attributes;
1644        }
1645      }
1646    
1647    
1648    
1649      /**
1650       * Retrieves a duplicate of the operational attribute list for the
1651       * specified type.
1652       *
1653       * @param  attributeType  The attribute type for which to retrieve a
1654       *                        duplicate attribute list.
1655       *
1656       * @return  A duplicate of the requested attribute list, or
1657       *          <CODE>null</CODE> if there is no such operational
1658       *          attribute.
1659       */
1660      public List<Attribute> duplicateOperationalAttribute(
1661                                  AttributeType attributeType)
1662      {
1663        LinkedList<Attribute> attributes = new LinkedList<Attribute>();
1664    
1665        List<Attribute> attrs = operationalAttributes.get(attributeType);
1666        if (attrs != null)
1667        {
1668          for (Attribute a : attrs)
1669          {
1670            attributes.add(a.duplicate());
1671          }
1672        }
1673    
1674        if (attributeType.mayHaveSubordinateTypes())
1675        {
1676          for (AttributeType at : schema.getSubTypes(attributeType))
1677          {
1678            attrs = operationalAttributes.get(at);
1679            if (attrs != null)
1680            {
1681              for (Attribute a : attrs)
1682              {
1683                attributes.add(a.duplicate());
1684              }
1685            }
1686          }
1687        }
1688    
1689        if (attributes.isEmpty())
1690        {
1691          return null;
1692        }
1693        else
1694        {
1695          return attributes;
1696        }
1697      }
1698    
1699    
1700      /**
1701       * Puts the provided attribute in this entry.  If an attribute
1702       * already exists with the provided type, it will be overwritten.
1703       * Otherwise, a new attribute will be added.  Note that no
1704       * validation will be performed.
1705       *
1706       * @param  attributeType  The attribute type for the set of
1707       *                        attributes to add.
1708       * @param  attributeList  The set of attributes to add for the given
1709       *                        type.
1710       */
1711      public void putAttribute(AttributeType attributeType,
1712                               List<Attribute> attributeList)
1713      {
1714        attachment = null;
1715    
1716    
1717        // See if there is already a set of attributes with the specified
1718        // type.  If so, then overwrite it.
1719        List<Attribute> attrList = userAttributes.get(attributeType);
1720        if (attrList != null)
1721        {
1722          userAttributes.put(attributeType, attributeList);
1723          return;
1724        }
1725    
1726        attrList = operationalAttributes.get(attributeType);
1727        if (attrList != null)
1728        {
1729          operationalAttributes.put(attributeType, attributeList);
1730          return;
1731        }
1732    
1733    
1734        // This is a new attribute, so add it to the set of user or
1735        // operational attributes as appropriate.
1736        if (attributeType.isOperational())
1737        {
1738          operationalAttributes.put(attributeType, attributeList);
1739        }
1740        else
1741        {
1742          userAttributes.put(attributeType, attributeList);
1743        }
1744      }
1745    
1746    
1747    
1748      /**
1749       * Adds the provided attribute to this entry.  If an attribute with
1750       * the provided type already exists, then the values will be merged.
1751       *
1752       * @param  attribute        The attribute to add or merge with this
1753       *                          entry.
1754       * @param  duplicateValues  A list to which any duplicate values
1755       *                          will be added.
1756       */
1757      public void addAttribute(Attribute attribute,
1758                               List<AttributeValue> duplicateValues)
1759      {
1760        attachment = null;
1761    
1762        List<Attribute> attrList =
1763             getAttribute(attribute.getAttributeType(), false);
1764        if (attrList == null)
1765        {
1766          // There are no instances of the specified attribute in this
1767          // entry, so simply add it.
1768          attrList = new ArrayList<Attribute>(1);
1769          attrList.add(attribute);
1770    
1771          AttributeType attrType = attribute.getAttributeType();
1772          if (attrType.isOperational())
1773          {
1774            operationalAttributes.put(attrType, attrList);
1775          }
1776          else
1777          {
1778            userAttributes.put(attrType, attrList);
1779          }
1780    
1781          return;
1782        }
1783        else
1784        {
1785          // There are some instances of this attribute, but they may not
1786          // have exactly the same set of options.  See if we can find an
1787          // attribute with the same set of options to merge in the
1788          // values.  If not, then add the new attribute to the list.
1789          HashSet<String> options = attribute.getOptions();
1790          for (Attribute a : attrList)
1791          {
1792            if (a.optionsEqual(options))
1793            {
1794              // There is an attribute with the same set of options.
1795              // Merge the value lists together.
1796              LinkedHashSet<AttributeValue> existingValues =
1797                   a.getValues();
1798              LinkedHashSet<AttributeValue> newValues =
1799                   attribute.getValues();
1800              for (AttributeValue v : newValues)
1801              {
1802                if (! existingValues.add(v))
1803                {
1804                  duplicateValues.add(v);
1805                }
1806              }
1807    
1808              return;
1809            }
1810          }
1811    
1812          attrList.add(attribute);
1813        }
1814      }
1815    
1816    
1817    
1818      /**
1819       * Removes all instances of the specified attribute type from this
1820       * entry, including any instances with options.  If the provided
1821       * attribute type is the objectclass type, then all objectclass
1822       * values will be removed (but must be replaced for the entry to
1823       * be valid).  If the specified attribute type is not present in
1824       * this entry, then this method will have no effect.
1825       *
1826       * @param  attributeType  The attribute type for the attribute to
1827       *                        remove from this entry.
1828       *
1829       * @return  <CODE>true</CODE> if the attribute was found and
1830       *          removed, or <CODE>false</CODE> if it was not present in
1831       *          the entry.
1832       */
1833      public boolean removeAttribute(AttributeType attributeType)
1834      {
1835        attachment = null;
1836    
1837        if (attributeType.isObjectClassType())
1838        {
1839          objectClasses.clear();
1840          return true;
1841        }
1842        else
1843        {
1844          return ((userAttributes.remove(attributeType) != null) ||
1845                  (operationalAttributes.remove(attributeType) != null));
1846        }
1847      }
1848    
1849    
1850    
1851      /**
1852       * Removes the attribute with the provided type and set of options
1853       * from this entry.  Only the instance with the exact set of
1854       * options provided will be removed.  This has no effect if the
1855       * specified attribute is not present in this entry with the given
1856       * set of options.
1857       *
1858       * @param  attributeType  The attribute type for the attribute to
1859       *                        remove from this entry.
1860       * @param  options        The set of attribute options to use when
1861       *                        determining which attribute to remove.
1862       *
1863       * @return  <CODE>true</CODE> if the attribute was found and
1864       *          removed, or <CODE>false</CODE> if it was not present in
1865       *          the entry.
1866       */
1867      public boolean removeAttribute(AttributeType attributeType,
1868                                     Set<String> options)
1869      {
1870        attachment = null;
1871    
1872        List<Attribute> attrList = userAttributes.get(attributeType);
1873        if (attrList == null)
1874        {
1875          attrList = operationalAttributes.get(attributeType);
1876          if (attrList == null)
1877          {
1878            return false;
1879          }
1880        }
1881    
1882        boolean removed = false;
1883    
1884        Iterator<Attribute> iterator = attrList.iterator();
1885        while (iterator.hasNext())
1886        {
1887          Attribute a = iterator.next();
1888          if (a.optionsEqual(options))
1889          {
1890            iterator.remove();
1891            removed = true;
1892            break;
1893          }
1894        }
1895    
1896        if (attrList.isEmpty())
1897        {
1898          userAttributes.remove(attributeType);
1899          operationalAttributes.remove(attributeType);
1900        }
1901    
1902        return removed;
1903      }
1904    
1905    
1906    
1907      /**
1908       * Removes the provided attribute from this entry.  If the given
1909       * attribute does not have any values, then all values of the
1910       * associated attribute type (taking into account the options in the
1911       * provided type) will be removed.  Otherwise, only the specified
1912       * values will be removed.
1913       *
1914       * @param  attribute      The attribute containing the information
1915       *                        to use to perform the removal.
1916       * @param  missingValues  A list to which any values contained in
1917       *                        the provided attribute but not present in
1918       *                        the entry will be added.
1919       *
1920       * @return  <CODE>true</CODE> if the attribute type was present and
1921       *          the specified values that were present were removed, or
1922       *          <CODE>false</CODE> if the attribute type was not present
1923       *          in the entry.  If the attribute type was present but
1924       *          only contained some of the values in the provided
1925       *          attribute, then this method will return
1926       *          <CODE>true</CODE> but will add those values to the
1927       *          provided list.
1928       */
1929      public boolean removeAttribute(Attribute attribute,
1930                                     List<AttributeValue> missingValues)
1931      {
1932        attachment = null;
1933    
1934    
1935        if (attribute.getAttributeType().isObjectClassType())
1936        {
1937          LinkedHashSet<AttributeValue> valueSet = attribute.getValues();
1938          if ((valueSet == null) || valueSet.isEmpty())
1939          {
1940            objectClasses.clear();
1941            return true;
1942          }
1943    
1944          boolean allSuccessful = true;
1945    
1946          for (AttributeValue v : attribute.getValues())
1947          {
1948            String ocName;
1949            try
1950            {
1951              ocName = v.getNormalizedStringValue();
1952            }
1953            catch (Exception e)
1954            {
1955              if (debugEnabled())
1956              {
1957                TRACER.debugCaught(DebugLogLevel.ERROR, e);
1958              }
1959    
1960              ocName = toLowerCase(v.getStringValue());
1961            }
1962    
1963            boolean matchFound = false;
1964    
1965            for (ObjectClass oc : objectClasses.keySet())
1966            {
1967              if (oc.hasNameOrOID(ocName))
1968              {
1969                matchFound = true;
1970                objectClasses.remove(oc);
1971                break;
1972              }
1973            }
1974    
1975            if (! matchFound)
1976            {
1977              allSuccessful = false;
1978              missingValues.add(v);
1979            }
1980          }
1981    
1982          return allSuccessful;
1983        }
1984    
1985    
1986        if (attribute.hasOptions())
1987        {
1988          HashSet<String> options = attribute.getOptions();
1989    
1990          LinkedHashSet<AttributeValue> valueSet = attribute.getValues();
1991          if ((valueSet == null) || valueSet.isEmpty())
1992          {
1993            return removeAttribute(attribute.getAttributeType(), options);
1994          }
1995    
1996          List<Attribute> attrList =
1997               getAttribute(attribute.getAttributeType(), false);
1998          if (attrList == null)
1999          {
2000            return false;
2001          }
2002    
2003          for (Attribute a : attrList)
2004          {
2005            if (a.optionsEqual(options))
2006            {
2007              LinkedHashSet<AttributeValue> existingValueSet =
2008                   a.getValues();
2009    
2010              for (AttributeValue v : valueSet)
2011              {
2012                if (! existingValueSet.remove(v))
2013                {
2014                  missingValues.add(v);
2015                }
2016              }
2017    
2018              if (existingValueSet.isEmpty())
2019              {
2020                return removeAttribute(attribute.getAttributeType(),
2021                                       options);
2022              }
2023    
2024              return true;
2025            }
2026          }
2027    
2028          return false;
2029        }
2030        else
2031        {
2032          LinkedHashSet<AttributeValue> valueSet = attribute.getValues();
2033          if ((valueSet == null) || valueSet.isEmpty())
2034          {
2035            return removeAttribute(attribute.getAttributeType(), null);
2036          }
2037    
2038          List<Attribute> attrList =
2039               getAttribute(attribute.getAttributeType(), false);
2040          if (attrList == null)
2041          {
2042            return false;
2043          }
2044    
2045          for (Attribute a : attrList)
2046          {
2047            if (! a.hasOptions())
2048            {
2049              LinkedHashSet<AttributeValue> existingValueSet =
2050                   a.getValues();
2051    
2052              for (AttributeValue v : valueSet)
2053              {
2054                if (! existingValueSet.remove(v))
2055                {
2056                  missingValues.add(v);
2057                }
2058              }
2059    
2060              if (existingValueSet.isEmpty())
2061              {
2062                return removeAttribute(attribute.getAttributeType(),
2063                    null);
2064              }
2065    
2066              return true;
2067            }
2068          }
2069    
2070          return false;
2071        }
2072      }
2073    
2074    
2075    
2076      /**
2077       * Indicates whether the specified attribute type is allowed by any
2078       * of the objectclasses associated with this entry.
2079       *
2080       * @param  attributeType  The attribute type for which to make the
2081       *                        determination.
2082       *
2083       * @return  <CODE>true</CODE> if the specified attribute is allowed
2084       *          by any of the objectclasses associated with this entry,
2085       *          or <CODE>false</CODE> if it is not.
2086       */
2087      public boolean allowsAttribute(AttributeType attributeType)
2088      {
2089        for (ObjectClass o : objectClasses.keySet())
2090        {
2091          if (o.isRequiredOrOptional(attributeType))
2092          {
2093            return true;
2094          }
2095        }
2096    
2097        return false;
2098      }
2099    
2100    
2101    
2102      /**
2103       * Indicates whether the specified attribute type is required by any
2104       * of the objectclasses associated with this entry.
2105       *
2106       * @param  attributeType  The attribute type for which to make the
2107       *                        determination.
2108       *
2109       * @return  <CODE>true</CODE> if the specified attribute is required
2110       *          by any of the objectclasses associated with this entry,
2111       *          o r<CODE>false</CODE> if it is not.
2112       */
2113      public boolean requiresAttribute(AttributeType attributeType)
2114      {
2115        for (ObjectClass o : objectClasses.keySet())
2116        {
2117          if (o.isRequired(attributeType))
2118          {
2119            return true;
2120          }
2121        }
2122    
2123        return false;
2124      }
2125    
2126    
2127    
2128      /**
2129       * Indicates whether this entry contains the specified attribute
2130       * value.
2131       *
2132       * @param  attributeType  The attribute type for the attribute.
2133       * @param  options        The set of options for the attribute.
2134       * @param  value          The value for the attribute.
2135       *
2136       * @return  <CODE>true</CODE> if this entry contains the specified
2137       *          attribute value, or <CODE>false</CODE> if it does not.
2138       */
2139      public boolean hasValue(AttributeType attributeType,
2140                              Set<String> options, AttributeValue value)
2141      {
2142        List<Attribute> attrList = getAttribute(attributeType, true);
2143        if ((attrList == null) || attrList.isEmpty())
2144        {
2145          return false;
2146        }
2147    
2148        for (Attribute a : attrList)
2149        {
2150          if (a.optionsEqual(options))
2151          {
2152            return a.hasValue(value);
2153          }
2154        }
2155    
2156        return false;
2157      }
2158    
2159    
2160    
2161      /**
2162       * Applies the provided modification to this entry.  No schema
2163       * checking will be performed.
2164       *
2165       * @param  mod  The modification to apply to this entry.
2166       *
2167       * @throws  DirectoryException  If a problem occurs while attempting
2168       *                              to apply the modification.  Note
2169       *                              that even if a problem occurs, then
2170       *                              the entry may have been altered in
2171       *                              some way.
2172       */
2173      public void applyModification(Modification mod)
2174             throws DirectoryException
2175      {
2176        Attribute     a = mod.getAttribute();
2177        AttributeType t = a.getAttributeType();
2178    
2179        // We'll need to handle changes to the objectclass attribute in a
2180        // special way.
2181        if (t.isObjectClassType())
2182        {
2183          LinkedHashMap<ObjectClass,String> ocs = new
2184                 LinkedHashMap<ObjectClass,String>();
2185          for (AttributeValue v : a.getValues())
2186          {
2187            String ocName    = v.getStringValue();
2188            String lowerName = toLowerCase(ocName);
2189            ObjectClass oc   =
2190                 DirectoryServer.getObjectClass(lowerName, true);
2191            ocs.put(oc, ocName);
2192          }
2193    
2194          switch (mod.getModificationType())
2195          {
2196            case ADD:
2197              for (ObjectClass oc : ocs.keySet())
2198              {
2199                if (objectClasses.containsKey(oc))
2200                {
2201                  Message message =
2202                      ERR_ENTRY_DUPLICATE_VALUES.get(a.getName());
2203                  throw new DirectoryException(
2204                                 ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
2205                                 message);
2206                }
2207                else
2208                {
2209                  objectClasses.put(oc, ocs.get(oc));
2210                }
2211              }
2212              break;
2213    
2214            case DELETE:
2215              for (ObjectClass oc : ocs.keySet())
2216              {
2217                if (objectClasses.remove(oc) == null)
2218                {
2219                  Message message =
2220                      ERR_ENTRY_NO_SUCH_VALUE.get(a.getName());
2221                  throw new DirectoryException(
2222                                 ResultCode.NO_SUCH_ATTRIBUTE, message);
2223                }
2224              }
2225              break;
2226    
2227            case REPLACE:
2228              objectClasses = ocs;
2229              break;
2230    
2231            case INCREMENT:
2232              Message message =
2233                  ERR_ENTRY_OC_INCREMENT_NOT_SUPPORTED.get();
2234              throw new DirectoryException(
2235                             ResultCode.UNWILLING_TO_PERFORM, message);
2236    
2237            default:
2238              message = ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE.get(
2239                  String.valueOf(mod.getModificationType()));
2240              throw new DirectoryException(
2241                             ResultCode.UNWILLING_TO_PERFORM, message);
2242          }
2243    
2244          return;
2245        }
2246    
2247        switch (mod.getModificationType())
2248        {
2249          case ADD:
2250            LinkedList<AttributeValue> duplicateValues =
2251                 new LinkedList<AttributeValue>();
2252            addAttribute(a, duplicateValues);
2253            if (! duplicateValues.isEmpty())
2254            {
2255              Message message =
2256                  ERR_ENTRY_DUPLICATE_VALUES.get(a.getName());
2257              throw new DirectoryException(
2258                             ResultCode.ATTRIBUTE_OR_VALUE_EXISTS,
2259                             message);
2260            }
2261            break;
2262    
2263          case DELETE:
2264            LinkedList<AttributeValue> missingValues =
2265                 new LinkedList<AttributeValue>();
2266            removeAttribute(a, missingValues);
2267            if (! missingValues.isEmpty())
2268            {
2269              Message message = ERR_ENTRY_NO_SUCH_VALUE.get(a.getName());
2270              throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
2271                                           message);
2272            }
2273            break;
2274    
2275          case REPLACE:
2276            removeAttribute(t, a.getOptions());
2277    
2278            if (a.hasValue())
2279            {
2280              // We know that we won't have any duplicate values, so  we
2281              // don't kneed to worry about checking for them.
2282              duplicateValues = new LinkedList<AttributeValue>();
2283              addAttribute(a, duplicateValues);
2284            }
2285            break;
2286    
2287          case INCREMENT:
2288            List<Attribute> attrList = getAttribute(t, false);
2289            if ((attrList == null) || attrList.isEmpty())
2290            {
2291              Message message =
2292                  ERR_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE.get(a.getName());
2293              throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
2294                                           message);
2295            }
2296            else if (attrList.size() != 1)
2297            {
2298              Message message =
2299                  ERR_ENTRY_INCREMENT_MULTIPLE_VALUES.get(a.getName());
2300              throw new DirectoryException(
2301                             ResultCode.CONSTRAINT_VIOLATION, message);
2302            }
2303    
2304            LinkedHashSet<AttributeValue> values =
2305                 attrList.get(0).getValues();
2306            if (values.isEmpty())
2307            {
2308              Message message =
2309                  ERR_ENTRY_INCREMENT_NO_SUCH_ATTRIBUTE.get(a.getName());
2310              throw new DirectoryException(ResultCode.NO_SUCH_ATTRIBUTE,
2311                                           message);
2312            }
2313            else if (values.size() > 1)
2314            {
2315              Message message =
2316                  ERR_ENTRY_INCREMENT_MULTIPLE_VALUES.get(a.getName());
2317              throw new DirectoryException(
2318                             ResultCode.CONSTRAINT_VIOLATION, message);
2319            }
2320    
2321            LinkedHashSet<AttributeValue> newValues = a.getValues();
2322            if (newValues.size() != 1)
2323            {
2324              Message message = ERR_ENTRY_INCREMENT_INVALID_VALUE_COUNT.
2325                  get(a.getName());
2326              throw new DirectoryException(
2327                             ResultCode.CONSTRAINT_VIOLATION, message);
2328            }
2329    
2330            long newValue;
2331            try
2332            {
2333              String s = values.iterator().next().getStringValue();
2334              long currentValue = Long.parseLong(s);
2335    
2336              s = a.getValues().iterator().next().getStringValue();
2337              long increment = Long.parseLong(s);
2338    
2339              newValue = currentValue+increment;
2340            }
2341            catch (NumberFormatException nfe)
2342            {
2343              Message message = ERR_ENTRY_INCREMENT_CANNOT_PARSE_AS_INT.
2344                  get(a.getName());
2345              throw new DirectoryException(
2346                             ResultCode.CONSTRAINT_VIOLATION, message);
2347            }
2348    
2349            values.clear();
2350            values.add(new AttributeValue(t, String.valueOf(newValue)));
2351            break;
2352    
2353          default:
2354            Message message = ERR_ENTRY_UNKNOWN_MODIFICATION_TYPE.get(
2355                String.valueOf(mod.getModificationType()));
2356            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
2357                                         message);
2358        }
2359      }
2360    
2361    
2362    
2363      /**
2364       * Applies all of the provided modifications to this entry.
2365       *
2366       * @param  mods  The modifications to apply to this entry.
2367       *
2368       * @throws  DirectoryException  If a problem occurs while attempting
2369       *                              to apply the modifications.  Note
2370       *                              that even if a problem occurs, then
2371       *                              the entry may have been altered in
2372       *                              some way.
2373       */
2374      public void applyModifications(List<Modification> mods)
2375             throws DirectoryException
2376      {
2377        for (Modification m : mods)
2378        {
2379          applyModification(m);
2380        }
2381      }
2382    
2383    
2384    
2385      /**
2386       * Indicates whether this entry conforms to the server's schema
2387       * requirements.  The checks performed by this method include:
2388       *
2389       * <UL>
2390       *   <LI>Make sure that all required attributes are present, either
2391       *       in the list of user or operational attributes.</LI>
2392       *   <LI>Make sure that all user attributes are allowed by at least
2393       *       one of the objectclasses.  The operational attributes will
2394       *       not be checked in this manner.</LI>
2395       *   <LI>Make sure that all single-valued attributes contained in
2396       *       the entry have only a single value.</LI>
2397       *   <LI>Make sure that the entry contains a single structural
2398       *       objectclass.</LI>
2399       *   <LI>Make sure that the entry complies with any defined name
2400       *       forms, DIT content rules, and DIT structure rules.</LI>
2401       * </UL>
2402       *
2403       * @param  parentEntry             The entry that is the immediate
2404       *                                 parent of this entry, which may
2405       *                                 be checked for DIT structure rule
2406       *                                 conformance.  This may be
2407       *                                 {@code null} if there is no
2408       *                                 parent or if it is unavailable
2409       *                                to the caller.
2410       * @param  parentProvided          Indicates whether the caller
2411       *                                 attempted to provide the parent.
2412       *                                 If not, then the parent entry
2413       *                                 will be loaded on demand if it is
2414       *                                 required.
2415       * @param  validateNameForms       Indicates whether to validate the
2416       *                                 entry against name form
2417       *                                 definitions.  This should only be
2418       *                                 {@code true} for add and modify
2419       *                                 DN operations, as well as for
2420       *                                 for imports.
2421       * @param  validateStructureRules  Indicates whether to validate the
2422       *                                 entry against DIT structure rule
2423       *                                 definitions.  This should only
2424       *                                 be {@code true} for add and
2425       *                                 modify DN operations.
2426       * @param  invalidReason           The buffer to which an
2427       *                                 explanation will be appended if
2428       *                                 this entry does not conform to
2429       *                                 the server's schema
2430       *                                 configuration.
2431       *
2432       * @return  {@code true} if this entry conforms to the server's
2433       *          schema requirements, or {@code false} if it does not.
2434       */
2435      public boolean conformsToSchema(Entry parentEntry,
2436                                      boolean parentProvided,
2437                                      boolean validateNameForms,
2438                                      boolean validateStructureRules,
2439                                      MessageBuilder invalidReason)
2440      {
2441        // Get the structural objectclass for the entry.  If there isn't
2442        // one, or if there's more than one, then see if that's OK.
2443        AcceptRejectWarn structuralPolicy =
2444             DirectoryServer.getSingleStructuralObjectClassPolicy();
2445        ObjectClass structuralClass = null;
2446        boolean multipleOCErrorLogged = false;
2447        for (ObjectClass oc : objectClasses.keySet())
2448        {
2449          if (oc.getObjectClassType() == ObjectClassType.STRUCTURAL)
2450          {
2451            if ((structuralClass == null) ||
2452                oc.isDescendantOf(structuralClass))
2453            {
2454              structuralClass = oc;
2455            }
2456            else if (! structuralClass.isDescendantOf(oc))
2457            {
2458              Message message =
2459                      ERR_ENTRY_SCHEMA_MULTIPLE_STRUCTURAL_CLASSES.get(
2460                        String.valueOf(dn),
2461                        structuralClass.getNameOrOID(),
2462                        oc.getNameOrOID());
2463    
2464              if (structuralPolicy == AcceptRejectWarn.REJECT)
2465              {
2466                invalidReason.append(message);
2467                return false;
2468              }
2469              else if (structuralPolicy == AcceptRejectWarn.WARN)
2470              {
2471                if (! multipleOCErrorLogged)
2472                {
2473                  logError(message);
2474                  multipleOCErrorLogged = true;
2475                }
2476              }
2477            }
2478          }
2479        }
2480    
2481        NameForm         nameForm         = null;
2482        DITContentRule   ditContentRule   = null;
2483        DITStructureRule ditStructureRule = null;
2484        if (structuralClass == null)
2485        {
2486          Message message = ERR_ENTRY_SCHEMA_NO_STRUCTURAL_CLASS.get(
2487                  String.valueOf(dn));
2488    
2489          if (structuralPolicy == AcceptRejectWarn.REJECT)
2490          {
2491            invalidReason.append(message);
2492            return false;
2493          }
2494          else if (structuralPolicy == AcceptRejectWarn.WARN)
2495          {
2496            logError(message);
2497          }
2498        }
2499        else
2500        {
2501          ditContentRule =
2502               DirectoryServer.getDITContentRule(structuralClass);
2503          if ((ditContentRule != null) && ditContentRule.isObsolete())
2504          {
2505            ditContentRule = null;
2506          }
2507    
2508          if (validateNameForms)
2509          {
2510            nameForm = DirectoryServer.getNameForm(structuralClass);
2511            if ((nameForm != null) && nameForm.isObsolete())
2512            {
2513              nameForm = null;
2514            }
2515    
2516            if (validateStructureRules && (nameForm != null))
2517            {
2518              ditStructureRule =
2519                   DirectoryServer.getDITStructureRule(nameForm);
2520              if ((ditStructureRule != null) &&
2521                  ditStructureRule.isObsolete())
2522              {
2523                ditStructureRule = null;
2524              }
2525            }
2526          }
2527        }
2528    
2529    
2530        if (! checkAttributesAndObjectClasses(ditContentRule,
2531                   structuralPolicy, invalidReason))
2532        {
2533          return false;
2534        }
2535    
2536    
2537        // If there is a name form for this entry, then make sure that the
2538        // RDN for the entry is in compliance with it.
2539        if (nameForm != null)
2540        {
2541          if (! checkNameForm(nameForm, structuralPolicy, invalidReason))
2542          {
2543            return false;
2544          }
2545        }
2546    
2547    
2548        // If there is a DIT content rule for this entry, then make sure
2549        // that the entry is in compliance with it.
2550        if (ditContentRule != null)
2551        {
2552          if (! checkDITContentRule(ditContentRule, structuralPolicy,
2553                                    invalidReason))
2554          {
2555            return false;
2556          }
2557        }
2558    
2559    
2560        if (! checkDITStructureRule(ditStructureRule, structuralClass,
2561                   parentEntry, parentProvided, validateStructureRules,
2562                   structuralPolicy, invalidReason))
2563        {
2564          return false;
2565        }
2566    
2567    
2568        // If we've gotten here, then the entry is acceptable.
2569        return true;
2570      }
2571    
2572    
2573    
2574      /**
2575       * Checks the attributes and object classes contained in this entry
2576       * to determine whether they conform to the server schema
2577       * requirements.
2578       *
2579       * @param  ditContentRule    The DIT content rule for this entry, if
2580       *                           any.
2581       * @param  structuralPolicy  The policy that should be used for
2582       *                           structural object class compliance.
2583       * @param  invalidReason     A buffer into which an invalid reason
2584       *                           may be added.
2585       *
2586       * @return {@code true} if this entry passes all of the checks, or
2587       *         {@code false} if there are any failures.
2588       */
2589      private boolean checkAttributesAndObjectClasses(
2590                           DITContentRule ditContentRule,
2591                           AcceptRejectWarn structuralPolicy,
2592                           MessageBuilder invalidReason)
2593      {
2594        // Make sure that we recognize all of the objectclasses, that all
2595        // auxiliary classes are allowed by the DIT content rule, and that
2596        // all attributes required by the object classes are present.
2597        for (ObjectClass o : objectClasses.keySet())
2598        {
2599          if (DirectoryServer.getObjectClass(o.getOID()) == null)
2600          {
2601            Message message = ERR_ENTRY_SCHEMA_UNKNOWN_OC.get(
2602                    String.valueOf(dn), o
2603                    .getNameOrOID());
2604            invalidReason.append(message);
2605            return false;
2606          }
2607    
2608          if ((o.getObjectClassType() == ObjectClassType.AUXILIARY) &&
2609              (ditContentRule != null) &&
2610              (! ditContentRule.getAuxiliaryClasses().contains(o)))
2611          {
2612            Message message =
2613                    ERR_ENTRY_SCHEMA_DISALLOWED_AUXILIARY_CLASS.get(
2614                      String.valueOf(dn),
2615                      o.getNameOrOID(),
2616                      ditContentRule.getName());
2617            if (structuralPolicy == AcceptRejectWarn.REJECT)
2618            {
2619              invalidReason.append(message);
2620              return false;
2621            }
2622            else if (structuralPolicy == AcceptRejectWarn.WARN)
2623            {
2624              logError(message);
2625            }
2626          }
2627    
2628          for (AttributeType t : o.getRequiredAttributes())
2629          {
2630            if (! (userAttributes.containsKey(t) ||
2631                   operationalAttributes.containsKey(t) ||
2632                   t.isObjectClassType()))
2633            {
2634              Message message =
2635                      ERR_ENTRY_SCHEMA_MISSING_REQUIRED_ATTR_FOR_OC.get(
2636                        String.valueOf(dn),
2637                        t.getNameOrOID(),
2638                        o.getNameOrOID());
2639              invalidReason.append(message);
2640              return false;
2641            }
2642          }
2643        }
2644    
2645    
2646        // Make sure all the user attributes are allowed, have at least
2647        // one value, and if they are single-valued that they have exactly
2648        // one value.
2649        for (AttributeType t : userAttributes.keySet())
2650        {
2651          boolean found = false;
2652          for (ObjectClass o : objectClasses.keySet())
2653          {
2654            if (o.isRequiredOrOptional(t))
2655            {
2656              found = true;
2657              break;
2658            }
2659          }
2660    
2661          if ((! found) && (ditContentRule != null))
2662          {
2663            if (ditContentRule.isRequiredOrOptional(t))
2664            {
2665              found = true;
2666            }
2667          }
2668    
2669          if (! found)
2670          {
2671            Message message =
2672                    ERR_ENTRY_SCHEMA_DISALLOWED_USER_ATTR_FOR_OC.get(
2673                      String.valueOf(dn),
2674                      t.getNameOrOID());
2675            invalidReason.append(message);
2676            return false;
2677          }
2678    
2679          List<Attribute> attrList = userAttributes.get(t);
2680          if (attrList != null)
2681          {
2682            for (Attribute a : attrList)
2683            {
2684              LinkedHashSet<AttributeValue> values = a.getValues();
2685              if (values.isEmpty())
2686              {
2687                Message message = ERR_ENTRY_SCHEMA_ATTR_NO_VALUES.get(
2688                        String.valueOf(dn),
2689                        t.getNameOrOID());
2690    
2691                invalidReason.append(message);
2692                return false;
2693              }
2694              else if (t.isSingleValue() && (values.size() != 1))
2695              {
2696                Message message = ERR_ENTRY_SCHEMA_ATTR_SINGLE_VALUED.get(
2697                        String.valueOf(dn),
2698                        t.getNameOrOID());
2699    
2700                invalidReason.append(message);
2701                return false;
2702              }
2703            }
2704          }
2705        }
2706    
2707    
2708        // Iterate through all of the operational attributes and make sure
2709        // that all of the single-valued attributes only have one value.
2710        for (AttributeType t : operationalAttributes.keySet())
2711        {
2712          if (t.isSingleValue())
2713          {
2714            List<Attribute> attrList = operationalAttributes.get(t);
2715            if (attrList != null)
2716            {
2717              for (Attribute a : attrList)
2718              {
2719                if (a.getValues().size() > 1)
2720                {
2721                  Message message =
2722                          ERR_ENTRY_SCHEMA_ATTR_SINGLE_VALUED.get(
2723                            String.valueOf(dn),
2724                            t.getNameOrOID());
2725    
2726                  invalidReason.append(message);
2727                  return false;
2728                }
2729              }
2730            }
2731          }
2732        }
2733    
2734    
2735        // If we've gotten here, then things are OK.
2736        return true;
2737      }
2738    
2739    
2740    
2741      /**
2742       * Performs any processing needed for name form validation.
2743       *
2744       * @param  nameForm          The name form to validate against this
2745       *                           entry.
2746       * @param  structuralPolicy  The policy that should be used for
2747       *                           structural object class compliance.
2748       * @param  invalidReason     A buffer into which an invalid reason
2749       *                           may be added.
2750       *
2751       * @return {@code true} if this entry passes all of the checks, or
2752       *         {@code false} if there are any failures.
2753       */
2754      private boolean checkNameForm(NameForm nameForm,
2755                           AcceptRejectWarn structuralPolicy,
2756                           MessageBuilder invalidReason)
2757      {
2758        RDN rdn = dn.getRDN();
2759        if (rdn != null)
2760        {
2761          // Make sure that all the required attributes are present.
2762          for (AttributeType t : nameForm.getRequiredAttributes())
2763          {
2764            if (! rdn.hasAttributeType(t))
2765            {
2766              Message message =
2767                      ERR_ENTRY_SCHEMA_RDN_MISSING_REQUIRED_ATTR.get(
2768                        String.valueOf(dn),
2769                        t.getNameOrOID(),
2770                        nameForm.getNameOrOID());
2771    
2772              if (structuralPolicy == AcceptRejectWarn.REJECT)
2773              {
2774                invalidReason.append(message);
2775                return false;
2776              }
2777              else if (structuralPolicy == AcceptRejectWarn.WARN)
2778              {
2779                logError(message);
2780              }
2781            }
2782          }
2783    
2784          // Make sure that all attributes in the RDN are allowed.
2785          int numAVAs = rdn.getNumValues();
2786          for (int i = 0; i < numAVAs; i++)
2787          {
2788            AttributeType t = rdn.getAttributeType(i);
2789            if (! nameForm.isRequiredOrOptional(t))
2790            {
2791              Message message =
2792                      ERR_ENTRY_SCHEMA_RDN_DISALLOWED_ATTR.get(
2793                        String.valueOf(dn),
2794                        t.getNameOrOID(),
2795                        nameForm.getNameOrOID());
2796    
2797              if (structuralPolicy == AcceptRejectWarn.REJECT)
2798              {
2799                invalidReason.append(message);
2800                return false;
2801              }
2802              else if (structuralPolicy == AcceptRejectWarn.WARN)
2803              {
2804                logError(message);
2805              }
2806            }
2807          }
2808        }
2809    
2810        // If we've gotten here, then things are OK.
2811        return true;
2812      }
2813    
2814    
2815    
2816      /**
2817       * Performs any processing needed for DIT content rule validation.
2818       *
2819       * @param  ditContentRule    The DIT content rule to validate
2820       *                           against this entry.
2821       * @param  structuralPolicy  The policy that should be used for
2822       *                           structural object class compliance.
2823       * @param  invalidReason     A buffer into which an invalid reason
2824       *                           may be added.
2825       *
2826       * @return {@code true} if this entry passes all of the checks, or
2827       *         {@code false} if there are any failures.
2828       */
2829      private boolean checkDITContentRule(DITContentRule ditContentRule,
2830                           AcceptRejectWarn structuralPolicy,
2831                           MessageBuilder invalidReason)
2832      {
2833        // Make sure that all of the required attributes are present.
2834        for (AttributeType t : ditContentRule.getRequiredAttributes())
2835        {
2836          if (! (userAttributes.containsKey(t) ||
2837                 operationalAttributes.containsKey(t) ||
2838                 t.isObjectClassType()))
2839          {
2840            Message message =
2841                    ERR_ENTRY_SCHEMA_MISSING_REQUIRED_ATTR_FOR_DCR.get(
2842                      String.valueOf(dn),
2843                      t.getNameOrOID(),
2844                      ditContentRule.getName());
2845    
2846            if (structuralPolicy == AcceptRejectWarn.REJECT)
2847            {
2848              invalidReason.append(message);
2849              return false;
2850            }
2851            else if (structuralPolicy == AcceptRejectWarn.WARN)
2852            {
2853              logError(message);
2854            }
2855          }
2856        }
2857    
2858        // Make sure that none of the prohibited attributes are present.
2859        for (AttributeType t : ditContentRule.getProhibitedAttributes())
2860        {
2861          if (userAttributes.containsKey(t) ||
2862              operationalAttributes.containsKey(t))
2863          {
2864            Message message =
2865                    ERR_ENTRY_SCHEMA_PROHIBITED_ATTR_FOR_DCR.get(
2866                      String.valueOf(dn),
2867                      t.getNameOrOID(),
2868                      ditContentRule.getName());
2869    
2870            if (structuralPolicy == AcceptRejectWarn.REJECT)
2871            {
2872              invalidReason.append(message);
2873              return false;
2874            }
2875            else if (structuralPolicy == AcceptRejectWarn.WARN)
2876            {
2877              logError(message);
2878            }
2879          }
2880        }
2881    
2882        // If we've gotten here, then things are OK.
2883        return true;
2884      }
2885    
2886    
2887    
2888      /**
2889       * Performs any processing needed for DIT structure rule validation.
2890       *
2891       * @param  ditStructureRule        The DIT structure rule for this
2892       *                                 entry.
2893       * @param  structuralClass         The structural object class for
2894       *                                 this entry.
2895       * @param  parentEntry             The parent entry, if available
2896       *                                 and applicable.
2897       * @param  parentProvided          Indicates whether the parent
2898       *                                 entry was provided.
2899       * @param  validateStructureRules  Indicates whether to check to see
2900       *                                 if this entry violates a DIT
2901       *                                 structure rule for its parent.
2902       * @param  structuralPolicy        The policy that should be used
2903       *                                 for structural object class
2904       *                                 compliance.
2905       * @param  invalidReason           A buffer into which an invalid
2906       *                                 reason may be added.
2907       *
2908       * @return {@code true} if this entry passes all of the checks, or
2909       *         {@code false} if there are any failures.
2910       */
2911      private boolean checkDITStructureRule(
2912                           DITStructureRule ditStructureRule,
2913                           ObjectClass structuralClass,
2914                           Entry parentEntry, boolean parentProvided,
2915                           boolean validateStructureRules,
2916                           AcceptRejectWarn structuralPolicy,
2917                           MessageBuilder invalidReason)
2918      {
2919        // If there is a DIT structure rule for this entry, then make sure
2920        // that the entry is in compliance with it.
2921        if ((ditStructureRule != null) &&
2922            ditStructureRule.hasSuperiorRules())
2923        {
2924          if (parentProvided)
2925          {
2926            if (parentEntry != null)
2927            {
2928              boolean dsrValid =
2929                   validateDITStructureRule(ditStructureRule,
2930                                            structuralClass, parentEntry,
2931                                            structuralPolicy,
2932                                            invalidReason);
2933              if (! dsrValid)
2934              {
2935                return false;
2936              }
2937            }
2938          }
2939          else
2940          {
2941            // Get the DN of the parent entry if possible.
2942            DN parentDN = dn.getParentDNInSuffix();
2943            if (parentDN != null)
2944            {
2945              // Get the parent entry and check its structural class.
2946              Lock lock = null;
2947              for (int i=0; i < 3; i++)
2948              {
2949                lock = LockManager.lockRead(parentDN);
2950                if (lock != null)
2951                {
2952                  break;
2953                }
2954              }
2955    
2956              if (lock == null)
2957              {
2958                Message message =
2959                        ERR_ENTRY_SCHEMA_DSR_COULD_NOT_LOCK_PARENT.get(
2960                          String.valueOf(dn), String.valueOf(parentDN));
2961    
2962                if (structuralPolicy == AcceptRejectWarn.REJECT)
2963                {
2964                  invalidReason.append(message);
2965                  return false;
2966                }
2967                else if (structuralPolicy == AcceptRejectWarn.WARN)
2968                {
2969                  logError(message);
2970                }
2971              }
2972              else
2973              {
2974                try
2975                {
2976                  parentEntry = DirectoryServer.getEntry(parentDN);
2977                  if (parentEntry == null)
2978                  {
2979                    Message message =
2980                         ERR_ENTRY_SCHEMA_DSR_NO_PARENT_ENTRY.get(
2981                                 String.valueOf(dn),
2982                                 String.valueOf(parentDN));
2983    
2984                    if (structuralPolicy == AcceptRejectWarn.REJECT)
2985                    {
2986                      invalidReason.append(message);
2987                      return false;
2988                    }
2989                    else if (structuralPolicy == AcceptRejectWarn.WARN)
2990                    {
2991                      logError(message);
2992                    }
2993                  }
2994                  else
2995                  {
2996                    boolean dsrValid =
2997                         validateDITStructureRule(ditStructureRule,
2998                                                  structuralClass,
2999                                                  parentEntry,
3000                                                  structuralPolicy,
3001                                                  invalidReason);
3002                    if (! dsrValid)
3003                    {
3004                      return false;
3005                    }
3006                  }
3007                }
3008                catch (Exception e)
3009                {
3010                  if (debugEnabled())
3011                  {
3012                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
3013                  }
3014    
3015                  Message message =
3016                       ERR_ENTRY_SCHEMA_COULD_NOT_CHECK_DSR.get(
3017                               String.valueOf(dn),
3018                               ditStructureRule.getNameOrRuleID(),
3019                               getExceptionMessage(e));
3020    
3021                  if (structuralPolicy == AcceptRejectWarn.REJECT)
3022                  {
3023                    invalidReason.append(message);
3024                    return false;
3025                  }
3026                  else if (structuralPolicy == AcceptRejectWarn.WARN)
3027                  {
3028                    logError(message);
3029                  }
3030                }
3031                finally
3032                {
3033                  LockManager.unlock(parentDN, lock);
3034                }
3035              }
3036            }
3037          }
3038        }
3039        else if (validateStructureRules)
3040        {
3041          // There is no DIT structure rule for this entry, but there may
3042          // be one for the parent entry.  If there is such a rule for the
3043          // parent entry, then this entry will not be valid.
3044          boolean parentExists = false;
3045          ObjectClass parentStructuralClass = null;
3046          if (parentEntry != null)
3047          {
3048            parentExists = true;
3049            parentStructuralClass =
3050                 parentEntry.getStructuralObjectClass();
3051          }
3052          else if (! parentProvided)
3053          {
3054            DN parentDN = getDN().getParentDNInSuffix();
3055            if (parentDN != null)
3056            {
3057              // Get the parent entry and check its structural class.
3058              Lock lock = null;
3059              for (int i=0; i < 3; i++)
3060              {
3061                lock = LockManager.lockRead(parentDN);
3062                if (lock != null)
3063                {
3064                  break;
3065                }
3066              }
3067    
3068              if (lock == null)
3069              {
3070                Message message =
3071                        ERR_ENTRY_SCHEMA_DSR_COULD_NOT_LOCK_PARENT.get(
3072                          String.valueOf(dn),
3073                          String.valueOf(parentDN));
3074    
3075                if (structuralPolicy == AcceptRejectWarn.REJECT)
3076                {
3077                  invalidReason.append(message);
3078                  return false;
3079                }
3080                else if (structuralPolicy == AcceptRejectWarn.WARN)
3081                {
3082                  logError(message);
3083                }
3084              }
3085              else
3086              {
3087                try
3088                {
3089                  parentEntry = DirectoryServer.getEntry(parentDN);
3090                  if (parentEntry == null)
3091                  {
3092                    Message message =
3093                         ERR_ENTRY_SCHEMA_DSR_NO_PARENT_ENTRY.get(
3094                                 String.valueOf(dn),
3095                                 String.valueOf(parentDN));
3096    
3097                    if (structuralPolicy == AcceptRejectWarn.REJECT)
3098                    {
3099                      invalidReason.append(message);
3100                      return false;
3101                    }
3102                    else if (structuralPolicy == AcceptRejectWarn.WARN)
3103                    {
3104                      logError(message);
3105                    }
3106                  }
3107                  else
3108                  {
3109                    parentExists = true;
3110                    parentStructuralClass =
3111                         parentEntry.getStructuralObjectClass();
3112                  }
3113                }
3114                catch (Exception e)
3115                {
3116                  if (debugEnabled())
3117                  {
3118                    TRACER.debugCaught(DebugLogLevel.ERROR, e);
3119                  }
3120    
3121                  Message message =
3122                       ERR_ENTRY_SCHEMA_COULD_NOT_CHECK_PARENT_DSR.get(
3123                               String.valueOf(dn),
3124                               getExceptionMessage(e));
3125    
3126                  if (structuralPolicy == AcceptRejectWarn.REJECT)
3127                  {
3128                    invalidReason.append(message);
3129                    return false;
3130                  }
3131                  else if (structuralPolicy == AcceptRejectWarn.WARN)
3132                  {
3133                    logError(message);
3134                  }
3135                }
3136                finally
3137                {
3138                  LockManager.unlock(parentDN, lock);
3139                }
3140              }
3141            }
3142          }
3143    
3144          if (parentExists)
3145          {
3146            if (parentStructuralClass == null)
3147            {
3148              Message message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC.get(
3149                      String.valueOf(dn),
3150                      String.valueOf(parentEntry.getDN()));
3151    
3152              if (structuralPolicy == AcceptRejectWarn.REJECT)
3153              {
3154                invalidReason.append(message);
3155                return false;
3156              }
3157              else if (structuralPolicy == AcceptRejectWarn.WARN)
3158              {
3159                logError(message);
3160              }
3161            }
3162            else
3163            {
3164              NameForm parentNF =
3165                   DirectoryServer.getNameForm(parentStructuralClass);
3166              if ((parentNF != null) && (! parentNF.isObsolete()))
3167              {
3168                DITStructureRule parentDSR =
3169                     DirectoryServer.getDITStructureRule(parentNF);
3170                if ((parentDSR != null) && (! parentDSR.isObsolete()))
3171                {
3172                  Message message =
3173                       ERR_ENTRY_SCHEMA_VIOLATES_PARENT_DSR.get(
3174                               String.valueOf(dn),
3175                               String.valueOf(parentEntry.getDN()));
3176    
3177                  if (structuralPolicy == AcceptRejectWarn.REJECT)
3178                  {
3179                    invalidReason.append(message);
3180                    return false;
3181                  }
3182                  else if (structuralPolicy == AcceptRejectWarn.WARN)
3183                  {
3184                    logError(message);
3185                  }
3186                }
3187              }
3188            }
3189          }
3190        }
3191    
3192        // If we've gotten here, then things are OK.
3193        return true;
3194      }
3195    
3196    
3197    
3198      /**
3199       * Determines whether this entry is in conformance to the provided
3200       * DIT structure rule.
3201       *
3202       * @param  dsr               The DIT structure rule to use in the
3203       *                           determination.
3204       * @param  structuralClass   The structural objectclass for this
3205       *                           entry to use in the determination.
3206       * @param  parentEntry       The reference to the parent entry to
3207       *                           check.
3208       * @param  structuralPolicy  The policy that should be used around
3209       *                           enforcement of DIT structure rules.
3210       * @param  invalidReason     The buffer to which the invalid reason
3211       *                           should be appended if a problem is
3212       *                           found.
3213       *
3214       * @return  <CODE>true</CODE> if this entry conforms to the provided
3215       *          DIT structure rule, or <CODE>false</CODE> if not.
3216       */
3217      private boolean validateDITStructureRule(DITStructureRule dsr,
3218                           ObjectClass structuralClass, Entry parentEntry,
3219                           AcceptRejectWarn structuralPolicy,
3220                           MessageBuilder invalidReason)
3221      {
3222        ObjectClass oc = parentEntry.getStructuralObjectClass();
3223        if (oc == null)
3224        {
3225          Message message = ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC.get(
3226                  String.valueOf(dn),
3227                  String.valueOf(parentEntry.getDN()));
3228    
3229          if (structuralPolicy == AcceptRejectWarn.REJECT)
3230          {
3231            invalidReason.append(message);
3232            return false;
3233          }
3234          else if (structuralPolicy == AcceptRejectWarn.WARN)
3235          {
3236            logError(message);
3237          }
3238        }
3239    
3240        boolean matchFound = false;
3241        for (DITStructureRule dsr2 : dsr.getSuperiorRules())
3242        {
3243          if (dsr2.getStructuralClass().equals(oc))
3244          {
3245            matchFound = true;
3246          }
3247        }
3248    
3249        if (! matchFound)
3250        {
3251          Message message =
3252                  ERR_ENTRY_SCHEMA_DSR_DISALLOWED_SUPERIOR_OC.get(
3253                    String.valueOf(dn),
3254                    dsr.getNameOrRuleID(),
3255                    structuralClass.getNameOrOID(),
3256                    oc.getNameOrOID());
3257    
3258          if (structuralPolicy == AcceptRejectWarn.REJECT)
3259          {
3260            invalidReason.append(message);
3261            return false;
3262          }
3263          else if (structuralPolicy == AcceptRejectWarn.WARN)
3264          {
3265            logError(message);
3266          }
3267        }
3268    
3269        return true;
3270      }
3271    
3272    
3273    
3274      /**
3275       * Retrieves the attachment for this entry.
3276       *
3277       * @return  The attachment for this entry, or <CODE>null</CODE> if
3278       *          there is none.
3279       */
3280      public Object getAttachment()
3281      {
3282        return attachment;
3283      }
3284    
3285    
3286    
3287      /**
3288       * Specifies the attachment for this entry.  This will replace any
3289       * existing attachment that might be defined.
3290       *
3291       * @param  attachment  The attachment for this entry, or
3292       *                     <CODE>null</CODE> if there should not be an
3293       *                     attachment.
3294       */
3295      public void setAttachment(Object attachment)
3296      {
3297        this.attachment = attachment;
3298      }
3299    
3300    
3301    
3302      /**
3303       * Creates a duplicate of this entry that may be altered without
3304       * impacting the information in this entry.
3305       *
3306       * @param  processVirtual  Indicates whether virtual attribute
3307       *                         processing should be performed for the
3308       *                         entry.
3309       *
3310       * @return  A duplicate of this entry that may be altered without
3311       *          impacting the information in this entry.
3312       */
3313      public Entry duplicate(boolean processVirtual)
3314      {
3315        HashMap<ObjectClass,String> objectClassesCopy =
3316             new HashMap<ObjectClass,String>(objectClasses);
3317    
3318        HashMap<AttributeType,List<Attribute>> userAttrsCopy =
3319             new HashMap<AttributeType,List<Attribute>>(
3320                  userAttributes.size());
3321        deepCopy(userAttributes, userAttrsCopy, false);
3322    
3323        HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
3324             new HashMap<AttributeType,List<Attribute>>(
3325                      operationalAttributes.size());
3326        deepCopy(operationalAttributes, operationalAttrsCopy, false);
3327    
3328        for (AttributeType t : suppressedAttributes.keySet())
3329        {
3330          List<Attribute> attrList = suppressedAttributes.get(t);
3331          if (t.isOperational())
3332          {
3333            operationalAttributes.put(t, attrList);
3334          }
3335          else
3336          {
3337            userAttributes.put(t, attrList);
3338          }
3339        }
3340    
3341        Entry e = new Entry(dn, objectClassesCopy, userAttrsCopy,
3342                            operationalAttrsCopy);
3343        if (processVirtual)
3344        {
3345          e.processVirtualAttributes();
3346        }
3347        return e;
3348      }
3349    
3350    
3351    
3352      /**
3353       * Creates a duplicate of this entry without any operational
3354       * attributes that may be altered without impacting the information
3355       * in this entry.
3356       *
3357       * @param  typesOnly       Indicates whether to include attribute
3358       *                         types only without values.
3359       * @param  processVirtual  Indicates whether virtual attribute
3360       *                         processing should be performed for the
3361       *                         entry.
3362       *
3363       * @return  A duplicate of this entry that may be altered without
3364       *          impacting the information in this entry and that does
3365       *          not contain any operational attributes.
3366       */
3367      public Entry duplicateWithoutOperationalAttributes(
3368                        boolean typesOnly, boolean processVirtual)
3369      {
3370        HashMap<ObjectClass,String> objectClassesCopy;
3371        if (typesOnly)
3372        {
3373          objectClassesCopy = new HashMap<ObjectClass,String>(0);
3374        }
3375        else
3376        {
3377          objectClassesCopy =
3378               new HashMap<ObjectClass,String>(objectClasses);
3379        }
3380    
3381        HashMap<AttributeType,List<Attribute>> userAttrsCopy =
3382             new HashMap<AttributeType,List<Attribute>>(
3383                  userAttributes.size());
3384        if (typesOnly)
3385        {
3386          // Make sure to include the objectClass attribute here because
3387          // it won't make it in otherwise.
3388          AttributeType ocType =
3389               DirectoryServer.getObjectClassAttributeType();
3390          ArrayList<Attribute> ocList = new ArrayList<Attribute>(1);
3391          ocList.add(new Attribute(ocType));
3392          userAttrsCopy.put(ocType, ocList);
3393        }
3394    
3395        deepCopy(userAttributes, userAttrsCopy, typesOnly);
3396    
3397        HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
3398             new HashMap<AttributeType,List<Attribute>>(0);
3399    
3400        for (AttributeType t : suppressedAttributes.keySet())
3401        {
3402          List<Attribute> attrList = suppressedAttributes.get(t);
3403          if (! t.isOperational())
3404          {
3405            userAttributes.put(t, attrList);
3406          }
3407        }
3408    
3409        Entry e = new Entry(dn, objectClassesCopy, userAttrsCopy,
3410                            operationalAttrsCopy);
3411    
3412        if (processVirtual)
3413        {
3414          e.processVirtualAttributes(false);
3415        }
3416    
3417        return e;
3418      }
3419    
3420    
3421    
3422      /**
3423       * Performs a deep copy from the source map to the target map.  In
3424       * this case, the attributes in the list will be duplicates rather
3425       * than re-using the same reference.  Virtual attributes will not be
3426       * included when making the copy.
3427       *
3428       * @param  source      The source map from which to obtain the
3429       *                     information.
3430       * @param  target      The target map into which to place the
3431       *                     copied information.
3432       * @param  omitValues  Indicates whether to omit attribute values
3433       *                     when processing.
3434       */
3435      private void deepCopy(Map<AttributeType,List<Attribute>> source,
3436                            Map<AttributeType,List<Attribute>> target,
3437                            boolean omitValues)
3438      {
3439        for (AttributeType t : source.keySet())
3440        {
3441          List<Attribute> sourceList = source.get(t);
3442          ArrayList<Attribute> targetList =
3443               new ArrayList<Attribute>(sourceList.size());
3444    
3445          for (Attribute a : sourceList)
3446          {
3447            if (a.isVirtual())
3448            {
3449              continue;
3450            }
3451    
3452            targetList.add(a.duplicate(omitValues));
3453          }
3454    
3455          if (! targetList.isEmpty())
3456          {
3457            target.put(t, targetList);
3458          }
3459        }
3460      }
3461    
3462    
3463    
3464      /**
3465       * Creates a duplicate of this entry without any attribute or
3466       * objectclass information (i.e., it will just contain the DN and
3467       * placeholders for adding attributes) and objectclasses.
3468       *
3469       * @return  A duplicate of this entry that may be altered without
3470       *          impacting the information in this entry and that does
3471       *          not contain attribute or objectclass information.
3472       */
3473      public Entry duplicateWithoutAttributes()
3474      {
3475        HashMap<ObjectClass,String> objectClassesCopy =
3476             new HashMap<ObjectClass,String>(objectClasses.size());
3477    
3478        HashMap<AttributeType,List<Attribute>> userAttrsCopy =
3479             new HashMap<AttributeType,List<Attribute>>(
3480                  userAttributes.size());
3481    
3482        HashMap<AttributeType,List<Attribute>> operationalAttrsCopy =
3483             new HashMap<AttributeType,List<Attribute>>(
3484                      operationalAttributes.size());
3485    
3486        return new Entry(dn, objectClassesCopy, userAttrsCopy,
3487                         operationalAttrsCopy);
3488      }
3489    
3490    
3491    
3492      /**
3493       * Indicates whether this entry meets the criteria to consider it a
3494       * referral (e.g., it contains the "referral" objectclass and a
3495       * "ref" attribute).
3496       *
3497       * @return  <CODE>true</CODE> if this entry meets the criteria to
3498       *          consider it a referral, or <CODE>false</CODE> if not.
3499       */
3500      public boolean isReferral()
3501      {
3502        ObjectClass referralOC =
3503             DirectoryServer.getObjectClass(OC_REFERRAL);
3504        if (referralOC == null)
3505        {
3506          // This should not happen -- The server doesn't have a referral
3507          // objectclass defined.
3508          if (debugEnabled())
3509          {
3510            TRACER.debugWarning(
3511                "No %s objectclass is defined in the server schema.",
3512                         OC_REFERRAL);
3513          }
3514    
3515          for (String ocName : objectClasses.values())
3516          {
3517            if (ocName.equalsIgnoreCase(OC_REFERRAL))
3518            {
3519              return true;
3520            }
3521          }
3522    
3523          return false;
3524        }
3525    
3526        if (! objectClasses.containsKey(referralOC))
3527        {
3528          return false;
3529        }
3530    
3531        AttributeType referralType =
3532             DirectoryServer.getAttributeType(ATTR_REFERRAL_URL);
3533        if (referralType == null)
3534        {
3535          // This should not happen -- The server doesn't have a ref
3536          // attribute type defined.
3537          if (debugEnabled())
3538          {
3539            TRACER.debugWarning(
3540                "No %s attribute type is defined in the server schema.",
3541                         ATTR_REFERRAL_URL);
3542          }
3543          return false;
3544        }
3545    
3546        return (userAttributes.containsKey(referralType) ||
3547                operationalAttributes.containsKey(referralType));
3548      }
3549    
3550    
3551    
3552      /**
3553       * Retrieves the set of referral URLs that are included in this
3554       * referral entry.  This should only be called if
3555       * <CODE>isReferral()</CODE> returns <CODE>true</CODE>.
3556       *
3557       * @return  The set of referral URLs that are included in this entry
3558       *          if it is a referral, or <CODE>null</CODE> if it is not a
3559       *          referral.
3560       */
3561      public LinkedHashSet<String> getReferralURLs()
3562      {
3563        AttributeType referralType =
3564             DirectoryServer.getAttributeType(ATTR_REFERRAL_URL);
3565        if (referralType == null)
3566        {
3567          // This should not happen -- The server doesn't have a ref
3568          // attribute type defined.
3569          if (debugEnabled())
3570          {
3571            TRACER.debugWarning(
3572                "No %s attribute type is defined in the server schema.",
3573                         ATTR_REFERRAL_URL);
3574          }
3575          return null;
3576        }
3577    
3578        List<Attribute> refAttrs = userAttributes.get(referralType);
3579        if (refAttrs == null)
3580        {
3581          refAttrs = operationalAttributes.get(referralType);
3582          if (refAttrs == null)
3583          {
3584            return null;
3585          }
3586        }
3587    
3588        LinkedHashSet<String> referralURLs = new LinkedHashSet<String>();
3589        for (Attribute a : refAttrs)
3590        {
3591          for (AttributeValue v : a.getValues())
3592          {
3593            referralURLs.add(v.getStringValue());
3594          }
3595        }
3596    
3597        return referralURLs;
3598      }
3599    
3600    
3601    
3602      /**
3603       * Indicates whether this entry meets the criteria to consider it an
3604       * alias (e.g., it contains the "aliasObject" objectclass and a
3605       * "alias" attribute).
3606       *
3607       * @return  <CODE>true</CODE> if this entry meets the criteria to
3608       *          consider it an alias, or <CODE>false</CODE> if not.
3609       */
3610      public boolean isAlias()
3611      {
3612        ObjectClass aliasOC = DirectoryServer.getObjectClass(OC_ALIAS);
3613        if (aliasOC == null)
3614        {
3615          // This should not happen -- The server doesn't have an alias
3616          // objectclass defined.
3617          if (debugEnabled())
3618          {
3619            TRACER.debugWarning(
3620                "No %s objectclass is defined in the server schema.",
3621                         OC_ALIAS);
3622          }
3623    
3624          for (String ocName : objectClasses.values())
3625          {
3626            if (ocName.equalsIgnoreCase(OC_ALIAS))
3627            {
3628              return true;
3629            }
3630          }
3631    
3632          return false;
3633        }
3634    
3635        if (! objectClasses.containsKey(aliasOC))
3636        {
3637          return false;
3638        }
3639    
3640        AttributeType aliasType =
3641             DirectoryServer.getAttributeType(ATTR_ALIAS_DN);
3642        if (aliasType == null)
3643        {
3644          // This should not happen -- The server doesn't have an
3645          // aliasedObjectName attribute type defined.
3646          if (debugEnabled())
3647          {
3648            TRACER.debugWarning(
3649                "No %s attribute type is defined in the server schema.",
3650                         ATTR_ALIAS_DN);
3651          }
3652          return false;
3653        }
3654    
3655        return (userAttributes.containsKey(aliasType) ||
3656                operationalAttributes.containsKey(aliasType));
3657      }
3658    
3659    
3660    
3661      /**
3662       * Retrieves the DN of the entry referenced by this alias entry.
3663       * This should only be called if <CODE>isAlias()</CODE> returns
3664       * <CODE>true</CODE>.
3665       *
3666       * @return  The DN of the entry referenced by this alias entry, or
3667       *          <CODE>null</CODE> if it is not an alias.
3668       *
3669       * @throws  DirectoryException  If there is an aliasedObjectName
3670       *                              attribute but its value cannot be
3671       *                              parsed as a DN.
3672       */
3673      public DN getAliasedDN()
3674             throws DirectoryException
3675      {
3676        AttributeType aliasType =
3677             DirectoryServer.getAttributeType(ATTR_REFERRAL_URL);
3678        if (aliasType == null)
3679        {
3680          // This should not happen -- The server doesn't have an
3681          // aliasedObjectName attribute type defined.
3682          if (debugEnabled())
3683          {
3684            TRACER.debugWarning(
3685                "No %s attribute type is defined in the server schema.",
3686                         ATTR_ALIAS_DN);
3687          }
3688          return null;
3689        }
3690    
3691        List<Attribute> aliasAttrs = userAttributes.get(aliasType);
3692        if (aliasAttrs == null)
3693        {
3694          aliasAttrs = operationalAttributes.get(aliasType);
3695          if (aliasAttrs == null)
3696          {
3697            return null;
3698          }
3699        }
3700    
3701        if (aliasAttrs.isEmpty())
3702        {
3703          return null;
3704        }
3705        else
3706        {
3707          // There should only be a single alias attribute in an entry,
3708          // and we'll skip the check for others for performance reasons.
3709          // We would just end up taking the first one anyway.  The same
3710          // is true with the set of values, since it should be a
3711          // single-valued attribute.
3712          Attribute aliasAttr = aliasAttrs.get(0);
3713          LinkedHashSet<AttributeValue> attrValues =
3714               aliasAttr.getValues();
3715          if (attrValues.isEmpty())
3716          {
3717            return null;
3718          }
3719          else
3720          {
3721            return
3722                 DN.decode(attrValues.iterator().next().getStringValue());
3723          }
3724        }
3725      }
3726    
3727    
3728    
3729      /**
3730       * Indicates whether this entry meets the criteria to consider it an
3731       * LDAP subentry (i.e., it contains the "ldapSubentry" objectclass).
3732       *
3733       * @return  <CODE>true</CODE> if this entry meets the criteria to
3734       *          consider it an LDAP subentry, or <CODE>false</CODE> if
3735       *          not.
3736       */
3737      public boolean isLDAPSubentry()
3738      {
3739        ObjectClass ldapSubentryOC =
3740             DirectoryServer.getObjectClass(OC_LDAP_SUBENTRY_LC);
3741        if (ldapSubentryOC == null)
3742        {
3743          // This should not happen -- The server doesn't have an
3744          // ldapsubentry objectclass defined.
3745          if (debugEnabled())
3746          {
3747            TRACER.debugWarning(
3748                "No %s objectclass is defined in the server schema.",
3749                         OC_LDAP_SUBENTRY);
3750          }
3751    
3752          for (String ocName : objectClasses.values())
3753          {
3754            if (ocName.equalsIgnoreCase(OC_LDAP_SUBENTRY))
3755            {
3756              return true;
3757            }
3758          }
3759    
3760          return false;
3761        }
3762    
3763    
3764        // Make the determination based on whether this entry has the
3765        // ldapSubentry objectclass.
3766        return objectClasses.containsKey(ldapSubentryOC);
3767      }
3768    
3769    
3770    
3771      /**
3772       * Indicates whether this entry falls within the range of the
3773       * provided search base DN and scope.
3774       *
3775       * @param  baseDN  The base DN for which to make the determination.
3776       * @param  scope   The search scope for which to make the
3777       *                 determination.
3778       *
3779       * @return  <CODE>true</CODE> if this entry is within the given
3780       *          base and scope, or <CODE>false</CODE> if it is not.
3781       */
3782      public boolean matchesBaseAndScope(DN baseDN, SearchScope scope)
3783      {
3784        return dn.matchesBaseAndScope(baseDN, scope);
3785      }
3786    
3787    
3788    
3789      /**
3790       * Performs any necessary virtual attribute processing for this
3791       * entry.  This should only be called at the time the entry is
3792       * decoded or created within the backend.
3793       */
3794      public void processVirtualAttributes()
3795      {
3796        processVirtualAttributes(true);
3797      }
3798    
3799    
3800    
3801      /**
3802       * Performs any necessary virtual attribute processing for this
3803       * entry.  This should only be called at the time the entry is
3804       * decoded or created within the backend.
3805       *
3806       * @param  includeOperational  Indicates whether to include
3807       *                             operational attributes.
3808       */
3809      public void processVirtualAttributes(boolean includeOperational)
3810      {
3811        for (VirtualAttributeRule rule :
3812             DirectoryServer.getVirtualAttributes(this))
3813        {
3814          AttributeType attributeType = rule.getAttributeType();
3815          if (attributeType.isOperational() && (! includeOperational))
3816          {
3817            continue;
3818          }
3819    
3820          List<Attribute> attrList = userAttributes.get(attributeType);
3821          if ((attrList == null) || attrList.isEmpty())
3822          {
3823            attrList = operationalAttributes.get(attributeType);
3824            if ((attrList == null) || attrList.isEmpty())
3825            {
3826              // There aren't any conflicts, so we can just add the
3827              // attribute to the entry.
3828              attrList = new LinkedList<Attribute>();
3829              attrList.add(new VirtualAttribute(attributeType, this,
3830                                                rule));
3831              if (attributeType.isOperational())
3832              {
3833                operationalAttributes.put(attributeType, attrList);
3834              }
3835              else
3836              {
3837                userAttributes.put(attributeType, attrList);
3838              }
3839            }
3840            else
3841            {
3842              // There is a conflict with an existing operational
3843              // attribute.
3844              if (attrList.get(0).isVirtual())
3845              {
3846                // The existing attribute is already virtual, so we've got
3847                // a different conflict, but we'll let the first win.
3848                // FIXME -- Should we handle this differently?
3849                continue;
3850              }
3851    
3852              // The conflict is with a real attribute.  See what the
3853              // conflict behavior is and figure out how to handle it.
3854              switch (rule.getConflictBehavior())
3855              {
3856                case REAL_OVERRIDES_VIRTUAL:
3857                  // We don't need to update the entry because the real
3858                  // attribute will take precedence.
3859                  break;
3860    
3861                case VIRTUAL_OVERRIDES_REAL:
3862                  // We need to move the real attribute to the suppressed
3863                  // list and replace it with the virtual attribute.
3864                  suppressedAttributes.put(attributeType, attrList);
3865                  attrList = new LinkedList<Attribute>();
3866                  attrList.add(new VirtualAttribute(attributeType, this,
3867                                                    rule));
3868                  operationalAttributes.put(attributeType, attrList);
3869                  break;
3870    
3871                case MERGE_REAL_AND_VIRTUAL:
3872                  // We need to add the virtual attribute to the list and
3873                  // keep the existing real attribute(s).
3874                  attrList.add(new VirtualAttribute(attributeType, this,
3875                                                    rule));
3876                  break;
3877              }
3878            }
3879          }
3880          else
3881          {
3882            // There is a conflict with an existing user attribute.
3883            if (attrList.get(0).isVirtual())
3884            {
3885              // The existing attribute is already virtual, so we've got
3886              // a different conflict, but we'll let the first win.
3887              // FIXME -- Should we handle this differently?
3888              continue;
3889            }
3890    
3891            // The conflict is with a real attribute.  See what the
3892            // conflict behavior is and figure out how to handle it.
3893            switch (rule.getConflictBehavior())
3894            {
3895              case REAL_OVERRIDES_VIRTUAL:
3896                // We don't need to update the entry because the real
3897                // attribute will take precedence.
3898                break;
3899    
3900              case VIRTUAL_OVERRIDES_REAL:
3901                // We need to move the real attribute to the suppressed
3902                // list and replace it with the virtual attribute.
3903                suppressedAttributes.put(attributeType, attrList);
3904                attrList = new LinkedList<Attribute>();
3905                attrList.add(new VirtualAttribute(attributeType, this,
3906                                                  rule));
3907                userAttributes.put(attributeType, attrList);
3908                break;
3909    
3910              case MERGE_REAL_AND_VIRTUAL:
3911                // We need to add the virtual attribute to the list and
3912                // keep the existing real attribute(s).
3913                attrList.add(new VirtualAttribute(attributeType, this,
3914                                                  rule));
3915                break;
3916            }
3917          }
3918        }
3919    
3920        virtualAttributeProcessingPerformed = true;
3921      }
3922    
3923    
3924    
3925      /**
3926       * Indicates whether virtual attribute processing has been performed
3927       * for this entry.
3928       *
3929       * @return  {@code true} if virtual attribute processing has been
3930       *          performed for this entry, or {@code false} if not.
3931       */
3932      public boolean virtualAttributeProcessingPerformed()
3933      {
3934        return virtualAttributeProcessingPerformed;
3935      }
3936    
3937    
3938    
3939      /**
3940       * Strips out all real attributes from this entry so that it only
3941       * contains virtual attributes.
3942       */
3943      public void stripRealAttributes()
3944      {
3945        // The objectClass attribute will always be a real attribute.
3946        objectClasses.clear();
3947    
3948        Iterator<Map.Entry<AttributeType,List<Attribute>>>
3949             attrListIterator = userAttributes.entrySet().iterator();
3950        while (attrListIterator.hasNext())
3951        {
3952          Map.Entry<AttributeType,List<Attribute>> mapEntry =
3953               attrListIterator.next();
3954          Iterator<Attribute> attrIterator =
3955               mapEntry.getValue().iterator();
3956          while (attrIterator.hasNext())
3957          {
3958            Attribute a = attrIterator.next();
3959            if (! a.isVirtual())
3960            {
3961              attrIterator.remove();
3962            }
3963          }
3964    
3965          if (mapEntry.getValue().isEmpty())
3966          {
3967            attrListIterator.remove();
3968          }
3969        }
3970    
3971        attrListIterator = operationalAttributes.entrySet().iterator();
3972        while (attrListIterator.hasNext())
3973        {
3974          Map.Entry<AttributeType,List<Attribute>> mapEntry =
3975               attrListIterator.next();
3976          Iterator<Attribute> attrIterator =
3977               mapEntry.getValue().iterator();
3978          while (attrIterator.hasNext())
3979          {
3980            Attribute a = attrIterator.next();
3981            if (! a.isVirtual())
3982            {
3983              attrIterator.remove();
3984            }
3985          }
3986    
3987          if (mapEntry.getValue().isEmpty())
3988          {
3989            attrListIterator.remove();
3990          }
3991        }
3992      }
3993    
3994    
3995    
3996      /**
3997       * Strips out all virtual attributes from this entry so that it only
3998       * contains real attributes.
3999       */
4000      public void stripVirtualAttributes()
4001      {
4002        Iterator<Map.Entry<AttributeType,List<Attribute>>>
4003             attrListIterator = userAttributes.entrySet().iterator();
4004        while (attrListIterator.hasNext())
4005        {
4006          Map.Entry<AttributeType,List<Attribute>> mapEntry =
4007               attrListIterator.next();
4008          Iterator<Attribute> attrIterator =
4009               mapEntry.getValue().iterator();
4010          while (attrIterator.hasNext())
4011          {
4012            Attribute a = attrIterator.next();
4013            if (a.isVirtual())
4014            {
4015              attrIterator.remove();
4016            }
4017          }
4018    
4019          if (mapEntry.getValue().isEmpty())
4020          {
4021            attrListIterator.remove();
4022          }
4023        }
4024    
4025        attrListIterator = operationalAttributes.entrySet().iterator();
4026        while (attrListIterator.hasNext())
4027        {
4028          Map.Entry<AttributeType,List<Attribute>> mapEntry =
4029               attrListIterator.next();
4030          Iterator<Attribute> attrIterator =
4031               mapEntry.getValue().iterator();
4032          while (attrIterator.hasNext())
4033          {
4034            Attribute a = attrIterator.next();
4035            if (a.isVirtual())
4036            {
4037              attrIterator.remove();
4038            }
4039          }
4040    
4041          if (mapEntry.getValue().isEmpty())
4042          {
4043            attrListIterator.remove();
4044          }
4045        }
4046      }
4047    
4048    
4049    
4050      /**
4051       * Encodes this entry into a form that is suitable for long-term
4052       * persistent storage.  The encoding will have a version number so
4053       * that if the way we store entries changes in the future we will
4054       * still be able to read entries encoded in an older format.
4055       *
4056       * @param  config  The configuration that may be used to control how
4057       *                 the entry is encoded.
4058       *
4059       * @return  The entry encoded in a form that is suitable for
4060       *          long-term persistent storage.
4061       *
4062       * @throws  DirectoryException  If a problem occurs while attempting
4063       *                              to encode the entry.
4064       */
4065      public byte[] encode(EntryEncodeConfig config)
4066             throws DirectoryException
4067      {
4068        return encodeV2(config);
4069      }
4070    
4071    
4072    
4073      /**
4074       * Encodes this entry using the V1 encoding.
4075       *
4076       * @return  The entry encoded in the V1 encoding.
4077       */
4078      public byte[] encodeV1()
4079      {
4080        // The version number will be one byte.  We'll add that later.
4081    
4082    
4083        // The DN will be encoded as a one-to-five byte length followed
4084        // byte the UTF-8 byte representation.
4085        byte[] dnBytes  = getBytes(dn.toString());
4086        byte[] dnLength = ASN1Element.encodeLength(dnBytes.length);
4087        int totalBytes = 1 + dnBytes.length + dnLength.length;
4088    
4089    
4090        // The object classes will be encoded as one-to-five byte length
4091        // followed by a zero-delimited UTF-8 byte representation of the
4092        // names (e.g., top\0person\0organizationalPerson\0inetOrgPerson).
4093        int i=0;
4094        int totalOCBytes = objectClasses.size() - 1;
4095        byte[][] ocBytes = new byte[objectClasses.size()][];
4096        for (String ocName : objectClasses.values())
4097        {
4098          ocBytes[i] = getBytes(ocName);
4099          totalOCBytes += ocBytes[i++].length;
4100        }
4101        byte[] ocLength = ASN1Element.encodeLength(totalOCBytes);
4102        totalBytes += totalOCBytes + ocLength.length;
4103    
4104    
4105        // The user attributes will be encoded as a one-to-five byte
4106        // number of attributes followed by a sequence of:
4107        // - A UTF-8 byte representation of the attribute name.
4108        // - A zero delimiter
4109        // - A one-to-five byte number of values for the attribute
4110        // - A sequence of:
4111        //   - A one-to-five byte length for the value
4112        //   - A UTF-8 byte representation for the value
4113        i=0;
4114        int numUserAttributes = 0;
4115        int totalUserAttrBytes = 0;
4116        LinkedList<byte[]> userAttrBytes = new LinkedList<byte[]>();
4117        for (List<Attribute> attrList : userAttributes.values())
4118        {
4119          for (Attribute a : attrList)
4120          {
4121            if (a.isVirtual() || (! a.hasValue()))
4122            {
4123              continue;
4124            }
4125    
4126            numUserAttributes++;
4127    
4128            byte[] nameBytes = getBytes(a.getNameWithOptions());
4129    
4130            int numValues = 0;
4131            int totalValueBytes = 0;
4132            LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
4133            for (AttributeValue v : a.getValues())
4134            {
4135              numValues++;
4136              byte[] vBytes = v.getValueBytes();
4137              byte[] vLength = ASN1Element.encodeLength(vBytes.length);
4138              valueBytes.add(vLength);
4139              valueBytes.add(vBytes);
4140              totalValueBytes += vLength.length + vBytes.length;
4141            }
4142            byte[] numValuesBytes = ASN1Element.encodeLength(numValues);
4143    
4144            byte[] attrBytes = new byte[nameBytes.length +
4145                                        numValuesBytes.length +
4146                                        totalValueBytes + 1];
4147            System.arraycopy(nameBytes, 0, attrBytes, 0,
4148                             nameBytes.length);
4149    
4150            int pos = nameBytes.length+1;
4151            System.arraycopy(numValuesBytes, 0, attrBytes, pos,
4152                             numValuesBytes.length);
4153            pos += numValuesBytes.length;
4154            for (byte[] b : valueBytes)
4155            {
4156              System.arraycopy(b, 0, attrBytes, pos, b.length);
4157              pos += b.length;
4158            }
4159    
4160            userAttrBytes.add(attrBytes);
4161            totalUserAttrBytes += attrBytes.length;
4162          }
4163        }
4164        byte[] userAttrCount =
4165             ASN1OctetString.encodeLength(numUserAttributes);
4166        totalBytes += totalUserAttrBytes + userAttrCount.length;
4167    
4168    
4169        // The operational attributes will be encoded in the same way as
4170        // the user attributes.
4171        i=0;
4172        int numOperationalAttributes = 0;
4173        int totalOperationalAttrBytes = 0;
4174        LinkedList<byte[]> operationalAttrBytes =
4175                                new LinkedList<byte[]>();
4176        for (List<Attribute> attrList : operationalAttributes.values())
4177        {
4178          for (Attribute a : attrList)
4179          {
4180            if (a.isVirtual() || (! a.hasValue()))
4181            {
4182              continue;
4183            }
4184    
4185            numOperationalAttributes++;
4186    
4187            byte[] nameBytes = getBytes(a.getNameWithOptions());
4188    
4189            int numValues = 0;
4190            int totalValueBytes = 0;
4191            LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
4192            for (AttributeValue v : a.getValues())
4193            {
4194              numValues++;
4195              byte[] vBytes = v.getValueBytes();
4196              byte[] vLength = ASN1Element.encodeLength(vBytes.length);
4197              valueBytes.add(vLength);
4198              valueBytes.add(vBytes);
4199              totalValueBytes += vLength.length + vBytes.length;
4200            }
4201            byte[] numValuesBytes = ASN1Element.encodeLength(numValues);
4202    
4203            byte[] attrBytes = new byte[nameBytes.length +
4204                                        numValuesBytes.length +
4205                                        totalValueBytes + 1];
4206            System.arraycopy(nameBytes, 0, attrBytes, 0,
4207                             nameBytes.length);
4208    
4209            int pos = nameBytes.length+1;
4210            System.arraycopy(numValuesBytes, 0, attrBytes, pos,
4211                             numValuesBytes.length);
4212            pos += numValuesBytes.length;
4213            for (byte[] b : valueBytes)
4214            {
4215              System.arraycopy(b, 0, attrBytes, pos, b.length);
4216              pos += b.length;
4217            }
4218    
4219            operationalAttrBytes.add(attrBytes);
4220            totalOperationalAttrBytes += attrBytes.length;
4221          }
4222        }
4223        byte[] operationalAttrCount =
4224             ASN1OctetString.encodeLength(numOperationalAttributes);
4225        totalBytes += totalOperationalAttrBytes +
4226                      operationalAttrCount.length;
4227    
4228    
4229        // Now we've got all the data that we need.  Create a big byte
4230        // array to hold it all and pack it in.
4231        byte[] entryBytes = new byte[totalBytes];
4232    
4233    
4234        // Add the entry version number as the first byte.
4235        entryBytes[0] = 0x01;
4236    
4237    
4238        // Next, add the DN length and value.
4239        System.arraycopy(dnLength, 0, entryBytes, 1, dnLength.length);
4240        int pos = 1 + dnLength.length;
4241        System.arraycopy(dnBytes, 0, entryBytes, pos,  dnBytes.length);
4242        pos += dnBytes.length;
4243    
4244    
4245        // Next, add the object classes length and values.
4246        System.arraycopy(ocLength, 0, entryBytes, pos, ocLength.length);
4247        pos += ocLength.length;
4248        for (byte[] b : ocBytes)
4249        {
4250          System.arraycopy(b, 0, entryBytes, pos, b.length);
4251          pos += b.length + 1;
4252        }
4253    
4254        // We need to back up one because there's no zero-teriminator
4255        // after the last object class name.
4256        pos--;
4257    
4258    
4259        // Next, add the user attribute count and the user attribute
4260        // data.
4261        System.arraycopy(userAttrCount, 0, entryBytes, pos,
4262                         userAttrCount.length);
4263        pos += userAttrCount.length;
4264        for (byte[] b : userAttrBytes)
4265        {
4266          System.arraycopy(b, 0, entryBytes, pos, b.length);
4267          pos += b.length;
4268        }
4269    
4270    
4271        // Finally, add the operational attribute count and the
4272        // operational attribute data.
4273        System.arraycopy(operationalAttrCount, 0, entryBytes, pos,
4274                         operationalAttrCount.length);
4275        pos += operationalAttrCount.length;
4276        for (byte[] b : operationalAttrBytes)
4277        {
4278          System.arraycopy(b, 0, entryBytes, pos, b.length);
4279          pos += b.length;
4280        }
4281    
4282        return entryBytes;
4283      }
4284    
4285    
4286    
4287      /**
4288       * Encodes this entry using the V2 encoding.
4289       *
4290       * @param  config  The configuration that should be used to encode
4291       *                 the entry.
4292       *
4293       * @return  The entry encoded in the V2 encoding.
4294       *
4295       * @throws  DirectoryException  If a problem occurs while attempting
4296       *                              to encode the entry.
4297       */
4298      public byte[] encodeV2(EntryEncodeConfig config)
4299             throws DirectoryException
4300      {
4301        // The version number will be one byte.  We'll add that later.
4302    
4303    
4304        // Get the encoded respresentation of the config.
4305        byte[] configBytes = config.encode();
4306        byte[] configLength =
4307                    ASN1Element.encodeLength(configBytes.length);
4308        int totalBytes = 1 + configBytes.length + configLength.length;
4309    
4310    
4311        // If we should include the DN, then it will be encoded as a
4312        // one-to-five byte length followed by the UTF-8 byte
4313        // representation.
4314        byte[] dnBytes  = null;
4315        byte[] dnLength = null;
4316        if (! config.excludeDN())
4317        {
4318          dnBytes  = getBytes(dn.toString());
4319          dnLength = ASN1Element.encodeLength(dnBytes.length);
4320          totalBytes += dnBytes.length + dnLength.length;
4321        }
4322    
4323    
4324        // Encode the object classes in the appropriate manner.
4325        byte[] ocLength;
4326        LinkedList<byte[]> ocBytes = new LinkedList<byte[]>();
4327        if (config.compressObjectClassSets())
4328        {
4329          byte[] b = config.getCompressedSchema().
4330                          encodeObjectClasses(objectClasses);
4331          ocBytes.add(b);
4332          ocLength = ASN1Element.encodeLength(b.length);
4333          totalBytes += ocLength.length + b.length;
4334        }
4335        else
4336        {
4337          int totalOCBytes = objectClasses.size() - 1;
4338          for (String ocName : objectClasses.values())
4339          {
4340            byte[] b = getBytes(ocName);
4341            ocBytes.add(b);
4342            totalOCBytes += b.length;
4343          }
4344          ocLength = ASN1Element.encodeLength(totalOCBytes);
4345          totalBytes += totalOCBytes + ocLength.length;
4346        }
4347    
4348    
4349        // Encode the user attributes in the appropriate manner.
4350        int numUserAttributes = 0;
4351        int totalUserAttrBytes = 0;
4352        LinkedList<byte[]> userAttrBytes = new LinkedList<byte[]>();
4353        if (config.compressAttributeDescriptions())
4354        {
4355          for (List<Attribute> attrList : userAttributes.values())
4356          {
4357            for (Attribute a : attrList)
4358            {
4359              if (a.isVirtual() || (! a.hasValue()))
4360              {
4361                continue;
4362              }
4363    
4364              numUserAttributes++;
4365    
4366              byte[] attrBytes =
4367                   config.getCompressedSchema().encodeAttribute(a);
4368              byte[] lengthBytes =
4369                   ASN1Element.encodeLength(attrBytes.length);
4370              userAttrBytes.add(lengthBytes);
4371              userAttrBytes.add(attrBytes);
4372              totalUserAttrBytes += lengthBytes.length + attrBytes.length;
4373            }
4374          }
4375        }
4376        else
4377        {
4378          // The user attributes will be encoded as a one-to-five byte
4379          // number of attributes followed by a sequence of:
4380          // - A UTF-8 byte representation of the attribute name.
4381          // - A zero delimiter
4382          // - A one-to-five byte number of values for the attribute
4383          // - A sequence of:
4384          //   - A one-to-five byte length for the value
4385          //   - A UTF-8 byte representation for the value
4386          for (List<Attribute> attrList : userAttributes.values())
4387          {
4388            for (Attribute a : attrList)
4389            {
4390              if (a.isVirtual() || (! a.hasValue()))
4391              {
4392                continue;
4393              }
4394    
4395              numUserAttributes++;
4396    
4397              byte[] nameBytes = getBytes(a.getNameWithOptions());
4398    
4399              int numValues = 0;
4400              int totalValueBytes = 0;
4401              LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
4402              for (AttributeValue v : a.getValues())
4403              {
4404                numValues++;
4405                byte[] vBytes = v.getValueBytes();
4406                byte[] vLength = ASN1Element.encodeLength(vBytes.length);
4407                valueBytes.add(vLength);
4408                valueBytes.add(vBytes);
4409                totalValueBytes += vLength.length + vBytes.length;
4410              }
4411              byte[] numValuesBytes = ASN1Element.encodeLength(numValues);
4412    
4413              byte[] attrBytes = new byte[nameBytes.length +
4414                                          numValuesBytes.length +
4415                                          totalValueBytes + 1];
4416              System.arraycopy(nameBytes, 0, attrBytes, 0,
4417                               nameBytes.length);
4418    
4419              int pos = nameBytes.length+1;
4420              System.arraycopy(numValuesBytes, 0, attrBytes, pos,
4421                               numValuesBytes.length);
4422              pos += numValuesBytes.length;
4423              for (byte[] b : valueBytes)
4424              {
4425                System.arraycopy(b, 0, attrBytes, pos, b.length);
4426                pos += b.length;
4427              }
4428    
4429              userAttrBytes.add(attrBytes);
4430              totalUserAttrBytes += attrBytes.length;
4431            }
4432          }
4433        }
4434        byte[] userAttrCount =
4435             ASN1OctetString.encodeLength(numUserAttributes);
4436        totalBytes += totalUserAttrBytes + userAttrCount.length;
4437    
4438    
4439        // Encode the operational attributes in the appropriate manner.
4440        int numOperationalAttributes = 0;
4441        int totalOperationalAttrBytes = 0;
4442        LinkedList<byte[]> operationalAttrBytes =
4443                                new LinkedList<byte[]>();
4444        if (config.compressAttributeDescriptions())
4445        {
4446          for (List<Attribute> attrList : operationalAttributes.values())
4447          {
4448            for (Attribute a : attrList)
4449            {
4450              if (a.isVirtual() || (! a.hasValue()))
4451              {
4452                continue;
4453              }
4454    
4455              numOperationalAttributes++;
4456    
4457              byte[] attrBytes =
4458                   config.getCompressedSchema().encodeAttribute(a);
4459              byte[] lengthBytes =
4460                   ASN1Element.encodeLength(attrBytes.length);
4461              operationalAttrBytes.add(lengthBytes);
4462              operationalAttrBytes.add(attrBytes);
4463              totalOperationalAttrBytes +=
4464                   lengthBytes.length + attrBytes.length;
4465            }
4466          }
4467        }
4468        else
4469        {
4470          // Encode the operational attributes in the same way as the user
4471          // attributes.
4472          for (List<Attribute> attrList : operationalAttributes.values())
4473          {
4474            for (Attribute a : attrList)
4475            {
4476              if (a.isVirtual() || (! a.hasValue()))
4477              {
4478                continue;
4479              }
4480    
4481              numOperationalAttributes++;
4482    
4483              byte[] nameBytes = getBytes(a.getNameWithOptions());
4484    
4485              int numValues = 0;
4486              int totalValueBytes = 0;
4487              LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
4488              for (AttributeValue v : a.getValues())
4489              {
4490                numValues++;
4491                byte[] vBytes = v.getValueBytes();
4492                byte[] vLength = ASN1Element.encodeLength(vBytes.length);
4493                valueBytes.add(vLength);
4494                valueBytes.add(vBytes);
4495                totalValueBytes += vLength.length + vBytes.length;
4496              }
4497              byte[] numValuesBytes = ASN1Element.encodeLength(numValues);
4498    
4499              byte[] attrBytes = new byte[nameBytes.length +
4500                                          numValuesBytes.length +
4501                                          totalValueBytes + 1];
4502              System.arraycopy(nameBytes, 0, attrBytes, 0,
4503                               nameBytes.length);
4504    
4505              int pos = nameBytes.length+1;
4506              System.arraycopy(numValuesBytes, 0, attrBytes, pos,
4507                               numValuesBytes.length);
4508              pos += numValuesBytes.length;
4509              for (byte[] b : valueBytes)
4510              {
4511                System.arraycopy(b, 0, attrBytes, pos, b.length);
4512                pos += b.length;
4513              }
4514    
4515              operationalAttrBytes.add(attrBytes);
4516              totalOperationalAttrBytes += attrBytes.length;
4517            }
4518          }
4519        }
4520        byte[] operationalAttrCount =
4521             ASN1OctetString.encodeLength(numOperationalAttributes);
4522        totalBytes += totalOperationalAttrBytes +
4523                      operationalAttrCount.length;
4524    
4525    
4526        // Now we've got all the data that we need.  Create a big byte
4527        // array to hold it all and pack it in.
4528        byte[] entryBytes = new byte[totalBytes];
4529    
4530    
4531        // Add the entry version number as the first byte.
4532        entryBytes[0] = 0x02;
4533    
4534    
4535        // Next, add the encoded config.
4536        System.arraycopy(configLength, 0, entryBytes, 1,
4537                         configLength.length);
4538        int pos = 1 + configLength.length;
4539        System.arraycopy(configBytes, 0, entryBytes, pos,
4540                         configBytes.length);
4541        pos += configBytes.length;
4542    
4543    
4544        // Next, add the DN length and value.
4545        if (! config.excludeDN())
4546        {
4547          System.arraycopy(dnLength, 0, entryBytes, pos, dnLength.length);
4548          pos += dnLength.length;
4549          System.arraycopy(dnBytes, 0, entryBytes, pos,  dnBytes.length);
4550          pos += dnBytes.length;
4551        }
4552    
4553    
4554        // Next, add the object classes length and values.
4555        System.arraycopy(ocLength, 0, entryBytes, pos, ocLength.length);
4556        pos += ocLength.length;
4557        if (config.compressObjectClassSets())
4558        {
4559          for (byte[] b : ocBytes)
4560          {
4561            System.arraycopy(b, 0, entryBytes, pos, b.length);
4562            pos += b.length;
4563          }
4564        }
4565        else
4566        {
4567          for (byte[] b : ocBytes)
4568          {
4569            System.arraycopy(b, 0, entryBytes, pos, b.length);
4570            pos += b.length + 1;
4571          }
4572    
4573          // We need to back up one because there's no zero-teriminator
4574          // after the last object class name.
4575          pos--;
4576        }
4577    
4578    
4579        // Next, add the user attribute count and the user attribute
4580        // data.
4581        System.arraycopy(userAttrCount, 0, entryBytes, pos,
4582                         userAttrCount.length);
4583        pos += userAttrCount.length;
4584        for (byte[] b : userAttrBytes)
4585        {
4586          System.arraycopy(b, 0, entryBytes, pos, b.length);
4587          pos += b.length;
4588        }
4589    
4590    
4591        // Finally, add the operational attribute count and the
4592        // operational attribute data.
4593        System.arraycopy(operationalAttrCount, 0, entryBytes, pos,
4594                         operationalAttrCount.length);
4595        pos += operationalAttrCount.length;
4596        for (byte[] b : operationalAttrBytes)
4597        {
4598          System.arraycopy(b, 0, entryBytes, pos, b.length);
4599          pos += b.length;
4600        }
4601    
4602        return entryBytes;
4603      }
4604    
4605    
4606    
4607      /**
4608       * Decodes the provided byte array as an entry.
4609       *
4610       * @param  entryBytes  The byte array containing the data to be
4611       *                     decoded.
4612       *
4613       * @return  The decoded entry.
4614       *
4615       * @throws  DirectoryException  If the provided byte array cannot be
4616       *                              decoded as an entry.
4617       */
4618      public static Entry decode(byte[] entryBytes)
4619             throws DirectoryException
4620      {
4621        return decode(entryBytes,
4622                      DirectoryServer.getDefaultCompressedSchema());
4623      }
4624    
4625    
4626    
4627      /**
4628       * Decodes the provided byte array as an entry.
4629       *
4630       * @param  entryBytes        The byte array containing the data to
4631       *                           be decoded.
4632       * @param  compressedSchema  The compressed schema manager to use
4633       *                           when decoding tokenized schema
4634       *                           elements.
4635       *
4636       * @return  The decoded entry.
4637       *
4638       * @throws  DirectoryException  If the provided byte array cannot be
4639       *                              decoded as an entry.
4640       */
4641      public static Entry decode(byte[] entryBytes,
4642                                 CompressedSchema compressedSchema)
4643             throws DirectoryException
4644      {
4645        switch(entryBytes[0])
4646        {
4647          case 0x01:
4648            return decodeV1(entryBytes);
4649          case 0x02:
4650            return decodeV2(entryBytes, compressedSchema);
4651          default:
4652            Message message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get(
4653                byteToHex(entryBytes[0]));
4654            throw new DirectoryException(
4655                           DirectoryServer.getServerErrorResultCode(),
4656                           message);
4657        }
4658      }
4659    
4660    
4661    
4662      /**
4663       * Decodes the provided byte array as an entry using the V1
4664       * encoding.
4665       *
4666       * @param  entryBytes  The byte array containing the data to be
4667       *                     decoded.
4668       *
4669       * @return  The decoded entry.
4670       *
4671       * @throws  DirectoryException  If the provided byte array cannot be
4672       *                              decoded as an entry.
4673       */
4674      public static Entry decodeV1(byte[] entryBytes)
4675             throws DirectoryException
4676      {
4677        try
4678        {
4679          // The first byte must be the entry version.  If it's not one
4680          // we recognize, then that's an error.
4681          if (entryBytes[0] != 0x01)
4682          {
4683            Message message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get(
4684                byteToHex(entryBytes[0]));
4685            throw new DirectoryException(
4686                           DirectoryServer.getServerErrorResultCode(),
4687                           message);
4688          }
4689    
4690    
4691          // Next is the length of the DN.  It may be a single byte or
4692          // multiple bytes.
4693          int pos = 1;
4694          int dnLength = entryBytes[pos] & 0x7F;
4695          if (entryBytes[pos++] != dnLength)
4696          {
4697            int numLengthBytes = dnLength;
4698            dnLength = 0;
4699            for (int i=0; i < numLengthBytes; i++, pos++)
4700            {
4701              dnLength = (dnLength << 8) | (entryBytes[pos] & 0xFF);
4702            }
4703          }
4704    
4705    
4706          // Next is the DN itself.
4707          byte[] dnBytes = new byte[dnLength];
4708          System.arraycopy(entryBytes, pos, dnBytes, 0, dnLength);
4709          pos += dnLength;
4710          DN dn = DN.decode(new ASN1OctetString(dnBytes));
4711    
4712    
4713          // Next is the length of the object classes.  It may be a single
4714          // byte or multiple bytes.
4715          int ocLength = entryBytes[pos] & 0x7F;
4716          if (entryBytes[pos++] != ocLength)
4717          {
4718            int numLengthBytes = ocLength;
4719            ocLength = 0;
4720            for (int i=0; i < numLengthBytes; i++, pos++)
4721            {
4722              ocLength = (ocLength << 8) | (entryBytes[pos] & 0xFF);
4723            }
4724          }
4725    
4726    
4727          // Next is the encoded set of object classes.  It will be a
4728          // single string with the object class names separated by zeros.
4729          LinkedHashMap<ObjectClass,String> objectClasses =
4730               new LinkedHashMap<ObjectClass,String>();
4731          int startPos = pos;
4732          for (int i=0; i < ocLength; i++,pos++)
4733          {
4734            if (entryBytes[pos] == 0x00)
4735            {
4736              String name = new String(entryBytes, startPos, pos-startPos,
4737                                       "UTF-8");
4738              String lowerName = toLowerCase(name);
4739              ObjectClass oc =
4740                   DirectoryServer.getObjectClass(lowerName, true);
4741              objectClasses.put(oc, name);
4742              startPos = pos+1;
4743            }
4744          }
4745          String name = new String(entryBytes, startPos, pos-startPos,
4746                                   "UTF-8");
4747          String lowerName = toLowerCase(name);
4748          ObjectClass oc =
4749               DirectoryServer.getObjectClass(lowerName, true);
4750          objectClasses.put(oc, name);
4751    
4752    
4753          // Next is the total number of user attributes.  It may be a
4754          // single byte or multiple bytes.
4755          int numUserAttrs = entryBytes[pos] & 0x7F;
4756          if (entryBytes[pos++] != numUserAttrs)
4757          {
4758            int numLengthBytes = numUserAttrs;
4759            numUserAttrs = 0;
4760            for (int i=0; i < numLengthBytes; i++, pos++)
4761            {
4762              numUserAttrs = (numUserAttrs << 8) |
4763                             (entryBytes[pos] & 0xFF);
4764            }
4765          }
4766    
4767    
4768          // Now, we should iterate through the user attributes and decode
4769          // each one.
4770          LinkedHashMap<AttributeType,List<Attribute>> userAttributes =
4771               new LinkedHashMap<AttributeType,List<Attribute>>();
4772          for (int i=0; i < numUserAttrs; i++)
4773          {
4774            // First, we have the zero-terminated attribute name.
4775            startPos = pos;
4776            while (entryBytes[pos] != 0x00)
4777            {
4778              pos++;
4779            }
4780            name = new String(entryBytes, startPos, pos-startPos,
4781                              "UTF-8");
4782            LinkedHashSet<String> options;
4783            int semicolonPos = name.indexOf(';');
4784            if (semicolonPos > 0)
4785            {
4786              String baseName = name.substring(0, semicolonPos);
4787              lowerName = toLowerCase(baseName);
4788              options   = new LinkedHashSet<String>();
4789    
4790              int nextPos = name.indexOf(';', semicolonPos+1);
4791              while (nextPos > 0)
4792              {
4793                String option = name.substring(semicolonPos+1, nextPos);
4794                if (option.length() > 0)
4795                {
4796                  options.add(option);
4797                }
4798    
4799                semicolonPos = nextPos;
4800                nextPos = name.indexOf(';', semicolonPos+1);
4801              }
4802    
4803              String option = name.substring(semicolonPos+1);
4804              if (option.length() > 0)
4805              {
4806                options.add(option);
4807              }
4808    
4809              name = baseName;
4810            }
4811            else
4812            {
4813              lowerName = toLowerCase(name);
4814              options   = new LinkedHashSet<String>(0);
4815            }
4816            AttributeType attributeType =
4817                 DirectoryServer.getAttributeType(lowerName, true);
4818    
4819    
4820    
4821            // Next, we have the number of values.
4822            int numValues = entryBytes[++pos] & 0x7F;
4823            if (entryBytes[pos++] != numValues)
4824            {
4825              int numLengthBytes = numValues;
4826              numValues = 0;
4827              for (int j=0; j < numLengthBytes; j++, pos++)
4828              {
4829                numValues = (numValues << 8) | (entryBytes[pos] & 0xFF);
4830              }
4831            }
4832    
4833            // Next, we have the sequence of length-value pairs.
4834            LinkedHashSet<AttributeValue> values =
4835                 new LinkedHashSet<AttributeValue>(numValues);
4836            for (int j=0; j < numValues; j++)
4837            {
4838              int valueLength = entryBytes[pos] & 0x7F;
4839              if (entryBytes[pos++] != valueLength)
4840              {
4841                int numLengthBytes = valueLength;
4842                valueLength = 0;
4843                for (int k=0; k < numLengthBytes; k++, pos++)
4844                {
4845                  valueLength = (valueLength << 8) |
4846                                (entryBytes[pos] & 0xFF);
4847                }
4848              }
4849    
4850              byte[] valueBytes = new byte[valueLength];
4851              System.arraycopy(entryBytes, pos, valueBytes, 0,
4852                               valueLength);
4853              values.add(new AttributeValue(attributeType,
4854                                  new ASN1OctetString(valueBytes)));
4855              pos += valueLength;
4856            }
4857    
4858    
4859            // Create the attribute and add it to the set of user
4860            // attributes.
4861            Attribute a = new Attribute(attributeType, name, options,
4862                                        values);
4863            List<Attribute> attrList = userAttributes.get(attributeType);
4864            if (attrList == null)
4865            {
4866              attrList = new ArrayList<Attribute>(1);
4867              attrList.add(a);
4868              userAttributes.put(attributeType, attrList);
4869            }
4870            else
4871            {
4872              attrList.add(a);
4873            }
4874          }
4875    
4876    
4877          // Next is the total number of operational attributes.  It may
4878          // be a single byte or multiple bytes.
4879          int numOperationalAttrs = entryBytes[pos] & 0x7F;
4880          if (entryBytes[pos++] != numOperationalAttrs)
4881          {
4882            int numLengthBytes = numOperationalAttrs;
4883            numOperationalAttrs = 0;
4884            for (int i=0; i < numLengthBytes; i++, pos++)
4885            {
4886              numOperationalAttrs =
4887                   (numOperationalAttrs << 8) | (entryBytes[pos] & 0xFF);
4888            }
4889          }
4890    
4891    
4892          // Now, we should iterate through the operational attributes and
4893          // decode each one.
4894          LinkedHashMap<AttributeType,List<Attribute>>
4895               operationalAttributes =
4896                  new LinkedHashMap<AttributeType,List<Attribute>>();
4897          for (int i=0; i < numOperationalAttrs; i++)
4898          {
4899            // First, we have the zero-terminated attribute name.
4900            startPos = pos;
4901            while (entryBytes[pos] != 0x00)
4902            {
4903              pos++;
4904            }
4905            name = new String(entryBytes, startPos, pos-startPos,
4906                              "UTF-8");
4907            LinkedHashSet<String> options;
4908            int semicolonPos = name.indexOf(';');
4909            if (semicolonPos > 0)
4910            {
4911              String baseName = name.substring(0, semicolonPos);
4912              lowerName = toLowerCase(baseName);
4913              options   = new LinkedHashSet<String>();
4914    
4915              int nextPos = name.indexOf(';', semicolonPos+1);
4916              while (nextPos > 0)
4917              {
4918                String option = name.substring(semicolonPos+1, nextPos);
4919                if (option.length() > 0)
4920                {
4921                  options.add(option);
4922                }
4923    
4924                semicolonPos = nextPos;
4925                nextPos = name.indexOf(';', semicolonPos+1);
4926              }
4927    
4928              String option = name.substring(semicolonPos+1);
4929              if (option.length() > 0)
4930              {
4931                options.add(option);
4932              }
4933    
4934              name = baseName;
4935            }
4936            else
4937            {
4938              lowerName = toLowerCase(name);
4939              options   = new LinkedHashSet<String>(0);
4940            }
4941            AttributeType attributeType =
4942                 DirectoryServer.getAttributeType(lowerName, true);
4943    
4944    
4945            // Next, we have the number of values.
4946            int numValues = entryBytes[++pos] & 0x7F;
4947            if (entryBytes[pos++] != numValues)
4948            {
4949              int numLengthBytes = numValues;
4950              numValues = 0;
4951              for (int j=0; j < numLengthBytes; j++, pos++)
4952              {
4953                numValues = (numValues << 8) | (entryBytes[pos] & 0xFF);
4954              }
4955            }
4956    
4957            // Next, we have the sequence of length-value pairs.
4958            LinkedHashSet<AttributeValue> values =
4959                 new LinkedHashSet<AttributeValue>(numValues);
4960            for (int j=0; j < numValues; j++)
4961            {
4962              int valueLength = entryBytes[pos] & 0x7F;
4963              if (entryBytes[pos++] != valueLength)
4964              {
4965                int numLengthBytes = valueLength;
4966                valueLength = 0;
4967                for (int k=0; k < numLengthBytes; k++, pos++)
4968                {
4969                  valueLength = (valueLength << 8) |
4970                                (entryBytes[pos] & 0xFF);
4971                }
4972              }
4973    
4974              byte[] valueBytes = new byte[valueLength];
4975              System.arraycopy(entryBytes, pos, valueBytes, 0,
4976                               valueLength);
4977              values.add(new AttributeValue(attributeType,
4978                                  new ASN1OctetString(valueBytes)));
4979              pos += valueLength;
4980            }
4981    
4982    
4983            // Create the attribute and add it to the set of operational
4984            // attributes.
4985            Attribute a = new Attribute(attributeType, name, options,
4986                                        values);
4987            List<Attribute> attrList =
4988                 operationalAttributes.get(attributeType);
4989            if (attrList == null)
4990            {
4991              attrList = new ArrayList<Attribute>(1);
4992              attrList.add(a);
4993              operationalAttributes.put(attributeType, attrList);
4994            }
4995            else
4996            {
4997              attrList.add(a);
4998            }
4999          }
5000    
5001    
5002          // We've got everything that we need, so create and return the
5003          // entry.
5004          return new Entry(dn, objectClasses, userAttributes,
5005                           operationalAttributes);
5006        }
5007        catch (DirectoryException de)
5008        {
5009          throw de;
5010        }
5011        catch (Exception e)
5012        {
5013          if (debugEnabled())
5014          {
5015            TRACER.debugCaught(DebugLogLevel.ERROR, e);
5016          }
5017    
5018          Message message =
5019              ERR_ENTRY_DECODE_EXCEPTION.get(getExceptionMessage(e));
5020          throw new DirectoryException(
5021                         DirectoryServer.getServerErrorResultCode(),
5022                         message, e);
5023        }
5024      }
5025    
5026    
5027    
5028      /**
5029       * Decodes the provided byte array as an entry using the V2
5030       * encoding.
5031       *
5032       * @param  entryBytes        The byte array containing the data to
5033       *                           be decoded.
5034       * @param  compressedSchema  The compressed schema manager to use
5035       *                           when decoding tokenized schema
5036       *                           elements.
5037       *
5038       * @return  The decoded entry.
5039       *
5040       * @throws  DirectoryException  If the provided byte array cannot be
5041       *                              decoded as an entry.
5042       */
5043      public static Entry decodeV2(byte[] entryBytes,
5044                                   CompressedSchema compressedSchema)
5045             throws DirectoryException
5046      {
5047        try
5048        {
5049          // The first byte must be the entry version.  If it's not one
5050          // we recognize, then that's an error.
5051          if (entryBytes[0] != 0x02)
5052          {
5053            Message message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get(
5054                byteToHex(entryBytes[0]));
5055            throw new DirectoryException(
5056                           DirectoryServer.getServerErrorResultCode(),
5057                           message);
5058          }
5059    
5060    
5061          // Next is the length of the encoded configuration.  It may be a
5062          // single byte or multiple bytes.
5063          int pos = 1;
5064          int configLength = entryBytes[pos] & 0x7F;
5065          if (entryBytes[pos++] != configLength)
5066          {
5067            int numLengthBytes = configLength;
5068            configLength = 0;
5069            for (int i=0; i < numLengthBytes; i++, pos++)
5070            {
5071              configLength =
5072                   (configLength << 8) | (entryBytes[pos] & 0xFF);
5073            }
5074          }
5075    
5076    
5077          // Next is the encoded configuration itself.
5078          EntryEncodeConfig config =
5079               EntryEncodeConfig.decode(entryBytes, pos, configLength,
5080                                        compressedSchema);
5081          pos += configLength;
5082    
5083    
5084          // If we should have included the DN in the entry, then it's
5085          // next.
5086          DN dn;
5087          if (config.excludeDN())
5088          {
5089            dn = DN.NULL_DN;
5090          }
5091          else
5092          {
5093            // Next is the length of the DN.  It may be a single byte or
5094            // multiple bytes.
5095            int dnLength = entryBytes[pos] & 0x7F;
5096            if (entryBytes[pos++] != dnLength)
5097            {
5098              int numLengthBytes = dnLength;
5099              dnLength = 0;
5100              for (int i=0; i < numLengthBytes; i++, pos++)
5101              {
5102                dnLength = (dnLength << 8) | (entryBytes[pos] & 0xFF);
5103              }
5104            }
5105    
5106    
5107            // Next is the DN itself.
5108            byte[] dnBytes = new byte[dnLength];
5109            System.arraycopy(entryBytes, pos, dnBytes, 0, dnLength);
5110            pos += dnLength;
5111            dn = DN.decode(new ASN1OctetString(dnBytes));
5112          }
5113    
5114    
5115          // Next is the length of the object classes.  It may be a single
5116          // byte or multiple bytes.
5117          int ocLength = entryBytes[pos] & 0x7F;
5118          if (entryBytes[pos++] != ocLength)
5119          {
5120            int numLengthBytes = ocLength;
5121            ocLength = 0;
5122            for (int i=0; i < numLengthBytes; i++, pos++)
5123            {
5124              ocLength = (ocLength << 8) | (entryBytes[pos] & 0xFF);
5125            }
5126          }
5127    
5128    
5129          // Next is the set of encoded object classes.  The encoding will
5130          // depend on the configuration.
5131          Map<ObjectClass,String> objectClasses;
5132          if (config.compressObjectClassSets())
5133          {
5134            byte[] ocBytes = new byte[ocLength];
5135            System.arraycopy(entryBytes, pos, ocBytes, 0, ocLength);
5136            objectClasses = config.getCompressedSchema().
5137                                 decodeObjectClasses(ocBytes);
5138            pos += ocLength;
5139          }
5140          else
5141          {
5142            // The set of object classes will be encoded as a single
5143            // string with the oibject class names separated by zeros.
5144            objectClasses = new LinkedHashMap<ObjectClass,String>();
5145            int startPos = pos;
5146            for (int i=0; i < ocLength; i++,pos++)
5147            {
5148              if (entryBytes[pos] == 0x00)
5149              {
5150                String name = new String(entryBytes, startPos,
5151                                         pos-startPos, "UTF-8");
5152                String lowerName = toLowerCase(name);
5153                ObjectClass oc =
5154                     DirectoryServer.getObjectClass(lowerName, true);
5155                objectClasses.put(oc, name);
5156                startPos = pos+1;
5157              }
5158            }
5159            String name = new String(entryBytes, startPos, pos-startPos,
5160                                     "UTF-8");
5161            String lowerName = toLowerCase(name);
5162            ObjectClass oc =
5163                 DirectoryServer.getObjectClass(lowerName, true);
5164            objectClasses.put(oc, name);
5165          }
5166    
5167    
5168          // Next is the total number of user attributes.  It may be a
5169          // single byte or multiple bytes.
5170          int numUserAttrs = entryBytes[pos] & 0x7F;
5171          if (entryBytes[pos++] != numUserAttrs)
5172          {
5173            int numLengthBytes = numUserAttrs;
5174            numUserAttrs = 0;
5175            for (int i=0; i < numLengthBytes; i++, pos++)
5176            {
5177              numUserAttrs = (numUserAttrs << 8) |
5178                             (entryBytes[pos] & 0xFF);
5179            }
5180          }
5181    
5182    
5183          // Now, we should iterate through the user attributes and decode
5184          // each one.
5185          LinkedHashMap<AttributeType,List<Attribute>> userAttributes =
5186               new LinkedHashMap<AttributeType,List<Attribute>>();
5187          if (config.compressAttributeDescriptions())
5188          {
5189            for (int i=0; i < numUserAttrs; i++)
5190            {
5191              // Get the length of the attribute.
5192              int attrLength = entryBytes[pos] & 0x7F;
5193              if (entryBytes[pos++] != attrLength)
5194              {
5195                int attrLengthBytes = attrLength;
5196                attrLength = 0;
5197                for (int j=0; j < attrLengthBytes; j++, pos++)
5198                {
5199                  attrLength = (attrLength << 8) |
5200                               (entryBytes[pos] & 0xFF);
5201                }
5202              }
5203    
5204              // Decode the attribute.
5205              Attribute a = config.getCompressedSchema().decodeAttribute(
5206                                 entryBytes, pos, attrLength);
5207              List<Attribute> attrList =
5208                   userAttributes.get(a.getAttributeType());
5209              if (attrList == null)
5210              {
5211                attrList = new ArrayList<Attribute>(1);
5212                userAttributes.put(a.getAttributeType(), attrList);
5213              }
5214    
5215              attrList.add(a);
5216              pos += attrLength;
5217            }
5218          }
5219          else
5220          {
5221            for (int i=0; i < numUserAttrs; i++)
5222            {
5223              // First, we have the zero-terminated attribute name.
5224              int startPos = pos;
5225              while (entryBytes[pos] != 0x00)
5226              {
5227                pos++;
5228              }
5229    
5230              String lowerName;
5231              String name = new String(entryBytes, startPos, pos-startPos,
5232                                       "UTF-8");
5233              LinkedHashSet<String> options;
5234              int semicolonPos = name.indexOf(';');
5235              if (semicolonPos > 0)
5236              {
5237                String baseName = name.substring(0, semicolonPos);
5238                lowerName = toLowerCase(baseName);
5239                options   = new LinkedHashSet<String>();
5240    
5241                int nextPos = name.indexOf(';', semicolonPos+1);
5242                while (nextPos > 0)
5243                {
5244                  String option = name.substring(semicolonPos+1, nextPos);
5245                  if (option.length() > 0)
5246                  {
5247                    options.add(option);
5248                  }
5249    
5250                  semicolonPos = nextPos;
5251                  nextPos = name.indexOf(';', semicolonPos+1);
5252                }
5253    
5254                String option = name.substring(semicolonPos+1);
5255                if (option.length() > 0)
5256                {
5257                  options.add(option);
5258                }
5259    
5260                name = baseName;
5261              }
5262              else
5263              {
5264                lowerName = toLowerCase(name);
5265                options   = new LinkedHashSet<String>(0);
5266              }
5267              AttributeType attributeType =
5268                   DirectoryServer.getAttributeType(lowerName, true);
5269    
5270    
5271    
5272              // Next, we have the number of values.
5273              int numValues = entryBytes[++pos] & 0x7F;
5274              if (entryBytes[pos++] != numValues)
5275              {
5276                int numLengthBytes = numValues;
5277                numValues = 0;
5278                for (int j=0; j < numLengthBytes; j++, pos++)
5279                {
5280                  numValues = (numValues << 8) | (entryBytes[pos] & 0xFF);
5281                }
5282              }
5283    
5284              // Next, we have the sequence of length-value pairs.
5285              LinkedHashSet<AttributeValue> values =
5286                   new LinkedHashSet<AttributeValue>(numValues);
5287              for (int j=0; j < numValues; j++)
5288              {
5289                int valueLength = entryBytes[pos] & 0x7F;
5290                if (entryBytes[pos++] != valueLength)
5291                {
5292                  int numLengthBytes = valueLength;
5293                  valueLength = 0;
5294                  for (int k=0; k < numLengthBytes; k++, pos++)
5295                  {
5296                    valueLength = (valueLength << 8) |
5297                                  (entryBytes[pos] & 0xFF);
5298                  }
5299                }
5300    
5301                byte[] valueBytes = new byte[valueLength];
5302                System.arraycopy(entryBytes, pos, valueBytes, 0,
5303                                 valueLength);
5304                values.add(new AttributeValue(attributeType,
5305                                    new ASN1OctetString(valueBytes)));
5306                pos += valueLength;
5307              }
5308    
5309    
5310              // Create the attribute and add it to the set of user
5311              // attributes.
5312              Attribute a = new Attribute(attributeType, name, options,
5313                                          values);
5314              List<Attribute> attrList =
5315                   userAttributes.get(attributeType);
5316              if (attrList == null)
5317              {
5318                attrList = new ArrayList<Attribute>(1);
5319                attrList.add(a);
5320                userAttributes.put(attributeType, attrList);
5321              }
5322              else
5323              {
5324                attrList.add(a);
5325              }
5326            }
5327          }
5328    
5329    
5330          // Next is the total number of operational attributes.  It may
5331          // be a single byte or multiple bytes.
5332          int numOperationalAttrs = entryBytes[pos] & 0x7F;
5333          if (entryBytes[pos++] != numOperationalAttrs)
5334          {
5335            int numLengthBytes = numOperationalAttrs;
5336            numOperationalAttrs = 0;
5337            for (int i=0; i < numLengthBytes; i++, pos++)
5338            {
5339              numOperationalAttrs =
5340                   (numOperationalAttrs << 8) | (entryBytes[pos] & 0xFF);
5341            }
5342          }
5343    
5344    
5345          // Now, we should iterate through the operational attributes and
5346          // decode each one.
5347          LinkedHashMap<AttributeType,List<Attribute>>
5348               operationalAttributes =
5349                  new LinkedHashMap<AttributeType,List<Attribute>>();
5350          if (config.compressAttributeDescriptions())
5351          {
5352            for (int i=0; i < numOperationalAttrs; i++)
5353            {
5354              // Get the length of the attribute.
5355              int attrLength = entryBytes[pos] & 0x7F;
5356              if (entryBytes[pos++] != attrLength)
5357              {
5358                int attrLengthBytes = attrLength;
5359                attrLength = 0;
5360                for (int j=0; j < attrLengthBytes; j++, pos++)
5361                {
5362                  attrLength = (attrLength << 8) |
5363                               (entryBytes[pos] & 0xFF);
5364                }
5365              }
5366    
5367              // Decode the attribute.
5368              Attribute a = config.getCompressedSchema().decodeAttribute(
5369                                 entryBytes, pos, attrLength);
5370              List<Attribute> attrList =
5371                   operationalAttributes.get(a.getAttributeType());
5372              if (attrList == null)
5373              {
5374                attrList = new ArrayList<Attribute>(1);
5375                operationalAttributes.put(a.getAttributeType(), attrList);
5376              }
5377    
5378              attrList.add(a);
5379              pos += attrLength;
5380            }
5381          }
5382          else
5383          {
5384            for (int i=0; i < numOperationalAttrs; i++)
5385            {
5386              // First, we have the zero-terminated attribute name.
5387              int startPos = pos;
5388              while (entryBytes[pos] != 0x00)
5389              {
5390                pos++;
5391              }
5392              String lowerName;
5393              String name = new String(entryBytes, startPos, pos-startPos,
5394                                       "UTF-8");
5395              LinkedHashSet<String> options;
5396              int semicolonPos = name.indexOf(';');
5397              if (semicolonPos > 0)
5398              {
5399                String baseName = name.substring(0, semicolonPos);
5400                lowerName = toLowerCase(baseName);
5401                options   = new LinkedHashSet<String>();
5402    
5403                int nextPos = name.indexOf(';', semicolonPos+1);
5404                while (nextPos > 0)
5405                {
5406                  String option = name.substring(semicolonPos+1, nextPos);
5407                  if (option.length() > 0)
5408                  {
5409                    options.add(option);
5410                  }
5411    
5412                  semicolonPos = nextPos;
5413                  nextPos = name.indexOf(';', semicolonPos+1);
5414                }
5415    
5416                String option = name.substring(semicolonPos+1);
5417                if (option.length() > 0)
5418                {
5419                  options.add(option);
5420                }
5421    
5422                name = baseName;
5423              }
5424              else
5425              {
5426                lowerName = toLowerCase(name);
5427                options   = new LinkedHashSet<String>(0);
5428              }
5429              AttributeType attributeType =
5430                   DirectoryServer.getAttributeType(lowerName, true);
5431    
5432    
5433              // Next, we have the number of values.
5434              int numValues = entryBytes[++pos] & 0x7F;
5435              if (entryBytes[pos++] != numValues)
5436              {
5437                int numLengthBytes = numValues;
5438                numValues = 0;
5439                for (int j=0; j < numLengthBytes; j++, pos++)
5440                {
5441                  numValues = (numValues << 8) | (entryBytes[pos] & 0xFF);
5442                }
5443              }
5444    
5445              // Next, we have the sequence of length-value pairs.
5446              LinkedHashSet<AttributeValue> values =
5447                   new LinkedHashSet<AttributeValue>(numValues);
5448              for (int j=0; j < numValues; j++)
5449              {
5450                int valueLength = entryBytes[pos] & 0x7F;
5451                if (entryBytes[pos++] != valueLength)
5452                {
5453                  int numLengthBytes = valueLength;
5454                  valueLength = 0;
5455                  for (int k=0; k < numLengthBytes; k++, pos++)
5456                  {
5457                    valueLength = (valueLength << 8) |
5458                                  (entryBytes[pos] & 0xFF);
5459                  }
5460                }
5461    
5462                byte[] valueBytes = new byte[valueLength];
5463                System.arraycopy(entryBytes, pos, valueBytes, 0,
5464                                 valueLength);
5465                values.add(new AttributeValue(attributeType,
5466                                    new ASN1OctetString(valueBytes)));
5467                pos += valueLength;
5468              }
5469    
5470    
5471              // Create the attribute and add it to the set of operational
5472              // attributes.
5473              Attribute a = new Attribute(attributeType, name, options,
5474                                          values);
5475              List<Attribute> attrList =
5476                   operationalAttributes.get(attributeType);
5477              if (attrList == null)
5478              {
5479                attrList = new ArrayList<Attribute>(1);
5480                attrList.add(a);
5481                operationalAttributes.put(attributeType, attrList);
5482              }
5483              else
5484              {
5485                attrList.add(a);
5486              }
5487            }
5488          }
5489    
5490    
5491          // We've got everything that we need, so create and return the
5492          // entry.
5493          return new Entry(dn, objectClasses, userAttributes,
5494                           operationalAttributes);
5495        }
5496        catch (DirectoryException de)
5497        {
5498          throw de;
5499        }
5500        catch (Exception e)
5501        {
5502          if (debugEnabled())
5503          {
5504            TRACER.debugCaught(DebugLogLevel.ERROR, e);
5505          }
5506    
5507          Message message =
5508              ERR_ENTRY_DECODE_EXCEPTION.get(getExceptionMessage(e));
5509          throw new DirectoryException(
5510                         DirectoryServer.getServerErrorResultCode(),
5511                         message, e);
5512        }
5513      }
5514    
5515    
5516    
5517      /**
5518       * Retrieves a list of the lines for this entry in LDIF form.  Long
5519       * lines will not be wrapped automatically.
5520       *
5521       * @return  A list of the lines for this entry in LDIF form.
5522       */
5523      public List<StringBuilder> toLDIF()
5524      {
5525        LinkedList<StringBuilder> ldifLines =
5526             new LinkedList<StringBuilder>();
5527    
5528    
5529        // First, append the DN.
5530        StringBuilder dnLine = new StringBuilder();
5531        dnLine.append("dn");
5532        appendLDIFSeparatorAndValue(dnLine, getBytes(dn.toString()));
5533        ldifLines.add(dnLine);
5534    
5535    
5536        // Next, add the set of objectclasses.
5537        for (String s : objectClasses.values())
5538        {
5539          StringBuilder ocLine = new StringBuilder();
5540          ocLine.append("objectClass: ");
5541          ocLine.append(s);
5542          ldifLines.add(ocLine);
5543        }
5544    
5545    
5546        // Next, add the set of user attributes.
5547        for (List<Attribute> attrList : userAttributes.values())
5548        {
5549          for (Attribute a : attrList)
5550          {
5551            StringBuilder attrName = new StringBuilder(a.getName());
5552            for (String o : a.getOptions())
5553            {
5554              attrName.append(";");
5555              attrName.append(o);
5556            }
5557    
5558            for (AttributeValue v : a.getValues())
5559            {
5560              StringBuilder attrLine = new StringBuilder();
5561              attrLine.append(attrName);
5562              appendLDIFSeparatorAndValue(attrLine, v.getValueBytes());
5563              ldifLines.add(attrLine);
5564            }
5565          }
5566        }
5567    
5568    
5569        // Finally, add the set of operational attributes.
5570        for (List<Attribute> attrList : operationalAttributes.values())
5571        {
5572          for (Attribute a : attrList)
5573          {
5574            StringBuilder attrName = new StringBuilder(a.getName());
5575            for (String o : a.getOptions())
5576            {
5577              attrName.append(";");
5578              attrName.append(o);
5579            }
5580    
5581            for (AttributeValue v : a.getValues())
5582            {
5583              StringBuilder attrLine = new StringBuilder();
5584              attrLine.append(attrName);
5585              appendLDIFSeparatorAndValue(attrLine, v.getValueBytes());
5586              ldifLines.add(attrLine);
5587            }
5588          }
5589        }
5590    
5591    
5592        return ldifLines;
5593      }
5594    
5595    
5596    
5597      /**
5598       * Writes this entry in LDIF form according to the provided
5599       * configuration.
5600       *
5601       * @param  exportConfig  The configuration that specifies how the
5602       *                       entry should be written.
5603       *
5604       * @return  <CODE>true</CODE> if the entry is actually written, or
5605       *          <CODE>false</CODE> if it is not for some reason.
5606       *
5607       * @throws  IOException  If a problem occurs while writing the
5608       *                       information.
5609       *
5610       * @throws  LDIFException  If a problem occurs while trying to
5611       *                         determine whether to write the entry.
5612       */
5613      public boolean toLDIF(LDIFExportConfig exportConfig)
5614             throws IOException, LDIFException
5615      {
5616        // See if this entry should be included in the export at all.
5617        try
5618        {
5619          if (! exportConfig.includeEntry(this))
5620          {
5621            if (debugEnabled())
5622            {
5623              TRACER.debugInfo(
5624                  "Skipping entry %s because of the export " +
5625                      "configuration.", String.valueOf(dn));
5626            }
5627            return false;
5628          }
5629        }
5630        catch (Exception e)
5631        {
5632          if (debugEnabled())
5633          {
5634            TRACER.debugCaught(DebugLogLevel.ERROR, e);
5635          }
5636    
5637          Message message =
5638              ERR_LDIF_COULD_NOT_EVALUATE_FILTERS_FOR_EXPORT.
5639                get(String.valueOf(dn), String.valueOf(e));
5640          throw new LDIFException(message, e);
5641        }
5642    
5643    
5644        // Invoke LDIF export plugins on the entry if appropriate.
5645        if (exportConfig.invokeExportPlugins())
5646        {
5647          PluginConfigManager pluginConfigManager =
5648               DirectoryServer.getPluginConfigManager();
5649          PluginResult.ImportLDIF pluginResult =
5650               pluginConfigManager.invokeLDIFExportPlugins(exportConfig,
5651                                                        this);
5652          if (! pluginResult.continueProcessing())
5653          {
5654            return false;
5655          }
5656        }
5657    
5658    
5659        // Get the information necessary to write the LDIF.
5660        BufferedWriter writer     = exportConfig.getWriter();
5661        int            wrapColumn = exportConfig.getWrapColumn();
5662        boolean        wrapLines  = (wrapColumn > 1);
5663    
5664    
5665        // First, write the DN.  It will always be included.
5666        StringBuilder dnLine = new StringBuilder();
5667        dnLine.append("dn");
5668        appendLDIFSeparatorAndValue(dnLine, getBytes(dn.toString()));
5669        writeLDIFLine(dnLine, writer, wrapLines, wrapColumn);
5670    
5671    
5672        // Next, the set of objectclasses.
5673        final boolean typesOnly = exportConfig.typesOnly();
5674        if (exportConfig.includeObjectClasses())
5675        {
5676          if (typesOnly)
5677          {
5678            StringBuilder ocLine = new StringBuilder("objectClass:");
5679            writeLDIFLine(ocLine, writer, wrapLines, wrapColumn);
5680          }
5681          else
5682          {
5683            for (String s : objectClasses.values())
5684            {
5685              StringBuilder ocLine = new StringBuilder();
5686              ocLine.append("objectClass: ");
5687              ocLine.append(s);
5688              writeLDIFLine(ocLine, writer, wrapLines, wrapColumn);
5689            }
5690          }
5691        }
5692        else
5693        {
5694          if (debugEnabled())
5695          {
5696            TRACER.debugVerbose(
5697                "Skipping objectclasses for entry %s because of " +
5698                "the export configuration.", String.valueOf(dn));
5699          }
5700        }
5701    
5702    
5703        // Now the set of user attributes.
5704        for (AttributeType attrType : userAttributes.keySet())
5705        {
5706          if (exportConfig.includeAttribute(attrType))
5707          {
5708            List<Attribute> attrList = userAttributes.get(attrType);
5709            for (Attribute a : attrList)
5710            {
5711              if (a.isVirtual() &&
5712                  (! exportConfig.includeVirtualAttributes()))
5713              {
5714                continue;
5715              }
5716    
5717              if (typesOnly)
5718              {
5719                StringBuilder attrName = new StringBuilder(a.getName());
5720                for (String o : a.getOptions())
5721                {
5722                  attrName.append(";");
5723                  attrName.append(o);
5724                }
5725                attrName.append(":");
5726    
5727                writeLDIFLine(attrName, writer, wrapLines, wrapColumn);
5728              }
5729              else
5730              {
5731                StringBuilder attrName = new StringBuilder(a.getName());
5732                for (String o : a.getOptions())
5733                {
5734                  attrName.append(";");
5735                  attrName.append(o);
5736                }
5737    
5738                for (AttributeValue v : a.getValues())
5739                {
5740                  StringBuilder attrLine = new StringBuilder();
5741                  attrLine.append(attrName);
5742                  appendLDIFSeparatorAndValue(attrLine,
5743                                              v.getValueBytes());
5744                  writeLDIFLine(attrLine, writer, wrapLines, wrapColumn);
5745                }
5746              }
5747            }
5748          }
5749          else
5750          {
5751            if (debugEnabled())
5752            {
5753              TRACER.debugVerbose(
5754                  "Skipping user attribute %s for entry %s because of " +
5755                           "the export configuration.",
5756                  attrType.getNameOrOID(), String.valueOf(dn));
5757            }
5758          }
5759        }
5760    
5761    
5762        // Next, the set of operational attributes.
5763        if (exportConfig.includeOperationalAttributes())
5764        {
5765          for (AttributeType attrType : operationalAttributes.keySet())
5766          {
5767            if (exportConfig.includeAttribute(attrType))
5768            {
5769              List<Attribute> attrList =
5770                   operationalAttributes.get(attrType);
5771              for (Attribute a : attrList)
5772              {
5773                if (a.isVirtual() &&
5774                    (! exportConfig.includeVirtualAttributes()))
5775                {
5776                  continue;
5777                }
5778    
5779                if (typesOnly)
5780                {
5781                  StringBuilder attrName = new StringBuilder(a.getName());
5782                  for (String o : a.getOptions())
5783                  {
5784                    attrName.append(";");
5785                    attrName.append(o);
5786                  }
5787                  attrName.append(":");
5788    
5789                  writeLDIFLine(attrName, writer, wrapLines, wrapColumn);
5790                }
5791                else
5792                {
5793                  StringBuilder attrName = new StringBuilder(a.getName());
5794                  for (String o : a.getOptions())
5795                  {
5796                    attrName.append(";");
5797                    attrName.append(o);
5798                  }
5799    
5800                  for (AttributeValue v : a.getValues())
5801                  {
5802                    StringBuilder attrLine = new StringBuilder();
5803                    attrLine.append(attrName);
5804                    appendLDIFSeparatorAndValue(attrLine,
5805                                                v.getValueBytes());
5806                    writeLDIFLine(attrLine, writer, wrapLines,
5807                                  wrapColumn);
5808                  }
5809                }
5810              }
5811            }
5812            else
5813            {
5814              if (debugEnabled())
5815              {
5816                TRACER.debugVerbose(
5817                    "Skipping operational attribute %s for entry %s " +
5818                             "because of the export configuration.",
5819                             attrType.getNameOrOID(), String.valueOf(dn));
5820              }
5821            }
5822          }
5823        }
5824        else
5825        {
5826          if (debugEnabled())
5827          {
5828            TRACER.debugVerbose(
5829                "Skipping all operational attributes for entry %s " +
5830                "because of the export configuration.",
5831                String.valueOf(dn));
5832          }
5833        }
5834    
5835    
5836        // If we are not supposed to include virtual attributes, then
5837        // write any attributes that may normally be suppressed by a
5838        // virtual attribute.
5839        if (! exportConfig.includeVirtualAttributes())
5840        {
5841          for (AttributeType t : suppressedAttributes.keySet())
5842          {
5843            if (exportConfig.includeAttribute(t))
5844            {
5845              for (Attribute a : suppressedAttributes.get(t))
5846              {
5847                if (typesOnly)
5848                {
5849                  StringBuilder attrName = new StringBuilder(a.getName());
5850                  for (String o : a.getOptions())
5851                  {
5852                    attrName.append(";");
5853                    attrName.append(o);
5854                  }
5855                  attrName.append(":");
5856    
5857                  writeLDIFLine(attrName, writer, wrapLines, wrapColumn);
5858                }
5859                else
5860                {
5861                  StringBuilder attrName = new StringBuilder(a.getName());
5862                  for (String o : a.getOptions())
5863                  {
5864                    attrName.append(";");
5865                    attrName.append(o);
5866                  }
5867    
5868                  for (AttributeValue v : a.getValues())
5869                  {
5870                    StringBuilder attrLine = new StringBuilder();
5871                    attrLine.append(attrName);
5872                    appendLDIFSeparatorAndValue(attrLine,
5873                                                v.getValueBytes());
5874                    writeLDIFLine(attrLine, writer, wrapLines,
5875                                  wrapColumn);
5876                  }
5877                }
5878              }
5879            }
5880          }
5881        }
5882    
5883    
5884        // Make sure there is a blank line after the entry.
5885        writer.newLine();
5886    
5887    
5888        return true;
5889      }
5890    
5891    
5892    
5893      /**
5894       * Retrieves the name of the protocol associated with this protocol
5895       * element.
5896       *
5897       * @return  The name of the protocol associated with this protocol
5898       *          element.
5899       */
5900      public String getProtocolElementName()
5901      {
5902        return "Entry";
5903      }
5904    
5905    
5906    
5907      /**
5908       * Retrieves a hash code for this entry.
5909       *
5910       * @return  The hash code for this entry.
5911       */
5912      @Override()
5913      public int hashCode()
5914      {
5915        int hashCode = dn.hashCode();
5916    
5917        for (ObjectClass oc : objectClasses.keySet())
5918        {
5919          hashCode += oc.hashCode();
5920        }
5921    
5922        for (List<Attribute> attrList : userAttributes.values())
5923        {
5924          for (Attribute a : attrList)
5925          {
5926            hashCode += a.hashCode();
5927          }
5928        }
5929    
5930        for (List<Attribute> attrList : operationalAttributes.values())
5931        {
5932          for (Attribute a : attrList)
5933          {
5934            hashCode += a.hashCode();
5935          }
5936        }
5937    
5938        return hashCode;
5939      }
5940    
5941    
5942    
5943      /**
5944       * Indicates whether the provided object is equal to this entry.  In
5945       * order for the object to be considered equal, it must be an entry
5946       * with the same DN, set of object classes, and set of user and
5947       * operational attributes.
5948       *
5949       * @param  o  The object for which to make the determination.
5950       *
5951       * @return  {@code true} if the provided object may be considered
5952       *          equal to this entry, or {@code false} if not.
5953       */
5954      @Override()
5955      public boolean equals(Object o)
5956      {
5957        if (this == o)
5958        {
5959          return true;
5960        }
5961    
5962        if (o == null)
5963        {
5964          return false;
5965        }
5966    
5967        if (! (o instanceof Entry))
5968        {
5969          return false;
5970        }
5971    
5972        Entry e = (Entry) o;
5973        if (! dn.equals(e.dn))
5974        {
5975          return false;
5976        }
5977    
5978        if (! objectClasses.keySet().equals(e.objectClasses.keySet()))
5979        {
5980          return false;
5981        }
5982    
5983        for (AttributeType at : userAttributes.keySet())
5984        {
5985          List<Attribute> list1 = userAttributes.get(at);
5986          List<Attribute> list2 = e.userAttributes.get(at);
5987          if ((list2 == null) || (list1.size() != list2.size()))
5988          {
5989            return false;
5990          }
5991          for (Attribute a : list1)
5992          {
5993            if (! list2.contains(a))
5994            {
5995              return false;
5996            }
5997          }
5998        }
5999    
6000        for (AttributeType at : operationalAttributes.keySet())
6001        {
6002          List<Attribute> list1 = operationalAttributes.get(at);
6003          List<Attribute> list2 = e.operationalAttributes.get(at);
6004          if ((list2 == null) || (list1.size() != list2.size()))
6005          {
6006            return false;
6007          }
6008          for (Attribute a : list1)
6009          {
6010            if (! list2.contains(a))
6011            {
6012              return false;
6013            }
6014          }
6015        }
6016    
6017        return true;
6018      }
6019    
6020    
6021    
6022      /**
6023       * Retrieves a string representation of this protocol element.
6024       *
6025       * @return  A string representation of this protocol element.
6026       */
6027      public String toString()
6028      {
6029        StringBuilder buffer = new StringBuilder();
6030        toString(buffer);
6031        return buffer.toString();
6032      }
6033    
6034    
6035    
6036      /**
6037       * Appends a string representation of this protocol element to the
6038       * provided buffer.
6039       *
6040       * @param  buffer  The buffer into which the string representation
6041       *                 should be written.
6042       */
6043      public void toString(StringBuilder buffer)
6044      {
6045        buffer.append("Entry(dn=\"");
6046        dn.toString(buffer);
6047    
6048        buffer.append("\", objectClasses={");
6049        if (! objectClasses.isEmpty())
6050        {
6051          Iterator<String> ocNames = objectClasses.values().iterator();
6052          buffer.append(ocNames.next());
6053    
6054          while (ocNames.hasNext())
6055          {
6056            buffer.append(",");
6057            buffer.append(ocNames.next());
6058          }
6059        }
6060    
6061        buffer.append("}, userAttrs={");
6062        if (! userAttributes.isEmpty())
6063        {
6064          Iterator<AttributeType> attrs =
6065               userAttributes.keySet().iterator();
6066          buffer.append(attrs.next().getNameOrOID());
6067    
6068          while (attrs.hasNext())
6069          {
6070            buffer.append(",");
6071            buffer.append(attrs.next().getNameOrOID());
6072          }
6073        }
6074    
6075        buffer.append("}, operationalAttrs={");
6076        if (! operationalAttributes.isEmpty())
6077        {
6078          Iterator<AttributeType> attrs =
6079               operationalAttributes.keySet().iterator();
6080          buffer.append(attrs.next().getNameOrOID());
6081    
6082          while (attrs.hasNext())
6083          {
6084            buffer.append(",");
6085            buffer.append(attrs.next().getNameOrOID());
6086          }
6087        }
6088    
6089        buffer.append("})");
6090      }
6091    
6092    
6093    
6094      /**
6095       * Appends a string representation of this protocol element to the
6096       * provided buffer.
6097       *
6098       * @param  buffer  The buffer into which the string representation
6099       *                 should be written.
6100       * @param  indent  The number of spaces that should be used to
6101       *                 indent the resulting string representation.
6102       */
6103      public void toString(StringBuilder buffer, int indent)
6104      {
6105        StringBuilder indentBuf = new StringBuilder(indent);
6106        for (int i=0 ; i < indent; i++)
6107        {
6108          indentBuf.append(' ');
6109        }
6110    
6111        for (StringBuilder b : toLDIF())
6112        {
6113          buffer.append(indentBuf);
6114          buffer.append(b);
6115          buffer.append(EOL);
6116        }
6117      }
6118    
6119    
6120    
6121      /**
6122       * Retrieves a string representation of this entry in LDIF form.
6123       *
6124       * @return  A string representation of this entry in LDIF form.
6125       */
6126      public String toLDIFString()
6127      {
6128        StringBuilder buffer = new StringBuilder();
6129    
6130        for (StringBuilder ldifLine : toLDIF())
6131        {
6132          buffer.append(ldifLine);
6133          buffer.append(EOL);
6134        }
6135    
6136        return buffer.toString();
6137      }
6138    
6139    
6140    
6141      /**
6142       * Retrieves a one-line representation of this entry.
6143       *
6144       * @return  A one-line representation of this entry.
6145       */
6146      public String toSingleLineString()
6147      {
6148        StringBuilder buffer = new StringBuilder();
6149        toSingleLineString(buffer);
6150        return buffer.toString();
6151      }
6152    
6153    
6154    
6155      /**
6156       * Appends a single-line representation of this entry to the
6157       * provided buffer.
6158       *
6159       * @param  buffer  The buffer to which the information should be
6160       *                 written.
6161       */
6162      public void toSingleLineString(StringBuilder buffer)
6163      {
6164        buffer.append("Entry(dn=\"");
6165        dn.toString(buffer);
6166        buffer.append("\",objectClasses={");
6167    
6168        Iterator<String> iterator = objectClasses.values().iterator();
6169        if (iterator.hasNext())
6170        {
6171          buffer.append(iterator.next());
6172    
6173          while (iterator.hasNext())
6174          {
6175            buffer.append(",");
6176            buffer.append(iterator.next());
6177          }
6178        }
6179    
6180        buffer.append("},userAttrs={");
6181    
6182        boolean firstAttr = true;
6183        for (List<Attribute> attrList : userAttributes.values())
6184        {
6185          for (Attribute a : attrList)
6186          {
6187            if (firstAttr)
6188            {
6189              firstAttr = false;
6190            }
6191            else
6192            {
6193              buffer.append(",");
6194            }
6195    
6196            buffer.append(a.getName());
6197    
6198            if (a.hasOptions())
6199            {
6200              for (String optionString : a.getOptions())
6201              {
6202                buffer.append(";");
6203                buffer.append(optionString);
6204              }
6205            }
6206    
6207            buffer.append("={");
6208            Iterator<AttributeValue> valueIterator =
6209                 a.getValues().iterator();
6210            if (valueIterator.hasNext())
6211            {
6212              buffer.append(valueIterator.next().getStringValue());
6213    
6214              while (valueIterator.hasNext())
6215              {
6216                buffer.append(",");
6217                buffer.append(valueIterator.next().getStringValue());
6218              }
6219            }
6220    
6221            buffer.append("}");
6222          }
6223        }
6224    
6225        buffer.append("},operationalAttrs={");
6226        for (List<Attribute> attrList : operationalAttributes.values())
6227        {
6228          for (Attribute a : attrList)
6229          {
6230            if (firstAttr)
6231            {
6232              firstAttr = false;
6233            }
6234            else
6235            {
6236              buffer.append(",");
6237            }
6238    
6239            buffer.append(a.getName());
6240    
6241            if (a.hasOptions())
6242            {
6243              for (String optionString : a.getOptions())
6244              {
6245                buffer.append(";");
6246                buffer.append(optionString);
6247              }
6248            }
6249    
6250            buffer.append("={");
6251            Iterator<AttributeValue> valueIterator =
6252                 a.getValues().iterator();
6253            if (valueIterator.hasNext())
6254            {
6255              buffer.append(valueIterator.next().getStringValue());
6256    
6257              while (valueIterator.hasNext())
6258              {
6259                buffer.append(",");
6260                buffer.append(valueIterator.next().getStringValue());
6261              }
6262            }
6263    
6264            buffer.append("}");
6265          }
6266        }
6267    
6268        buffer.append("})");
6269      }
6270    }
6271