mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

neil_a_wilson
07.07.2007 8395e95048f9670f0889eb1b0518d1595f01dd07
Provide a new mechanism for encoding entries.  This method adds an extra
element that includes flags that indicate how the entry was encoded. The
flags currently defined include:

- Whether to exclude the DN from the encoded entry. This may be useful for the
filesystem entry cache, since it will already have a reference to the DN.

- Whether to compress the set of object classes contained in the entry with a
binary token.

- Whether to compress the attribute descriptions in the entry with binary
tokens.

These changes can help improve the encode/decode performance, and can reduce
the entry footprint (by about 30% for entries based on the example.template).
None of these options are enabled at the present time, but components which
call the Entry.encode() method will be able to indicate which of them should be
used for that entry.

OpenDS Issue Numbers: 660, 1675, 1770
3 files added
7 files modified
2366 ■■■■■ changed files
opends/src/server/org/opends/server/backends/jeb/ID2Entry.java 13 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ImportThread.java 8 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/JebFormat.java 20 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/config/ConfigConstants.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 57 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/ByteArray.java 127 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/CompressedSchema.java 758 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Entry.java 972 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/EntryEncodeConfig.java 291 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestJebFormat.java 111 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
@@ -41,6 +41,7 @@
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
/**
@@ -140,8 +141,12 @@
   *
   * @param entry The LDAP entry to be converted.
   * @return The database entry.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to encode
   *                              the entry.
   */
  private DatabaseEntry entryData(Entry entry)
          throws DirectoryException
  {
    byte[] entryBytes;
    entryBytes = JebFormat.entryToDatabase(entry, dataConfig);
@@ -157,9 +162,11 @@
   * @return true if the entry was inserted, false if a record with that
   *         ID already existed.
   * @throws DatabaseException If an error occurs in the JE database.
   * @throws  DirectoryException  If a problem occurs while attempting to encode
   *                              the entry.
   */
  public boolean insert(Transaction txn, EntryID id, Entry entry)
       throws DatabaseException
       throws DatabaseException, DirectoryException
  {
    DatabaseEntry key = id.getDatabaseEntry();
    DatabaseEntry data = entryData(entry);
@@ -181,9 +188,11 @@
   * @param entry The LDAP entry.
   * @return true if the entry was written, false if it was not.
   * @throws DatabaseException If an error occurs in the JE database.
   * @throws  DirectoryException  If a problem occurs while attempting to encode
   *                              the entry.
   */
  public boolean put(Transaction txn, EntryID id, Entry entry)
       throws DatabaseException
       throws DatabaseException, DirectoryException
  {
    DatabaseEntry key = id.getDatabaseEntry();
    DatabaseEntry data = entryData(entry);
opends/src/server/org/opends/server/backends/jeb/ImportThread.java
@@ -31,6 +31,7 @@
import org.opends.server.types.DebugLogLevel;
import org.opends.server.api.DirectoryThread;
import org.opends.server.types.AttributeType;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import com.sleepycat.je.Transaction;
@@ -323,6 +324,13 @@
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    catch (DirectoryException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    catch (IOException e)
    {
      if (debugEnabled())
opends/src/server/org/opends/server/backends/jeb/JebFormat.java
@@ -36,6 +36,7 @@
import org.opends.server.types.CryptoManager;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.EntryEncodeConfig;
import org.opends.server.types.LDAPException;
import static org.opends.server.loggers.debug.DebugLogger.*;
@@ -72,6 +73,11 @@
  public static final byte TAG_DIRECTORY_SERVER_ENTRY = 0x61;
  /**
   * The configuration to use when encoding entries in the database.
   */
  private static EntryEncodeConfig encodeConfig = new EntryEncodeConfig();
  /**
   * Decode a DatabaseEntry.  The encoded bytes may be compressed and/or
   * encrypted.
   *
@@ -236,8 +242,12 @@
   * @param entry The entry to encode.
   * @param dataConfig Compression and cryptographic options.
   * @return A byte array containing the encoded database value.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to encode
   *                              the entry.
   */
  static public byte[] entryToDatabase(Entry entry, DataConfig dataConfig)
         throws DirectoryException
  {
    byte[] uncompressedBytes = encodeDirectoryServerEntry(entry);
    return encodeDatabaseEntry(uncompressedBytes, dataConfig);
@@ -248,8 +258,12 @@
   *
   * @param entry The entry to encode.
   * @return A byte array containing the encoded database value.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to encode
   *                              the entry.
   */
  static public byte[] entryToDatabase(Entry entry)
         throws DirectoryException
  {
    return entryToDatabase(entry, new DataConfig());
  }
@@ -259,10 +273,14 @@
   *
   * @param entry The entry to encode.
   * @return A byte array containing the encoded DirectoryServerEntry.
   *
   * @throws  DirectoryException  If a problem occurs while attempting to encode
   *                              the entry.
   */
  static private byte[] encodeDirectoryServerEntry(Entry entry)
         throws DirectoryException
  {
    return entry.encode();
    return entry.encode(encodeConfig);
  }
  /**
opends/src/server/org/opends/server/config/ConfigConstants.java
@@ -2674,6 +2674,15 @@
  /**
   * The base name (with no path information) of the file that will be used to
   * hold schema tokens used for compressed schema elements.
   */
  public static final String COMPRESSED_SCHEMA_FILE_NAME =
       "schematokens.dat";
  /**
   * The base name (with no path information) of the directory that will hold
   * the archived versions of previous configurations.
   */
opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6171,6 +6171,47 @@
  /**
   * The message ID for the message that will be used if an attribute used an
   * undefined token.  This takes a single argument, which is the hex
   * representation of the token bytes.
   */
  public static final int MSGID_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 620;
  /**
   * The message ID for the message that will be used if an object class set
   * used an undefined token.  This takes a single argument, which is the hex
   * representation of the token bytes.
   */
  public static final int MSGID_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 621;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to write the updated compressed schema definitions.  This takes a
   * single argument, which is a string representation of the exception that was
   * caught.
   */
  public static final int MSGID_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 622;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to decode an entry encode config object because it had an invalid
   * length.  This does not take any arguments.
   */
  public static final int MSGID_ENTRYENCODECFG_INVALID_LENGTH =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 623;
  /**
   * Associates a set of generic messages with the message IDs defined
   * in this class.
   */
@@ -8401,6 +8442,22 @@
    registerMessage(MSGID_DIRECTORY_SERVER_LEAVING_LOCKDOWN_MODE,
                    "The Directory Server is leaving lockdown mode and will " +
                    "resume normal operation");
    registerMessage(MSGID_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN,
                    "Unable to decode the provided attribute because it " +
                    "used an undefined attribute description token %s");
    registerMessage(MSGID_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN,
                    "Unable to decode the provided object class set because " +
                    "it used an undefined token %s");
    registerMessage(MSGID_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA,
                    "Unable to write the updated compressed schema token " +
                    "data:  %s");
    registerMessage(MSGID_ENTRYENCODECFG_INVALID_LENGTH,
                    "Unable to decode the provided entry encode " +
                    "configuration element because it has an invalid length");
  }
}
opends/src/server/org/opends/server/types/ByteArray.java
New file
@@ -0,0 +1,127 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import java.util.Arrays;
/**
 * This class provides a data structure that holds a byte array but
 * also includes the necessary {@code equals} and {@code hashCode}
 * methods to make it suitable for use in maps.
 */
public class ByteArray
{
  // The array that will be wrapped by this object.
  private final byte[] array;
  /**
   * Creates a new {@code ByteArray} object that wraps the provided
   * array.
   *
   * @param  array  The array to be wrapped with this
   *                {@code ByteArray}.
   */
  public ByteArray(byte[] array)
  {
    this.array = array;
  }
  /**
   * Retrieves the array wrapped by this {@code ByteArray} object.
   *
   * @return  The array wrapped by this {@code ByteArray} object.
   */
  public byte[] array()
  {
    return array;
  }
  /**
   * Retrieves a hash code for this {@code ByteArray}.  It will be the
   * sum of all of the bytes contained in the wrapped array.
   *
   * @return  A hash code for this {@code ByteArray}.
   */
  public int hashCode()
  {
    int hashCode = 0;
    for (int i=0; i < array.length; i++)
    {
      hashCode += array[i];
    }
    return hashCode;
  }
  /**
   * Indicates whether the provided object is equal to this
   * {@code ByteArray}.  In order for it to be considered equal, the
   * provided object must be a non-null {@code ByteArray} object with
   * a wrapped array containing the same bytes in the same order.
   *
   * @param  o  The object for which to make the determination.
   *
   * @return  {@code true} if the provided object is a
   *          {@code ByteArray} whose content is equal to that of this
   *          {@code ByteArray}, or {@code false} if not.
   */
  public boolean equals(Object o)
  {
    if (o == this)
    {
      return true;
    }
    if (o == null)
    {
      return false;
    }
    if (o instanceof ByteArray)
    {
      ByteArray ba = (ByteArray) o;
      return Arrays.equals(array, ba.array);
    }
    else
    {
      return false;
    }
  }
}
opends/src/server/org/opends/server/types/CompressedSchema.java
New file
@@ -0,0 +1,758 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1Integer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Reader;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.asn1.ASN1Writer;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class provides a utility for interacting with compressed
 * representations of schema elements.
 */
public class CompressedSchema
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The singleton instance that will be used to perform the mapping.
   */
  private static CompressedSchema instance = new CompressedSchema();
  // The counter used for attribute descriptions.
  private AtomicInteger adCounter;
  // The counter used for object class sets.
  private AtomicInteger ocCounter;
  // The map between encoded representations and attribute types.
  private ConcurrentHashMap<ByteArray,AttributeType> atDecodeMap;
  // The map between encoded representations and attribute options.
  private ConcurrentHashMap<ByteArray,
               LinkedHashSet<String>> aoDecodeMap;
  // The map between encoded representations and object class sets.
  private ConcurrentHashMap<ByteArray,
               Map<ObjectClass,String>> ocDecodeMap;
  // The map between attribute descriptions and their encoded
  // representations.
  private ConcurrentHashMap<AttributeType,
               ConcurrentHashMap<LinkedHashSet<String>,ByteArray>>
                    adEncodeMap;
  // The map between object class sets and encoded representations.
  private ConcurrentHashMap<Map<ObjectClass,String>,
               ByteArray> ocEncodeMap;
  /**
   * Creates a new instance of this compressed schema manager.
   */
  private CompressedSchema()
  {
    atDecodeMap = new ConcurrentHashMap<ByteArray,AttributeType>();
    aoDecodeMap = new ConcurrentHashMap<ByteArray,
                           LinkedHashSet<String>>();
    ocDecodeMap = new ConcurrentHashMap<ByteArray,
                           Map<ObjectClass,String>>();
    adEncodeMap = new ConcurrentHashMap<AttributeType,
                           ConcurrentHashMap<LinkedHashSet<String>,
                                ByteArray>>();
    ocEncodeMap = new ConcurrentHashMap<Map<ObjectClass,String>,
                           ByteArray>();
    adCounter = new AtomicInteger(1);
    ocCounter = new AtomicInteger(1);
    load();
  }
  /**
   * Loads the compressed schema information from disk.
   */
  private void load()
  {
    ASN1Reader reader = null;
    try
    {
      // Determine the location of the compressed schema data file
      // It should be in the config directory with a name of
      // "schematokens.dat".  If that file doesn't exist, then don't
      // do anything.
      String path = DirectoryServer.getServerRoot() + File.separator +
                    CONFIG_DIR_NAME + File.separator +
                    COMPRESSED_SCHEMA_FILE_NAME;
      if (! new File(path).exists())
      {
        return;
      }
      FileInputStream inputStream = new FileInputStream(path);
      reader = new ASN1Reader(inputStream);
      // The first element in the file should be a sequence of object
      // class sets.  Each object class set will itself be a sequence
      // of octet strings, where the first one is the token and the
      // remaining elements are the names of the associated object
      // classes.
      ASN1Sequence ocSequence =
           reader.readElement().decodeAsSequence();
      for (ASN1Element element : ocSequence.elements())
      {
        ArrayList<ASN1Element> elements =
             element.decodeAsSequence().elements();
        ASN1OctetString os = elements.get(0).decodeAsOctetString();
        ByteArray token = new ByteArray(os.value());
        LinkedHashMap<ObjectClass,String> ocMap =
             new LinkedHashMap<ObjectClass,String>(elements.size()-1);
        for (int i=1; i < elements.size(); i++)
        {
          os = elements.get(i).decodeAsOctetString();
          String ocName = os.stringValue();
          String lowerName = toLowerCase(ocName);
          ObjectClass oc =
               DirectoryServer.getObjectClass(lowerName, true);
          ocMap.put(oc, ocName);
        }
        ocEncodeMap.put(ocMap, token);
        ocDecodeMap.put(token, ocMap);
      }
      // The second element in the file should be an integer element
      // that holds the value to use to initialize the object class
      // counter.
      ASN1Element counterElement = reader.readElement();
      ocCounter.set(counterElement.decodeAsInteger().intValue());
      // The third element in the file should be a sequence of
      // attribute description components.  Each attribute description
      // component will itself be a sequence of octet strings, where
      // the first one is the token, the second is the attribute name,
      // and all remaining elements are the attribute options.
      ASN1Sequence adSequence =
           reader.readElement().decodeAsSequence();
      for (ASN1Element element : adSequence.elements())
      {
        ArrayList<ASN1Element> elements =
             element.decodeAsSequence().elements();
        ASN1OctetString os = elements. get(0).decodeAsOctetString();
        ByteArray token = new ByteArray(os.value());
        os = elements.get(1).decodeAsOctetString();
        String attrName = os.stringValue();
        String lowerName = toLowerCase(attrName);
        AttributeType attrType =
             DirectoryServer.getAttributeType(lowerName, true);
        LinkedHashSet<String> options =
             new LinkedHashSet<String>(elements.size()-2);
        for (int i=2; i < elements.size(); i++)
        {
          os = elements.get(i).decodeAsOctetString();
          options.add(os.stringValue());
        }
        atDecodeMap.put(token, attrType);
        aoDecodeMap.put(token, options);
        ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map =
             adEncodeMap.get(attrType);
        if (map == null)
        {
          map = new ConcurrentHashMap<LinkedHashSet<String>,
                         ByteArray>(1);
          map.put(options, token);
          adEncodeMap.put(attrType, map);
        }
        else
        {
          map.put(options, token);
        }
      }
      // The fourth element in the file should be an integer element
      // that holds the value to use to initialize the attribute
      // description counter.
      counterElement = reader.readElement();
      adCounter.set(counterElement.decodeAsInteger().intValue());
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      // FIXME -- Should we do something else here?
      throw new RuntimeException(e);
    }
    finally
    {
      try
      {
        if (reader != null)
        {
          reader.close();
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
  }
  /**
   * Writes the compressed schema information to disk.
   *
   * @throws  DirectoryException  If a problem occurs while writing
   *                              the updated information.
   */
  private void save()
          throws DirectoryException
  {
    ASN1Writer writer = null;
    try
    {
      // Determine the location of the "live" compressed schema data
      // file, and then append ".tmp" to get the name of the temporary
      // file that we will use.
      String path = DirectoryServer.getServerRoot() + File.separator +
                    CONFIG_DIR_NAME + File.separator +
                    COMPRESSED_SCHEMA_FILE_NAME;
      String tempPath = path + ".tmp";
      FileOutputStream outputStream = new FileOutputStream(tempPath);
      writer = new ASN1Writer(outputStream);
      // The first element in the file should be a sequence of object
      // class sets.  Each object class set will itself be a sequence
      // of octet strings, where the first one is the token and the
      // remaining elements are the names of the associated object
      // classes.
      ArrayList<ASN1Element> ocElements =
           new ArrayList<ASN1Element>(ocDecodeMap.size());
      for (Map.Entry<ByteArray,Map<ObjectClass,String>> mapEntry :
           ocDecodeMap.entrySet())
      {
        ByteArray token = mapEntry.getKey();
        Map<ObjectClass,String> ocMap = mapEntry.getValue();
        ArrayList<ASN1Element> elements =
             new ArrayList<ASN1Element>(ocMap.size()+1);
        elements.add(new ASN1OctetString(token.array()));
        for (String ocName : ocMap.values())
        {
          elements.add(new ASN1OctetString(ocName));
        }
        ocElements.add(new ASN1Sequence(elements));
      }
      writer.writeElement(new ASN1Sequence(ocElements));
      // The second element in the file should be an integer element
      // that holds the value to use to initialize the object class
      // counter.
      writer.writeElement(new ASN1Integer(ocCounter.get()));
      // The third element in the file should be a sequence of
      // attribute description components.  Each attribute description
      // component will itself be a sequence of octet strings, where
      // the first one is the token, the second is the attribute name,
      // and all remaining elements are the attribute options.
      ArrayList<ASN1Element> adElements =
           new ArrayList<ASN1Element>(atDecodeMap.size());
      for (ByteArray token : atDecodeMap.keySet())
      {
        AttributeType attrType = atDecodeMap.get(token);
        LinkedHashSet<String> options = aoDecodeMap.get(token);
        ArrayList<ASN1Element> elements =
             new ArrayList<ASN1Element>(options.size()+2);
        elements.add(new ASN1OctetString(token.array()));
        elements.add(new ASN1OctetString(attrType.getNameOrOID()));
        for (String option : options)
        {
          elements.add(new ASN1OctetString(option));
        }
        adElements.add(new ASN1Sequence(elements));
      }
      writer.writeElement(new ASN1Sequence(adElements));
      // The fourth element in the file should be an integer element
      // that holds the value to use to initialize the attribute
      // description counter.
      writer.writeElement(new ASN1Integer(adCounter.get()));
      // Close the writer and swing the temp file into place.
      writer.close();
      File liveFile = new File(path);
      File tempFile = new File(tempPath);
      if (liveFile.exists())
      {
        File saveFile =
                  new File(liveFile.getAbsolutePath() + ".save");
        if (saveFile.exists())
        {
          saveFile.delete();
        }
        liveFile.renameTo(saveFile);
      }
      tempFile.renameTo(liveFile);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA;
      String message = getMessage(msgID,
                                  stackTraceToSingleLineString(e));
      throw new DirectoryException(
                     DirectoryServer.getServerErrorResultCode(),
                     message, msgID, e);
    }
    finally
    {
      try
      {
        if (writer != null)
        {
          writer.close();
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
  }
  /**
   * Encodes the provided set of object classes to a byte array.  If
   * the same set had been previously encoded, then the cached value
   * will be used.  Otherwise, a new value will be created.
   *
   * @param  objectClasses  The set of object classes for which to
   *                        retrieve the corresponding byte array
   *                        token.
   *
   * @return  A byte array containing the identifier assigned to the
   *          object class set.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to determine the appropriate
   *                              identifier.
   */
  public static byte[]
         encodeObjectClasses(Map<ObjectClass,String> objectClasses)
         throws DirectoryException
  {
    ByteArray encodedClasses =
                    instance.ocEncodeMap.get(objectClasses);
    if (encodedClasses == null)
    {
      synchronized (instance.ocEncodeMap)
      {
        int setValue = instance.ocCounter.getAndIncrement();
        byte[] array = encodeInt(setValue);
        encodedClasses = new ByteArray(array);
        instance.ocEncodeMap.put(objectClasses, encodedClasses);
        instance.ocDecodeMap.put(encodedClasses, objectClasses);
        instance.save();
        return array;
      }
    }
    else
    {
      return encodedClasses.array();
    }
  }
  /**
   * Decodes an object class set from the provided byte array.
   *
   * @param  encodedObjectClasses  The byte array containing the
   *                               object class set identifier.
   *
   * @return  The decoded object class set.
   *
   * @throws  DirectoryException  If the provided byte array cannot be
   *                              decoded as an object class set.
   */
  public static Map<ObjectClass,String>
         decodeObjectClasses(byte[] encodedObjectClasses)
         throws DirectoryException
  {
    ByteArray byteArray = new ByteArray(encodedObjectClasses);
    Map<ObjectClass,String> ocMap =
         instance.ocDecodeMap.get(byteArray);
    if (ocMap == null)
    {
      int    msgID   = MSGID_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN;
      String message = getMessage(msgID,
                                  bytesToHex(encodedObjectClasses));
      throw new DirectoryException(
                     DirectoryServer.getServerErrorResultCode(),
                     message, msgID);
    }
    else
    {
      return ocMap;
    }
  }
  /**
   * Encodes the information in the provided attribute to a byte
   * array.
   *
   * @param  attribute  The attribute to be encoded.
   *
   * @return  An encoded representation of the provided attribute.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to determine the appropriate
   *                              identifier.
   */
  public static byte[] encodeAttribute(Attribute attribute)
         throws DirectoryException
  {
    AttributeType type = attribute.getAttributeType();
    LinkedHashSet<String> options = attribute.getOptions();
    ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map =
         instance.adEncodeMap.get(type);
    if (map == null)
    {
      byte[] array;
      synchronized (instance.adEncodeMap)
      {
        map = new ConcurrentHashMap<LinkedHashSet<String>,
                       ByteArray>(1);
        int intValue = instance.adCounter.getAndIncrement();
        array = encodeInt(intValue);
        ByteArray byteArray = new ByteArray(array);
        map.put(options,byteArray);
        instance.adEncodeMap.put(type, map);
        instance.atDecodeMap.put(byteArray, type);
        instance.aoDecodeMap.put(byteArray, options);
        instance.save();
      }
      return encodeAttribute(array, attribute);
    }
    else
    {
      ByteArray byteArray = map.get(options);
      if (byteArray == null)
      {
        byte[] array;
        synchronized (map)
        {
          int intValue = instance.adCounter.getAndIncrement();
          array = encodeInt(intValue);
          byteArray = new ByteArray(array);
          map.put(options,byteArray);
          instance.atDecodeMap.put(byteArray, type);
          instance.aoDecodeMap.put(byteArray, options);
          instance.save();
        }
        return encodeAttribute(array, attribute);
      }
      else
      {
        return encodeAttribute(byteArray.array(), attribute);
      }
    }
  }
  /**
   * Encodes the information in the provided attribute to a byte
   * array.
   *
   * @param  adArray    The byte array that is a placeholder for the
   *                    attribute type and set of options.
   * @param  attribute  The attribute to be encoded.
   *
   * @return  An encoded representation of the provided attribute.
   */
  private static byte[] encodeAttribute(byte[] adArray,
                                        Attribute attribute)
  {
    LinkedHashSet<AttributeValue> values = attribute.getValues();
    int totalValuesLength = 0;
    byte[][] subArrays = new  byte[values.size()*2][];
    int pos = 0;
    for (AttributeValue v : values)
    {
      byte[] vBytes = v.getValueBytes();
      byte[] lBytes = ASN1Element.encodeLength(vBytes.length);
      subArrays[pos++] = lBytes;
      subArrays[pos++] = vBytes;
      totalValuesLength += lBytes.length + vBytes.length;
    }
    byte[] adArrayLength = ASN1Element.encodeLength(adArray.length);
    byte[] countBytes = ASN1Element.encodeLength(values.size());
    int totalLength = adArrayLength.length + adArray.length +
                      countBytes.length + totalValuesLength;
    byte[] array = new byte[totalLength];
    System.arraycopy(adArrayLength, 0, array, 0,
                     adArrayLength.length);
    pos = adArrayLength.length;
    System.arraycopy(adArray, 0, array, pos, adArray.length);
    pos += adArray.length;
    System.arraycopy(countBytes, 0, array, pos, countBytes.length);
    pos += countBytes.length;
    for (int i=0; i < subArrays.length; i++)
    {
      System.arraycopy(subArrays[i], 0, array, pos,
                       subArrays[i].length);
      pos += subArrays[i].length;
    }
    return array;
  }
  /**
   * Decodes the contents of the provided array as an attribute.
   *
   * @param  encodedEntry  The byte array containing the encoded
   *                       entry.
   * @param  startPos      The position within the array of the first
   *                       byte for the attribute to decode.
   * @param  length        The number of bytes contained in the
   *                       encoded attribute.
   *
   * @return  The decoded attribute.
   *
   * @throws  DirectoryException  If the attribute could not be
   *                              decoded properly for some reason.
   */
  public static Attribute decodeAttribute(byte[] encodedEntry,
                                          int startPos, int length)
         throws DirectoryException
  {
    // Figure out how many bytes are in the token that is the
    // placeholder for the attribute description.
    int pos = startPos;
    int adArrayLength = encodedEntry[pos] & 0x7F;
    if (adArrayLength != encodedEntry[pos++])
    {
      int numLengthBytes = adArrayLength;
      adArrayLength = 0;
      for (int i=0; i < numLengthBytes; i++, pos++)
      {
        adArrayLength =
             (adArrayLength << 8) | (encodedEntry[pos] & 0xFF);
      }
    }
    // Get the attribute description token and make sure it resolves
    // to an attribute type and option set.
    ByteArray adArray = new ByteArray(new byte[adArrayLength]);
    System.arraycopy(encodedEntry, pos, adArray.array(), 0,
                     adArrayLength);
    pos += adArrayLength;
    AttributeType attrType = instance.atDecodeMap.get(adArray);
    LinkedHashSet<String> options = instance.aoDecodeMap.get(adArray);
    if ((attrType == null) || (options == null))
    {
      int msgID = MSGID_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN;
      String message = getMessage(msgID, bytesToHex(adArray.array()));
      throw new DirectoryException(
                     DirectoryServer.getServerErrorResultCode(),
                     message, msgID);
    }
    // Determine the number of values for the attribute.
    int numValues = encodedEntry[pos] & 0x7F;
    if (numValues != encodedEntry[pos++])
    {
      int numValuesBytes = numValues;
      numValues = 0;
      for (int i=0; i < numValuesBytes; i++, pos++)
      {
        numValues = (numValues << 8) | (encodedEntry[pos] & 0xFF);
      }
    }
    // Read the appropriate number of values.
    LinkedHashSet<AttributeValue> values =
         new LinkedHashSet<AttributeValue>(numValues);
    for (int i=0; i < numValues; i++)
    {
      int valueLength = encodedEntry[pos] & 0x7F;
      if (valueLength != encodedEntry[pos++])
      {
        int valueLengthBytes = valueLength;
        valueLength = 0;
        for (int j=0; j < valueLengthBytes; j++, pos++)
        {
          valueLength =
               (valueLength << 8) | (encodedEntry[pos] & 0xFF);
        }
      }
      byte[] valueBytes = new byte[valueLength];
      System.arraycopy(encodedEntry, pos, valueBytes, 0, valueLength);
      pos += valueLength;
      values.add(new AttributeValue(attrType,
                                    new ASN1OctetString(valueBytes)));
    }
    return new Attribute(attrType, attrType.getPrimaryName(), options,
                         values);
  }
  /**
   * Encodes the provided int value to a byte array.
   *
   * @param  intValue  The int value to be encoded.
   *
   * @return  The byte array containing the encoded int value.
   */
  private static byte[] encodeInt(int intValue)
  {
    byte[] array;
    if (intValue <= 0xFF)
    {
      array = new byte[1];
      array[0] = (byte) (intValue & 0xFF);
    }
    else if (intValue <= 0xFFFF)
    {
      array = new byte[2];
      array[0] = (byte) ((intValue >> 8)  & 0xFF);
      array[1] = (byte) (intValue & 0xFF);
    }
    else if (intValue <= 0xFFFFFF)
    {
      array = new byte[3];
      array[0] = (byte) ((intValue >> 16) & 0xFF);
      array[1] = (byte) ((intValue >> 8)  & 0xFF);
      array[2] = (byte) (intValue & 0xFF);
    }
    else
    {
      array = new byte[4];
      array[0] = (byte) ((intValue >> 24) & 0xFF);
      array[1] = (byte) ((intValue >> 16) & 0xFF);
      array[2] = (byte) ((intValue >> 8)  & 0xFF);
      array[3] = (byte) (intValue & 0xFF);
    }
    return array;
  }
}
opends/src/server/org/opends/server/types/Entry.java
@@ -3855,10 +3855,29 @@
   * that if the way we store entries changes in the future we will
   * still be able to read entries encoded in an older format.
   *
   * @param  config  The configuration that may be used to control how
   *                 the entry is encoded.
   *
   * @return  The entry encoded in a form that is suitable for
   *          long-term persistent storage.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to encode the entry.
   */
  public byte[] encode()
  public byte[] encode(EntryEncodeConfig config)
         throws DirectoryException
  {
    return encodeV2(config);
  }
  /**
   * Encodes this entry using the V1 encoding.
   *
   * @return  The entry encoded in the V1 encoding.
   */
  public byte[] encodeV1()
  {
    // The version number will be one byte.  We'll add that later.
@@ -4068,6 +4087,323 @@
  /**
   * Encodes this entry using the V2 encoding.
   *
   * @param  config  The configuration that should be used to encode
   *                 the entry.
   *
   * @return  The entry encoded in the V2 encoding.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to encode the entry.
   */
  public byte[] encodeV2(EntryEncodeConfig config)
         throws DirectoryException
  {
    // The version number will be one byte.  We'll add that later.
    // Get the encoded respresentation of the config.
    byte[] configBytes = config.encode();
    byte[] configLength =
                ASN1Element.encodeLength(configBytes.length);
    int totalBytes = 1 + configBytes.length + configLength.length;
    // If we should include the DN, then it will be encoded as a
    // one-to-five byte length followed by the UTF-8 byte
    // representation.
    byte[] dnBytes  = null;
    byte[] dnLength = null;
    if (! config.excludeDN())
    {
      dnBytes  = getBytes(dn.toString());
      dnLength = ASN1Element.encodeLength(dnBytes.length);
      totalBytes += dnBytes.length + dnLength.length;
    }
    // Encode the object classes in the appropriate manner.
    byte[] ocLength;
    LinkedList<byte[]> ocBytes = new LinkedList<byte[]>();
    if (config.compressObjectClassSets())
    {
      byte[] b = CompressedSchema.encodeObjectClasses(objectClasses);
      ocBytes.add(b);
      ocLength = ASN1Element.encodeLength(b.length);
      totalBytes += ocLength.length + b.length;
    }
    else
    {
      int totalOCBytes = objectClasses.size() - 1;
      for (String ocName : objectClasses.values())
      {
        byte[] b = getBytes(ocName);
        ocBytes.add(b);
        totalOCBytes += b.length;
      }
      ocLength = ASN1Element.encodeLength(totalOCBytes);
      totalBytes += totalOCBytes + ocLength.length;
    }
    // Encode the user attributes in the appropriate manner.
    int numUserAttributes = 0;
    int totalUserAttrBytes = 0;
    LinkedList<byte[]> userAttrBytes = new LinkedList<byte[]>();
    if (config.compressAttributeDescriptions())
    {
      for (List<Attribute> attrList : userAttributes.values())
      {
        for (Attribute a : attrList)
        {
          if (a.isVirtual() || (! a.hasValue()))
          {
            continue;
          }
          numUserAttributes++;
          byte[] attrBytes = CompressedSchema.encodeAttribute(a);
          byte[] lengthBytes =
               ASN1Element.encodeLength(attrBytes.length);
          userAttrBytes.add(lengthBytes);
          userAttrBytes.add(attrBytes);
          totalUserAttrBytes += lengthBytes.length + attrBytes.length;
        }
      }
    }
    else
    {
      // The user attributes will be encoded as a one-to-five byte
      // number of attributes followed by a sequence of:
      // - A UTF-8 byte representation of the attribute name.
      // - A zero delimiter
      // - A one-to-five byte number of values for the attribute
      // - A sequence of:
      //   - A one-to-five byte length for the value
      //   - A UTF-8 byte representation for the value
      for (List<Attribute> attrList : userAttributes.values())
      {
        for (Attribute a : attrList)
        {
          if (a.isVirtual() || (! a.hasValue()))
          {
            continue;
          }
          numUserAttributes++;
          byte[] nameBytes = getBytes(a.getNameWithOptions());
          int numValues = 0;
          int totalValueBytes = 0;
          LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
          for (AttributeValue v : a.getValues())
          {
            numValues++;
            byte[] vBytes = v.getValueBytes();
            byte[] vLength = ASN1Element.encodeLength(vBytes.length);
            valueBytes.add(vLength);
            valueBytes.add(vBytes);
            totalValueBytes += vLength.length + vBytes.length;
          }
          byte[] numValuesBytes = ASN1Element.encodeLength(numValues);
          byte[] attrBytes = new byte[nameBytes.length +
                                      numValuesBytes.length +
                                      totalValueBytes + 1];
          System.arraycopy(nameBytes, 0, attrBytes, 0,
                           nameBytes.length);
          int pos = nameBytes.length+1;
          System.arraycopy(numValuesBytes, 0, attrBytes, pos,
                           numValuesBytes.length);
          pos += numValuesBytes.length;
          for (byte[] b : valueBytes)
          {
            System.arraycopy(b, 0, attrBytes, pos, b.length);
            pos += b.length;
          }
          userAttrBytes.add(attrBytes);
          totalUserAttrBytes += attrBytes.length;
        }
      }
    }
    byte[] userAttrCount =
         ASN1OctetString.encodeLength(numUserAttributes);
    totalBytes += totalUserAttrBytes + userAttrCount.length;
    // Encode the operational attributes in the appropriate manner.
    int numOperationalAttributes = 0;
    int totalOperationalAttrBytes = 0;
    LinkedList<byte[]> operationalAttrBytes =
                            new LinkedList<byte[]>();
    if (config.compressAttributeDescriptions())
    {
      for (List<Attribute> attrList : operationalAttributes.values())
      {
        for (Attribute a : attrList)
        {
          if (a.isVirtual() || (! a.hasValue()))
          {
            continue;
          }
          numOperationalAttributes++;
          byte[] attrBytes = CompressedSchema.encodeAttribute(a);
          byte[] lengthBytes =
               ASN1Element.encodeLength(attrBytes.length);
          operationalAttrBytes.add(lengthBytes);
          operationalAttrBytes.add(attrBytes);
          totalOperationalAttrBytes +=
               lengthBytes.length + attrBytes.length;
        }
      }
    }
    else
    {
      // Encode the operational attributes in the same way as the user
      // attributes.
      for (List<Attribute> attrList : operationalAttributes.values())
      {
        for (Attribute a : attrList)
        {
          if (a.isVirtual() || (! a.hasValue()))
          {
            continue;
          }
          numOperationalAttributes++;
          byte[] nameBytes = getBytes(a.getNameWithOptions());
          int numValues = 0;
          int totalValueBytes = 0;
          LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
          for (AttributeValue v : a.getValues())
          {
            numValues++;
            byte[] vBytes = v.getValueBytes();
            byte[] vLength = ASN1Element.encodeLength(vBytes.length);
            valueBytes.add(vLength);
            valueBytes.add(vBytes);
            totalValueBytes += vLength.length + vBytes.length;
          }
          byte[] numValuesBytes = ASN1Element.encodeLength(numValues);
          byte[] attrBytes = new byte[nameBytes.length +
                                      numValuesBytes.length +
                                      totalValueBytes + 1];
          System.arraycopy(nameBytes, 0, attrBytes, 0,
                           nameBytes.length);
          int pos = nameBytes.length+1;
          System.arraycopy(numValuesBytes, 0, attrBytes, pos,
                           numValuesBytes.length);
          pos += numValuesBytes.length;
          for (byte[] b : valueBytes)
          {
            System.arraycopy(b, 0, attrBytes, pos, b.length);
            pos += b.length;
          }
          operationalAttrBytes.add(attrBytes);
          totalOperationalAttrBytes += attrBytes.length;
        }
      }
    }
    byte[] operationalAttrCount =
         ASN1OctetString.encodeLength(numOperationalAttributes);
    totalBytes += totalOperationalAttrBytes +
                  operationalAttrCount.length;
    // Now we've got all the data that we need.  Create a big byte
    // array to hold it all and pack it in.
    byte[] entryBytes = new byte[totalBytes];
    // Add the entry version number as the first byte.
    entryBytes[0] = 0x02;
    // Next, add the encoded config.
    System.arraycopy(configLength, 0, entryBytes, 1,
                     configLength.length);
    int pos = 1 + configLength.length;
    System.arraycopy(configBytes, 0, entryBytes, pos,
                     configBytes.length);
    pos += configBytes.length;
    // Next, add the DN length and value.
    if (! config.excludeDN())
    {
      System.arraycopy(dnLength, 0, entryBytes, pos, dnLength.length);
      pos += dnLength.length;
      System.arraycopy(dnBytes, 0, entryBytes, pos,  dnBytes.length);
      pos += dnBytes.length;
    }
    // Next, add the object classes length and values.
    System.arraycopy(ocLength, 0, entryBytes, pos, ocLength.length);
    pos += ocLength.length;
    if (config.compressObjectClassSets())
    {
      for (byte[] b : ocBytes)
      {
        System.arraycopy(b, 0, entryBytes, pos, b.length);
        pos += b.length;
      }
    }
    else
    {
      for (byte[] b : ocBytes)
      {
        System.arraycopy(b, 0, entryBytes, pos, b.length);
        pos += b.length + 1;
      }
      // We need to back up one because there's no zero-teriminator
      // after the last object class name.
      pos--;
    }
    // Next, add the user attribute count and the user attribute
    // data.
    System.arraycopy(userAttrCount, 0, entryBytes, pos,
                     userAttrCount.length);
    pos += userAttrCount.length;
    for (byte[] b : userAttrBytes)
    {
      System.arraycopy(b, 0, entryBytes, pos, b.length);
      pos += b.length;
    }
    // Finally, add the operational attribute count and the
    // operational attribute data.
    System.arraycopy(operationalAttrCount, 0, entryBytes, pos,
                     operationalAttrCount.length);
    pos += operationalAttrCount.length;
    for (byte[] b : operationalAttrBytes)
    {
      System.arraycopy(b, 0, entryBytes, pos, b.length);
      pos += b.length;
    }
    return entryBytes;
  }
  /**
   * Decodes the provided byte array as an entry.
   *
   * @param  entryBytes  The byte array containing the data to be
@@ -4081,12 +4417,43 @@
  public static Entry decode(byte[] entryBytes)
         throws DirectoryException
  {
    switch(entryBytes[0])
    {
      case 0x01:
        return decodeV1(entryBytes);
      case 0x02:
        return decodeV2(entryBytes);
      default:
        int    msgID   = MSGID_ENTRY_DECODE_UNRECOGNIZED_VERSION;
        String message = getMessage(msgID,
                                    byteToHex(entryBytes[0]));
        throw new DirectoryException(
                       DirectoryServer.getServerErrorResultCode(),
                       message, msgID);
    }
  }
  /**
   * Decodes the provided byte array as an entry using the V1
   * encoding.
   *
   * @param  entryBytes  The byte array containing the data to be
   *                     decoded.
   *
   * @return  The decoded entry.
   *
   * @throws  DirectoryException  If the provided byte array cannot be
   *                              decoded as an entry.
   */
  public static Entry decodeV1(byte[] entryBytes)
         throws DirectoryException
  {
    try
    {
      // The first byte must be the entry version.  If it's not one
      // we recognize, then that's an error.
      // NOTE:  If we ever change the encoding, then we'll need a
      //        new method for each one.
      if (entryBytes[0] != 0x01)
      {
        int    msgID   = MSGID_ENTRY_DECODE_UNRECOGNIZED_VERSION;
@@ -4436,6 +4803,490 @@
  /**
   * Decodes the provided byte array as an entry using the V2
   * encoding.
   *
   * @param  entryBytes  The byte array containing the data to be
   *                     decoded.
   *
   * @return  The decoded entry.
   *
   * @throws  DirectoryException  If the provided byte array cannot be
   *                              decoded as an entry.
   */
  public static Entry decodeV2(byte[] entryBytes)
         throws DirectoryException
  {
    try
    {
      // The first byte must be the entry version.  If it's not one
      // we recognize, then that's an error.
      if (entryBytes[0] != 0x02)
      {
        int    msgID   = MSGID_ENTRY_DECODE_UNRECOGNIZED_VERSION;
        String message = getMessage(msgID,
                                    byteToHex(entryBytes[0]));
        throw new DirectoryException(
                       DirectoryServer.getServerErrorResultCode(),
                       message, msgID);
      }
      // Next is the length of the encoded configuration.  It may be a
      // single byte or multiple bytes.
      int pos = 1;
      int configLength = entryBytes[pos] & 0x7F;
      if (entryBytes[pos++] != configLength)
      {
        int numLengthBytes = configLength;
        configLength = 0;
        for (int i=0; i < numLengthBytes; i++, pos++)
        {
          configLength =
               (configLength << 8) | (entryBytes[pos] & 0xFF);
        }
      }
      // Next is the encoded configuration itself.
      EntryEncodeConfig config =
           EntryEncodeConfig.decode(entryBytes, pos, configLength);
      pos += configLength;
      // If we should have included the DN in the entry, then it's
      // next.
      DN dn;
      if (config.excludeDN())
      {
        dn = DN.NULL_DN;
      }
      else
      {
        // Next is the length of the DN.  It may be a single byte or
        // multiple bytes.
        int dnLength = entryBytes[pos] & 0x7F;
        if (entryBytes[pos++] != dnLength)
        {
          int numLengthBytes = dnLength;
          dnLength = 0;
          for (int i=0; i < numLengthBytes; i++, pos++)
          {
            dnLength = (dnLength << 8) | (entryBytes[pos] & 0xFF);
          }
        }
        // Next is the DN itself.
        byte[] dnBytes = new byte[dnLength];
        System.arraycopy(entryBytes, pos, dnBytes, 0, dnLength);
        pos += dnLength;
        dn = DN.decode(new ASN1OctetString(dnBytes));
      }
      // Next is the length of the object classes.  It may be a single
      // byte or multiple bytes.
      int ocLength = entryBytes[pos] & 0x7F;
      if (entryBytes[pos++] != ocLength)
      {
        int numLengthBytes = ocLength;
        ocLength = 0;
        for (int i=0; i < numLengthBytes; i++, pos++)
        {
          ocLength = (ocLength << 8) | (entryBytes[pos] & 0xFF);
        }
      }
      // Next is the set of encoded object classes.  The encoding will
      // depend on the configuration.
      Map<ObjectClass,String> objectClasses;
      if (config.compressObjectClassSets())
      {
        byte[] ocBytes = new byte[ocLength];
        System.arraycopy(entryBytes, pos, ocBytes, 0, ocLength);
        objectClasses = CompressedSchema.decodeObjectClasses(ocBytes);
        pos += ocLength;
      }
      else
      {
        // The set of object classes will be encoded as a single
        // string with the oibject class names separated by zeros.
        objectClasses = new LinkedHashMap<ObjectClass,String>();
        int startPos = pos;
        for (int i=0; i < ocLength; i++,pos++)
        {
          if (entryBytes[pos] == 0x00)
          {
            String name = new String(entryBytes, startPos,
                                     pos-startPos, "UTF-8");
            String lowerName = toLowerCase(name);
            ObjectClass oc =
                 DirectoryServer.getObjectClass(lowerName, true);
            objectClasses.put(oc, name);
            startPos = pos+1;
          }
        }
        String name = new String(entryBytes, startPos, pos-startPos,
                                 "UTF-8");
        String lowerName = toLowerCase(name);
        ObjectClass oc =
             DirectoryServer.getObjectClass(lowerName, true);
        objectClasses.put(oc, name);
      }
      // Next is the total number of user attributes.  It may be a
      // single byte or multiple bytes.
      int numUserAttrs = entryBytes[pos] & 0x7F;
      if (entryBytes[pos++] != numUserAttrs)
      {
        int numLengthBytes = numUserAttrs;
        numUserAttrs = 0;
        for (int i=0; i < numLengthBytes; i++, pos++)
        {
          numUserAttrs = (numUserAttrs << 8) |
                         (entryBytes[pos] & 0xFF);
        }
      }
      // Now, we should iterate through the user attributes and decode
      // each one.
      LinkedHashMap<AttributeType,List<Attribute>> userAttributes =
           new LinkedHashMap<AttributeType,List<Attribute>>();
      if (config.compressAttributeDescriptions())
      {
        for (int i=0; i < numUserAttrs; i++)
        {
          // Get the length of the attribute.
          int attrLength = entryBytes[pos] & 0x7F;
          if (entryBytes[pos++] != attrLength)
          {
            int attrLengthBytes = attrLength;
            attrLength = 0;
            for (int j=0; j < attrLengthBytes; j++, pos++)
            {
              attrLength = (attrLength << 8) |
                           (entryBytes[pos] & 0xFF);
            }
          }
          // Decode the attribute.
          Attribute a = CompressedSchema.decodeAttribute(entryBytes,
                             pos, attrLength);
          List<Attribute> attrList =
               userAttributes.get(a.getAttributeType());
          if (attrList == null)
          {
            attrList = new ArrayList<Attribute>(1);
            userAttributes.put(a.getAttributeType(), attrList);
          }
          attrList.add(a);
          pos += attrLength;
        }
      }
      else
      {
        for (int i=0; i < numUserAttrs; i++)
        {
          // First, we have the zero-terminated attribute name.
          int startPos = pos;
          while (entryBytes[pos] != 0x00)
          {
            pos++;
          }
          String lowerName;
          String name = new String(entryBytes, startPos, pos-startPos,
                                   "UTF-8");
          LinkedHashSet<String> options;
          int semicolonPos = name.indexOf(';');
          if (semicolonPos > 0)
          {
            String baseName = name.substring(0, semicolonPos);
            lowerName = toLowerCase(baseName);
            options   = new LinkedHashSet<String>();
            int nextPos = name.indexOf(';', semicolonPos+1);
            while (nextPos > 0)
            {
              String option = name.substring(semicolonPos+1, nextPos);
              if (option.length() > 0)
              {
                options.add(option);
              }
              semicolonPos = nextPos;
              nextPos = name.indexOf(';', semicolonPos+1);
            }
            String option = name.substring(semicolonPos+1);
            if (option.length() > 0)
            {
              options.add(option);
            }
            name = baseName;
          }
          else
          {
            lowerName = toLowerCase(name);
            options   = new LinkedHashSet<String>(0);
          }
          AttributeType attributeType =
               DirectoryServer.getAttributeType(lowerName, true);
          // Next, we have the number of values.
          int numValues = entryBytes[++pos] & 0x7F;
          if (entryBytes[pos++] != numValues)
          {
            int numLengthBytes = numValues;
            numValues = 0;
            for (int j=0; j < numLengthBytes; j++, pos++)
            {
              numValues = (numValues << 8) | (entryBytes[pos] & 0xFF);
            }
          }
          // Next, we have the sequence of length-value pairs.
          LinkedHashSet<AttributeValue> values =
               new LinkedHashSet<AttributeValue>(numValues);
          for (int j=0; j < numValues; j++)
          {
            int valueLength = entryBytes[pos] & 0x7F;
            if (entryBytes[pos++] != valueLength)
            {
              int numLengthBytes = valueLength;
              valueLength = 0;
              for (int k=0; k < numLengthBytes; k++, pos++)
              {
                valueLength = (valueLength << 8) |
                              (entryBytes[pos] & 0xFF);
              }
            }
            byte[] valueBytes = new byte[valueLength];
            System.arraycopy(entryBytes, pos, valueBytes, 0,
                             valueLength);
            values.add(new AttributeValue(attributeType,
                                new ASN1OctetString(valueBytes)));
            pos += valueLength;
          }
          // Create the attribute and add it to the set of user
          // attributes.
          Attribute a = new Attribute(attributeType, name, options,
                                      values);
          List<Attribute> attrList =
               userAttributes.get(attributeType);
          if (attrList == null)
          {
            attrList = new ArrayList<Attribute>(1);
            attrList.add(a);
            userAttributes.put(attributeType, attrList);
          }
          else
          {
            attrList.add(a);
          }
        }
      }
      // Next is the total number of operational attributes.  It may
      // be a single byte or multiple bytes.
      int numOperationalAttrs = entryBytes[pos] & 0x7F;
      if (entryBytes[pos++] != numOperationalAttrs)
      {
        int numLengthBytes = numOperationalAttrs;
        numOperationalAttrs = 0;
        for (int i=0; i < numLengthBytes; i++, pos++)
        {
          numOperationalAttrs =
               (numOperationalAttrs << 8) | (entryBytes[pos] & 0xFF);
        }
      }
      // Now, we should iterate through the operational attributes and
      // decode each one.
      LinkedHashMap<AttributeType,List<Attribute>>
           operationalAttributes =
              new LinkedHashMap<AttributeType,List<Attribute>>();
      if (config.compressAttributeDescriptions())
      {
        for (int i=0; i < numOperationalAttrs; i++)
        {
          // Get the length of the attribute.
          int attrLength = entryBytes[pos] & 0x7F;
          if (entryBytes[pos++] != attrLength)
          {
            int attrLengthBytes = attrLength;
            attrLength = 0;
            for (int j=0; j < attrLengthBytes; j++, pos++)
            {
              attrLength = (attrLength << 8) |
                           (entryBytes[pos] & 0xFF);
            }
          }
          // Decode the attribute.
          Attribute a = CompressedSchema.decodeAttribute(entryBytes,
                             pos, attrLength);
          List<Attribute> attrList =
               operationalAttributes.get(a.getAttributeType());
          if (attrList == null)
          {
            attrList = new ArrayList<Attribute>(1);
            operationalAttributes.put(a.getAttributeType(), attrList);
          }
          attrList.add(a);
          pos += attrLength;
        }
      }
      else
      {
        for (int i=0; i < numOperationalAttrs; i++)
        {
          // First, we have the zero-terminated attribute name.
          int startPos = pos;
          while (entryBytes[pos] != 0x00)
          {
            pos++;
          }
          String lowerName;
          String name = new String(entryBytes, startPos, pos-startPos,
                                   "UTF-8");
          LinkedHashSet<String> options;
          int semicolonPos = name.indexOf(';');
          if (semicolonPos > 0)
          {
            String baseName = name.substring(0, semicolonPos);
            lowerName = toLowerCase(baseName);
            options   = new LinkedHashSet<String>();
            int nextPos = name.indexOf(';', semicolonPos+1);
            while (nextPos > 0)
            {
              String option = name.substring(semicolonPos+1, nextPos);
              if (option.length() > 0)
              {
                options.add(option);
              }
              semicolonPos = nextPos;
              nextPos = name.indexOf(';', semicolonPos+1);
            }
            String option = name.substring(semicolonPos+1);
            if (option.length() > 0)
            {
              options.add(option);
            }
            name = baseName;
          }
          else
          {
            lowerName = toLowerCase(name);
            options   = new LinkedHashSet<String>(0);
          }
          AttributeType attributeType =
               DirectoryServer.getAttributeType(lowerName, true);
          // Next, we have the number of values.
          int numValues = entryBytes[++pos] & 0x7F;
          if (entryBytes[pos++] != numValues)
          {
            int numLengthBytes = numValues;
            numValues = 0;
            for (int j=0; j < numLengthBytes; j++, pos++)
            {
              numValues = (numValues << 8) | (entryBytes[pos] & 0xFF);
            }
          }
          // Next, we have the sequence of length-value pairs.
          LinkedHashSet<AttributeValue> values =
               new LinkedHashSet<AttributeValue>(numValues);
          for (int j=0; j < numValues; j++)
          {
            int valueLength = entryBytes[pos] & 0x7F;
            if (entryBytes[pos++] != valueLength)
            {
              int numLengthBytes = valueLength;
              valueLength = 0;
              for (int k=0; k < numLengthBytes; k++, pos++)
              {
                valueLength = (valueLength << 8) |
                              (entryBytes[pos] & 0xFF);
              }
            }
            byte[] valueBytes = new byte[valueLength];
            System.arraycopy(entryBytes, pos, valueBytes, 0,
                             valueLength);
            values.add(new AttributeValue(attributeType,
                                new ASN1OctetString(valueBytes)));
            pos += valueLength;
          }
          // Create the attribute and add it to the set of operational
          // attributes.
          Attribute a = new Attribute(attributeType, name, options,
                                      values);
          List<Attribute> attrList =
               operationalAttributes.get(attributeType);
          if (attrList == null)
          {
            attrList = new ArrayList<Attribute>(1);
            attrList.add(a);
            operationalAttributes.put(attributeType, attrList);
          }
          else
          {
            attrList.add(a);
          }
        }
      }
      // We've got everything that we need, so create and return the
      // entry.
      return new Entry(dn, objectClasses, userAttributes,
                       operationalAttributes);
    }
    catch (DirectoryException de)
    {
      throw de;
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      int    msgID   = MSGID_ENTRY_DECODE_EXCEPTION;
      String message = getMessage(msgID, getExceptionMessage(e));
      throw new DirectoryException(
                     DirectoryServer.getServerErrorResultCode(),
                     message, msgID, e);
    }
  }
  /**
   * Retrieves a list of the lines for this entry in LDIF form.  Long
   * lines will not be wrapped automatically.
   *
@@ -4825,6 +5676,121 @@
  /**
   * Retrieves a hash code for this entry.
   *
   * @return  The hash code for this entry.
   */
  @Override()
  public int hashCode()
  {
    int hashCode = dn.hashCode();
    for (ObjectClass oc : objectClasses.keySet())
    {
      hashCode += oc.hashCode();
    }
    for (List<Attribute> attrList : userAttributes.values())
    {
      for (Attribute a : attrList)
      {
        hashCode += a.hashCode();
      }
    }
    for (List<Attribute> attrList : operationalAttributes.values())
    {
      for (Attribute a : attrList)
      {
        hashCode += a.hashCode();
      }
    }
    return hashCode;
  }
  /**
   * Indicates whether the provided object is equal to this entry.  In
   * order for the object to be considered equal, it must be an entry
   * with the same DN, set of object classes, and set of user and
   * operational attributes.
   *
   * @param  o  The object for which to make the determination.
   *
   * @return  {@code true} if the provided object may be considered
   *          equal to this entry, or {@code false} if not.
   */
  @Override()
  public boolean equals(Object o)
  {
    if (this == o)
    {
      return true;
    }
    if (o == null)
    {
      return false;
    }
    if (! (o instanceof Entry))
    {
      return false;
    }
    Entry e = (Entry) o;
    if (! dn.equals(e.dn))
    {
      return false;
    }
    if (! objectClasses.keySet().equals(e.objectClasses.keySet()))
    {
      return false;
    }
    for (AttributeType at : userAttributes.keySet())
    {
      List<Attribute> list1 = userAttributes.get(at);
      List<Attribute> list2 = e.userAttributes.get(at);
      if ((list2 == null) || (list1.size() != list2.size()))
      {
        return false;
      }
      for (Attribute a : list1)
      {
        if (! list2.contains(a))
        {
          return false;
        }
      }
    }
    for (AttributeType at : operationalAttributes.keySet())
    {
      List<Attribute> list1 = operationalAttributes.get(at);
      List<Attribute> list2 = e.operationalAttributes.get(at);
      if ((list2 == null) || (list1.size() != list2.size()))
      {
        return false;
      }
      for (Attribute a : list1)
      {
        if (! list2.contains(a))
        {
          return false;
        }
      }
    }
    return true;
  }
  /**
   * Retrieves a string representation of this protocol element.
   *
   * @return  A string representation of this protocol element.
opends/src/server/org/opends/server/types/EntryEncodeConfig.java
New file
@@ -0,0 +1,291 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import org.opends.server.core.DirectoryServer;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
/**
 * This class defines a data structure that contains configuration
 * information about how an entry should be encoded.
 */
public class EntryEncodeConfig
{
  /**
   * The encode mask value that can be used to indicate that the
   * encoded entry should not contain a DN.
   */
  private static final byte ENCODE_FLAG_EXCLUDE_DN = 0x01;
  /**
   * The encode mask value that can be used that the encoded
   * representation should compress the set of object classes.
   */
  private static final byte ENCODE_FLAG_COMPRESS_OCS = 0x02;
  /**
   * The encode mask value that can be used that the encoded
   * representation should compress the set of attribute descriptions
   * to conserve space and improve performance.
   */
  private static final byte ENCODE_FLAG_COMPRESS_ADS = 0x04;
  /**
   * A reference to an entry encode configuration with all the default
   * settings.
   */
  public static final EntryEncodeConfig
       DEFAULT_CONFIG = new EntryEncodeConfig();
  // Indicates whether to compress the attribute descriptions.
  private final boolean compressAttrDescriptions;
  // Indicates whether to compress the object class sets.
  private final boolean compressObjectClassSets;
  // Indicates whether to exclude the DN.
  private final boolean excludeDN;
  // The encoded representation of this encode configuration.
  private final byte[] encodedRepresentation;
  /**
   * Creates a new encoded entry configuration wtih the default
   * settings.
   */
  public EntryEncodeConfig()
  {
    excludeDN                = false;
    compressAttrDescriptions = false;
    compressObjectClassSets  = false;
    encodedRepresentation = new byte[] { 0x00 };
  }
  /**
   * Creates a new encoded entry configuration wtih the specified
   * settings.
   *
   * @param  excludeDN                 Indicates whether to exclude
   *                                   the DN from the encoded entry.
   * @param  compressAttrDescriptions  Indicates whether to compress
   *                                   attribute descriptions.
   * @param  compressObjectClassSets   Indicates whether to compress
   *                                   object class sets.
   */
  public EntryEncodeConfig(boolean excludeDN,
                           boolean compressAttrDescriptions,
                           boolean compressObjectClassSets)
  {
    this.excludeDN                = excludeDN;
    this.compressAttrDescriptions = compressAttrDescriptions;
    this.compressObjectClassSets  = compressObjectClassSets;
    byte flagByte = 0x00;
    if (excludeDN)
    {
      flagByte |= ENCODE_FLAG_EXCLUDE_DN;
    }
    if (compressAttrDescriptions)
    {
      flagByte |= ENCODE_FLAG_COMPRESS_ADS;
    }
    if (compressObjectClassSets)
    {
      flagByte |= ENCODE_FLAG_COMPRESS_OCS;
    }
    encodedRepresentation = new byte[] { flagByte };
  }
  /**
   * Indicates whether the encoded entry should exclude the DN.
   *
   * @return  {@code true} if the encoded entry should exclude the DN,
   *          or {@code false} if not.
   */
  public boolean excludeDN()
  {
    return excludeDN;
  }
  /**
   * Indicates whether the encoded entry should use compressed
   * attribute descriptions.
   *
   * @return  {@code true} if the encoded entry should use compressed
   *          attribute descriptions, or {@code false} if not.
   */
  public boolean compressAttributeDescriptions()
  {
    return compressAttrDescriptions;
  }
  /**
   * Indicates whether the encoded entry should use compressed object
   * class sets.
   *
   * @return  {@code true} if the encoded entry should use compressed
   *          object class sets, or {@code false} if not.
   */
  public boolean compressObjectClassSets()
  {
    return compressObjectClassSets;
  }
  /**
   * Encodes this entry encode configuration into a byte array
   * suitable for inclusion in the encoded entry.
   *
   * @return  A byte array containing the encoded configuration.
   */
  public byte[] encode()
  {
    return encodedRepresentation;
  }
  /**
   * Decodes the entry encode configuration from the specified portion
   * of the given byte array.
   *
   * @param  encodedEntry  The byte array containing the encoded
   *                       entry.
   * @param  startPos      The position at which to start decoding the
   *                       encode configuration.
   * @param  length        The number of bytes contained in the encode
   *                       configuration.
   *
   * @return  The decoded configuration.
   *
   * @throws  DirectoryException  If the configuration cannot be
   *                              properly decoded.
   */
  public static EntryEncodeConfig decode(byte[] encodedEntry,
                                         int startPos, int length)
         throws DirectoryException
  {
    if (length != 1)
    {
      int    msgID   = MSGID_ENTRYENCODECFG_INVALID_LENGTH;
      String message = getMessage(msgID);
      throw new DirectoryException(
                     DirectoryServer.getServerErrorResultCode(),
                     message, msgID);
    }
    boolean excludeDN = false;
    if ((encodedEntry[startPos] & ENCODE_FLAG_EXCLUDE_DN) ==
        ENCODE_FLAG_EXCLUDE_DN)
    {
      excludeDN = true;
    }
    boolean compressAttrDescriptions = false;
    if ((encodedEntry[startPos] & ENCODE_FLAG_COMPRESS_ADS) ==
        ENCODE_FLAG_COMPRESS_ADS)
    {
      compressAttrDescriptions = true;
    }
    boolean compressObjectClassSets = false;
    if ((encodedEntry[startPos] & ENCODE_FLAG_COMPRESS_OCS) ==
        ENCODE_FLAG_COMPRESS_OCS)
    {
      compressObjectClassSets = true;
    }
    return new EntryEncodeConfig(excludeDN, compressAttrDescriptions,
                                 compressObjectClassSets);
  }
  /**
   * Retrieves a string representation of this entry encode
   * configuration.
   *
   * @return  A string representation of this entry encode
   *          configuration.
   */
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
  }
  /**
   * Appends a string representation of this entry encode
   * configuration to the provided buffer.
   *
   * @param  buffer  The buffer to which the information should be
   *                 appended.
   */
  public void toString(StringBuilder buffer)
  {
    buffer.append("EntryEncodeConfig(excludeDN=");
    buffer.append(excludeDN);
    buffer.append(", compressAttrDescriptions=");
    buffer.append(compressAttrDescriptions);
    buffer.append(", compressObjectClassSets=");
    buffer.append(compressObjectClassSets);
    buffer.append(")");
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestJebFormat.java
@@ -38,10 +38,13 @@
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.Entry;
import org.opends.server.types.EntryEncodeConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.ObjectClass;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.StaticUtils;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
@@ -235,25 +238,113 @@
        assertEquals(listBefore.size(), listAfter.size());
        for (Attribute attrBefore : listBefore) {
          boolean found = false;
          for (Attribute attrAfter : listAfter) {
            if (attrAfter.optionsEqual(attrBefore.getOptions())) {
              // Found the corresponding attribute
              String beforeAttrString = attrBefore.toString();
              String afterAttrString = attrAfter.toString();
              if (!beforeAttrString.equals(afterAttrString)) {
                System.out.printf(
                    "Original attr:\n%s\nRetrieved attr:\n%s\n\n",
                    beforeAttrString, afterAttrString);
              }
              assertEquals(beforeAttrString, afterAttrString);
              assertEquals(attrBefore, attrAfter);
              found = true;
            }
          }
          assertTrue(found);
        }
      }
    }
    reader.close();
  }
  /**
   * Tests the entry encoding and decoding process the version 1 encoding.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test()
  public void testEntryToAndFromDatabaseV1() throws Exception {
    // Make sure that the server is up and running.
    TestCaseUtils.startServer();
    // Convert the test LDIF string to a byte array
    byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString);
    LDIFReader reader = new LDIFReader(new LDIFImportConfig(
        new ByteArrayInputStream(originalLDIFBytes)));
    Entry entryBefore, entryAfterGeneric, entryAfterV1;
    while ((entryBefore = reader.readEntry(false)) != null) {
      byte[] entryBytes = entryBefore.encodeV1();
      entryAfterGeneric = Entry.decode(entryBytes);
      assertEquals(entryBefore, entryAfterGeneric);
      entryAfterV1 = Entry.decodeV1(entryBytes);
      assertEquals(entryBefore, entryAfterV1);
      assertEquals(entryAfterGeneric, entryAfterV1);
    }
    reader.close();
  }
  /**
   * Retrieves a set of entry encode configurations that may be used to test the
   * entry encoding and decoding capabilities.
   */
  @DataProvider(name = "encodeConfigs")
  public Object[][] getEntryEncodeConfigs()
  {
    return new Object[][]
    {
      new Object[] { new EntryEncodeConfig() },
      new Object[] { new EntryEncodeConfig(false, false, false) },
      new Object[] { new EntryEncodeConfig(true, false, false) },
      new Object[] { new EntryEncodeConfig(false, true, false) },
      new Object[] { new EntryEncodeConfig(false, false, true) },
      new Object[] { new EntryEncodeConfig(true, true, false) },
      new Object[] { new EntryEncodeConfig(true, false, true) },
      new Object[] { new EntryEncodeConfig(false, true, true) },
      new Object[] { new EntryEncodeConfig(true, true, true) },
    };
  }
  /**
   * Tests the entry encoding and decoding process the version 1 encoding.
   *
   * @throws Exception
   *           If the test failed unexpectedly.
   */
  @Test(dataProvider = "encodeConfigs")
  public void testEntryToAndFromDatabaseV2(EntryEncodeConfig config)
         throws Exception {
    // Make sure that the server is up and running.
    TestCaseUtils.startServer();
    // Convert the test LDIF string to a byte array
    byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString);
    LDIFReader reader = new LDIFReader(new LDIFImportConfig(
        new ByteArrayInputStream(originalLDIFBytes)));
    Entry entryBefore, entryAfterGeneric, entryAfterV2;
    while ((entryBefore = reader.readEntry(false)) != null) {
      byte[] entryBytes = entryBefore.encodeV2(config);
      entryAfterGeneric = Entry.decode(entryBytes);
      if (config.excludeDN())
      {
        entryAfterGeneric.setDN(entryBefore.getDN());
      }
      assertEquals(entryBefore, entryAfterGeneric);
      entryAfterV2 = Entry.decodeV2(entryBytes);
      if (config.excludeDN())
      {
        entryAfterV2.setDN(entryBefore.getDN());
      }
      assertEquals(entryBefore, entryAfterV2);
      assertEquals(entryAfterGeneric, entryAfterV2);
    }
    reader.close();
  }
}