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

Matthew Swift
30.34.2012 787a62c89cd05bdd68c391adb8b5c62f04257a4a
Fix OPENDJ-656: Add support for generating LDAP filters and DNs from printf style templates

* added support for formatting DNs from templates
* renamed Filter.valueOf(String, Object...) -> Filter.format(...)
* improved Javadoc
5 files modified
215 ■■■■ changed files
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AVA.java 58 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/DN.java 109 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Filter.java 15 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java 31 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/FilterTestCase.java 2 ●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AVA.java
@@ -148,6 +148,35 @@
        return new AVA(attribute, value);
    }
    static void escapeAttributeValue(final String str, final StringBuilder builder) {
        if (str.length() > 0) {
            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);
                }
            }
        }
    }
    private static void appendHexChars(final SubstringReader reader,
            final StringBuilder valueBuffer, final StringBuilder hexBuffer) throws DecodeException {
        final int length = hexBuffer.length();
@@ -720,34 +749,7 @@
                builder.append("#");
                StaticUtils.toHex(attributeValue, builder);
            } else {
                final String str = attributeValue.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);
                    }
                }
                escapeAttributeValue(attributeValue.toString(), builder);
            }
        }
        return builder;
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/DN.java
@@ -86,6 +86,113 @@
            };
    /**
     * Returns the LDAP string representation of the provided DN attribute value
     * in a form suitable for substitution directly into a DN string. This
     * method may be useful in cases where a DN is to be constructed from a DN
     * template using {@code String#format(String, Object...)}. The following
     * example illustrates two approaches to constructing a DN:
     *
     * <pre>
     * // This may contain user input.
     * String attributeValue = ...;
     *
     * // Using the equality filter constructor:
     * DN dn = DN.valueOf("ou=people,dc=example,dc=com").child("uid", attributeValue);
     *
     * // Using a String template:
     * String dnTemplate = "uid=%s,ou=people,dc=example,dc=com";
     * String dnString = String.format(dnTemplate,
     *                                 DN.escapeAttributeValue(attributeValue));
     * DN dn = DN.valueOf(dnString);
     * </pre>
     *
     * <b>Note:</b> attribute values do not and should not be escaped before
     * passing them to constructors like {@link #child(String, Object)}.
     * Escaping is only required when creating DN strings.
     *
     * @param attributeValue
     *            The attribute value.
     * @return The LDAP string representation of the provided filter assertion
     *         value in a form suitable for substitution directly into a filter
     *         string.
     */
    public static String escapeAttributeValue(final Object attributeValue) {
        Validator.ensureNotNull(attributeValue);
        final String s = String.valueOf(attributeValue);
        final StringBuilder builder = new StringBuilder(s.length());
        AVA.escapeAttributeValue(s, builder);
        return builder.toString();
    }
    /**
     * Creates a new DN using the provided DN template and unescaped attribute
     * values using the default schema. This method first escapes each of the
     * attribute values and then substitutes them into the template using
     * {@link String#format(String, Object...)}. Finally, the formatted string
     * is parsed as an LDAP DN using {@link #valueOf(String)}.
     * <p>
     * This method may be useful in cases where the structure of a DN is not
     * known at compile time, for example, it may be obtained from a
     * configuration file. Example usage:
     *
     * <pre>
     * String template = &quot;uid=%s,ou=people,dc=example,dc=com&quot;;
     * DN dn = DN.format(template, &quot;bjensen&quot;);
     * </pre>
     *
     * @param template
     *            The DN template.
     * @param attributeValues
     *            The attribute values to be substituted into the template.
     * @return The formatted template parsed as a {@code DN}.
     * @throws LocalizedIllegalArgumentException
     *             If the formatted template is not a valid LDAP string
     *             representation of a DN.
     * @see #escapeAttributeValue(Object)
     */
    public static DN format(final String template, final Object... attributeValues) {
        return format(template, Schema.getDefaultSchema(), attributeValues);
    }
    /**
     * Creates a new DN using the provided DN template and unescaped attribute
     * values using the provided schema. This method first escapes each of the
     * attribute values and then substitutes them into the template using
     * {@link String#format(String, Object...)}. Finally, the formatted string
     * is parsed as an LDAP DN using {@link #valueOf(String)}.
     * <p>
     * This method may be useful in cases where the structure of a DN is not
     * known at compile time, for example, it may be obtained from a
     * configuration file. Example usage:
     *
     * <pre>
     * String template = &quot;uid=%s,ou=people,dc=example,dc=com&quot;;
     * DN dn = DN.format(template, &quot;bjensen&quot;);
     * </pre>
     *
     * @param template
     *            The DN template.
     * @param schema
     *            The schema to use when parsing the DN.
     * @param attributeValues
     *            The attribute values to be substituted into the template.
     * @return The formatted template parsed as a {@code DN}.
     * @throws LocalizedIllegalArgumentException
     *             If the formatted template is not a valid LDAP string
     *             representation of a DN.
     * @see #escapeAttributeValue(Object)
     */
    public static DN format(final String template, final Schema schema,
            final Object... attributeValues) {
        final String[] attributeValueStrings = new String[attributeValues.length];
        for (int i = 0; i < attributeValues.length; i++) {
            attributeValueStrings[i] = escapeAttributeValue(attributeValues[i]);
        }
        final String dnString = String.format(template, (Object[]) attributeValueStrings);
        return valueOf(dnString, schema);
    }
    /**
     * Returns the Root DN. The Root DN does not contain and RDN components and
     * is superior to all other DNs.
     *
@@ -107,6 +214,7 @@
     *             DN.
     * @throws NullPointerException
     *             If {@code dn} was {@code null}.
     * @see #format(String, Object...)
     */
    public static DN valueOf(final String dn) {
        return valueOf(dn, Schema.getDefaultSchema());
@@ -126,6 +234,7 @@
     *             DN.
     * @throws NullPointerException
     *             If {@code dn} or {@code schema} was {@code null}.
     * @see #format(String, Schema, Object...)
     */
    public static DN valueOf(final String dn, final Schema schema) {
        Validator.ensureNotNull(dn, schema);
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Filter.java
@@ -66,6 +66,9 @@
 * import static org.forgerock.opendj.Filter.*;
 *
 * Filter filter = and(equality("cn", "bjensen"), greaterOrEqual("age", 21));
 *
 * // Alternatively use a filter template:
 * Filter filter = Filter.format("(&(cn=%s)(age>=%s))", "bjensen", 21);
 * </pre>
 *
 * @see <a href="http://tools.ietf.org/html/rfc4511">RFC 4511 - Lightweight
@@ -564,20 +567,20 @@
     *                                     Filter.escapeAssertionValue(assertionValue));
     * Filter filter = Filter.valueOf(filterString);
     * </pre>
     *
     * If {@code assertionValue} is not an instance of {@code ByteString} then
     * it will be converted using the {@link ByteString#valueOf(Object)} method.
     *
     * <p>
     * <b>Note:</b> assertion values do not and should not be escaped before
     * passing them to constructors like {@link #equality(String, Object)}.
     * Escaping is only required when creating filter strings.
     * <p>
     * <b>Note:</b>
     *
     * @param assertionValue
     *            The assertion value.
     * @return The LDAP string representation of the provided filter assertion
     *         value in a form suitable for substitution directly into a filter
     *         string.
     * @see #format(String, Object...)
     */
    public static String escapeAssertionValue(final Object assertionValue) {
        Validator.ensureNotNull(assertionValue);
@@ -881,6 +884,7 @@
     * @throws LocalizedIllegalArgumentException
     *             If {@code string} is not a valid LDAP string representation
     *             of a filter.
     * @see #format(String, Object...)
     */
    public static Filter valueOf(final String string) {
        Validator.ensureNotNull(string);
@@ -928,7 +932,7 @@
     *
     * <pre>
     * String template = &quot;(|(cn=%s)(uid=user.%s))&quot;;
     * Filter filter = Filter.valueOf(template, &quot;alice&quot;, 123);
     * Filter filter = Filter.format(template, &quot;alice&quot;, 123);
     * </pre>
     *
     * Any assertion values which are not instances of {@code ByteString} will
@@ -942,8 +946,9 @@
     * @throws LocalizedIllegalArgumentException
     *             If the formatted template is not a valid LDAP string
     *             representation of a filter.
     * @see #escapeAssertionValue(Object)
     */
    public static Filter valueOf(final String template, final Object... assertionValues) {
    public static Filter format(final String template, final Object... assertionValues) {
        final String[] assertionValueStrings = new String[assertionValues.length];
        for (int i = 0; i < assertionValues.length; i++) {
            assertionValueStrings[i] = escapeAssertionValue(assertionValues[i]);
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/DNTestCase.java
@@ -979,4 +979,35 @@
        assertEquals(DN.valueOf(dn).rename(DN.valueOf(fromDN), DN.valueOf(toDN)), DN
                .valueOf(expectedDN));
    }
    /**
     * Tests the {@link DN#format(String, Object...)} method.
     */
    @Test
    public void testFormatNoEscape() {
        DN actual = DN.format("deviceId=%s,uid=%s,dc=test", 123, "bjensen");
        DN expected = DN.valueOf("dc=test").child("uid", "bjensen").child("deviceId", 123);
        assertEquals(actual, expected);
        assertEquals(actual.toString(), "deviceId=123,uid=bjensen,dc=test");
    }
    /**
     * Tests the {@link DN#format(String, Object...)} method.
     */
    @Test
    public void testFormatEscape() {
        DN actual = DN.format("uid=%s,dc=test", "#cn=foo+sn=bar");
        DN expected = DN.valueOf("dc=test").child("uid", "#cn=foo+sn=bar");
        assertEquals(actual, expected);
        assertEquals(actual.toString(), "uid=\\#cn\\=foo\\+sn\\=bar,dc=test");
    }
    /**
     * Tests the {@link DN#escapeAttributeValue(Object)} method.
     */
    @Test
    public void testEscapeAttributeValue() {
        String actual = DN.escapeAttributeValue("#cn=foo+sn=bar");
        assertEquals(actual, "\\#cn\\=foo\\+sn\\=bar");
    }
}
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/FilterTestCase.java
@@ -307,7 +307,7 @@
    @Test(dataProvider = "getAssertionValues")
    public void testValueOfTemplate(String template, List<?> assertionValues, String expected)
            throws Exception {
        Filter filter = Filter.valueOf(template, assertionValues.toArray());
        Filter filter = Filter.format(template, assertionValues.toArray());
        assertEquals(filter.toString(), expected);
    }