| New file |
| | |
| | | /* |
| | | * 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(); |
| | | } |
| | | } |