/*
|
* 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.jeb;
|
|
import java.io.ByteArrayInputStream;
|
import java.util.Arrays;
|
import java.util.List;
|
import java.util.Map;
|
|
import org.forgerock.opendj.ldap.ByteString;
|
import org.forgerock.opendj.ldap.ByteStringBuilder;
|
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.backends.jeb.JebFormat.*;
|
import static org.opends.server.util.StaticUtils.*;
|
import static org.testng.Assert.*;
|
|
/**
|
* JebFormat Tester.
|
*/
|
@SuppressWarnings("javadoc")
|
public class TestJebFormat extends JebTestCase {
|
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" + "";
|
|
@DataProvider
|
public Object[][] entryIDToAndFromDatabaseDataProvider()
|
{
|
return new Object[][] {
|
{ 128 }, { 1234567 }, { 0 }, { 1 }, { -1 },
|
{ 2 ^ 32 - 1 }, { 2 ^ 63 - 1 }, { Long.MIN_VALUE }, { Long.MAX_VALUE },
|
};
|
}
|
|
/**
|
* Test entry IDs.
|
*/
|
@Test(dataProvider = "entryIDToAndFromDatabaseDataProvider")
|
public void testEntryIDToAndFromDatabase(long before) throws Exception
|
{
|
byte[] bytes = JebFormat.entryIDToDatabase(before);
|
long after = JebFormat.entryIDFromDatabase(bytes);
|
assertEquals(after, before);
|
}
|
|
@DataProvider
|
public Object[][] entryIDUndefinedSizeToAndFromDatabaseDataProvider()
|
{
|
return new Object[][] {
|
{ 128 }, { 1234567 }, { 0 }, { 1 },
|
{ 2 ^ 32 - 1 }, { 2 ^ 63 - 1 }, { Long.MAX_VALUE },
|
};
|
}
|
|
/**
|
* Test entry ID set counts.
|
*/
|
@Test(dataProvider = "entryIDUndefinedSizeToAndFromDatabaseDataProvider")
|
public void testEntryIDUndefinedSizeToAndFromDatabase(long before) throws Exception
|
{
|
byte[] bytes = JebFormat.entryIDUndefinedSizeToDatabase(before);
|
assertEquals(bytes[0] & 0x80, 0x80);
|
long after = JebFormat.entryIDUndefinedSizeFromDatabase(bytes);
|
assertEquals(after, before);
|
}
|
|
@Test
|
public void testEntryIDUndefinedSizeFromDatabase() throws Exception
|
{
|
assertEquals(JebFormat.entryIDUndefinedSizeFromDatabase(null), 0);
|
assertEquals(JebFormat.entryIDUndefinedSizeFromDatabase(new byte[0]), Long.MAX_VALUE);
|
assertEquals(JebFormat.entryIDUndefinedSizeFromDatabase(new byte[9]), Long.MAX_VALUE);
|
}
|
|
/**
|
* Test entry ID lists.
|
*/
|
@Test(dataProvider = "entryIDListToAndFromDatabaseDataProvider")
|
public void entryIDListToAndFromDatabase(long[] before) throws Exception
|
{
|
byte[] bytes = JebFormat.entryIDListToDatabase(before);
|
long[] after = JebFormat.entryIDListFromDatabase(bytes);
|
assertTrue(Arrays.equals(before, after));
|
}
|
|
@DataProvider
|
public Object[][] entryIDListToAndFromDatabaseDataProvider() throws Exception
|
{
|
long[] array = new long[100000];
|
for (int i = 0; i < 100000; i++) {
|
array[i] = i * 2 + 1;
|
}
|
return new Object[][] {
|
{ new long[] { 1, 2, 3, 4, 5 } },
|
{ new long[] { 999999 } },
|
{ new long[] { 1, 128, 1234567 } },
|
{ array },
|
};
|
}
|
|
/**
|
* 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();
|
byte[] dnKey = dnToDNKey(DN.valueOf(dn), 0);
|
assertThat(findDNKeyParent(dnKey)).isEqualTo(expectedLength);
|
}
|
|
private void ensureTheServerIsUpAndRunning() throws Exception
|
{
|
TestCaseUtils.startServer();
|
}
|
}
|