From 6bcc78cb0b93450fc97be9ad9f40639543e69738 Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Thu, 15 Jan 2015 17:02:27 +0000
Subject: [PATCH] OPENDJ-1585 CR-5758 Fix DN normalization in backends.pluggable package
---
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java | 63 +++-
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/backends/pluggable/TestJebFormat.java | 558 ++++++++++++++++++++++++++++++++++++++++++
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java | 2
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/JebFormat.java | 100 +------
4 files changed, 618 insertions(+), 105 deletions(-)
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java
index d25e7ec..fa9d68a 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2006-2010 Sun Microsystems, Inc.
- * Portions Copyright 2012-2014 ForgeRock AS
+ * Portions Copyright 2012-2015 ForgeRock AS
*/
package org.opends.server.backends.pluggable;
@@ -58,16 +58,22 @@
import org.opends.server.types.SearchResultReference;
import org.opends.server.util.StaticUtils;
+import com.forgerock.opendj.util.Pair;
+
import static org.opends.messages.JebMessages.*;
import static org.opends.server.util.ServerConstants.*;
/**
* This class represents the referral database which contains URIs from referral
- * entries. The key is the DN of the referral entry and the value is that of a
- * labeled URI in the ref attribute for that entry. Duplicate keys are permitted
- * since a referral entry can contain multiple values of the ref attribute. Key
- * order is the same as in the DN database so that all referrals in a subtree
- * can be retrieved by cursoring through a range of the records.
+ * entries.
+ * <p>
+ * The key is the DN of the referral entry and the value is that of a pair
+ * (DN, list of labeled URI in the ref attribute for that entry). The DN must be
+ * duplicated in the value because the key is suitable for comparisons but is
+ * not reversible to a valid DN. Duplicate keys are permitted since a referral
+ * entry can contain multiple values of the ref attribute. Key order is the same
+ * as in the DN database so that all referrals in a subtree can be retrieved by
+ * cursoring through a range of the records.
*/
public class DN2URI extends DatabaseContainer
{
@@ -112,11 +118,15 @@
prefixRDNComponents = entryContainer.getBaseDN().size();
}
- private ByteSequence encode(Collection<String> col)
+ private ByteSequence encode(DN dn, Collection<String> col)
{
if (col != null)
{
ByteStringBuilder b = new ByteStringBuilder();
+ byte[] dnBytes = StaticUtils.getBytes(dn.toString());
+ b.append(dnBytes.length);
+ b.append(dnBytes);
+
b.append(col.size());
for (String s : col)
{
@@ -129,21 +139,31 @@
return ByteString.empty();
}
- private Collection<String> decode(ByteSequence bs)
+ private Pair<DN, List<String>> decode(ByteSequence bs) throws StorageRuntimeException
{
if (!bs.isEmpty())
{
ByteSequenceReader r = bs.asReader();
+ final int dnLength = r.getInt();
+ DN dn = null;
+ try
+ {
+ dn = DN.valueOf(r.getString(dnLength));
+ }
+ catch (DirectoryException e)
+ {
+ throw new StorageRuntimeException("Unable to decode DN from binary value", e);
+ }
final int nbElems = r.getInt();
- ArrayList<String> results = new ArrayList<String>(nbElems);
+ List<String> results = new ArrayList<String>(nbElems);
for (int i = 0; i < nbElems; i++)
{
final int stringLength = r.getInt();
results.add(r.getString(stringLength));
}
- return results;
+ return Pair.of(dn, results);
}
- return new ArrayList<String>();
+ return Pair.empty();
}
/**
@@ -162,15 +182,16 @@
ByteString oldValue = read(txn, key, true);
if (oldValue != null)
{
- final Collection<String> newUris = decode(oldValue);
+ final Pair<DN, List<String>> dnAndUris = decode(oldValue);
+ final Collection<String> newUris = dnAndUris.getSecond();
if (newUris.addAll(labeledURIs))
{
- put(txn, key, encode(newUris));
+ put(txn, key, encode(dn, newUris));
}
}
else
{
- txn.putIfAbsent(treeName, key, encode(labeledURIs));
+ txn.putIfAbsent(treeName, key, encode(dn, labeledURIs));
}
containsReferrals = ConditionResult.TRUE;
}
@@ -214,10 +235,11 @@
ByteString oldValue = read(txn, key, true);
if (oldValue != null)
{
- final Collection<String> oldUris = decode(oldValue);
+ final Pair<DN, List<String>> dnAndUris = decode(oldValue);
+ final Collection<String> oldUris = dnAndUris.getSecond();
if (oldUris.removeAll(labeledURIs))
{
- put(txn, key, encode(oldUris));
+ put(txn, key, encode(dn, oldUris));
containsReferrals = containsReferrals(txn);
return true;
}
@@ -522,7 +544,8 @@
if (cursor.positionToKey(toKey(dn)))
{
// Construct a set of all the labeled URIs in the referral.
- Collection<String> labeledURIs = decode(cursor.getValue());
+ final Pair<DN, List<String>> dnAndUris = decode(cursor.getValue());
+ Collection<String> labeledURIs = dnAndUris.getSecond();
throwReferralException(targetDN, dn, labeledURIs, searchScope);
}
}
@@ -591,8 +614,6 @@
while (success && cursor.getKey().compareTo(end) < 0)
{
// We have found a subordinate referral.
- DN dn = JebFormat.dnFromDNKey(cursor.getKey(), entryContainer.getBaseDN());
-
// Make sure the referral is within scope.
if (searchOp.getScope() == SearchScope.SINGLE_LEVEL
&& JebFormat.findDNKeyParent(cursor.getKey()) != baseDN.length())
@@ -601,7 +622,9 @@
}
// Construct a list of all the URIs in the referral.
- Collection<String> labeledURIs = decode(cursor.getValue());
+ final Pair<DN, List<String>> dnAndUris = decode(cursor.getValue());
+ final DN dn = dnAndUris.getFirst();
+ final Collection<String> labeledURIs = dnAndUris.getSecond();
SearchResultReference reference = toSearchResultReference(dn, labeledURIs, searchOp.getScope());
if (!searchOp.returnReference(dn, reference))
{
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java
index 9bab1ca..b31e75c 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java
@@ -1749,7 +1749,7 @@
if (!pluginResult.continueProcessing())
{
LocalizableMessage message =
- ERR_JEB_DELETE_ABORTED_BY_SUBORDINATE_PLUGIN.get(dnFromDNKey(cursor.getKey(), getBaseDN()));
+ ERR_JEB_DELETE_ABORTED_BY_SUBORDINATE_PLUGIN.get(subordinateEntry.getName().toString());
throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message);
}
}
diff --git a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/JebFormat.java b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/JebFormat.java
index 89c2de3..e90c91b 100644
--- a/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/JebFormat.java
+++ b/opendj3-server-dev/src/server/org/opends/server/backends/pluggable/JebFormat.java
@@ -33,7 +33,6 @@
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.opends.server.types.DN;
-import org.opends.server.types.DirectoryException;
import org.opends.server.types.RDN;
import org.opends.server.util.StaticUtils;
@@ -51,59 +50,14 @@
public static final byte TAG_DIRECTORY_SERVER_ENTRY = 0x61;
/**
- * Decode a DN value from its database key representation.
+ * Find the length of bytes that represents the superior DN of the given DN
+ * key. The superior DN is represented by the initial bytes of the DN key.
*
- * @param dnKey The database key value of the DN.
- * @param prefix The DN to prefix the decoded DN value.
- * @return The decoded DN value.
- * @throws DirectoryException if an error occurs while decoding the DN value.
- * @see #dnToDNKey(DN, int)
+ * @param dnKey
+ * The database key value of the DN.
+ * @return The length of the superior DN or -1 if the given dn is the root DN
+ * or 0 if the superior DN is removed.
*/
- public static DN dnFromDNKey(ByteSequence dnKey, DN prefix) throws DirectoryException
- {
- DN dn = prefix;
- boolean escaped = false;
- ByteStringBuilder buffer = new ByteStringBuilder();
- for(int i = 0; i < dnKey.length(); i++)
- {
- if(dnKey.byteAt(i) == 0x5C)
- {
- escaped = true;
- continue;
- }
- else if(!escaped && dnKey.byteAt(i) == 0x01)
- {
- buffer.append(0x01);
- escaped = false;
- continue;
- }
- else if(!escaped && dnKey.byteAt(i) == 0x00)
- {
- if(buffer.length() > 0)
- {
- dn = dn.child(RDN.decode(buffer.toString()));
- buffer.clear();
- }
- }
- else
- {
- if(escaped)
- {
- buffer.append(0x5C);
- escaped = false;
- }
- buffer.append(dnKey.byteAt(i));
- }
- }
-
- if(buffer.length() > 0)
- {
- dn = dn.child(RDN.decode(buffer.toString()));
- }
-
- return dn;
- }
-
public static int findDNKeyParent(ByteSequence dnKey)
{
if (dnKey.length() == 0)
@@ -112,10 +66,11 @@
return -1;
}
- // We will walk backwards through the buffer and find the first unescaped comma
+ // We will walk backwards through the buffer
+ // and find the first unescaped NORMALIZED_RDN_SEPARATOR
for (int i = dnKey.length() - 1; i >= 0; i--)
{
- if (dnKey.byteAt(i) == 0x00 && i - 1 >= 0 && dnKey.byteAt(i - 1) != 0x5C)
+ if (dnKey.byteAt(i) == DN.NORMALIZED_RDN_SEPARATOR && i - 1 >= 0 && dnKey.byteAt(i - 1) != DN.NORMALIZED_ESC_BYTE)
{
return i;
}
@@ -125,6 +80,7 @@
/**
* Create a DN database key from an entry DN.
+ *
* @param dn The entry DN.
* @param prefixRDNs The number of prefix RDNs to remove from the encoded
* representation.
@@ -132,39 +88,15 @@
*/
public static ByteString dnToDNKey(DN dn, int prefixRDNs)
{
- StringBuilder buffer = new StringBuilder();
- for (int i = dn.size() - prefixRDNs - 1; i >= 0; i--)
+ ByteStringBuilder builder = new ByteStringBuilder();
+ int startSize = dn.size() - prefixRDNs - 1;
+ for (int i = startSize; i >= 0; i--)
{
- buffer.append('\u0000');
- formatRDNKey(dn.getRDN(i), buffer);
+ builder.append(DN.NORMALIZED_RDN_SEPARATOR);
+ dn.getRDN(i).toNormalizedByteString(builder);
}
- return ByteString.wrap(StaticUtils.getBytes(buffer.toString()));
+ return builder.toByteString();
}
- private static void formatRDNKey(RDN rdn, StringBuilder buffer)
- {
- if (!rdn.isMultiValued())
- {
- rdn.toString(buffer);
- }
- else
- {
- TreeSet<String> rdnElementStrings = new TreeSet<String>();
-
- for (int i=0; i < rdn.getNumValues(); i++)
- {
- StringBuilder b2 = new StringBuilder();
- rdnElementStrings.add(b2.toString());
- }
-
- Iterator<String> iterator = rdnElementStrings.iterator();
- buffer.append(iterator.next().replace("\u0001", "\\\u0001"));
- while (iterator.hasNext())
- {
- buffer.append('\u0001');
- buffer.append(iterator.next().replace("\u0001", "\\\u0001"));
- }
- }
- }
}
diff --git a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/backends/pluggable/TestJebFormat.java b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/backends/pluggable/TestJebFormat.java
new file mode 100644
index 0000000..b3a75fa
--- /dev/null
+++ b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/backends/pluggable/TestJebFormat.java
@@ -0,0 +1,558 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * 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
+ *
+ *
+ * Copyright 2006-2009 Sun Microsystems, Inc.
+ * Portions Copyright 2014-2015 ForgeRock AS
+ */
+package org.opends.server.backends.pluggable;
+
+import java.io.ByteArrayInputStream;
+import java.util.List;
+import java.util.Map;
+
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.*;
+import org.opends.server.util.LDIFReader;
+import org.opends.server.util.StaticUtils;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.opends.server.util.StaticUtils.*;
+import static org.testng.Assert.*;
+
+/**
+ * JebFormat Tester.
+ */
+@SuppressWarnings("javadoc")
+public class TestJebFormat extends DirectoryServerTestCase {
+
+ private static final String ldifString =
+ "dn: uid=user.1,ou=People,dc=example,dc=com\n"
+ + "objectClass: top\n"
+ + "objectClass: person\n"
+ + "objectClass: organizationalPerson\n"
+ + "objectClass: inetOrgPerson\n"
+ + "uid: user.1\n"
+ + "homePhone: 951-245-7634\n"
+ + "description: This is the description for Aaccf Amar.\n"
+ + "st: NC\n"
+ + "mobile: 027-085-0537\n"
+ + "postalAddress: Aaccf Amar$17984 Thirteenth Street"
+ + "$Rockford, NC 85762\n"
+ + "mail: user.1@example.com\n"
+ + "cn: Aaccf Amar\n"
+ + "l: Rockford\n"
+ + "pager: 508-763-4246\n"
+ + "street: 17984 Thirteenth Street\n"
+ + "telephoneNumber: 216-564-6748\n"
+ + "employeeNumber: 1\n"
+ + "sn: Amar\n"
+ + "givenName: Aaccf\n"
+ + "postalCode: 85762\n"
+ + "userPassword: password\n"
+ + "initials: AA\n"
+ + "\n"
+ + "dn:: b3U95Za25qWt6YOoLG89QWlyaXVz\n"
+ + "# dn:: ou=<JapaneseOU>,o=Airius\n"
+ + "objectclass: top\n"
+ + "objectclass: organizationalUnit\n"
+ + "ou:: 5Za25qWt6YOo\n"
+ + "# ou:: <JapaneseOU>\n"
+ + "ou;lang-ja:: 5Za25qWt6YOo\n"
+ + "# ou;lang-ja:: <JapaneseOU>\n"
+ + "ou;lang-ja;phonetic:: 44GI44GE44GO44KH44GG44G2\n"
+ + "# ou;lang-ja:: <JapaneseOU_in_phonetic_representation>\n"
+ + "ou;lang-en: Sales\n"
+ + "description: Japanese office\n"
+ + "\n"
+ + "dn:: dWlkPXJvZ2FzYXdhcmEsb3U95Za25qWt6YOoLG89QWlyaXVz\n"
+ + "# dn:: uid=<uid>,ou=<JapaneseOU>,o=Airius\n"
+ + "userpassword: {SHA}O3HSv1MusyL4kTjP+HKI5uxuNoM=\n"
+ + "objectclass: top\n"
+ + "objectclass: person\n"
+ + "objectclass: organizationalPerson\n"
+ + "objectclass: inetOrgPerson\n"
+ + "uid: rogasawara\n"
+ + "mail: rogasawara@airius.co.jp\n"
+ + "givenname;lang-ja:: 44Ot44OJ44OL44O8\n"
+ + "# givenname;lang-ja:: <JapaneseGivenname>\n"
+ + "sn;lang-ja:: 5bCP56yg5Y6f\n"
+ + "# sn;lang-ja:: <JapaneseSn>\n"
+ + "cn;lang-ja:: 5bCP56yg5Y6fIOODreODieODi+ODvA==\n"
+ + "# cn;lang-ja:: <JapaneseCn>\n"
+ + "title;lang-ja:: 5Za25qWt6YOoIOmDqOmVtw==\n"
+ + "# title;lang-ja:: <JapaneseTitle>\n"
+ + "preferredlanguage: ja\n"
+ + "givenname:: 44Ot44OJ44OL44O8\n"
+ + "# givenname:: <JapaneseGivenname>\n"
+ + "sn:: 5bCP56yg5Y6f\n"
+ + "# sn:: <JapaneseSn>\n"
+ + "cn:: 5bCP56yg5Y6fIOODreODieODi+ODvA==\n"
+ + "# cn:: <JapaneseCn>\n"
+ + "title:: 5Za25qWt6YOoIOmDqOmVtw==\n"
+ + "# title:: <JapaneseTitle>\n"
+ + "givenname;lang-ja;phonetic:: 44KN44Gp44Gr44O8\n"
+ + "# givenname;lang-ja;phonetic:: "
+ + "<JapaneseGivenname_in_phonetic_representation_kana>\n"
+ + "sn;lang-ja;phonetic:: 44GK44GM44GV44KP44KJ\n"
+ + "# sn;lang-ja;phonetic:: "
+ + "<JapaneseSn_in_phonetic_representation_kana>\n"
+ + "cn;lang-ja;phonetic:: 44GK44GM44GV44KP44KJIOOCjeOBqeOBq+ODvA==\n"
+ + "# cn;lang-ja;phonetic:: "
+ + "<JapaneseCn_in_phonetic_representation_kana>\n"
+ + "title;lang-ja;phonetic:: "
+ + ""
+ + "44GI44GE44GO44KH44GG44G2IOOBtuOBoeOCh+OBhg==\n"
+ + "# title;lang-ja;phonetic::\n"
+ + "# <JapaneseTitle_in_phonetic_representation_kana>\n"
+ + "givenname;lang-en: Rodney\n"
+ + "sn;lang-en: Ogasawara\n"
+ + "cn;lang-en: Rodney Ogasawara\n"
+ + "title;lang-en: Sales, Director\n" + "\n" + "";
+
+
+ /**
+ * Encodes this entry using the V3 encoding.
+ *
+ * @param buffer The buffer to encode into.
+ *
+ * @throws DirectoryException If a problem occurs while attempting
+ * to encode the entry.
+ */
+ private void encodeV1(Entry entry, ByteStringBuilder buffer)
+ throws DirectoryException
+ {
+ // The version number will be one byte.
+ buffer.append((byte)0x01);
+
+ // TODO: Can we encode the DN directly into buffer?
+ byte[] dnBytes = getBytes(entry.getName().toString());
+ buffer.appendBERLength(dnBytes.length);
+ buffer.append(dnBytes);
+
+
+ // Encode number of OCs and 0 terminated names.
+ int i = 1;
+ ByteStringBuilder bsb = new ByteStringBuilder();
+ for (String ocName : entry.getObjectClasses().values())
+ {
+ bsb.append(ocName);
+ if(i < entry.getObjectClasses().values().size())
+ {
+ bsb.append((byte)0x00);
+ }
+ i++;
+ }
+ buffer.appendBERLength(bsb.length());
+ buffer.append(bsb);
+
+
+ // Encode the user attributes in the appropriate manner.
+ encodeV1Attributes(buffer, entry.getUserAttributes());
+
+
+ // The operational attributes will be encoded in the same way as
+ // the user attributes.
+ encodeV1Attributes(buffer, entry.getOperationalAttributes());
+ }
+
+ private void encodeV1Attributes(ByteStringBuilder buffer,
+ Map<AttributeType,List<Attribute>> attributes)
+ throws DirectoryException
+ {
+ int numAttributes = 0;
+
+ // First count how many attributes are there to encode.
+ for (List<Attribute> attrList : attributes.values())
+ {
+ for (Attribute a : attrList)
+ {
+ if (a.isVirtual() || a.isEmpty())
+ {
+ continue;
+ }
+
+ numAttributes++;
+ }
+ }
+
+ // Encoded one-to-five byte number of attributes
+ buffer.appendBERLength(numAttributes);
+
+ append(buffer, attributes);
+ }
+
+ /**
+ * Encodes this entry using the V3 encoding.
+ *
+ * @param buffer The buffer to encode into.
+ *
+ * @throws DirectoryException If a problem occurs while attempting
+ * to encode the entry.
+ */
+ private void encodeV2(Entry entry, ByteStringBuilder buffer,
+ EntryEncodeConfig config)
+ throws DirectoryException
+ {
+ // The version number will be one byte.
+ buffer.append((byte)0x02);
+
+ // Get the encoded respresentation of the config.
+ config.encode(buffer);
+
+ // 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.
+ if (! config.excludeDN())
+ {
+ // TODO: Can we encode the DN directly into buffer?
+ byte[] dnBytes = getBytes(entry.getName().toString());
+ buffer.appendBERLength(dnBytes.length);
+ buffer.append(dnBytes);
+ }
+
+
+ // Encode the object classes in the appropriate manner.
+ if (config.compressObjectClassSets())
+ {
+ config.getCompressedSchema().encodeObjectClasses(buffer,
+ entry.getObjectClasses());
+ }
+ else
+ {
+ // Encode number of OCs and 0 terminated names.
+ int i = 1;
+ ByteStringBuilder bsb = new ByteStringBuilder();
+ for (String ocName : entry.getObjectClasses().values())
+ {
+ bsb.append(ocName);
+ if(i < entry.getObjectClasses().values().size())
+ {
+ bsb.append((byte)0x00);
+ }
+ i++;
+ }
+ buffer.appendBERLength(bsb.length());
+ buffer.append(bsb);
+ }
+
+
+ // Encode the user attributes in the appropriate manner.
+ encodeV2Attributes(buffer, entry.getUserAttributes(), config);
+
+
+ // The operational attributes will be encoded in the same way as
+ // the user attributes.
+ encodeV2Attributes(buffer, entry.getOperationalAttributes(), config);
+ }
+
+ private void encodeV2Attributes(ByteStringBuilder buffer,
+ Map<AttributeType,List<Attribute>> attributes,
+ EntryEncodeConfig config)
+ throws DirectoryException
+ {
+ int numAttributes = 0;
+
+ // First count how many attributes are there to encode.
+ for (List<Attribute> attrList : attributes.values())
+ {
+ for (Attribute a : attrList)
+ {
+ if (a.isVirtual() || a.isEmpty())
+ {
+ continue;
+ }
+
+ numAttributes++;
+ }
+ }
+
+ // Encoded one-to-five byte number of attributes
+ buffer.appendBERLength(numAttributes);
+
+ if (config.compressAttributeDescriptions())
+ {
+ for (List<Attribute> attrList : attributes.values())
+ {
+ for (Attribute a : attrList)
+ {
+ if (a.isVirtual() || a.isEmpty())
+ {
+ continue;
+ }
+
+ ByteStringBuilder bsb = new ByteStringBuilder();
+ config.getCompressedSchema().encodeAttribute(bsb, a);
+ buffer.appendBERLength(bsb.length());
+ buffer.append(bsb);
+ }
+ }
+ }
+ else
+ {
+ append(buffer, attributes);
+ }
+ }
+
+ /**
+ * The attributes will be encoded as 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
+ */
+ private void append(ByteStringBuilder buffer,
+ Map<AttributeType, List<Attribute>> attributes)
+ {
+ for (List<Attribute> attrList : attributes.values())
+ {
+ for (Attribute a : attrList)
+ {
+ byte[] nameBytes = getBytes(a.getNameWithOptions());
+ buffer.append(nameBytes);
+ buffer.append((byte)0x00);
+
+ buffer.appendBERLength(a.size());
+ for (ByteString v : a)
+ {
+ buffer.appendBERLength(v.length());
+ buffer.append(v);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test entry.
+ *
+ * @throws Exception
+ * If the test failed unexpectedly.
+ */
+ @Test()
+ public void testEntryToAndFromDatabase() throws Exception {
+ ensureTheServerIsUpAndRunning();
+
+ // 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, entryAfter;
+ while ((entryBefore = reader.readEntry(false)) != null) {
+ ByteString bytes = ID2Entry.entryToDatabase(entryBefore,
+ new DataConfig(false, false, null));
+
+ entryAfter = ID2Entry.entryFromDatabase(bytes,
+ DirectoryServer.getDefaultCompressedSchema());
+
+ // check DN and number of attributes
+ assertEquals(entryBefore.getAttributes().size(), entryAfter
+ .getAttributes().size());
+
+ assertEquals(entryBefore.getName(), entryAfter.getName());
+
+ // check the object classes were not changed
+ for (String ocBefore : entryBefore.getObjectClasses().values()) {
+ ObjectClass objectClass = DirectoryServer.getObjectClass(ocBefore
+ .toLowerCase());
+ if (objectClass == null) {
+ objectClass = DirectoryServer.getDefaultObjectClass(ocBefore);
+ }
+ String ocAfter = entryAfter.getObjectClasses().get(objectClass);
+
+ assertEquals(ocBefore, ocAfter);
+ }
+
+ // check the user attributes were not changed
+ for (AttributeType attrType : entryBefore.getUserAttributes()
+ .keySet()) {
+ List<Attribute> listBefore = entryBefore.getAttribute(attrType);
+ List<Attribute> listAfter = entryAfter.getAttribute(attrType);
+
+ assertTrue(listAfter != null);
+
+ 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
+
+ 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 {
+ ensureTheServerIsUpAndRunning();
+
+ // 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, entryAfterV1;
+ while ((entryBefore = reader.readEntry(false)) != null) {
+ ByteStringBuilder bsb = new ByteStringBuilder();
+ encodeV1(entryBefore, bsb);
+ entryAfterV1 = Entry.decode(bsb.asReader());
+
+ assertEquals(entryBefore, 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 {
+ ensureTheServerIsUpAndRunning();
+
+ // 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, entryAfterV2;
+ while ((entryBefore = reader.readEntry(false)) != null) {
+ ByteStringBuilder bsb = new ByteStringBuilder();
+ encodeV2(entryBefore, bsb, config);
+ entryAfterV2 = Entry.decode(bsb.asReader());
+ if (config.excludeDN())
+ {
+ entryAfterV2.setDN(entryBefore.getName());
+ }
+ assertEquals(entryBefore, entryAfterV2);
+ }
+ reader.close();
+ }
+
+ /**
+ * Tests the entry encoding and decoding process the version 1 encoding.
+ *
+ * @throws Exception
+ * If the test failed unexpectedly.
+ */
+ @Test(dataProvider = "encodeConfigs")
+ public void testEntryToAndFromDatabaseV3(EntryEncodeConfig config)
+ throws Exception {
+ ensureTheServerIsUpAndRunning();
+
+ // 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, entryAfterV3;
+ while ((entryBefore = reader.readEntry(false)) != null) {
+ ByteStringBuilder bsb = new ByteStringBuilder();
+ entryBefore.encode(bsb, config);
+ entryAfterV3 = Entry.decode(bsb.asReader());
+ if (config.excludeDN())
+ {
+ entryAfterV3.setDN(entryBefore.getName());
+ }
+ assertEquals(entryBefore, entryAfterV3);
+ }
+ reader.close();
+ }
+
+ @DataProvider
+ private Object[][] findDnKeyParentData()
+ {
+ return new Object[][]
+ {
+ // dn, expected length of parent
+ { "dc=example", 0 },
+ { "dc=example,dc=com", 7 },
+ { "dc=example,dc=com\\,org", 11 },
+
+ };
+ }
+
+ @Test(dataProvider="findDnKeyParentData")
+ public void testFindDnKeyParent(String dn, int expectedLength) throws Exception
+ {
+ ensureTheServerIsUpAndRunning();
+ ByteString dnKey = JebFormat.dnToDNKey(DN.valueOf(dn), 0);
+ assertThat(JebFormat.findDNKeyParent(dnKey)).isEqualTo(expectedLength);
+ }
+
+ private void ensureTheServerIsUpAndRunning() throws Exception
+ {
+ TestCaseUtils.startServer();
+ }
+}
--
Gitblit v1.10.0