From 4ccf2e43d0714e5fda04ce64071ccab0961147f9 Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Thu, 11 Dec 2014 13:44:07 +0000
Subject: [PATCH] OPENDJ-1585 CR-5594 Re-implement DN normalization in SDK
---
opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java | 114 +++++++++
opendj-core/clirr-ignored-api-changes.xml | 12 +
opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java | 117 ++++++++++
opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteStringBuilder.java | 25 ++
opendj-core/src/test/java/org/forgerock/opendj/ldap/ByteStringTestCase.java | 53 ++++
opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java | 74 ++++++
opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteString.java | 44 +++
opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequence.java | 30 ++
opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java | 173 ++++-----------
opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleTest.java | 16
10 files changed, 516 insertions(+), 142 deletions(-)
diff --git a/opendj-core/clirr-ignored-api-changes.xml b/opendj-core/clirr-ignored-api-changes.xml
index ac802e6..e69eea0 100644
--- a/opendj-core/clirr-ignored-api-changes.xml
+++ b/opendj-core/clirr-ignored-api-changes.xml
@@ -396,4 +396,16 @@
<method>%regex[(boolean|org.forgerock.opendj.ldap.schema.SchemaBuilder) allow(.)*\((boolean)?\)]</method>
<justification>OPENDJ-1478 Make it easier to add compatibility options to schemas</justification>
</difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/ByteSequence</className>
+ <differenceType>7012</differenceType>
+ <method>java.nio.ByteBuffer copyTo(java.nio.ByteBuffer)</method>
+ <justification>Added new utility method copyTo() for a byte buffer</justification>
+ </difference>
+ <difference>
+ <className>org/forgerock/opendj/ldap/ByteSequence</className>
+ <differenceType>7012</differenceType>
+ <method>boolean copyTo(java.nio.CharBuffer, java.nio.charset.CharsetDecoder)</method>
+ <justification>OPENDJ-1585: Added new utility method copyTo for a char buffer</justification>
+ </difference>
</differences>
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java
index 874898d..6562263 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java
@@ -30,6 +30,12 @@
import static com.forgerock.opendj.util.StaticUtils.*;
import static com.forgerock.opendj.ldap.CoreMessages.*;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
import java.util.Comparator;
import org.forgerock.i18n.LocalizableMessage;
@@ -62,6 +68,9 @@
* Models </a>
*/
public final class AVA implements Comparable<AVA> {
+
+ private static final char HEX_STRING_SEPARATOR = '%';
+
/**
* Parses the provided LDAP string representation of an AVA using the
* default schema.
@@ -473,7 +482,11 @@
// that means that the value should be a hex string.
char c = reader.read();
int length = 0;
- if (c == '#') {
+ if (c == '+') {
+ // Value is empty and followed by another AVA
+ reader.reset();
+ return ByteString.empty();
+ } else if (c == '#') {
// The first two characters must be hex characters.
reader.mark();
if (reader.remaining() < 2) {
@@ -792,4 +805,106 @@
return orderingNormalizedAttributeValue;
}
+
+ /**
+ * Returns the normalized byte string representation of this AVA.
+ * <p>
+ * The representation is not a valid AVA.
+ *
+ * @param builder
+ * The builder to use to construct the normalized byte string.
+ * @return The normalized byte string representation.
+ * @see DN#toIrreversibleNormalizedByteString()
+ */
+ ByteStringBuilder toNormalizedByteString(final ByteStringBuilder builder) {
+ builder.append(toLowerCase(attributeType.getNameOrOID()));
+ builder.append("=");
+ final ByteString value = getEqualityNormalizedValue();
+ if (value.length() > 0) {
+ builder.append(escapeBytes(value));
+ }
+ return builder;
+ }
+
+ /**
+ * Returns the normalized readable string representation of this AVA.
+ * <p>
+ * The representation is not a valid AVA.
+ *
+ * @param builder
+ * The builder to use to construct the normalized string.
+ * @return The normalized readable string representation.
+ * @see DN#toIrreversibleReadableString()
+ */
+ StringBuilder toNormalizedReadableString(final StringBuilder builder) {
+ builder.append(toLowerCase(attributeType.getNameOrOID()));
+ builder.append('=');
+ final ByteString value = getEqualityNormalizedValue();
+
+ if (value.length() == 0) {
+ return builder;
+ }
+ final boolean hasAttributeName = !attributeType.getNames().isEmpty();
+ final boolean isHumanReadable = attributeType.getSyntax().isHumanReadable();
+ if (!hasAttributeName || !isHumanReadable) {
+ builder.append(value.toHexString(AVA.HEX_STRING_SEPARATOR));
+ } else {
+ // try to decode value as UTF-8 string
+ final CharBuffer buffer = CharBuffer.allocate(value.length());
+ final CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT);
+ if (value.copyTo(buffer, decoder)) {
+ try {
+ // URL encoding encodes space char as '+' instead of using hex code
+ final String val = URLEncoder.encode(buffer.toString(), "UTF-8").replaceAll("\\+", "%20");
+ builder.append(val);
+ } catch (UnsupportedEncodingException e) {
+ // should never happen
+ builder.append(value.toHexString(AVA.HEX_STRING_SEPARATOR));
+ }
+ } else {
+ builder.append(value.toHexString(AVA.HEX_STRING_SEPARATOR));
+ }
+ }
+ return builder;
+ }
+
+ /**
+ * Return a new byte string with bytes 0x00, 0x01 and 0x02 escaped.
+ * <p>
+ * These bytes are reserved to represent respectively the RDN separator,
+ * the AVA separator and the escape byte in a normalized byte string.
+ */
+ private ByteString escapeBytes(final ByteString value) {
+ if (!needEscaping(value)) {
+ return value;
+ }
+
+ final ByteStringBuilder builder = new ByteStringBuilder();
+ for (int i = 0; i < value.length(); i++) {
+ final byte b = value.byteAt(i);
+ if (isByteToEscape(b)) {
+ builder.append(DN.NORMALIZED_ESC_BYTE);
+ }
+ builder.append(b);
+ }
+ return builder.toByteString();
+ }
+
+ private boolean needEscaping(final ByteString value) {
+ boolean needEscaping = false;
+ for (int i = 0; i < value.length(); i++) {
+ final byte b = value.byteAt(i);
+ if (isByteToEscape(b)) {
+ needEscaping = true;
+ break;
+ }
+ }
+ return needEscaping;
+ }
+
+ private boolean isByteToEscape(final byte b) {
+ return b == DN.NORMALIZED_RDN_SEPARATOR || b == DN.NORMALIZED_AVA_SEPARATOR || b == DN.NORMALIZED_ESC_BYTE;
+ }
}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequence.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequence.java
index 0105c25..9676275 100755
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequence.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteSequence.java
@@ -28,6 +28,10 @@
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetDecoder;
import java.util.Comparator;
/**
@@ -189,6 +193,32 @@
ByteStringBuilder copyTo(ByteStringBuilder builder);
/**
+ * Appends the content of this byte sequence to the provided {@link ByteBuffer}.
+ *
+ * @param buffer
+ * The buffer to copy to.
+ * It must be large enough to receive all bytes.
+ * @return The buffer.
+ * @throws BufferOverflowException
+ * If there is insufficient space in the provided buffer
+ */
+ ByteBuffer copyTo(ByteBuffer buffer);
+
+ /**
+ * Appends the content of this byte sequence decoded using provided charset decoder,
+ * to the provided {@link CharBuffer}.
+ *
+ * @param charBuffer
+ * The buffer to copy to, if decoding is successful.
+ * It must be large enough to receive all decoded characters.
+ * @param decoder
+ * The charset decoder to use for decoding.
+ * @return {@code true} if byte string was successfully decoded and charBuffer is
+ * large enough to receive the resulting string, {@code false} otherwise
+ */
+ boolean copyTo(CharBuffer charBuffer, CharsetDecoder decoder);
+
+ /**
* Copies the entire contents of this byte sequence to the provided
* {@code OutputStream}.
*
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteString.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteString.java
index 0da9322..0c5ac3a 100755
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteString.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteString.java
@@ -35,6 +35,8 @@
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
import java.util.Arrays;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
@@ -619,11 +621,38 @@
}
/** {@inheritDoc} */
+ public ByteBuffer copyTo(final ByteBuffer byteBuffer) {
+ byteBuffer.put(buffer, offset, length);
+ byteBuffer.flip();
+ return byteBuffer;
+ }
+
+ /** {@inheritDoc} */
public ByteStringBuilder copyTo(final ByteStringBuilder builder) {
builder.append(buffer, offset, length);
return builder;
}
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean copyTo(CharBuffer charBuffer, CharsetDecoder decoder) {
+ return copyTo(ByteBuffer.wrap(buffer, offset, length), charBuffer, decoder);
+ }
+
+ /**
+ * Convenience method to copy from a byte buffer to a char buffer using provided decoder to decode
+ * bytes into characters.
+ * <p>
+ * It should not be used directly, prefer instance method of ByteString or ByteStringBuilder instead.
+ */
+ static boolean copyTo(ByteBuffer inBuffer, CharBuffer outBuffer, CharsetDecoder decoder) {
+ final CoderResult result = decoder.decode(inBuffer, outBuffer, true);
+ decoder.flush(outBuffer);
+ outBuffer.flip();
+ return !result.isError() && !result.isOverflow();
+ }
+
/** {@inheritDoc} */
public OutputStream copyTo(final OutputStream stream) throws IOException {
stream.write(buffer, offset, length);
@@ -701,10 +730,23 @@
* using hexadecimal characters.
*/
public String toHexString() {
+ return toHexString(' ');
+ }
+
+ /**
+ * Returns a string representation of the contents of this byte sequence
+ * using hexadecimal characters and the provided separator between each byte.
+ *
+ * @param separator
+ * Character used to separate each byte
+ * @return A string representation of the contents of this byte sequence
+ * using hexadecimal characters.
+ */
+ public String toHexString(char separator) {
StringBuilder builder = new StringBuilder((length - 1) * 3 + 2);
builder.append(StaticUtils.byteToHex(buffer[offset]));
for (int i = 1; i < length; i++) {
- builder.append(" ");
+ builder.append(separator);
builder.append(StaticUtils.byteToHex(buffer[offset + i]));
}
return builder.toString();
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteStringBuilder.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteStringBuilder.java
index ef6d5dc..c4079cf 100755
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteStringBuilder.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/ByteStringBuilder.java
@@ -36,6 +36,7 @@
import java.nio.CharBuffer;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
/**
* A mutable sequence of bytes backed by a byte array.
@@ -149,6 +150,13 @@
}
/** {@inheritDoc} */
+ public ByteBuffer copyTo(final ByteBuffer byteBuffer) {
+ byteBuffer.put(buffer, subOffset, subLength);
+ byteBuffer.flip();
+ return byteBuffer;
+ }
+
+ /** {@inheritDoc} */
@Override
public ByteStringBuilder copyTo(final ByteStringBuilder builder) {
// Protect against reallocation: use builder's buffer.
@@ -156,6 +164,11 @@
}
/** {@inheritDoc} */
+ public boolean copyTo(CharBuffer charBuffer, CharsetDecoder decoder) {
+ return ByteString.copyTo(ByteBuffer.wrap(buffer, subOffset, subLength), charBuffer, decoder);
+ }
+
+ /** {@inheritDoc} */
@Override
public OutputStream copyTo(final OutputStream stream) throws IOException {
// Protect against reallocation: use builder's buffer.
@@ -842,6 +855,13 @@
}
/** {@inheritDoc} */
+ public ByteBuffer copyTo(final ByteBuffer byteBuffer) {
+ byteBuffer.put(buffer, 0, length);
+ byteBuffer.flip();
+ return byteBuffer;
+ }
+
+ /** {@inheritDoc} */
@Override
public ByteStringBuilder copyTo(final ByteStringBuilder builder) {
builder.append(buffer, 0, length);
@@ -849,6 +869,11 @@
}
/** {@inheritDoc} */
+ public boolean copyTo(CharBuffer charBuffer, CharsetDecoder decoder) {
+ return ByteString.copyTo(ByteBuffer.wrap(buffer, 0, length), charBuffer, decoder);
+ }
+
+ /** {@inheritDoc} */
@Override
public OutputStream copyTo(final OutputStream stream) throws IOException {
stream.write(buffer, 0, length);
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java
index 2f57c7b..622967f 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/DN.java
@@ -31,20 +31,15 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
-import java.util.TreeSet;
import java.util.WeakHashMap;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
-import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.CoreSchema;
-import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.forgerock.opendj.ldap.schema.Schema;
-import org.forgerock.opendj.ldap.schema.Syntax;
import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException;
import org.forgerock.util.Reject;
-import com.forgerock.opendj.util.StaticUtils;
import com.forgerock.opendj.util.SubstringReader;
import static com.forgerock.opendj.ldap.CoreMessages.*;
@@ -67,6 +62,11 @@
* Models </a>
*/
public final class DN implements Iterable<RDN>, Comparable<DN> {
+
+ static final byte NORMALIZED_RDN_SEPARATOR = 0x00;
+ static final byte NORMALIZED_AVA_SEPARATOR = 0x01;
+ static final byte NORMALIZED_ESC_BYTE = 0x02;
+
private static final DN ROOT_DN = new DN(CoreSchema.getInstance(), null, null, "");
/**
@@ -920,31 +920,64 @@
}
/**
- * 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.
+ * 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.
+ * 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, not usable as a valid DN
+ * @return The normalized byte string representation of the provided DN, not
+ * usable as a valid DN
*/
public ByteString toIrreversibleNormalizedByteString() {
if (rdn() == null) {
return ByteString.empty();
}
- final StringBuilder builder = new StringBuilder();
+ final ByteStringBuilder builder = new ByteStringBuilder();
int i = size() - 1;
- normalizeRDN(builder, parent(i).rdn());
+ 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('\u0000');
+ builder.append(DN.NORMALIZED_RDN_SEPARATOR);
}
- normalizeRDN(builder, rdn);
+ rdn.toNormalizedByteString(builder);
}
- return ByteString.valueOf(builder.toString());
+ return builder.toByteString();
+ }
+
+ /**
+ * 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.
+ * <p>
+ * This representation should be used only when a string representation is
+ * needed and when no reversibility to a valid DN is needed.
+ *
+ * @return The readable string representation of the provided DN,
+ * not usable as a valid DN
+ */
+ public String toIrreversibleReadableString() {
+ if (rdn() == null) {
+ return "";
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ int i = size() - 1;
+ parent(i).rdn().toNormalizedReadableString(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);
+ }
+ return builder.toString();
}
/**
@@ -960,6 +993,8 @@
* <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>
+ *
+ * @Deprecated This class will eventually be replaced by a compact implementation of a DN.
*/
public static final class CompactDn implements Comparable<CompactDn> {
@@ -1038,112 +1073,4 @@
return new CompactDn(this);
}
- /**
- * Returns the normalized string representation of a RDN.
- *
- * @param builder
- * The StringBuilder to use to construct the normalized string.
- * @param rdn
- * The RDN.
- * @return The normalized string representation of the provided RDN.
- */
- private static StringBuilder normalizeRDN(final StringBuilder builder, final RDN rdn) {
- final int sz = rdn.size();
- switch (sz) {
- case 0:
- // Handle RDN.maxValue().
- builder.append('\u0001');
- break;
- case 1:
- normalizeAVA(builder, rdn.getFirstAVA());
- break;
- default:
- // Need to sort the AVAs before comparing.
- TreeSet<AVA> a = new TreeSet<AVA>();
- for (AVA ava : rdn) {
- a.add(ava);
- }
- Iterator<AVA> i = a.iterator();
- // Normalize the first AVA.
- normalizeAVA(builder, i.next());
- while (i.hasNext()) {
- builder.append('\u0001');
- normalizeAVA(builder, i.next());
- }
- break;
- }
- return builder;
- }
-
- /**
- * Returns the normalized string representation of an AVA.
- *
- * @param builder
- * The StringBuilder to use to construct the normalized string.
- * @param ava
- * The AVA.
- * @return The normalized string representation of the provided AVA.
- */
- private static StringBuilder normalizeAVA(final StringBuilder builder, final AVA ava) {
- final AttributeType attributeType = ava.getAttributeType();
-
- ByteString value = ava.getAttributeValue();
- final MatchingRule matchingRule = attributeType.getEqualityMatchingRule();
- if (matchingRule != null) {
- try {
- value = matchingRule.normalizeAttributeValue(ava.getAttributeValue());
- } catch (final DecodeException de) {
- // Ignore - we'll drop back to the user provided value.
- }
- }
-
- if (attributeType.getNames().isEmpty()) {
- builder.append(attributeType.getOID());
- builder.append("=#");
- builder.append(value.toHexString());
- } else {
- final String name = attributeType.getNameOrOID();
- // Normalizing.
- StaticUtils.toLowerCase(name, builder);
-
- builder.append("=");
-
- final Syntax syntax = attributeType.getSyntax();
- if (!syntax.isHumanReadable()) {
- builder.append("#");
- builder.append(value.toHexString());
- } else {
- final String str = value.toString();
- if (str.length() == 0) {
- return builder;
- }
- char c = str.charAt(0);
- int startPos = 0;
- if (c == ' ' || c == '#') {
- builder.append('\\');
- builder.append(c);
- startPos = 1;
- }
- final int length = str.length();
- for (int si = startPos; si < length; si++) {
- c = str.charAt(si);
- if (c < ' ') {
- for (final byte b : getBytes(String.valueOf(c))) {
- builder.append('\\');
- builder.append(StaticUtils.byteToLowerHex(b));
- }
- } else {
- if ((c == ' ' && si == length - 1)
- || (c == '"' || c == '+' || c == ',' || c == ';' || c == '<'
- || c == '=' || c == '>' || c == '\\' || c == '\u0000')) {
- builder.append('\\');
- }
- builder.append(c);
- }
- }
- }
- }
- return builder;
- }
-
}
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java
index 15ea6b0..eb05a90 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/RDN.java
@@ -34,6 +34,7 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.TreeSet;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
@@ -73,6 +74,9 @@
*/
public final class RDN implements Iterable<AVA>, Comparable<RDN> {
+ /** Separator for AVAs. */
+ private static final char AVA_CHAR_SEPARATOR = '+';
+
/**
* A constant holding a special RDN having zero AVAs and which always
* compares greater than any other RDN other than itself.
@@ -408,4 +412,74 @@
StringBuilder toString(final StringBuilder builder) {
return builder.append(this);
}
+
+ /**
+ * Returns the normalized byte string representation of this RDN.
+ * <p>
+ * The representation is not a valid RDN.
+ *
+ * @param builder
+ * The builder to use to construct the normalized byte string.
+ * @return The normalized byte string representation.
+ * @see DN#toIrreversibleNormalizedByteString()
+ */
+ ByteStringBuilder toNormalizedByteString(final ByteStringBuilder builder) {
+ switch (size()) {
+ case 0:
+ // Handle RDN.maxValue().
+ builder.append(DN.NORMALIZED_AVA_SEPARATOR);
+ break;
+ case 1:
+ getFirstAVA().toNormalizedByteString(builder);
+ break;
+ default:
+ Iterator<AVA> it = getSortedAvas();
+ it.next().toNormalizedByteString(builder);
+ while (it.hasNext()) {
+ builder.append(DN.NORMALIZED_AVA_SEPARATOR);
+ it.next().toNormalizedByteString(builder);
+ }
+ break;
+ }
+ return builder;
+ }
+
+ /**
+ * Returns the normalized readable string representation of this RDN.
+ * <p>
+ * The representation is not a valid RDN.
+ *
+ * @param builder
+ * The builder to use to construct the normalized string.
+ * @return The normalized readable string representation.
+ * @see DN#toIrreversibleReadableString()
+ */
+ StringBuilder toNormalizedReadableString(final StringBuilder builder) {
+ switch (size()) {
+ case 0:
+ // Handle RDN.maxValue().
+ builder.append(RDN.AVA_CHAR_SEPARATOR);
+ break;
+ case 1:
+ getFirstAVA().toNormalizedReadableString(builder);
+ break;
+ default:
+ Iterator<AVA> it = getSortedAvas();
+ it.next().toNormalizedReadableString(builder);
+ while (it.hasNext()) {
+ builder.append(RDN.AVA_CHAR_SEPARATOR);
+ it.next().toNormalizedReadableString(builder);
+ }
+ break;
+ }
+ return builder;
+ }
+
+ private Iterator<AVA> getSortedAvas() {
+ TreeSet<AVA> sortedAvas = new TreeSet<AVA>();
+ for (AVA ava : avas) {
+ sortedAvas.add(ava);
+ }
+ return sortedAvas.iterator();
+ }
}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/ByteStringTestCase.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/ByteStringTestCase.java
index e141d06..dedeea1 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/ByteStringTestCase.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/ByteStringTestCase.java
@@ -26,6 +26,10 @@
*/
package org.forgerock.opendj.ldap;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
import java.util.Arrays;
import javax.xml.bind.DatatypeConverter;
@@ -260,6 +264,55 @@
}
@Test
+ public void testToHex() throws Exception {
+ ByteString byteString = new ByteStringBuilder().append("org=example").toByteString();
+ assertThat(byteString.toHexString()).isEqualTo("6F 72 67 3D 65 78 61 6D 70 6C 65");
+ assertThat(byteString.toHexString('-')).isEqualTo("6F-72-67-3D-65-78-61-6D-70-6C-65");
+ }
+
+ @Test
+ public void testCopyToCharBuffer() throws Exception {
+ String value = "org=example";
+ ByteString byteString = new ByteStringBuilder().append(value).toByteString();
+ CharBuffer buffer = CharBuffer.allocate(value.length());
+ final CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
+
+ boolean isCopied = byteString.copyTo(buffer, decoder);
+
+ assertThat(isCopied).isTrue();
+ assertThat(buffer.toString()).isEqualTo(value);
+ }
+
+ @Test
+ public void testCopyToCharBufferFailure() throws Exception {
+ // Non valid UTF-8 byte sequence
+ ByteString byteString = new ByteStringBuilder().append((byte) 0x80).toByteString();
+ CharBuffer buffer = CharBuffer.allocate(1);
+ final CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
+
+ boolean isCopied = byteString.copyTo(buffer, decoder);
+
+ assertThat(isCopied).isFalse();
+ }
+
+ @Test
+ public void testCopyToByteBuffer() throws Exception {
+ String value = "org=example";
+ ByteString byteString = new ByteStringBuilder().append(value).toByteString();
+ ByteBuffer buffer = ByteBuffer.allocate(value.length());
+
+ byteString.copyTo(buffer);
+
+ assertSameByteContent(buffer, byteString);
+ }
+
+ private void assertSameByteContent(ByteBuffer buffer, ByteString byteString) {
+ for (byte b : byteString.toByteArray()) {
+ assertThat(buffer.get()).isEqualTo(b);
+ }
+ }
+
+ @Test
public void testToHexPlusAsciiString() throws Exception {
ByteString byteString = new ByteStringBuilder().append("cn=testvalue,org=example").toByteString();
assertThat(byteString.toHexPlusAsciiString(10)).isEqualTo(
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
index c5fe282..cc892fb 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
@@ -33,6 +33,7 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
+import static org.fest.assertions.Assertions.*;
import static org.testng.Assert.*;
/**
@@ -126,7 +127,7 @@
{ "AB-global=", "ab-global=", "AB-global=" },
{ "OU= Sales + CN = J. Smith ,DC=example,DC=net",
"cn=j. smith+ou=sales,dc=example,dc=net", "OU=Sales+CN=J. Smith,DC=example,DC=net" },
- { "cn=John+a=", "a=+cn=john", "cn=John+a=" },
+ { "cn=John+dc=", "dc=+cn=john", "cn=John+dc=" },
{ "O=\"Sue, Grabbit and Runn\",C=US", "o=sue\\, grabbit and runn,c=us",
"O=Sue\\, Grabbit and Runn,C=US" }, };
}
@@ -1042,19 +1043,112 @@
public Object[][] toIrreversibleNormalizedByteStringDataProvider() {
// @formatter:off
return new Object[][] {
- { "dc=com", "dc=com" },
- { "dc=example,dc=com", "dc=example,dc=com" },
- { "dc=example,dc=com", "dc = example, dc = com" },
- { "dc=example+cn=test,dc=com", "cn=test+dc=example,dc=com" },
+ // first value to normalize, second value to normalize, expected sign of comparison between the two
+ { "dc=com", "dc=com", 0 },
+ { "dc=example,dc=com", "dc=example,dc=com", 0 },
+ { "cn=test+dc=example,dc=com", "cn=test+dc=example,dc=com", 0 },
+ { "dc=example+cn=test,dc=com", "cn=test+dc=example,dc=com", 0 },
+ // parent entry is followed by its children, not its siblings
+ { "dc=com", "dc=example,dc=com", -1 },
+ { "dc=com", "dc=test,dc=example,dc=com", -1},
+ { "dc=example,dc=com", "dc=test,dc=example,dc=com", -1},
+ { "dc=example,dc=com", "dc=example2,dc=com", -1},
+ { "dc=example2,dc=com", "dc=test,dc=example,dc=com", 1},
+ // with space
+ { "dc=example,dc=com", "dc = example, dc = com", 0 },
+ { "dc=example\\20test,dc=com", "dc=example test,dc=com", 0 },
+ { "dc=example test,dc=com", "dc=exampletest,dc=com", -1 },
+ // with various escaped characters
+ { "dc=example\\2Dtest,dc=com", "dc=example-test,dc=com", 0 },
+ { "dc=example\\28test,dc=com", "dc=example(test,dc=com", 0 },
+ { "dc=example\\3Ftest,dc=com", "dc=example?test,dc=com", 0 },
+ // with escaped comma
+ { "dc=example\\,dc=com,dc=com", "dc=example\\2Cdc=com,dc=com", 0 },
+ { "dc=example\\2Cdc=com,dc=com", "dc=example\\2Cdc\\3Dcom,dc=com", 0 },
+ { "dc=example,dc=com", "dc=example\\,dc=com,dc=com", -1 },
+ { "dc=example2,dc=com", "dc=example\\,dc=com,dc=com", 1 },
+ // with escaped "="
+ { "dc=example\\=other,dc=com", "dc=example\\3Dother,dc=com", 0 },
+ // with escaped "+"
+ { "dc=example\\+other,dc=com", "dc=example\\2Bother,dc=com", 0 },
+ // integer
+ { "governingStructureRule=10,dc=com", "governingStructureRule=10, dc=com", 0 },
+ { "governingStructureRule=99,dc=com", "governingStructureRule=100, dc=com", -1 },
+ { "governingStructureRule=999999,dc=com", "governingStructureRule=1000000, dc=com", -1 },
+ // no matching rule for the attribute
+ { "dummy=9,dc=com", "dummy=10,dc=com", 1 }
};
// @formatter:on
}
- /** Tests the {@link DN#toIrreversibleNormalizedByteString()} method. */
@Test(dataProvider = "toIrreversibleNormalizedByteStringDataProvider")
- public void testToIrreversibleNormalizedByteString(String actualStr, String expectedStr) {
- DN actual = DN.valueOf(actualStr);
- DN expected = DN.valueOf(expectedStr);
- assertEquals(actual.toIrreversibleNormalizedByteString(), expected.toIrreversibleNormalizedByteString());
+ 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);
+ }
+
+ @Test(dataProvider = "testDNs")
+ /** Additional tests with testDNs data provider */
+ 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);
+ }
+
+ @DataProvider
+ public Object[][] toIrreversibleReadableStringDataProvider() {
+ // @formatter:off
+ return new Object[][] {
+ // first value = string used to build DN, second value = expected readable string
+ { "dc=com", "dc=com" },
+ { "dc=example,dc=com", "dc=com,dc=example" },
+ { "dc = example, dc = com", "dc=com,dc=example" },
+ { "dc=example+cn=test,dc=com", "dc=com,cn=test+dc=example" },
+ { "cn=test+dc=example,dc=com", "dc=com,cn=test+dc=example" },
+ // with space
+ { "dc=example test,dc=com", "dc=com,dc=example%20test" },
+ { "dc=example\\20test,dc=com", "dc=com,dc=example%20test" },
+ // with escaped comma
+ { "dc=example\\,dc=com,dc=com", "dc=com,dc=example%2Cdc%3Dcom" },
+ { "dc=example\\2Cdc=com,dc=com", "dc=com,dc=example%2Cdc%3Dcom" },
+ // with escaped "="
+ { "dc=example\\=other,dc=com", "dc=com,dc=example%3Dother" },
+ { "dc=example\\3Dother,dc=com", "dc=com,dc=example%3Dother" },
+ // with escaped "+"
+ { "dc=example\\+other,dc=com", "dc=com,dc=example%2Bother" },
+ { "dc=example\\2Bother,dc=com", "dc=com,dc=example%2Bother" },
+ // integer
+ { "governingStructureRule=256,dc=com", "dc=com,governingstructurerule=%01%00" },
+ // uuid
+ { "entryUUID=597ae2f6-16a6-1027-98f4-d28b5365dc14,dc=com",
+ "dc=com,entryuuid=597ae2f6-16a6-1027-98f4-d28b5365dc14" },
+ // characters unescaped by URL encoding (-, _, ., ~)
+ { "dc=example\\2Dtest,dc=com", "dc=com,dc=example-test" },
+ { "dc=example\\5Ftest,dc=com", "dc=com,dc=example_test" },
+ };
+ // @formatter:on
+ }
+
+ @Test(dataProvider = "toIrreversibleReadableStringDataProvider")
+ public void testToIrreversibleReadableString(String dnAsString, String expectedReadableString) {
+ DN actual = DN.valueOf(dnAsString);
+ assertEquals(actual.toIrreversibleReadableString(), expectedReadableString);
+ }
+
+ @Test(dataProvider = "testDNs")
+ /** Additional tests with testDNs data provider */
+ 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());
}
}
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleTest.java b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleTest.java
index ced2d91..8699270 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleTest.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/DistinguishedNameEqualityMatchingRuleTest.java
@@ -149,18 +149,20 @@
{ "givenName=John+cn=Doe,ou=People,dc=example,dc=com",
"dc=com\u0000dc=example\u0000ou=people\u0000cn=doe\u0001givenname=john" },
{ "givenName=John\\+cn=Doe,ou=People,dc=example,dc=com",
- "dc=com\u0000dc=example\u0000ou=people\u0000givenname=john\\+cn\\=doe" },
+ "dc=com\u0000dc=example\u0000ou=people\u0000givenname=john\u002Bcn\u003Ddoe" },
{ "cn=Doe\\, John,ou=People,dc=example,dc=com",
- "dc=com\u0000dc=example\u0000ou=people\u0000cn=doe\\, john" },
+ "dc=com\u0000dc=example\u0000ou=people\u0000cn=doe, john" },
{ "UID=jsmith,DC=example,DC=net", "dc=net\u0000dc=example\u0000uid=jsmith" },
{ "OU=Sales+CN=J. Smith,DC=example,DC=net",
"dc=net\u0000dc=example\u0000cn=j. smith\u0001ou=sales" },
- { "CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net",
- "dc=net\u0000dc=example\u0000cn=james \\\"jim\\\" smith\\, iii" },
+ // commented due to checkstyle bug : https://github.com/checkstyle/checkstyle/issues/157
+ // uncomment when it is fixed
+ // { "CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net",
+ // "dc=net\u0000dc=example\u0000cn=james " + "\u005C\u0022jim\u005C\u0022" + " smith\u002C iii" },
{ "CN=John Smith\\2C III,DC=example,DC=net",
- "dc=net\u0000dc=example\u0000cn=john smith\\, iii" },
+ "dc=net\u0000dc=example\u0000cn=john smith\u002C iii" },
{ "CN=\\23John Smith\\20,DC=example,DC=net",
- "dc=net\u0000dc=example\u0000cn=\\#john smith" },
+ "dc=net\u0000dc=example\u0000cn=\u0023john smith" },
{ "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)
@@ -179,7 +181,7 @@
{ "OU= Sales + CN = J. Smith ,DC=example,DC=net",
"dc=net\u0000dc=example\u0000cn=j. smith\u0001ou=sales" },
{ "cn=John+a=", "a=\u0001cn=john" },
- { "O=\"Sue, Grabbit and Runn\",C=US", "c=us\u0000o=sue\\, grabbit and runn" }, };
+ { "O=\"Sue, Grabbit and Runn\",C=US", "c=us\u0000o=sue\u002C grabbit and runn" }, };
}
protected MatchingRule getRule() {
--
Gitblit v1.10.0