From c759d48434d26addbfacb09a0e60fc1906bfbb5b Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Fri, 06 Apr 2007 19:03:09 +0000
Subject: [PATCH] Make a change to the way that we encode entries for storage in the backend, and move the logic from the JE backend into the core server. Testing with entries generated from the example.template shows that the new encoding is about 15% smaller than the previous form, and exact search tests with no entry cache are about 7% faster with the new encoding.
---
opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java | 28 ++
opendj-sdk/opends/src/server/org/opends/server/types/Entry.java | 588 +++++++++++++++++++++++++++++++++++++++++++++
opendj-sdk/opends/src/server/org/opends/server/types/Attribute.java | 28 ++
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/JebFormat.java | 128 ---------
4 files changed, 646 insertions(+), 126 deletions(-)
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 c5e141f..68515a6 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
@@ -33,23 +33,15 @@
import org.opends.server.protocols.asn1.ASN1Integer;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Sequence;
-import org.opends.server.protocols.asn1.ASN1Set;
-import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPException;
-import org.opends.server.types.Attribute;
-import org.opends.server.types.AttributeType;
import org.opends.server.types.CryptoManager;
import org.opends.server.types.DirectoryException;
-import org.opends.server.types.DN;
import org.opends.server.types.Entry;
-import org.opends.server.types.ObjectClass;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.debugInfo;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
import java.util.zip.DataFormatException;
@@ -177,78 +169,7 @@
static private Entry decodeDirectoryServerEntry(byte[] bytes)
throws DirectoryException,ASN1Exception,LDAPException
{
- HashMap<ObjectClass, String> objectClasses;
- HashMap<AttributeType, List<Attribute>> userAttributes =
- new HashMap<AttributeType, List<Attribute>>();
- HashMap<AttributeType, List<Attribute>> operationalAttributes =
- new HashMap<AttributeType, List<Attribute>>();
-
- // Decode the sequence.
- List<ASN1Element> elements;
- elements = ASN1Sequence.decodeAsSequence(bytes).elements();
-
- // Decode the dn (LDAPDN).
- DN dn;
- dn = DN.decode(elements.get(0).decodeAsOctetString().stringValue());
-
- // Decode the object classes (SET OF LDAPString).
- List<ASN1Element> attrElements =
- elements.get(1).decodeAsSet().elements();
- objectClasses =
- new HashMap<ObjectClass, String>(attrElements.size() * 4 / 3);
- for (ASN1Element e : attrElements)
- {
- String ocName = e.decodeAsOctetString().stringValue();
- String lowerOCName = ocName.toLowerCase();
-
- ObjectClass objectClass = DirectoryServer.getObjectClass(lowerOCName);
- if (objectClass == null)
- {
- objectClass = DirectoryServer.getDefaultObjectClass(ocName);
- }
-
- objectClasses.put(objectClass, ocName);
- }
-
- // Decode the user attributes (AttributeList).
- attrElements = elements.get(2).decodeAsSequence().elements();
- for (ASN1Element e : attrElements)
- {
- Attribute a = LDAPAttribute.decode(e).toAttribute();
- List<Attribute> attrList;
- attrList = userAttributes.get(a.getAttributeType());
- if (attrList == null)
- {
- attrList = new ArrayList<Attribute>(1);
- attrList.add(a);
- userAttributes.put(a.getAttributeType(), attrList);
- }
- else
- {
- attrList.add(a);
- }
- }
-
- // Decode the operational attributes (AttributeList).
- attrElements = elements.get(3).decodeAsSequence().elements();
- for (ASN1Element e : attrElements)
- {
- Attribute a = LDAPAttribute.decode(e).toAttribute();
- List<Attribute> attrList;
- attrList = operationalAttributes.get(a.getAttributeType());
- if (attrList == null)
- {
- attrList = new ArrayList<Attribute>(1);
- attrList.add(a);
- operationalAttributes.put(a.getAttributeType(), attrList);
- }
- else
- {
- attrList.add(a);
- }
- }
-
- return new Entry(dn, objectClasses, userAttributes, operationalAttributes);
+ return Entry.decode(bytes);
}
/**
@@ -336,52 +257,7 @@
*/
static private byte[] encodeDirectoryServerEntry(Entry entry)
{
- // Encode the DN (LDAPDN).
- ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(4);
- elements.add(new ASN1OctetString(entry.getDN().toString()));
-
- // Encode the object classes (SET OF LDAPString).
- Collection<String> objectClasses = entry.getObjectClasses().values();
- ArrayList<ASN1Element> objectClassElements;
- objectClassElements = new ArrayList<ASN1Element>(objectClasses.size());
- for (String s : objectClasses)
- {
- objectClassElements.add(new ASN1OctetString(s));
- }
- elements.add(new ASN1Set(objectClassElements));
-
- // Encode the user attributes (AttributeList).
- ArrayList<ASN1Element> userAttrElements = new ArrayList<ASN1Element>();
- for (List<Attribute> list : entry.getUserAttributes().values())
- {
- for (Attribute a : list)
- {
- if (a.isVirtual())
- {
- continue;
- }
- userAttrElements.add(new LDAPAttribute(a).encode());
- }
- }
- elements.add(new ASN1Sequence(userAttrElements));
-
- // Encode the operational attributes (AttributeList).
- ArrayList<ASN1Element> opAttrElements = new ArrayList<ASN1Element>();
- for (List<Attribute> list : entry.getOperationalAttributes().values())
- {
- for (Attribute a : list)
- {
- if (a.isVirtual())
- {
- continue;
- }
- opAttrElements.add(new LDAPAttribute(a).encode());
- }
- }
- elements.add(new ASN1Sequence(opAttrElements));
-
- // Encode the sequence.
- return new ASN1Sequence(TAG_DIRECTORY_SERVER_ENTRY, elements).encode();
+ return entry.encode();
}
/**
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 6229ac3..b969e7e 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
@@ -6008,6 +6008,28 @@
public static final int MSGID_DSCORE_DESCRIPTION_WINDOWS_NET_START =
CATEGORY_MASK_CORE | SEVERITY_MASK_INFORMATIONAL | 599;
+
+
+ /**
+ * The message ID for the message that will be used if an entry is encoded
+ * with a version number that we don't support. This takes a single argument,
+ * which is a hexadecimal representation of the version number byte.
+ */
+ public static final int MSGID_ENTRY_DECODE_UNRECOGNIZED_VERSION =
+ CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 600;
+
+
+
+ /**
+ * The message ID for the message that will be used if an exception is caught
+ * while attempting to decode an entry. This takes a single argument, which
+ * is a string representation of the exception that was caught.
+ */
+ public static final int MSGID_ENTRY_DECODE_EXCEPTION =
+ CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 601;
+
+
+
/**
* Associates a set of generic messages with the message IDs defined
* in this class.
@@ -6466,6 +6488,12 @@
"Unable to increment the value of attribute %s because " +
"either the current value or the increment could not " +
"be parsed as an integer.");
+ registerMessage(MSGID_ENTRY_DECODE_UNRECOGNIZED_VERSION,
+ "Unable to decode an entry because it had an unsupported " +
+ "entry version byte value of %s.");
+ registerMessage(MSGID_ENTRY_DECODE_EXCEPTION,
+ "Unable to decode an entry because an unexpected " +
+ "exception was caught during processing: %s.");
registerMessage(MSGID_SEARCH_FILTER_NULL,
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/Attribute.java b/opendj-sdk/opends/src/server/org/opends/server/types/Attribute.java
index 029f43a..44d8c1e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/Attribute.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/Attribute.java
@@ -228,6 +228,34 @@
/**
+ * Retrieves the user-provided name of the attribute, along with any
+ * options that might have been provided.
+ *
+ * @return The user-provided name of the attribute, along with any
+ * options that might have been provided.
+ */
+ public String getNameWithOptions()
+ {
+ if (options.isEmpty())
+ {
+ return name;
+ }
+ else
+ {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append(name);
+ for (String option : options)
+ {
+ buffer.append(';');
+ buffer.append(option);
+ }
+ return buffer.toString();
+ }
+ }
+
+
+
+ /**
* Retrieves the set of attribute options for this attribute.
*
* @return The set of attribute options for this attribute.
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 214405a..edf86a8 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
@@ -48,6 +48,7 @@
import org.opends.server.api.plugin.LDIFPluginResult;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PluginConfigManager;
+import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.util.LDIFException;
@@ -3841,6 +3842,593 @@
/**
+ * Encodes this entry into a form that is suitable for long-term
+ * persistent storage. The encoding will have a version number so
+ * that if the way we store entries changes in the future we will
+ * still be able to read entries encoded in an older format.
+ *
+ * @return The entry encoded in a form that is suitable for
+ * long-term persistent storage.
+ */
+ public byte[] encode()
+ {
+ // The version number will be one byte. We'll add that later.
+
+
+ // The DN will be encoded as a one-to-five byte length followed
+ // byte the UTF-8 byte representation.
+ byte[] dnBytes = getBytes(dn.toString());
+ byte[] dnLength = ASN1Element.encodeLength(dnBytes.length);
+ int totalBytes = 1 + dnBytes.length + dnLength.length;
+
+
+ // The object classes will be encoded as one-to-five byte length
+ // followed by a zero-delimited UTF-8 byte representation of the
+ // names (e.g., top\0person\0organizationalPerson\0inetOrgPerson).
+ int i=0;
+ int totalOCBytes = objectClasses.size() - 1;
+ byte[][] ocBytes = new byte[objectClasses.size()][];
+ for (String ocName : objectClasses.values())
+ {
+ ocBytes[i] = getBytes(ocName);
+ totalOCBytes += ocBytes[i++].length;
+ }
+ byte[] ocLength = ASN1Element.encodeLength(totalOCBytes);
+ totalBytes += totalOCBytes + ocLength.length;
+
+
+ // 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
+ i=0;
+ int numUserAttributes = 0;
+ int totalUserAttrBytes = 0;
+ LinkedList<byte[]> userAttrBytes = new LinkedList<byte[]>();
+ 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;
+
+
+ // The operational attributes will be encoded in the same way as
+ // the user attributes.
+ i=0;
+ int numOperationalAttributes = 0;
+ int totalOperationalAttrBytes = 0;
+ LinkedList<byte[]> operationalAttrBytes =
+ new LinkedList<byte[]>();
+ 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] = 0x01;
+
+
+ // Next, add the DN length and value.
+ System.arraycopy(dnLength, 0, entryBytes, 1, dnLength.length);
+ int pos = 1 + 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;
+ 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
+ * decoded.
+ *
+ * @return The decoded entry.
+ *
+ * @throws DirectoryException If the provided byte array cannot be
+ * decoded as an entry.
+ */
+ public static Entry decode(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;
+ String message = getMessage(msgID,
+ byteToHex(entryBytes[0]));
+ throw new DirectoryException(
+ DirectoryServer.getServerErrorResultCode(),
+ message, msgID);
+ }
+
+
+ // Next is the length of the DN. It may be a single byte or
+ // multiple bytes.
+ int pos = 1;
+ 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 = 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 encoded set of object classes. It will be a
+ // single string with the object class names separated by zeros.
+ LinkedHashMap<ObjectClass,String> 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>>();
+ for (int i=0; i < numUserAttrs; i++)
+ {
+ // First, we have the zero-terminated attribute name.
+ startPos = pos;
+ while (entryBytes[pos] != 0x00)
+ {
+ pos++;
+ }
+ 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>>();
+ for (int i=0; i < numOperationalAttrs; i++)
+ {
+ // First, we have the zero-terminated attribute name.
+ startPos = pos;
+ while (entryBytes[pos] != 0x00)
+ {
+ pos++;
+ }
+ 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())
+ {
+ debugCaught(DebugLogLevel.ERROR, e);
+ }
+
+ int msgID = MSGID_ENTRY_DECODE_EXCEPTION;
+ String message = getMessage(msgID,
+ stackTraceToSingleLineString(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.
*
--
Gitblit v1.10.0