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

Nicolas Capponi
16.52.2015 b82be51745e0db2395e3ee3c08e28ec3da869e29
OPENDJ-1585 Update DN normalization in SDK to align with server

- Rename normalization methods to DN.toNormalizedByteString() and
DN.toNormalizedUrlSafeString() according to same methods in server

- Update implementation of equals(), hashCode() and compareTo()
methods according to same methods in server

- Update DN class to cache normalized byte string in a field according
to server behavior

- Rename RDN and AVA normalization methods to be consistent in
naming
8 files modified
250 ■■■■■ changed files
opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java 6 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java 133 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java 23 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java 2 ●●● 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/ldap/schema/UniqueMemberEqualityMatchingRuleImpl.java 2 ●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIF.java 2 ●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java 80 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java
@@ -799,7 +799,7 @@
     * @param builder
     *            The builder to use to construct the normalized byte string.
     * @return The normalized byte string representation.
     * @see DN#toIrreversibleNormalizedByteString()
     * @see DN#toNormalizedByteString()
     */
    ByteStringBuilder toNormalizedByteString(final ByteStringBuilder builder) {
        builder.append(toLowerCase(attributeType.getNameOrOID()));
@@ -819,9 +819,9 @@
     * @param builder
     *            The builder to use to construct the normalized string.
     * @return The normalized readable string representation.
     * @see DN#toIrreversibleReadableString()
     * @see DN#toNormalizedUrlSafeString()
     */
    StringBuilder toNormalizedReadableString(final StringBuilder builder) {
    StringBuilder toNormalizedUrlSafe(final StringBuilder builder) {
        builder.append(toLowerCase(attributeType.getNameOrOID()));
        builder.append('=');
        final ByteString value = getEqualityNormalizedValue();
opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java
@@ -258,7 +258,8 @@
    /**
     * Compares the provided DN values to determine their relative order in a
     * sorted list.
     * sorted list. The order is the natural order as defined by the
     * {@code toNormalizedByteString()} method.
     *
     * @param dn1
     *            The first DN to be compared. It must not be {@code null}.
@@ -270,42 +271,7 @@
     *         values can be considered equal.
     */
    private static int compareTo(final DN dn1, final DN dn2) {
        // Quickly check if we are comparing against root dse.
        if (dn1.isRootDN()) {
            if (dn2.isRootDN()) {
                // both are equal.
                return 0;
            }
            // dn1 comes before dn2.
            return -1;
        }
        if (dn2.isRootDN()) {
            // dn1 comes after dn2.
            return 1;
        }
        int dn1Size = dn1.size - 1;
        int dn2Size = dn2.size - 1;
        while (dn1Size >= 0 && dn2Size >= 0) {
            final DN dn1Parent = dn1.parent(dn1Size--);
            final DN dn2Parent = dn2.parent(dn2Size--);
            final int result = dn1Parent.rdn.compareTo(dn2Parent.rdn);
            if (result > 0) {
                return 1;
            } else if (result < 0) {
                return -1;
            }
        }
        // What do we have here?
        if (dn1Size > dn2Size) {
            return 1;
        } else if (dn1Size < dn2Size) {
            return -1;
        }
        return 0;
        return dn1.toNormalizedByteString().compareTo(dn2.toNormalizedByteString());
    }
    /** Decodes a DN using the provided reader and schema. */
@@ -370,6 +336,12 @@
    private final int size;
    /**
     * The normalized byte string representation of this DN, which is not
     * a valid DN and is not reversible to a valid DN.
     */
    private ByteString normalizedDN;
    /**
     * We need to store the original string value if provided in order to
     * preserve the original whitespace.
     */
@@ -497,29 +469,18 @@
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        } else if (obj instanceof DN) {
            DN other = (DN) obj;
            if (size == other.size()) {
                if (size == 0) {
                    return true;
                }
                if (rdn.equals(other.rdn)) {
                    return parent.equals(other.parent);
                }
            }
        }
        if (obj instanceof DN) {
            DN otherDN = (DN) obj;
            return toNormalizedByteString().equals(otherDN.toNormalizedByteString());
        }
        return false;
    }
    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        if (size == 0) {
            return 0;
        }
        return 31 * parent.hashCode() + rdn.hashCode();
        return toNormalizedByteString().hashCode();
    }
    /**
@@ -920,62 +881,60 @@
    }
    /**
     * 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.
     * Retrieves a normalized byte string representation of this 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.
     * This representation is suitable for equality and comparisons, and
     * for providing a natural hierarchical ordering.
     * However, it is not a valid DN and can't be reverted to a valid DN.
     * You should consider using a {@code CompactDn} as an alternative.
     *
     * @return The normalized byte string representation of the provided DN, not
     *         usable as a valid DN
     * @return The normalized string representation of this DN.
     */
    public ByteString toIrreversibleNormalizedByteString() {
        if (rdn() == null) {
            return ByteString.empty();
        }
        final ByteStringBuilder builder = new ByteStringBuilder();
        int i = size() - 1;
        parent(i).rdn().toNormalizedByteString(builder);
        for (i--; i >= 0; i--) {
            final RDN rdn = parent(i).rdn();
            // Only add a separator if the RDN is not RDN.maxValue().
            if (rdn.size() != 0) {
                builder.append(DN.NORMALIZED_RDN_SEPARATOR);
    public ByteString toNormalizedByteString() {
        if (normalizedDN == null) {
            if (rdn() == null) {
                normalizedDN = ByteString.empty();
            } else {
                final ByteStringBuilder builder = new ByteStringBuilder();
                int i = size() - 1;
                parent(i).rdn().toNormalizedByteString(builder);
                for (i--; i >= 0; i--) {
                    final RDN rdn = parent(i).rdn();
                    // Only add a separator if the RDN is not RDN.maxValue().
                    if (rdn.size() != 0) {
                        builder.append(DN.NORMALIZED_RDN_SEPARATOR);
                    }
                    rdn.toNormalizedByteString(builder);
                }
                normalizedDN = builder.toByteString();
            }
            rdn.toNormalizedByteString(builder);
        }
        return builder.toByteString();
        return normalizedDN;
    }
    /**
     * Returns the irreversible readable string representation of a DN, suitable
     * for equality and usage as a name in file system or URL, but not usable as
     * a valid DN nor reversible to a valid DN.
     * Retrieves a normalized string representation of this DN.
     * <p>
     * This representation should be used only when a string representation is
     * needed and when no reversibility to a valid DN is needed.
     * This representation is safe to use in an URL or in a file name.
     * However, it is not a valid DN and can't be reverted to a valid DN.
     *
     * @return The readable string representation of the provided DN,
     *         not usable as a valid DN
     * @return The normalized string representation of this DN.
     */
    public String toIrreversibleReadableString() {
    public String toNormalizedUrlSafeString() {
        if (rdn() == null) {
            return "";
        }
        final StringBuilder builder = new StringBuilder();
        int i = size() - 1;
        parent(i).rdn().toNormalizedReadableString(builder);
        parent(i).rdn().toNormalizedUrlSafeString(builder);
        for (i--; i >= 0; i--) {
            final RDN rdn = parent(i).rdn();
            // Only add a separator if the RDN is not RDN.maxValue().
            if (rdn.size() != 0) {
                builder.append(',');
            }
            rdn.toNormalizedReadableString(builder);
            rdn.toNormalizedUrlSafeString(builder);
        }
        return builder.toString();
    }
@@ -1058,7 +1017,7 @@
        private byte[] getNormalizedValue() {
            if (normalizedValue == null) {
                normalizedValue = toDn().toIrreversibleNormalizedByteString().toByteArray();
                normalizedValue = toDn().toNormalizedByteString().toByteArray();
            }
            return normalizedValue;
        }
opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java
@@ -257,6 +257,7 @@
    }
    /** {@inheritDoc} */
    @Override
    public int compareTo(final RDN rdn) {
        // Identity.
        if (this == rdn) {
@@ -373,6 +374,7 @@
     *
     * @return An iterator of the AVAs contained in this RDN.
     */
    @Override
    public Iterator<AVA> iterator() {
        return Iterators.arrayIterator(avas);
    }
@@ -421,7 +423,7 @@
     * @param builder
     *            The builder to use to construct the normalized byte string.
     * @return The normalized byte string representation.
     * @see DN#toIrreversibleNormalizedByteString()
     * @see DN#toNormalizedByteString()
     */
    ByteStringBuilder toNormalizedByteString(final ByteStringBuilder builder) {
        switch (size()) {
@@ -445,30 +447,29 @@
    }
    /**
     * Returns the normalized readable string representation of this RDN.
     * Retrieves a normalized string representation of this RDN.
     * <p>
     * The representation is not a valid RDN.
     * This representation is safe to use in an URL or in a file name.
     * However, it is not a valid RDN and can't be reverted to a valid RDN.
     *
     * @param builder
     *            The builder to use to construct the normalized string.
     * @return The normalized readable string representation.
     * @see DN#toIrreversibleReadableString()
     * @return The normalized string representation of this RDN.
     * @see DN#toNormalizedUrlSafeString()
     */
    StringBuilder toNormalizedReadableString(final StringBuilder builder) {
    StringBuilder toNormalizedUrlSafeString(final StringBuilder builder) {
        switch (size()) {
        case 0:
            // Handle RDN.maxValue().
            builder.append(RDN.AVA_CHAR_SEPARATOR);
            break;
        case 1:
            getFirstAVA().toNormalizedReadableString(builder);
            getFirstAVA().toNormalizedUrlSafe(builder);
            break;
        default:
            Iterator<AVA> it = getSortedAvas();
            it.next().toNormalizedReadableString(builder);
            it.next().toNormalizedUrlSafe(builder);
            while (it.hasNext()) {
                builder.append(RDN.AVA_CHAR_SEPARATOR);
                it.next().toNormalizedReadableString(builder);
                it.next().toNormalizedUrlSafe(builder);
            }
            break;
        }
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java
@@ -187,7 +187,7 @@
    private ByteString normalizeDN(final Schema schema, final String dnstring) throws DecodeException {
        try {
            DN dn = DN.valueOf(dnstring, schema.asNonStrictSchema());
            return dn.toIrreversibleNormalizedByteString();
            return dn.toNormalizedByteString();
        } catch (Exception e) {
            logger.traceException(e);
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 dn.toIrreversibleNormalizedByteString();
            return dn.toNormalizedByteString();
        } catch (final LocalizedIllegalArgumentException e) {
            throw DecodeException.error(e.getMessageObject());
        }
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/UniqueMemberEqualityMatchingRuleImpl.java
@@ -57,7 +57,7 @@
        try {
            DN dn = DN.valueOf(stringValue.substring(0, dnEndPosition), schema.asNonStrictSchema());
            return new ByteStringBuilder()
                .append(dn.toIrreversibleNormalizedByteString())
                .append(dn.toNormalizedByteString())
                .append(optionalUid).toByteString();
        } catch (final LocalizedIllegalArgumentException e) {
            throw DecodeException.error(e.getMessageObject());
opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIF.java
@@ -830,7 +830,7 @@
    }
    private static byte[] toNormalizedByteArray(DN dn) {
        return dn.toIrreversibleNormalizedByteString().toByteArray();
        return dn.toNormalizedByteString().toByteArray();
    }
    private static byte[][] encodeEntry(final Entry entry) {
opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
@@ -26,6 +26,8 @@
 */
package org.forgerock.opendj.ldap;
import static java.lang.Integer.*;
import static org.fest.assertions.Assertions.*;
import static org.testng.Assert.*;
@@ -389,8 +391,8 @@
        assertEquals(c.size(), 3);
        assertEquals(c.compareTo(p), 1);
        assertEquals(p.compareTo(c), -1);
        assertEquals(signum(c.compareTo(p)), 1);
        assertEquals(signum(p.compareTo(c)), -1);
        assertTrue(p.isParentOf(c));
        assertFalse(c.isParentOf(p));
@@ -666,8 +668,8 @@
        assertEquals(p.size(), 3);
        assertEquals(p.compareTo(c), -1);
        assertEquals(c.compareTo(p), 1);
        assertEquals(signum(p.compareTo(c)), -1);
        assertEquals(signum(c.compareTo(p)), 1);
        assertTrue(p.isParentOf(c));
        assertFalse(c.isParentOf(p));
@@ -1005,12 +1007,12 @@
        assertEquals(actual, "\\#cn\\=foo\\+sn\\=bar");
    }
    /** Tests the {@link DN#toIrreversibleNormalizedByteString()} method. */
    @Test
    public void testToIrreversibleNormalizedByteStringWithRootDN() {
        ByteString actual = DN.rootDN().toIrreversibleNormalizedByteString();
        assertEquals(actual, ByteString.empty());
    }
    /** Tests the {@link DN#toNormalizedByteString()} method. */
        @Test
        public void testToNormalizedByteStringWithRootDN() {
            ByteString actual = DN.rootDN().toNormalizedByteString();
            assertEquals(actual, ByteString.empty());
        }
    /** Tests the {@link DN#iterator()} method. */
    @Test
@@ -1080,24 +1082,24 @@
    }
    @Test(dataProvider = "toIrreversibleNormalizedByteStringDataProvider")
    public void testToIrreversibleNormalizedByteString(String first, String second, int expectedCompareResult) {
        DN actual = DN.valueOf(first);
        DN expected = DN.valueOf(second);
        int cmp = actual.toIrreversibleNormalizedByteString().compareTo(expected.toIrreversibleNormalizedByteString());
        assertThat(Integer.signum(cmp)).isEqualTo(expectedCompareResult);
    }
        public void testToNormalizedByteString(String first, String second, int expectedCompareResult) {
            DN actual = DN.valueOf(first);
            DN expected = DN.valueOf(second);
            int cmp = actual.toNormalizedByteString().compareTo(expected.toNormalizedByteString());
            assertThat(signum(cmp)).isEqualTo(expectedCompareResult);
        }
    /** Additional tests with testDNs data provider. */
    @Test(dataProvider = "testDNs")
    public void testToIrreversibleNormalizedByteString2(String one, String two, String three) {
        DN dn1 = DN.valueOf(one);
        DN dn2 = DN.valueOf(two);
        DN dn3 = DN.valueOf(three);
        int cmp = dn1.toIrreversibleNormalizedByteString().compareTo(dn2.toIrreversibleNormalizedByteString());
        assertThat(cmp).isEqualTo(0);
        int cmp2 = dn1.toIrreversibleNormalizedByteString().compareTo(dn3.toIrreversibleNormalizedByteString());
        assertThat(cmp2).isEqualTo(0);
    }
        @Test(dataProvider = "testDNs")
        public void testToNormalizedByteString2(String one, String two, String three) {
            DN dn1 = DN.valueOf(one);
            DN dn2 = DN.valueOf(two);
            DN dn3 = DN.valueOf(three);
            int cmp = dn1.toNormalizedByteString().compareTo(dn2.toNormalizedByteString());
            assertThat(cmp).isEqualTo(0);
            int cmp2 = dn1.toNormalizedByteString().compareTo(dn3.toNormalizedByteString());
            assertThat(cmp2).isEqualTo(0);
        }
    @DataProvider
    public Object[][] toIrreversibleReadableStringDataProvider() {
@@ -1134,19 +1136,19 @@
    }
    @Test(dataProvider = "toIrreversibleReadableStringDataProvider")
    public void testToIrreversibleReadableString(String dnAsString, String expectedReadableString) {
        DN actual = DN.valueOf(dnAsString);
        assertEquals(actual.toIrreversibleReadableString(), expectedReadableString);
    }
        public void testToNormalizedUrlSafeString(String dnAsString, String expectedReadableString) {
            DN actual = DN.valueOf(dnAsString);
            assertEquals(actual.toNormalizedUrlSafeString(), expectedReadableString);
        }
    /** Additional tests with testDNs data provider. */
    @Test(dataProvider = "testDNs")
    public void testToIrreversibleReadableString2(String one, String two, String three) {
        DN dn1 = DN.valueOf(one);
        DN dn2 = DN.valueOf(two);
        DN dn3 = DN.valueOf(three);
        String irreversibleReadableString = dn1.toIrreversibleReadableString();
        assertEquals(irreversibleReadableString, dn2.toIrreversibleReadableString());
        assertEquals(irreversibleReadableString, dn3.toIrreversibleReadableString());
    }
        @Test(dataProvider = "testDNs")
        public void testToNormalizedUrlSafeString2(String one, String two, String three) {
            DN dn1 = DN.valueOf(one);
            DN dn2 = DN.valueOf(two);
            DN dn3 = DN.valueOf(three);
            String irreversibleReadableString = dn1.toNormalizedUrlSafeString();
            assertEquals(irreversibleReadableString, dn2.toNormalizedUrlSafeString());
            assertEquals(irreversibleReadableString, dn3.toNormalizedUrlSafeString());
        }
}