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