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

Matthew Swift
15.58.2013 fbea82c15cf5f1d069e5aa00dcdfdd5172a2cc7b
Final fix for OPENDJ-758 : Implement configurable update policy for simple and default mappers

More API improvements. Roll SimpleAttributeMapper.ignoreUpdates() into WritabilityPolicy.
4 files modified
151 ■■■■■ changed files
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java 59 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java 24 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/WritabilityPolicy.java 50 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java 18 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -20,11 +20,12 @@
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.forgerock.opendj.ldap.Functions.fixedFunction;
import static org.forgerock.opendj.rest2ldap.Utils.base64ToByteString;
import static org.forgerock.opendj.rest2ldap.Utils.byteStringToBase64;
import static org.forgerock.opendj.rest2ldap.Utils.byteStringToJson;
import static org.forgerock.opendj.rest2ldap.Utils.jsonToAttribute;
import static org.forgerock.opendj.rest2ldap.Utils.jsonToByteString;
import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_ONLY;
import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE;
import java.util.Collection;
@@ -55,7 +56,6 @@
    private Collection<Object> defaultJSONValues = Collections.emptySet();
    private ByteString defaultLDAPValue = null;
    private Function<Object, ByteString, Void> encoder = null;
    private boolean isIgnoreUpdates = true;
    private boolean isRequired = false;
    private boolean isSingleValued = false;
    private final AttributeDescription ldapAttributeName;
@@ -119,45 +119,41 @@
    }
    /**
     * Indicates whether or not an attempt to update the LDAP attribute should
     * be ignored when the update is incompatible with the writability policy.
     * The default is {@code true}.
     * Indicates that JSON values are base 64 encodings of binary data. Calling
     * this method is equivalent to the following:
     *
     * @param ignore
     *            {@code true} an attempt to update the LDAP attribute should be
     *            ignored.
     * <pre>
     * mapper.decoder(...); // function that converts binary data to base 64
     * mapper.encoder(...); // function that converts base 64 to binary data
     * </pre>
     *
     * @return This attribute mapper.
     */
    public SimpleAttributeMapper ignoreUpdates(final boolean ignore) {
        this.isIgnoreUpdates = ignore;
    public SimpleAttributeMapper isBinary() {
        decoder = byteStringToBase64();
        encoder = base64ToByteString();
        return this;
    }
    /**
     * Indicates that the LDAP attribute is mandatory and must be provided
     * during create requests. The default is {@code false}.
     * during create requests.
     *
     * @param isRequired
     *            {@code true} if the LDAP attribute is mandatory and must be
     *            provided during create requests.
     * @return This attribute mapper.
     */
    public SimpleAttributeMapper required(final boolean isRequired) {
        this.isRequired = isRequired;
    public SimpleAttributeMapper isRequired() {
        this.isRequired = true;
        return this;
    }
    /**
     * Forces a multi-valued LDAP attribute to be represented as a single-valued
     * JSON value, rather than an array of values. The default is {@code false}.
     * Indicates that multi-valued LDAP attribute should be represented as a
     * single-valued JSON value, rather than an array of values.
     *
     * @param isSingleValued
     *            {@code true} if the LDAP attribute should be treated as a
     *            single-valued attribute.
     * @return This attribute mapper.
     */
    public SimpleAttributeMapper singleValued(final boolean isSingleValued) {
        this.isSingleValued = isSingleValued;
    public SimpleAttributeMapper isSingleValued() {
        this.isSingleValued = true;
        return this;
    }
@@ -209,7 +205,7 @@
        try {
            final List<Attribute> result;
            if (v == null || v.isNull()) {
                if (isRequired()) {
                if (attributeIsRequired()) {
                    // FIXME: improve error message.
                    throw new BadRequestException("no value provided");
                } else if (defaultLDAPValue != null) {
@@ -219,11 +215,11 @@
                } else {
                    result = emptyList();
                }
            } else if (v.isList() && isSingleValued()) {
            } else if (v.isList() && attributeIsSingleValued()) {
                // FIXME: improve error message.
                throw new BadRequestException("expected single value, but got multiple values");
            } else if (isCreate()) {
                if (isIgnoreUpdates) {
            } else if (!writabilityPolicy.canCreate(ldapAttributeName)) {
                if (writabilityPolicy.discardWrites()) {
                    result = emptyList();
                } else {
                    // FIXME: improve error message.
@@ -253,16 +249,11 @@
        }
    }
    private boolean isCreate() {
        return writabilityPolicy != READ_ONLY
                && ldapAttributeName.getAttributeType().isNoUserModification();
    }
    private boolean isRequired() {
    private boolean attributeIsRequired() {
        return isRequired && defaultJSONValue == null;
    }
    private boolean isSingleValued() {
    private boolean attributeIsSingleValued() {
        return isSingleValued || ldapAttributeName.getAttributeType().isSingleValue();
    }
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -91,6 +91,22 @@
    }
    private static final Function<ByteString, String, Void> BYTESTRING_TO_BASE64 =
            new Function<ByteString, String, Void>() {
                @Override
                public String apply(ByteString value, Void p) {
                    return value.toBase64String();
                }
            };
    private static final Function<Object, ByteString, Void> BASE64_TO_BYTESTRING =
            new Function<Object, ByteString, Void>() {
                @Override
                public ByteString apply(Object value, Void p) {
                    return ByteString.valueOfBase64(String.valueOf(value));
                }
            };
    private static final Function<ByteString, Object, AttributeDescription> BYTESTRING_TO_JSON =
            new Function<ByteString, Object, AttributeDescription>() {
                @Override
@@ -144,6 +160,14 @@
        return BYTESTRING_TO_JSON;
    }
    static Function<ByteString, String, Void> byteStringToBase64() {
        return BYTESTRING_TO_BASE64;
    }
    static Function<Object, ByteString, Void> base64ToByteString() {
        return BASE64_TO_BYTESTRING;
    }
    static <T> T ensureNotNull(final T object) {
        if (object == null) {
            throw new NullPointerException();
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/WritabilityPolicy.java
@@ -16,26 +16,64 @@
package org.forgerock.opendj.rest2ldap;
import org.forgerock.opendj.ldap.AttributeDescription;
/**
 * The writability policy determines whether or not an attribute supports
 * updates.
 */
public enum WritabilityPolicy {
    // @formatter:off
    /**
     * The attribute may be provided when creating a new resource, but cannot be
     * modified afterwards.
     * The attribute cannot be provided when creating a new resource, nor
     * modified afterwards. Attempts to update the attribute will result in an
     * error.
     */
    CREATE_ONLY,
    READ_ONLY(false),
    /**
     * The attribute cannot be provided when creating a new resource, nor
     * modified afterwards.
     * modified afterwards. Attempts to update the attribute will not result in
     * an error (the new values will be ignored).
     */
    READ_ONLY,
    READ_ONLY_DISCARD_WRITES(true),
    /**
     * The attribute may be provided when creating a new resource, but cannot be
     * modified afterwards. Attempts to update the attribute will result in an
     * error.
     */
    CREATE_ONLY(false),
    /**
     * The attribute may be provided when creating a new resource, but cannot be
     * modified afterwards. Attempts to update the attribute will not result in
     * an error (the new values will be ignored).
     */
    CREATE_ONLY_DISCARD_WRITES(true),
    /**
     * The attribute may be provided when creating a new resource, and modified
     * afterwards.
     */
    READ_WRITE;
    READ_WRITE(false);
    // @formatter:on
    private final boolean discardWrites;
    private WritabilityPolicy(final boolean discardWrites) {
        this.discardWrites = discardWrites;
    }
    boolean canCreate(final AttributeDescription attribute) {
        return this != READ_ONLY && !attribute.getAttributeType().isNoUserModification();
    }
    boolean canWrite(final AttributeDescription attribute) {
        return this == READ_WRITE && !attribute.getAttributeType().isNoUserModification();
    }
    boolean discardWrites() {
        return discardWrites;
    }
}
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -68,16 +68,16 @@
        CollectionResourceProvider users =
                builder().factory(ldapFactory).baseDN("ou=people,dc=example,dc=com")
                    .attribute("schemas", constant(Arrays.asList("urn:scim:schemas:core:1.0")))
                    .attribute("id", simple("uid").singleValued(true).required(true).writability(CREATE_ONLY))
                    .attribute("rev", simple("etag").singleValued(true).writability(READ_ONLY))
                    .attribute("userName", simple("mail").singleValued(true).writability(READ_ONLY))
                    .attribute("displayName", simple("cn").singleValued(true).required(true))
                    .attribute("id", simple("uid").isSingleValued().isRequired().writability(CREATE_ONLY))
                    .attribute("rev", simple("etag").isSingleValued().writability(READ_ONLY))
                    .attribute("userName", simple("mail").isSingleValued().writability(READ_ONLY))
                    .attribute("displayName", simple("cn").isSingleValued().isRequired())
                    .attribute("name", object()
                            .attribute("givenName", simple("givenName").singleValued(true))
                            .attribute("familyName", simple("sn").singleValued(true).required(true)))
                            .attribute("givenName", simple("givenName").isSingleValued())
                            .attribute("familyName", simple("sn").isSingleValued().isRequired()))
                    .attribute("contactInformation", object()
                            .attribute("telephoneNumber", simple("telephoneNumber").singleValued(true))
                            .attribute("emailAddress", simple("mail").singleValued(true)))
                            .attribute("telephoneNumber", simple("telephoneNumber").isSingleValued())
                            .attribute("emailAddress", simple("mail").isSingleValued()))
                    .additionalLDAPAttribute("objectClass", "top", "person", "organizationalPerson", "inetOrgPerson")
                    .build();
        router.addRoute("/users", users);
@@ -85,7 +85,7 @@
        // Create group resource.
        CollectionResourceProvider groups =
                builder().factory(ldapFactory).baseDN("ou=groups,dc=example,dc=com")
                    .attribute("cn", simple("cn").singleValued(true))
                    .attribute("cn", simple("cn").isSingleValued())
                    .attribute("description", simple("description"))
                    .attribute("member", simple("uniquemember"))
                    .build();