From 22094368c2865dcfb6daf8366425212b721a4657 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Thu, 05 Feb 2009 17:42:14 +0000
Subject: [PATCH] Merge ASN1 branch to trunk

---
 opends/src/server/org/opends/server/backends/jeb/ID2Entry.java |  276 +++++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 229 insertions(+), 47 deletions(-)

diff --git a/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java b/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
index e054633..0f55969 100644
--- a/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
+++ b/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
@@ -33,9 +33,16 @@
 
 import com.sleepycat.je.*;
 
-import org.opends.server.types.DirectoryException;
-import org.opends.server.types.Entry;
+import org.opends.server.types.*;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.protocols.asn1.ASN1Writer;
+import org.opends.server.protocols.asn1.ASN1;
+import org.opends.server.protocols.asn1.ASN1Reader;
+import org.opends.server.protocols.asn1.ASN1Exception;
+import org.opends.server.api.CompressedSchema;
+
+import java.io.IOException;
+import java.util.zip.DataFormatException;
 
 /**
  * Represents the database containing the LDAP entries. The database key is
@@ -54,6 +61,139 @@
    */
   private DataConfig dataConfig;
 
+  private static ThreadLocal<EntryCoder> entryCodingBuffers =
+      new ThreadLocal<EntryCoder>();
+
+  /**
+   * A cached set of ByteStringBuilder buffers and ASN1Writer used to encode
+   * entries.
+   */
+  private static class EntryCoder
+  {
+    ByteStringBuilder encodedBuffer;
+    private ByteStringBuilder entryBuffer;
+    private ByteStringBuilder compressedEntryBuffer;
+    private ASN1Writer writer;
+
+    private EntryCoder()
+    {
+      encodedBuffer = new ByteStringBuilder();
+      entryBuffer = new ByteStringBuilder();
+      compressedEntryBuffer = new ByteStringBuilder();
+      writer = ASN1.getWriter(encodedBuffer);
+    }
+
+    private Entry decode(ByteString bytes, CompressedSchema compressedSchema)
+        throws DirectoryException,ASN1Exception,LDAPException,
+        DataFormatException, IOException
+    {
+      // Get the format version.
+      byte formatVersion = bytes.byteAt(0);
+      if(formatVersion != JebFormat.FORMAT_VERSION)
+      {
+        Message message =
+            ERR_JEB_INCOMPATIBLE_ENTRY_VERSION.get(formatVersion);
+        throw new ASN1Exception(message);
+      }
+
+      // Read the ASN1 sequence.
+      ASN1Reader reader = ASN1.getReader(bytes.subSequence(1, bytes.length()));
+      reader.readStartSequence();
+
+      // See if it was compressed.
+      int uncompressedSize = (int)reader.readInteger();
+
+      if(uncompressedSize > 0)
+      {
+        // We will use the cached buffers to avoid allocations.
+        // Reset the buffers;
+        entryBuffer.clear();
+        compressedEntryBuffer.clear();
+
+        // It was compressed.
+        reader.readOctetString(compressedEntryBuffer);
+        CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
+        // TODO: Should handle the case where uncompress returns < 0
+        compressedEntryBuffer.uncompress(entryBuffer, cryptoManager,
+            uncompressedSize);
+
+        // Since we are used the cached buffers (ByteStringBuilders),
+        // the decoded attribute values will not refer back to the
+        // original buffer.
+        return Entry.decode(entryBuffer.asReader(), compressedSchema);
+      }
+      else
+      {
+        // Since we don't have to do any decompression, we can just decode
+        // the entry off the
+        ByteString encodedEntry = reader.readOctetString();
+        return Entry.decode(encodedEntry.asReader(), compressedSchema);
+      }
+    }
+
+    private ByteString encodeCopy(Entry entry, DataConfig dataConfig)
+        throws DirectoryException
+    {
+      encodeVolatile(entry, dataConfig);
+      return encodedBuffer.toByteString();
+    }
+
+    private DatabaseEntry encodeInternal(Entry entry, DataConfig dataConfig)
+        throws DirectoryException
+    {
+      encodeVolatile(entry, dataConfig);
+      return new DatabaseEntry(encodedBuffer.getBackingArray(), 0,
+          encodedBuffer.length());
+    }
+
+    private void encodeVolatile(Entry entry, DataConfig dataConfig)
+        throws DirectoryException
+    {
+      // Reset the buffers;
+      encodedBuffer.clear();
+      entryBuffer.clear();
+      compressedEntryBuffer.clear();
+
+      // Encode the entry for later use.
+      entry.encode(entryBuffer, dataConfig.getEntryEncodeConfig());
+
+      // First write the DB format version byte.
+      encodedBuffer.append(JebFormat.FORMAT_VERSION);
+
+      try
+      {
+        // Then start the ASN1 sequence.
+        writer.writeStartSequence(JebFormat.TAG_DATABASE_ENTRY);
+
+        // Do optional compression.
+        CryptoManager cryptoManager = DirectoryServer.getCryptoManager();
+        if (dataConfig.isCompressed() && cryptoManager != null &&
+            entryBuffer.compress(compressedEntryBuffer, cryptoManager))
+        {
+          // Compression needed and successful.
+          writer.writeInteger(entryBuffer.length());
+          writer.writeOctetString(compressedEntryBuffer);
+        }
+        else
+        {
+          writer.writeInteger(0);
+          writer.writeOctetString(entryBuffer);
+        }
+
+        writer.writeEndSequence();
+      }
+      catch(IOException ioe)
+      {
+        // TODO: This should never happen with byte buffer.
+        if(debugEnabled())
+        {
+          TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
+        }
+
+      }
+    }
+  }
+
   /**
    * Create a new ID2Entry object.
    *
@@ -96,20 +236,75 @@
   }
 
   /**
-   * Convert an entry to its database format.
+   * Decodes an entry from its database representation.
+   * <p>
+   * An entry on disk is ASN1 encoded in this format:
    *
-   * @param entry The LDAP entry to be converted.
-   * @return The database entry.
+   * <pre>
+   * DatabaseEntry ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+   *  uncompressedSize      INTEGER,      -- A zero value means not compressed.
+   *  dataBytes             OCTET STRING  -- Optionally compressed encoding of
+   *                                         the data bytes.
+   * }
+   *
+   * ID2EntryValue ::= DatabaseEntry
+   *  -- Where dataBytes contains an encoding of DirectoryServerEntry.
+   *
+   * DirectoryServerEntry ::= [APPLICATION 1] IMPLICIT SEQUENCE {
+   *  dn                      LDAPDN,
+   *  objectClasses           SET OF LDAPString,
+   *  userAttributes          AttributeList,
+   *  operationalAttributes   AttributeList
+   * }
+   * </pre>
+   *
+   * @param bytes A byte array containing the encoded database value.
+   * @param compressedSchema The compressed schema manager to use when decoding.
+   * @return The decoded entry.
+   * @throws ASN1Exception If the data is not in the expected ASN.1 encoding
+   * format.
+   * @throws LDAPException If the data is not in the expected ASN.1 encoding
+   * format.
+   * @throws DataFormatException If an error occurs while trying to decompress
+   * compressed data.
+   * @throws DirectoryException If a Directory Server error occurs.
+   * @throws IOException if an error occurs while reading the ASN1 sequence.
+   */
+  static public Entry entryFromDatabase(ByteString bytes,
+                                        CompressedSchema compressedSchema)
+      throws DirectoryException,ASN1Exception,LDAPException,
+      DataFormatException,IOException
+  {
+    EntryCoder coder = entryCodingBuffers.get();
+    if(coder == null)
+    {
+      coder = new EntryCoder();
+      entryCodingBuffers.set(coder);
+    }
+    return coder.decode(bytes, compressedSchema);
+  }
+
+  /**
+   * Encodes an entry to the raw database format, with optional compression.
+   *
+   * @param entry The entry to encode.
+   * @param dataConfig Compression and cryptographic options.
+   * @return A ByteSTring containing the encoded database value.
    *
    * @throws  DirectoryException  If a problem occurs while attempting to encode
    *                              the entry.
    */
-  public DatabaseEntry entryData(Entry entry)
-          throws DirectoryException
+  static public ByteString entryToDatabase(Entry entry, DataConfig dataConfig)
+      throws DirectoryException
   {
-    byte[] entryBytes;
-    entryBytes = JebFormat.entryToDatabase(entry, dataConfig);
-    return new DatabaseEntry(entryBytes);
+    EntryCoder coder = entryCodingBuffers.get();
+    if(coder == null)
+    {
+      coder = new EntryCoder();
+      entryCodingBuffers.set(coder);
+    }
+
+    return coder.encodeCopy(entry, dataConfig);
   }
 
   /**
@@ -128,7 +323,13 @@
        throws DatabaseException, DirectoryException
   {
     DatabaseEntry key = id.getDatabaseEntry();
-    DatabaseEntry data = entryData(entry);
+    EntryCoder coder = entryCodingBuffers.get();
+    if(coder == null)
+    {
+      coder = new EntryCoder();
+      entryCodingBuffers.set(coder);
+    }
+    DatabaseEntry data = coder.encodeInternal(entry, dataConfig);
 
     OperationStatus status;
     status = insert(txn, key, data);
@@ -154,7 +355,13 @@
        throws DatabaseException, DirectoryException
   {
     DatabaseEntry key = id.getDatabaseEntry();
-    DatabaseEntry data = entryData(entry);
+    EntryCoder coder = entryCodingBuffers.get();
+    if(coder == null)
+    {
+      coder = new EntryCoder();
+      entryCodingBuffers.set(coder);
+    }
+    DatabaseEntry data = coder.encodeInternal(entry, dataConfig);
 
     OperationStatus status;
     status = put(txn, key, data);
@@ -231,44 +438,19 @@
       return null;
     }
 
-    byte[] entryBytes = data.getData();
-    byte entryVersion = JebFormat.getEntryVersion(entryBytes);
-
-    //Try to decode the entry based on the version number. On later versions,
-    //a case could be written to upgrade entries if it is not the current
-    //version
-    Entry entry = null;
-    switch(entryVersion)
+    try
     {
-      case JebFormat.FORMAT_VERSION :
-        try
-        {
-          entry = JebFormat.entryFromDatabase(entryBytes,
-                       entryContainer.getRootContainer().getCompressedSchema());
-        }
-        catch (Exception e)
-        {
-          Message message = ERR_JEB_ENTRY_DATABASE_CORRUPT.get(id.toString());
-          throw new DirectoryException(
-              DirectoryServer.getServerErrorResultCode(), message);
-        }
-        break;
-
-      //case 0x00                     :
-      //  Call upgrade method? Call 0x00 decode method?
-      default   :
-        Message message =
-            ERR_JEB_INCOMPATIBLE_ENTRY_VERSION.get(id.toString(), entryVersion);
-        throw new DirectoryException(
-              DirectoryServer.getServerErrorResultCode(), message);
-    }
-
-    if (entry != null)
-    {
+      Entry entry = entryFromDatabase(ByteString.wrap(data.getData()),
+          entryContainer.getRootContainer().getCompressedSchema());
       entry.processVirtualAttributes();
+      return entry;
     }
-
-    return entry;
+    catch (Exception e)
+    {
+      Message message = ERR_JEB_ENTRY_DATABASE_CORRUPT.get(id.toString());
+      throw new DirectoryException(
+          DirectoryServer.getServerErrorResultCode(), message);
+    }
   }
 
   /**

--
Gitblit v1.10.0