mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Nicolas Capponi
28.27.2014 fc3433194b536e769330dd79caf7f2d5f3ba6717
OPENDJ-1585 Add CompactDn class as compact representation of a DN

Rename DN.toNormalizedString() to DN.toIrreversibleNormalizedByteString()
1 files added
5 files modified
294 ■■■■ changed files
opendj-core/clirr-ignored-api-changes.xml 11 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java 113 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java 13 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleImpl.java 2 ●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIF.java 40 ●●●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/CompactDnTestCase.java 115 ●●●●● patch | view | raw | blame | history
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>
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);
    }
    /**
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();
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());
        }
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();
opendj-core/src/test/java/org/forgerock/opendj/ldap/CompactDnTestCase.java
New file
@@ -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);
    }
}