From 339cfd02f1ac4ae204ef5bdff53c9b56aef96add Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Tue, 28 Oct 2014 15:27:32 +0000
Subject: [PATCH] OPENDJ-1585 Add CompactDn class as compact representation of a DN
---
opendj-sdk/opendj-core/clirr-ignored-api-changes.xml | 11 +
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIF.java | 40 +++----
opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/CompactDnTestCase.java | 115 +++++++++++++++++++++++
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleImpl.java | 2
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java | 13 +-
opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java | 113 +++++++++++++++++++++-
6 files changed, 256 insertions(+), 38 deletions(-)
diff --git a/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml b/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml
index f074429..8b7b89b 100644
--- a/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml
+++ b/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml
@@ -22,7 +22,7 @@
! CDDL HEADER END
!
! Copyright 2014 ForgeRock AS
- !
+ !
-->
<differences>
<!--
@@ -190,7 +190,7 @@
<differenceType>8001</differenceType>
<justification>Renamed SchemaValidationPolicy.Policy to SchemaValidationPolicy.Action</justification>
</difference>
-
+
<difference>
<className>org/forgerock/opendj/ldap/LDAPListenerOptions</className>
<differenceType>7002</differenceType>
@@ -383,4 +383,11 @@
<to>org.forgerock.util.promise.Function</to>
<justification>OPENDJ-1550 Replace SDK Function with Function from forgerock-util</justification>
</difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/DN</className>
+ <differenceType>7002</differenceType>
+ <method>*toNormalizedString()</method>
+ <to>*toIrreversibleNormalizedByteString()</to>
+ <justification>OPENDJ-1585 Function has been renamed to avoid abuse</justification>
+ </difference>
</differences>
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java
index 4ed0fa0..1cd8d2d 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java
@@ -30,6 +30,7 @@
import static com.forgerock.opendj.util.StaticUtils.getBytes;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_DN_TYPE_NOT_FOUND;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -911,14 +912,18 @@
}
/**
- * Returns the normalized string representation of a DN.
+ * Returns the irreversible normalized byte string representation of a DN, suitable for equality and comparisons,
+ * and providing a natural hierarchical ordering but not usable as a valid DN nor reversible to a valid DN.
+ * <p>
+ * This representation should be used only when a byte string representation is needed and when no reversibility to
+ * a valid DN is needed. Always consider using a {@code CompactDn} as an alternative.
*
- * @return The normalized string representation of the provided DN.
+ * @return The normalized string representation of the provided DN, not usable as a valid DN
*/
- public String toNormalizedString() {
+ public ByteString toIrreversibleNormalizedByteString() {
final StringBuilder builder = new StringBuilder(size());
if (rdn() == null) {
- return builder.toString();
+ return ByteString.empty();
}
int i = size() - 1;
@@ -931,7 +936,105 @@
}
normalizeRDN(builder, rdn);
}
- return builder.toString();
+ return ByteString.valueOf(builder.toString());
+ }
+
+ /**
+ * A compact representation of a DN, suitable for equality and comparisons, and providing a natural hierarchical
+ * ordering.
+ * <p>
+ * This representation should be used when it is important to reduce memory usage. The memory consumption compared
+ * to a regular DN object is minimal. Prototypical usage is for static groups implementation where large groups of
+ * DNs must be recorded and must be converted back to DNs.
+ * <p>
+ * This representation can be created either eagerly or lazily.
+ * <ul>
+ * <li>eagerly: the normalized value is computed immediately at creation time.</li>
+ * <li>lazily: the normalized value is computed only the first time it is needed.</li>
+ * </ul>
+ */
+ public static final class CompactDn implements Comparable<CompactDn> {
+
+ /** Original string corresponding to the DN. */
+ private final byte[] originalValue;
+
+ /**
+ * Normalized byte string, suitable for equality and comparisons, and providing a natural hierarchical ordering,
+ * but not usable as a valid DN.
+ */
+ private byte[] normalizedValue;
+
+ private CompactDn(final DN dn, final boolean initializeNormalizedValueEagerly) {
+ this.originalValue = dn.stringValue != null ? getBytes(dn.stringValue) : new byte[0];
+ if (initializeNormalizedValueEagerly) {
+ normalizedValue = dn.toIrreversibleNormalizedByteString().toByteArray();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int compareTo(final CompactDn other) {
+ return ByteString.wrap(getNormalizedValue()).compareTo(ByteString.wrap(other.getNormalizedValue()));
+ }
+
+ /**
+ * Returns the DN corresponding to this compact representation.
+ *
+ * @return the DN
+ */
+ public DN toDn() {
+ return DN.valueOf(ByteString.wrap(originalValue).toString());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(getNormalizedValue());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ } else if (obj instanceof CompactDn) {
+ final CompactDn other = (CompactDn) obj;
+ return Arrays.equals(getNormalizedValue(), other.getNormalizedValue());
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return ByteString.wrap(originalValue).toString();
+ }
+
+ private byte[] getNormalizedValue() {
+ if (normalizedValue == null) {
+ normalizedValue = toDn().toIrreversibleNormalizedByteString().toByteArray();
+ }
+ return normalizedValue;
+ }
+ }
+
+ /**
+ * Returns a compact representation of this DN, with lazy evaluation of the normalized value.
+ *
+ * @return the DN compact representation
+ */
+ public CompactDn compact() {
+ return new CompactDn(this, false);
+ }
+
+ /**
+ * Returns a compact representation of this DN, with eager evaluation of the normalized value.
+ *
+ * @return the DN compact representation
+ */
+ public CompactDn compactEagerly() {
+ return new CompactDn(this, true);
}
/**
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java
index af27b44..a1f8724 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java
@@ -110,7 +110,7 @@
return value.toByteString();
}
- final String certificateIssuer = normalizeDN(schema, dnstring);
+ final ByteString certificateIssuer = normalizeDN(schema, dnstring);
return createEncodedValue(serialNumber, certificateIssuer);
}
@@ -183,14 +183,14 @@
throw DecodeException.error(message);
}
- final String certificateIssuer = normalizeDN(schema, dnstring);
+ final ByteString certificateIssuer = normalizeDN(schema, dnstring);
return DefaultAssertion.equality(createEncodedValue(serialNumber, certificateIssuer));
}
- private String normalizeDN(final Schema schema, final String dnstring) throws DecodeException {
+ private ByteString normalizeDN(final Schema schema, final String dnstring) throws DecodeException {
try {
DN dn = DN.valueOf(dnstring, schema.asNonStrictSchema());
- return dn.toNormalizedString();
+ return dn.toIrreversibleNormalizedByteString();
} catch (Exception e) {
logger.traceException(e);
@@ -210,10 +210,9 @@
*
* @return the encoded ByteString
*/
- private static ByteString createEncodedValue(BigInteger serial,
- String issuerDN) {
+ private static ByteString createEncodedValue(BigInteger serial, ByteString issuerDN) {
ByteStringBuilder builder = new ByteStringBuilder();
- builder.append(StaticUtils.getBytes(issuerDN));
+ builder.append(issuerDN);
builder.append((byte) 0); // Separator
builder.append(serial.toByteArray());
return builder.toByteString();
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleImpl.java
index ad87cce..71a36f5 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleImpl.java
@@ -43,7 +43,7 @@
throws DecodeException {
try {
DN dn = DN.valueOf(value.toString(), schema.asNonStrictSchema());
- return ByteString.valueOf(dn.toNormalizedString());
+ return dn.toIrreversibleNormalizedByteString();
} catch (final LocalizedIllegalArgumentException e) {
throw DecodeException.error(e.getMessageObject());
}
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIF.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIF.java
index 3486995..989eb25 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIF.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIF.java
@@ -26,7 +26,6 @@
package org.forgerock.opendj.ldif;
import static com.forgerock.opendj.ldap.CoreMessages.*;
-import static com.forgerock.opendj.util.StaticUtils.getBytes;
import java.io.IOException;
import java.util.ArrayList;
@@ -448,7 +447,7 @@
while (patch.hasNext()) {
final ChangeRecord change = patch.readChangeRecord();
final DN changeDN = change.getName();
- final byte[] changeNormDN = getBytes(change.getName().toNormalizedString());
+ final byte[] changeNormDN = toNormalizedByteArray(change.getName());
final DecodeException de =
change.accept(new ChangeRecordVisitor<DecodeException, Void>() {
@@ -462,8 +461,7 @@
try {
final Entry entry =
listener.handleDuplicateEntry(change, existingEntry);
- entries.put(getBytes(entry.getName().toNormalizedString()),
- encodeEntry(entry)[1]);
+ entries.put(toNormalizedByteArray(entry.getName()), encodeEntry(entry)[1]);
} catch (final DecodeException e) {
return e;
}
@@ -489,9 +487,8 @@
if (change.getControl(SubtreeDeleteRequestControl.DECODER,
new DecodeOptions()) != null) {
entries.subMap(
- getBytes(change.getName().toNormalizedString()),
- getBytes(change.getName().child(RDN.maxValue())
- .toNormalizedString())).clear();
+ toNormalizedByteArray(change.getName()),
+ toNormalizedByteArray(change.getName().child(RDN.maxValue()))).clear();
} else {
entries.remove(changeNormDN);
}
@@ -535,8 +532,7 @@
// @formatter:off
final Iterator<Map.Entry<byte[], byte[]>> i =
entries.subMap(changeNormDN,
- getBytes(changeDN.child(RDN.maxValue()).
- toNormalizedString())).entrySet().iterator();
+ toNormalizedByteArray(changeDN.child(RDN.maxValue()))).entrySet().iterator();
// @formatter:on
while (i.hasNext()) {
@@ -544,8 +540,7 @@
final Entry entry = decodeEntry(e.getValue());
final DN renamedDN = entry.getName().rename(oldDN, newDN);
entry.setName(renamedDN);
- renamedEntries.put(getBytes(renamedDN.toNormalizedString()),
- encodeEntry(entry)[1]);
+ renamedEntries.put(toNormalizedByteArray(renamedDN), encodeEntry(entry)[1]);
i.remove();
}
@@ -562,18 +557,16 @@
targetEntry.addAttribute(ava.toAttribute());
}
- renamedEntries.remove(getBytes(targetEntry.getName()
- .toNormalizedString()));
- renamedEntries.put(getBytes(targetEntry.getName()
- .toNormalizedString()), encodeEntry(targetEntry)[1]);
+ renamedEntries.remove(toNormalizedByteArray(targetEntry.getName()));
+ renamedEntries.put(toNormalizedByteArray(targetEntry.getName()),
+ encodeEntry(targetEntry)[1]);
// Add the renamed entries.
final Iterator<byte[]> j = renamedEntries.values().iterator();
while (j.hasNext()) {
final Entry renamedEntry = decodeEntry(j.next());
final byte[] existingEntryDn =
- entries.get(getBytes(renamedEntry.getName()
- .toNormalizedString()));
+ entries.get(toNormalizedByteArray(renamedEntry.getName()));
if (existingEntryDn != null) {
final Entry existingEntry = decodeEntry(existingEntryDn);
@@ -581,15 +574,12 @@
final Entry tmp =
listener.handleDuplicateEntry(change,
existingEntry, renamedEntry);
- entries.put(
- getBytes(tmp.getName().toNormalizedString()),
- encodeEntry(tmp)[1]);
+ entries.put(toNormalizedByteArray(tmp.getName()), encodeEntry(tmp)[1]);
} catch (final DecodeException e) {
return e;
}
} else {
- entries.put(getBytes(renamedEntry.getName()
- .toNormalizedString()),
+ entries.put(toNormalizedByteArray(renamedEntry.getName()),
encodeEntry(renamedEntry)[1]);
}
}
@@ -839,10 +829,14 @@
}
}
+ private static byte[] toNormalizedByteArray(DN dn) {
+ return dn.toIrreversibleNormalizedByteString().toByteArray();
+ }
+
private static byte[][] encodeEntry(final Entry entry) {
final byte[][] bEntry = new byte[2][];
// Store normalized DN
- bEntry[0] = getBytes(entry.getName().toNormalizedString());
+ bEntry[0] = toNormalizedByteArray(entry.getName());
try {
// Store ASN1 representation of the entry.
final ByteStringBuilder bsb = new ByteStringBuilder();
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/CompactDnTestCase.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/CompactDnTestCase.java
new file mode 100644
index 0000000..7c52578
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/CompactDnTestCase.java
@@ -0,0 +1,115 @@
+/*
+ * 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 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap;
+
+import org.forgerock.opendj.ldap.DN.CompactDn;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.fest.assertions.Assertions.*;
+
+/**
+ * This class defines a set of tests for the org.forgerock.opendj.ldap.DN.CompactDn class.
+ */
+@SuppressWarnings("javadoc")
+public class CompactDnTestCase extends SdkTestCase {
+
+ /**
+ * DN test data provider.
+ *
+ * @return The array of test DN strings.
+ */
+ @DataProvider
+ public Object[][] equivalentDnRepresentations() {
+ return new Object[][] {
+ { "" , "" },
+ { " " , "" },
+ { "cn=" , "cn=" },
+ { "cn= " , "cn=" },
+ { "cn =" , "cn=" },
+ { "cn = " , "cn=" },
+ { "dc=com" , "dc=com" },
+ { "dc=com+o=com" , "dc=com+o=com" },
+ { "DC=COM" , "DC=COM" },
+ { "dc = com" , "dc=com" },
+ { " dc = com " , "dc=com" },
+ { "dc=example,dc=com" , "dc=example,dc=com" },
+ { "dc=example, dc=com" , "dc=example,dc=com" },
+ { "dc=example ,dc=com" , "dc=example,dc=com" },
+ { "dc =example , dc = com" , "dc=example,dc=com" },
+ { "givenName=John+cn=Doe,ou=People,dc=example,dc=com",
+ "givenName=John+cn=Doe,ou=People,dc=example,dc=com" },
+ { "givenName=John\\+cn=Doe,ou=People,dc=example,dc=com",
+ "givenName=John\\+cn=Doe,ou=People,dc=example,dc=com" },
+ { "cn=Doe\\, John,ou=People,dc=example,dc=com", "cn=Doe\\, John,ou=People,dc=example,dc=com" },
+ { "UID=jsmith,DC=example,DC=net", "UID=jsmith,DC=example,DC=net" },
+ { "OU=Sales+CN=J. Smith,DC=example,DC=net",
+ "OU=Sales+CN=J. Smith,DC=example,DC=net" },
+ { "CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net",
+ "CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net" },
+ { "CN=John Smith\\2C III,DC=example,DC=net", "CN=John Smith\\, III,DC=example,DC=net" },
+ { "CN=\\23John Smith\\20,DC=example,DC=net", "CN=\\#John Smith\\ ,DC=example,DC=net" },
+ { "CN=Before\\0dAfter,DC=example,DC=net",
+ // \0d is a hex representation of Carriage return. It is mapped
+ // to a SPACE as defined in the MAP ( RFC 4518)
+ "CN=Before\\0dAfter,DC=example,DC=net" },
+ { "2.5.4.3=#04024869",
+ // Unicode codepoints from 0000-0008 are mapped to nothing.
+ "2.5.4.3=\\04\\02Hi" },
+ { "1.1.1=" , "1.1.1=" },
+ { "CN=Lu\\C4\\8Di\\C4\\87" , "CN=Lu\u010di\u0107" },
+ { "ou=\\e5\\96\\b6\\e6\\a5\\ad\\e9\\83\\a8,o=Airius", "ou=\u55b6\u696d\u90e8,o=Airius" },
+ { "photo=\\ john \\ ,dc=com" , "photo=\\ john \\ ,dc=com" },
+ { "AB-global=" , "AB-global=" },
+ { "OU= Sales + CN = J. Smith ,DC=example,DC=net", "OU=Sales+CN=J. Smith,DC=example,DC=net" },
+ { "cn=John+a=b" , "cn=John+a=b" },
+ { "O=\"Sue, Grabbit and Runn\",C=US", "O=Sue\\, Grabbit and Runn,C=US" }, };
+ }
+
+ @Test(dataProvider = "equivalentDnRepresentations")
+ public void testEquals(String dn, String otherDn) throws Exception {
+ CompactDn compactDn = DN.valueOf(dn).compact();
+ CompactDn compactDnEager = DN.valueOf(dn).compactEagerly();
+ CompactDn compactOtherDn = DN.valueOf(otherDn).compact();
+ CompactDn compactOtherDnEager = DN.valueOf(otherDn).compactEagerly();
+
+ assertThat(compactDn).isEqualTo(compactOtherDn);
+ assertThat(compactDn).isEqualTo(compactOtherDnEager);
+ assertThat(compactDnEager).isEqualTo(compactOtherDn);
+ }
+
+ @Test(dataProvider = "equivalentDnRepresentations")
+ public void testCompareTo(String dn, String otherDn) throws Exception {
+ CompactDn compactDn = DN.valueOf(dn).compact();
+ CompactDn compactDnEager = DN.valueOf(dn).compactEagerly();
+ CompactDn compactOtherDn = DN.valueOf(otherDn).compact();
+ CompactDn compactOtherDnEager = DN.valueOf(otherDn).compactEagerly();
+
+ assertThat(compactDn.compareTo(compactOtherDn)).isEqualTo(0);
+ assertThat(compactDn.compareTo(compactOtherDnEager)).isEqualTo(0);
+ assertThat(compactDnEager.compareTo(compactOtherDn)).isEqualTo(0);
+ }
+}
--
Gitblit v1.10.0