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

neil_a_wilson
07.07.2007 5cede67db8c1fea1e4009b99c8aedf1b61c8453d
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);
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())
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);
  }
  /**
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.
   */
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");
  }
}
opendj-sdk/opends/src/server/org/opends/server/types/ByteArray.java
New file
@@ -0,0 +1,127 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import java.util.Arrays;
/**
 * This class provides a data structure that holds a byte array but
 * also includes the necessary {@code equals} and {@code hashCode}
 * methods to make it suitable for use in maps.
 */
public class ByteArray
{
  // The array that will be wrapped by this object.
  private final byte[] array;
  /**
   * Creates a new {@code ByteArray} object that wraps the provided
   * array.
   *
   * @param  array  The array to be wrapped with this
   *                {@code ByteArray}.
   */
  public ByteArray(byte[] array)
  {
    this.array = array;
  }
  /**
   * Retrieves the array wrapped by this {@code ByteArray} object.
   *
   * @return  The array wrapped by this {@code ByteArray} object.
   */
  public byte[] array()
  {
    return array;
  }
  /**
   * Retrieves a hash code for this {@code ByteArray}.  It will be the
   * sum of all of the bytes contained in the wrapped array.
   *
   * @return  A hash code for this {@code ByteArray}.
   */
  public int hashCode()
  {
    int hashCode = 0;
    for (int i=0; i < array.length; i++)
    {
      hashCode += array[i];
    }
    return hashCode;
  }
  /**
   * Indicates whether the provided object is equal to this
   * {@code ByteArray}.  In order for it to be considered equal, the
   * provided object must be a non-null {@code ByteArray} object with
   * a wrapped array containing the same bytes in the same order.
   *
   * @param  o  The object for which to make the determination.
   *
   * @return  {@code true} if the provided object is a
   *          {@code ByteArray} whose content is equal to that of this
   *          {@code ByteArray}, or {@code false} if not.
   */
  public boolean equals(Object o)
  {
    if (o == this)
    {
      return true;
    }
    if (o == null)
    {
      return false;
    }
    if (o instanceof ByteArray)
    {
      ByteArray ba = (ByteArray) o;
      return Arrays.equals(array, ba.array);
    }
    else
    {
      return false;
    }
  }
}
opendj-sdk/opends/src/server/org/opends/server/types/CompressedSchema.java
New file
@@ -0,0 +1,758 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1Integer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Reader;
import org.opends.server.protocols.asn1.ASN1Sequence;
import org.opends.server.protocols.asn1.ASN1Writer;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class provides a utility for interacting with compressed
 * representations of schema elements.
 */
public class CompressedSchema
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * The singleton instance that will be used to perform the mapping.
   */
  private static CompressedSchema instance = new CompressedSchema();
  // The counter used for attribute descriptions.
  private AtomicInteger adCounter;
  // The counter used for object class sets.
  private AtomicInteger ocCounter;
  // The map between encoded representations and attribute types.
  private ConcurrentHashMap<ByteArray,AttributeType> atDecodeMap;
  // The map between encoded representations and attribute options.
  private ConcurrentHashMap<ByteArray,
               LinkedHashSet<String>> aoDecodeMap;
  // The map between encoded representations and object class sets.
  private ConcurrentHashMap<ByteArray,
               Map<ObjectClass,String>> ocDecodeMap;
  // The map between attribute descriptions and their encoded
  // representations.
  private ConcurrentHashMap<AttributeType,
               ConcurrentHashMap<LinkedHashSet<String>,ByteArray>>
                    adEncodeMap;
  // The map between object class sets and encoded representations.
  private ConcurrentHashMap<Map<ObjectClass,String>,
               ByteArray> ocEncodeMap;
  /**
   * Creates a new instance of this compressed schema manager.
   */
  private CompressedSchema()
  {
    atDecodeMap = new ConcurrentHashMap<ByteArray,AttributeType>();
    aoDecodeMap = new ConcurrentHashMap<ByteArray,
                           LinkedHashSet<String>>();
    ocDecodeMap = new ConcurrentHashMap<ByteArray,
                           Map<ObjectClass,String>>();
    adEncodeMap = new ConcurrentHashMap<AttributeType,
                           ConcurrentHashMap<LinkedHashSet<String>,
                                ByteArray>>();
    ocEncodeMap = new ConcurrentHashMap<Map<ObjectClass,String>,
                           ByteArray>();
    adCounter = new AtomicInteger(1);
    ocCounter = new AtomicInteger(1);
    load();
  }
  /**
   * Loads the compressed schema information from disk.
   */
  private void load()
  {
    ASN1Reader reader = null;
    try
    {
      // Determine the location of the compressed schema data file
      // It should be in the config directory with a name of
      // "schematokens.dat".  If that file doesn't exist, then don't
      // do anything.
      String path = DirectoryServer.getServerRoot() + File.separator +
                    CONFIG_DIR_NAME + File.separator +
                    COMPRESSED_SCHEMA_FILE_NAME;
      if (! new File(path).exists())
      {
        return;
      }
      FileInputStream inputStream = new FileInputStream(path);
      reader = new ASN1Reader(inputStream);
      // The first element in the file should be a sequence of object
      // class sets.  Each object class set will itself be a sequence
      // of octet strings, where the first one is the token and the
      // remaining elements are the names of the associated object
      // classes.
      ASN1Sequence ocSequence =
           reader.readElement().decodeAsSequence();
      for (ASN1Element element : ocSequence.elements())
      {
        ArrayList<ASN1Element> elements =
             element.decodeAsSequence().elements();
        ASN1OctetString os = elements.get(0).decodeAsOctetString();
        ByteArray token = new ByteArray(os.value());
        LinkedHashMap<ObjectClass,String> ocMap =
             new LinkedHashMap<ObjectClass,String>(elements.size()-1);
        for (int i=1; i < elements.size(); i++)
        {
          os = elements.get(i).decodeAsOctetString();
          String ocName = os.stringValue();
          String lowerName = toLowerCase(ocName);
          ObjectClass oc =
               DirectoryServer.getObjectClass(lowerName, true);
          ocMap.put(oc, ocName);
        }
        ocEncodeMap.put(ocMap, token);
        ocDecodeMap.put(token, ocMap);
      }
      // The second element in the file should be an integer element
      // that holds the value to use to initialize the object class
      // counter.
      ASN1Element counterElement = reader.readElement();
      ocCounter.set(counterElement.decodeAsInteger().intValue());
      // The third element in the file should be a sequence of
      // attribute description components.  Each attribute description
      // component will itself be a sequence of octet strings, where
      // the first one is the token, the second is the attribute name,
      // and all remaining elements are the attribute options.
      ASN1Sequence adSequence =
           reader.readElement().decodeAsSequence();
      for (ASN1Element element : adSequence.elements())
      {
        ArrayList<ASN1Element> elements =
             element.decodeAsSequence().elements();
        ASN1OctetString os = elements. get(0).decodeAsOctetString();
        ByteArray token = new ByteArray(os.value());
        os = elements.get(1).decodeAsOctetString();
        String attrName = os.stringValue();
        String lowerName = toLowerCase(attrName);
        AttributeType attrType =
             DirectoryServer.getAttributeType(lowerName, true);
        LinkedHashSet<String> options =
             new LinkedHashSet<String>(elements.size()-2);
        for (int i=2; i < elements.size(); i++)
        {
          os = elements.get(i).decodeAsOctetString();
          options.add(os.stringValue());
        }
        atDecodeMap.put(token, attrType);
        aoDecodeMap.put(token, options);
        ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map =
             adEncodeMap.get(attrType);
        if (map == null)
        {
          map = new ConcurrentHashMap<LinkedHashSet<String>,
                         ByteArray>(1);
          map.put(options, token);
          adEncodeMap.put(attrType, map);
        }
        else
        {
          map.put(options, token);
        }
      }
      // The fourth element in the file should be an integer element
      // that holds the value to use to initialize the attribute
      // description counter.
      counterElement = reader.readElement();
      adCounter.set(counterElement.decodeAsInteger().intValue());
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      // FIXME -- Should we do something else here?
      throw new RuntimeException(e);
    }
    finally
    {
      try
      {
        if (reader != null)
        {
          reader.close();
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
  }
  /**
   * Writes the compressed schema information to disk.
   *
   * @throws  DirectoryException  If a problem occurs while writing
   *                              the updated information.
   */
  private void save()
          throws DirectoryException
  {
    ASN1Writer writer = null;
    try
    {
      // Determine the location of the "live" compressed schema data
      // file, and then append ".tmp" to get the name of the temporary
      // file that we will use.
      String path = DirectoryServer.getServerRoot() + File.separator +
                    CONFIG_DIR_NAME + File.separator +
                    COMPRESSED_SCHEMA_FILE_NAME;
      String tempPath = path + ".tmp";
      FileOutputStream outputStream = new FileOutputStream(tempPath);
      writer = new ASN1Writer(outputStream);
      // The first element in the file should be a sequence of object
      // class sets.  Each object class set will itself be a sequence
      // of octet strings, where the first one is the token and the
      // remaining elements are the names of the associated object
      // classes.
      ArrayList<ASN1Element> ocElements =
           new ArrayList<ASN1Element>(ocDecodeMap.size());
      for (Map.Entry<ByteArray,Map<ObjectClass,String>> mapEntry :
           ocDecodeMap.entrySet())
      {
        ByteArray token = mapEntry.getKey();
        Map<ObjectClass,String> ocMap = mapEntry.getValue();
        ArrayList<ASN1Element> elements =
             new ArrayList<ASN1Element>(ocMap.size()+1);
        elements.add(new ASN1OctetString(token.array()));
        for (String ocName : ocMap.values())
        {
          elements.add(new ASN1OctetString(ocName));
        }
        ocElements.add(new ASN1Sequence(elements));
      }
      writer.writeElement(new ASN1Sequence(ocElements));
      // The second element in the file should be an integer element
      // that holds the value to use to initialize the object class
      // counter.
      writer.writeElement(new ASN1Integer(ocCounter.get()));
      // The third element in the file should be a sequence of
      // attribute description components.  Each attribute description
      // component will itself be a sequence of octet strings, where
      // the first one is the token, the second is the attribute name,
      // and all remaining elements are the attribute options.
      ArrayList<ASN1Element> adElements =
           new ArrayList<ASN1Element>(atDecodeMap.size());
      for (ByteArray token : atDecodeMap.keySet())
      {
        AttributeType attrType = atDecodeMap.get(token);
        LinkedHashSet<String> options = aoDecodeMap.get(token);
        ArrayList<ASN1Element> elements =
             new ArrayList<ASN1Element>(options.size()+2);
        elements.add(new ASN1OctetString(token.array()));
        elements.add(new ASN1OctetString(attrType.getNameOrOID()));
        for (String option : options)
        {
          elements.add(new ASN1OctetString(option));
        }
        adElements.add(new ASN1Sequence(elements));
      }
      writer.writeElement(new ASN1Sequence(adElements));
      // The fourth element in the file should be an integer element
      // that holds the value to use to initialize the attribute
      // description counter.
      writer.writeElement(new ASN1Integer(adCounter.get()));
      // Close the writer and swing the temp file into place.
      writer.close();
      File liveFile = new File(path);
      File tempFile = new File(tempPath);
      if (liveFile.exists())
      {
        File saveFile =
                  new File(liveFile.getAbsolutePath() + ".save");
        if (saveFile.exists())
        {
          saveFile.delete();
        }
        liveFile.renameTo(saveFile);
      }
      tempFile.renameTo(liveFile);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_COMPRESSEDSCHEMA_CANNOT_WRITE_UPDATED_DATA;
      String message = getMessage(msgID,
                                  stackTraceToSingleLineString(e));
      throw new DirectoryException(
                     DirectoryServer.getServerErrorResultCode(),
                     message, msgID, e);
    }
    finally
    {
      try
      {
        if (writer != null)
        {
          writer.close();
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
  }
  /**
   * Encodes the provided set of object classes to a byte array.  If
   * the same set had been previously encoded, then the cached value
   * will be used.  Otherwise, a new value will be created.
   *
   * @param  objectClasses  The set of object classes for which to
   *                        retrieve the corresponding byte array
   *                        token.
   *
   * @return  A byte array containing the identifier assigned to the
   *          object class set.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to determine the appropriate
   *                              identifier.
   */
  public static byte[]
         encodeObjectClasses(Map<ObjectClass,String> objectClasses)
         throws DirectoryException
  {
    ByteArray encodedClasses =
                    instance.ocEncodeMap.get(objectClasses);
    if (encodedClasses == null)
    {
      synchronized (instance.ocEncodeMap)
      {
        int setValue = instance.ocCounter.getAndIncrement();
        byte[] array = encodeInt(setValue);
        encodedClasses = new ByteArray(array);
        instance.ocEncodeMap.put(objectClasses, encodedClasses);
        instance.ocDecodeMap.put(encodedClasses, objectClasses);
        instance.save();
        return array;
      }
    }
    else
    {
      return encodedClasses.array();
    }
  }
  /**
   * Decodes an object class set from the provided byte array.
   *
   * @param  encodedObjectClasses  The byte array containing the
   *                               object class set identifier.
   *
   * @return  The decoded object class set.
   *
   * @throws  DirectoryException  If the provided byte array cannot be
   *                              decoded as an object class set.
   */
  public static Map<ObjectClass,String>
         decodeObjectClasses(byte[] encodedObjectClasses)
         throws DirectoryException
  {
    ByteArray byteArray = new ByteArray(encodedObjectClasses);
    Map<ObjectClass,String> ocMap =
         instance.ocDecodeMap.get(byteArray);
    if (ocMap == null)
    {
      int    msgID   = MSGID_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN;
      String message = getMessage(msgID,
                                  bytesToHex(encodedObjectClasses));
      throw new DirectoryException(
                     DirectoryServer.getServerErrorResultCode(),
                     message, msgID);
    }
    else
    {
      return ocMap;
    }
  }
  /**
   * Encodes the information in the provided attribute to a byte
   * array.
   *
   * @param  attribute  The attribute to be encoded.
   *
   * @return  An encoded representation of the provided attribute.
   *
   * @throws  DirectoryException  If a problem occurs while attempting
   *                              to determine the appropriate
   *                              identifier.
   */
  public static byte[] encodeAttribute(Attribute attribute)
         throws DirectoryException
  {
    AttributeType type = attribute.getAttributeType();
    LinkedHashSet<String> options = attribute.getOptions();
    ConcurrentHashMap<LinkedHashSet<String>,ByteArray> map =
         instance.adEncodeMap.get(type);
    if (map == null)
    {
      byte[] array;
      synchronized (instance.adEncodeMap)
      {
        map = new ConcurrentHashMap<LinkedHashSet<String>,
                       ByteArray>(1);
        int intValue = instance.adCounter.getAndIncrement();
        array = encodeInt(intValue);
        ByteArray byteArray = new ByteArray(array);
        map.put(options,byteArray);
        instance.adEncodeMap.put(type, map);
        instance.atDecodeMap.put(byteArray, type);
        instance.aoDecodeMap.put(byteArray, options);
        instance.save();
      }
      return encodeAttribute(array, attribute);
    }
    else
    {
      ByteArray byteArray = map.get(options);
      if (byteArray == null)
      {
        byte[] array;
        synchronized (map)
        {
          int intValue = instance.adCounter.getAndIncrement();
          array = encodeInt(intValue);
          byteArray = new ByteArray(array);
          map.put(options,byteArray);
          instance.atDecodeMap.put(byteArray, type);
          instance.aoDecodeMap.put(byteArray, options);
          instance.save();
        }
        return encodeAttribute(array, attribute);
      }
      else
      {
        return encodeAttribute(byteArray.array(), attribute);
      }
    }
  }
  /**
   * Encodes the information in the provided attribute to a byte
   * array.
   *
   * @param  adArray    The byte array that is a placeholder for the
   *                    attribute type and set of options.
   * @param  attribute  The attribute to be encoded.
   *
   * @return  An encoded representation of the provided attribute.
   */
  private static byte[] encodeAttribute(byte[] adArray,
                                        Attribute attribute)
  {
    LinkedHashSet<AttributeValue> values = attribute.getValues();
    int totalValuesLength = 0;
    byte[][] subArrays = new  byte[values.size()*2][];
    int pos = 0;
    for (AttributeValue v : values)
    {
      byte[] vBytes = v.getValueBytes();
      byte[] lBytes = ASN1Element.encodeLength(vBytes.length);
      subArrays[pos++] = lBytes;
      subArrays[pos++] = vBytes;
      totalValuesLength += lBytes.length + vBytes.length;
    }
    byte[] adArrayLength = ASN1Element.encodeLength(adArray.length);
    byte[] countBytes = ASN1Element.encodeLength(values.size());
    int totalLength = adArrayLength.length + adArray.length +
                      countBytes.length + totalValuesLength;
    byte[] array = new byte[totalLength];
    System.arraycopy(adArrayLength, 0, array, 0,
                     adArrayLength.length);
    pos = adArrayLength.length;
    System.arraycopy(adArray, 0, array, pos, adArray.length);
    pos += adArray.length;
    System.arraycopy(countBytes, 0, array, pos, countBytes.length);
    pos += countBytes.length;
    for (int i=0; i < subArrays.length; i++)
    {
      System.arraycopy(subArrays[i], 0, array, pos,
                       subArrays[i].length);
      pos += subArrays[i].length;
    }
    return array;
  }
  /**
   * Decodes the contents of the provided array as an attribute.
   *
   * @param  encodedEntry  The byte array containing the encoded
   *                       entry.
   * @param  startPos      The position within the array of the first
   *                       byte for the attribute to decode.
   * @param  length        The number of bytes contained in the
   *                       encoded attribute.
   *
   * @return  The decoded attribute.
   *
   * @throws  DirectoryException  If the attribute could not be
   *                              decoded properly for some reason.
   */
  public static Attribute decodeAttribute(byte[] encodedEntry,
                                          int startPos, int length)
         throws DirectoryException
  {
    // Figure out how many bytes are in the token that is the
    // placeholder for the attribute description.
    int pos = startPos;
    int adArrayLength = encodedEntry[pos] & 0x7F;
    if (adArrayLength != encodedEntry[pos++])
    {
      int numLengthBytes = adArrayLength;
      adArrayLength = 0;
      for (int i=0; i < numLengthBytes; i++, pos++)
      {
        adArrayLength =
             (adArrayLength << 8) | (encodedEntry[pos] & 0xFF);
      }
    }
    // Get the attribute description token and make sure it resolves
    // to an attribute type and option set.
    ByteArray adArray = new ByteArray(new byte[adArrayLength]);
    System.arraycopy(encodedEntry, pos, adArray.array(), 0,
                     adArrayLength);
    pos += adArrayLength;
    AttributeType attrType = instance.atDecodeMap.get(adArray);
    LinkedHashSet<String> options = instance.aoDecodeMap.get(adArray);
    if ((attrType == null) || (options == null))
    {
      int msgID = MSGID_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN;
      String message = getMessage(msgID, bytesToHex(adArray.array()));
      throw new DirectoryException(
                     DirectoryServer.getServerErrorResultCode(),
                     message, msgID);
    }
    // Determine the number of values for the attribute.
    int numValues = encodedEntry[pos] & 0x7F;
    if (numValues != encodedEntry[pos++])
    {
      int numValuesBytes = numValues;
      numValues = 0;
      for (int i=0; i < numValuesBytes; i++, pos++)
      {
        numValues = (numValues << 8) | (encodedEntry[pos] & 0xFF);
      }
    }
    // Read the appropriate number of values.
    LinkedHashSet<AttributeValue> values =
         new LinkedHashSet<AttributeValue>(numValues);
    for (int i=0; i < numValues; i++)
    {
      int valueLength = encodedEntry[pos] & 0x7F;
      if (valueLength != encodedEntry[pos++])
      {
        int valueLengthBytes = valueLength;
        valueLength = 0;
        for (int j=0; j < valueLengthBytes; j++, pos++)
        {
          valueLength =
               (valueLength << 8) | (encodedEntry[pos] & 0xFF);
        }
      }
      byte[] valueBytes = new byte[valueLength];
      System.arraycopy(encodedEntry, pos, valueBytes, 0, valueLength);
      pos += valueLength;
      values.add(new AttributeValue(attrType,
                                    new ASN1OctetString(valueBytes)));
    }
    return new Attribute(attrType, attrType.getPrimaryName(), options,
                         values);
  }
  /**
   * Encodes the provided int value to a byte array.
   *
   * @param  intValue  The int value to be encoded.
   *
   * @return  The byte array containing the encoded int value.
   */
  private static byte[] encodeInt(int intValue)
  {
    byte[] array;
    if (intValue <= 0xFF)
    {
      array = new byte[1];
      array[0] = (byte) (intValue & 0xFF);
    }
    else if (intValue <= 0xFFFF)
    {
      array = new byte[2];
      array[0] = (byte) ((intValue >> 8)  & 0xFF);
      array[1] = (byte) (intValue & 0xFF);
    }
    else if (intValue <= 0xFFFFFF)
    {
      array = new byte[3];
      array[0] = (byte) ((intValue >> 16) & 0xFF);
      array[1] = (byte) ((intValue >> 8)  & 0xFF);
      array[2] = (byte) (intValue & 0xFF);
    }
    else
    {
      array = new byte[4];
      array[0] = (byte) ((intValue >> 24) & 0xFF);
      array[1] = (byte) ((intValue >> 16) & 0xFF);
      array[2] = (byte) ((intValue >> 8)  & 0xFF);
      array[3] = (byte) (intValue & 0xFF);
    }
    return array;
  }
}
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.
opendj-sdk/opends/src/server/org/opends/server/types/EntryEncodeConfig.java
New file
@@ -0,0 +1,291 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.types;
import org.opends.server.core.DirectoryServer;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
/**
 * This class defines a data structure that contains configuration
 * information about how an entry should be encoded.
 */
public class EntryEncodeConfig
{
  /**
   * The encode mask value that can be used to indicate that the
   * encoded entry should not contain a DN.
   */
  private static final byte ENCODE_FLAG_EXCLUDE_DN = 0x01;
  /**
   * The encode mask value that can be used that the encoded
   * representation should compress the set of object classes.
   */
  private static final byte ENCODE_FLAG_COMPRESS_OCS = 0x02;
  /**
   * The encode mask value that can be used that the encoded
   * representation should compress the set of attribute descriptions
   * to conserve space and improve performance.
   */
  private static final byte ENCODE_FLAG_COMPRESS_ADS = 0x04;
  /**
   * A reference to an entry encode configuration with all the default
   * settings.
   */
  public static final EntryEncodeConfig
       DEFAULT_CONFIG = new EntryEncodeConfig();
  // Indicates whether to compress the attribute descriptions.
  private final boolean compressAttrDescriptions;
  // Indicates whether to compress the object class sets.
  private final boolean compressObjectClassSets;
  // Indicates whether to exclude the DN.
  private final boolean excludeDN;
  // The encoded representation of this encode configuration.
  private final byte[] encodedRepresentation;
  /**
   * Creates a new encoded entry configuration wtih the default
   * settings.
   */
  public EntryEncodeConfig()
  {
    excludeDN                = false;
    compressAttrDescriptions = false;
    compressObjectClassSets  = false;
    encodedRepresentation = new byte[] { 0x00 };
  }
  /**
   * Creates a new encoded entry configuration wtih the specified
   * settings.
   *
   * @param  excludeDN                 Indicates whether to exclude
   *                                   the DN from the encoded entry.
   * @param  compressAttrDescriptions  Indicates whether to compress
   *                                   attribute descriptions.
   * @param  compressObjectClassSets   Indicates whether to compress
   *                                   object class sets.
   */
  public EntryEncodeConfig(boolean excludeDN,
                           boolean compressAttrDescriptions,
                           boolean compressObjectClassSets)
  {
    this.excludeDN                = excludeDN;
    this.compressAttrDescriptions = compressAttrDescriptions;
    this.compressObjectClassSets  = compressObjectClassSets;
    byte flagByte = 0x00;
    if (excludeDN)
    {
      flagByte |= ENCODE_FLAG_EXCLUDE_DN;
    }
    if (compressAttrDescriptions)
    {
      flagByte |= ENCODE_FLAG_COMPRESS_ADS;
    }
    if (compressObjectClassSets)
    {
      flagByte |= ENCODE_FLAG_COMPRESS_OCS;
    }
    encodedRepresentation = new byte[] { flagByte };
  }
  /**
   * Indicates whether the encoded entry should exclude the DN.
   *
   * @return  {@code true} if the encoded entry should exclude the DN,
   *          or {@code false} if not.
   */
  public boolean excludeDN()
  {
    return excludeDN;
  }
  /**
   * Indicates whether the encoded entry should use compressed
   * attribute descriptions.
   *
   * @return  {@code true} if the encoded entry should use compressed
   *          attribute descriptions, or {@code false} if not.
   */
  public boolean compressAttributeDescriptions()
  {
    return compressAttrDescriptions;
  }
  /**
   * Indicates whether the encoded entry should use compressed object
   * class sets.
   *
   * @return  {@code true} if the encoded entry should use compressed
   *          object class sets, or {@code false} if not.
   */
  public boolean compressObjectClassSets()
  {
    return compressObjectClassSets;
  }
  /**
   * Encodes this entry encode configuration into a byte array
   * suitable for inclusion in the encoded entry.
   *
   * @return  A byte array containing the encoded configuration.
   */
  public byte[] encode()
  {
    return encodedRepresentation;
  }
  /**
   * Decodes the entry encode configuration from the specified portion
   * of the given byte array.
   *
   * @param  encodedEntry  The byte array containing the encoded
   *                       entry.
   * @param  startPos      The position at which to start decoding the
   *                       encode configuration.
   * @param  length        The number of bytes contained in the encode
   *                       configuration.
   *
   * @return  The decoded configuration.
   *
   * @throws  DirectoryException  If the configuration cannot be
   *                              properly decoded.
   */
  public static EntryEncodeConfig decode(byte[] encodedEntry,
                                         int startPos, int length)
         throws DirectoryException
  {
    if (length != 1)
    {
      int    msgID   = MSGID_ENTRYENCODECFG_INVALID_LENGTH;
      String message = getMessage(msgID);
      throw new DirectoryException(
                     DirectoryServer.getServerErrorResultCode(),
                     message, msgID);
    }
    boolean excludeDN = false;
    if ((encodedEntry[startPos] & ENCODE_FLAG_EXCLUDE_DN) ==
        ENCODE_FLAG_EXCLUDE_DN)
    {
      excludeDN = true;
    }
    boolean compressAttrDescriptions = false;
    if ((encodedEntry[startPos] & ENCODE_FLAG_COMPRESS_ADS) ==
        ENCODE_FLAG_COMPRESS_ADS)
    {
      compressAttrDescriptions = true;
    }
    boolean compressObjectClassSets = false;
    if ((encodedEntry[startPos] & ENCODE_FLAG_COMPRESS_OCS) ==
        ENCODE_FLAG_COMPRESS_OCS)
    {
      compressObjectClassSets = true;
    }
    return new EntryEncodeConfig(excludeDN, compressAttrDescriptions,
                                 compressObjectClassSets);
  }
  /**
   * Retrieves a string representation of this entry encode
   * configuration.
   *
   * @return  A string representation of this entry encode
   *          configuration.
   */
  public String toString()
  {
    StringBuilder buffer = new StringBuilder();
    toString(buffer);
    return buffer.toString();
  }
  /**
   * Appends a string representation of this entry encode
   * configuration to the provided buffer.
   *
   * @param  buffer  The buffer to which the information should be
   *                 appended.
   */
  public void toString(StringBuilder buffer)
  {
    buffer.append("EntryEncodeConfig(excludeDN=");
    buffer.append(excludeDN);
    buffer.append(", compressAttrDescriptions=");
    buffer.append(compressAttrDescriptions);
    buffer.append(", compressObjectClassSets=");
    buffer.append(compressObjectClassSets);
    buffer.append(")");
  }
}
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();
  }
}