OPENDJ-1585 Add CompactDn class as compact representation of a DN
Rename DN.toNormalizedString() to DN.toIrreversibleNormalizedByteString()
1 files added
5 files modified
| | |
| | | ! CDDL HEADER END |
| | | ! |
| | | ! Copyright 2014 ForgeRock AS |
| | | ! |
| | | ! |
| | | --> |
| | | <differences> |
| | | <!-- |
| | |
| | | <differenceType>8001</differenceType> |
| | | <justification>Renamed SchemaValidationPolicy.Policy to SchemaValidationPolicy.Action</justification> |
| | | </difference> |
| | | |
| | | |
| | | <difference> |
| | | <className>org/forgerock/opendj/ldap/LDAPListenerOptions</className> |
| | | <differenceType>7002</differenceType> |
| | |
| | | <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> |
| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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; |
| | |
| | | } |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | |
| | | return value.toByteString(); |
| | | } |
| | | |
| | | final String certificateIssuer = normalizeDN(schema, dnstring); |
| | | final ByteString certificateIssuer = normalizeDN(schema, dnstring); |
| | | return createEncodedValue(serialNumber, certificateIssuer); |
| | | } |
| | | |
| | |
| | | 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); |
| | | |
| | |
| | | * |
| | | * @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(); |
| | |
| | | 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()); |
| | | } |
| | |
| | | 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; |
| | |
| | | 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>() { |
| | |
| | | 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; |
| | | } |
| | |
| | | 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); |
| | | } |
| | |
| | | // @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()) { |
| | |
| | | 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(); |
| | | } |
| | | |
| | |
| | | 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); |
| | |
| | | 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]); |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | 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(); |
| 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 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); |
| | | } |
| | | } |