From 5cede67db8c1fea1e4009b99c8aedf1b61c8453d Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Thu, 07 Jun 2007 20:07:26 +0000
Subject: [PATCH] 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:

---
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java                              |   13 
 opendj-sdk/opends/src/server/org/opends/server/types/CompressedSchema.java                             |  758 ++++++++++++++++++
 opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java                              |   57 +
 opendj-sdk/opends/src/server/org/opends/server/types/EntryEncodeConfig.java                            |  291 +++++++
 opendj-sdk/opends/src/server/org/opends/server/types/Entry.java                                        |  972 ++++++++++++++++++++++++
 opendj-sdk/opends/src/server/org/opends/server/types/ByteArray.java                                    |  127 +++
 opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java                             |    9 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestJebFormat.java |  111 ++
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportThread.java                          |    8 
 opendj-sdk/opends/src/server/org/opends/server/backends/jeb/JebFormat.java                             |   20 
 10 files changed, 2,350 insertions(+), 16 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
index 70ddbe0..e372bfe 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
+++ b/opendj-sdk/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);
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportThread.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportThread.java
index 642ba2f..edebcdb 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ImportThread.java
+++ b/opendj-sdk/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())
diff --git a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/JebFormat.java b/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/JebFormat.java
index 2100d37..6c950fb 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/backends/jeb/JebFormat.java
+++ b/opendj-sdk/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);
   }
 
   /**
diff --git a/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java b/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java
index e97af04..09ad9cf 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/config/ConfigConstants.java
+++ b/opendj-sdk/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.
    */
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
index 2ec29cc..ac124af 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opendj-sdk/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");
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/ByteArray.java b/opendj-sdk/opends/src/server/org/opends/server/types/ByteArray.java
new file mode 100644
index 0000000..9761f1e
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/ByteArray.java
@@ -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;
+    }
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/CompressedSchema.java b/opendj-sdk/opends/src/server/org/opends/server/types/CompressedSchema.java
new file mode 100644
index 0000000..ec5836a
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/CompressedSchema.java
@@ -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;
+  }
+}
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java b/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java
index 6acdc91..c97a3a9 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/Entry.java
+++ b/opendj-sdk/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.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/EntryEncodeConfig.java b/opendj-sdk/opends/src/server/org/opends/server/types/EntryEncodeConfig.java
new file mode 100644
index 0000000..7709210
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/EntryEncodeConfig.java
@@ -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(")");
+  }
+}
+
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestJebFormat.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestJebFormat.java
index 73cdee3..b905de5 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestJebFormat.java
+++ b/opendj-sdk/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();
+  }
 }

--
Gitblit v1.10.0