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

Matthew Swift
08.23.2013 33255bfee39fb3cb3813894831a76bc7c014c13a
Partial fix for OPENDJ-691: Implement add/create support

Still need to add support for simple attribute mapper.
2 files added
1 files renamed
6 files modified
630 ■■■■■ changed files
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java 6 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java 26 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java 177 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java 51 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java 15 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java 154 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java 67 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java 48 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java 86 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
@@ -99,7 +99,11 @@
    @Override
    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
        // TODO Auto-generated method stub
        if (v.isDefined(jsonAttributeName)) {
            mapper.toLDAP(c, v.get(jsonAttributeName), h);
        } else {
            mapper.toLDAP(c, new JsonValue(Collections.emptyMap()), h);
        }
    }
    private boolean matches(final JsonPointer jsonAttribute) {
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
@@ -121,7 +121,31 @@
    @Override
    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
        // TODO Auto-generated method stub
        final ResultHandler<List<Attribute>> handler =
                accumulate(attributeMappers.size(), transform(
                        new Function<List<List<Attribute>>, List<Attribute>, Void>() {
                            @Override
                            public List<Attribute> apply(final List<List<Attribute>> value,
                                    final Void p) {
                                switch (value.size()) {
                                case 0:
                                    return Collections.emptyList();
                                case 1:
                                    return value.get(0) != null ? value.get(0) : Collections
                                            .<Attribute> emptyList();
                                default:
                                    List<Attribute> attributes =
                                            new ArrayList<Attribute>(value.size());
                                    for (List<Attribute> a : value) {
                                        attributes.addAll(a);
                                    }
                                    return attributes;
                                }
                            }
                        }, h));
        for (final AttributeMapper mapper : attributeMappers) {
            mapper.toLDAP(c, v, handler);
        }
    }
    @SuppressWarnings("unchecked")
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
@@ -15,165 +15,50 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Config.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.schema.Schema;
/**
 * Common configuration options.
 */
public final class Config {
    /**
     * An interface for incrementally constructing common configuration options.
     */
    public static final class Builder {
        private Filter falseFilter;
        private ReadOnUpdatePolicy readOnUpdatePolicy;
        private Filter trueFilter;
        private Builder() {
            // Nothing to do.
        }
        /**
         * Returns a new configuration based on the current state of this
         * builder.
         *
         * @return A new configuration based on the current state of this
         *         builder.
         */
        public Config build() {
            return new Config(trueFilter, falseFilter, readOnUpdatePolicy);
        }
        /**
         * Sets the absolute false filter which should be used when querying the
         * LDAP server.
         *
         * @param filter
         *            The absolute false filter.
         * @return A reference to this builder.
         */
        public Builder falseFilter(final Filter filter) {
            this.trueFilter = ensureNotNull(filter);
            return this;
        }
        /**
         * Sets the policy which should be used in order to read an entry before
         * it is deleted, or after it is added or modified.
         *
         * @param policy
         *            The policy which should be used in order to read an entry
         *            before it is deleted, or after it is added or modified.
         * @return A reference to this builder.
         */
        public Builder readOnUpdatePolicy(final ReadOnUpdatePolicy policy) {
            this.readOnUpdatePolicy = ensureNotNull(policy);
            return this;
        }
        /**
         * Sets the absolute true filter which should be used when querying the
         * LDAP server.
         *
         * @param filter
         *            The absolute true filter.
         * @return A reference to this builder.
         */
        public Builder trueFilter(final Filter filter) {
            this.trueFilter = ensureNotNull(filter);
            return this;
        }
    };
    /**
     * The policy which should be used in order to read an entry before it is
     * deleted, or after it is added or modified.
     */
    public static enum ReadOnUpdatePolicy {
        /**
         * The LDAP entry will not be read when an update is performed. More
         * specifically, the REST resource will not be returned as part of a
         * create, delete, patch, or update request.
         */
        DISABLED,
        /**
         * The LDAP entry will be read atomically using the RFC 4527 read-entry
         * controls. More specifically, the REST resource will be returned as
         * part of a create, delete, patch, or update request, and it will
         * reflect the state of the resource at the time the update was
         * performed. This policy requires that the LDAP server supports RFC
         * 4527.
         */
        USE_READ_ENTRY_CONTROLS,
        /**
         * The LDAP entry will be read non-atomically using an LDAP search when
         * an update is performed. More specifically, the REST resource will be
         * returned as part of a create, delete, patch, or update request, but
         * it may not reflect the state of the resource at the time the update
         * was performed.
         */
        USE_SEARCH;
    }
    private static final Config DEFAULT = new Builder().trueFilter(Filter.objectClassPresent())
            .falseFilter(Filter.present("1.1")).readOnUpdatePolicy(USE_READ_ENTRY_CONTROLS).build();
    /**
     * Returns a new builder which can be used for incrementally constructing
     * common configuration options. The builder will initially have
     * {@link #defaultConfig() default} settings.
     *
     * @return The new builder.
     */
    public static Builder builder() {
        return builder(DEFAULT);
    }
    /**
     * Returns a new builder which can be used for incrementally constructing
     * common configuration options. The builder will initially have the same
     * settings as the provided configuration.
     *
     * @param config
     *            The initial settings.
     * @return The new builder.
     */
    public static Builder builder(final Config config) {
        return new Builder().trueFilter(config.trueFilter()).falseFilter(config.falseFilter())
                .readOnUpdatePolicy(config.readOnUpdatePolicy());
    }
    /**
     * Returns the default configuration having the following settings:
     * <ul>
     * <li>the absolute true filter {@code (objectClass=*)}
     * <li>the absolute false filter {@code (1.1=*)}
     * <li>the read on update policy
     * {@link ReadOnUpdatePolicy#USE_READ_ENTRY_CONTROLS}.
     * </ul>
     *
     * @return The default configuration.
     */
    public static Config defaultConfig() {
        return DEFAULT;
    }
final class Config {
    private final Filter falseFilter;
    private final Schema schema;
    private final ReadOnUpdatePolicy readOnUpdatePolicy;
    private final Filter trueFilter;
    private final DecodeOptions options;
    private Config(final Filter trueFilter, final Filter falseFilter,
            final ReadOnUpdatePolicy readOnUpdatePolicy) {
    Config(final Filter trueFilter, final Filter falseFilter,
            final ReadOnUpdatePolicy readOnUpdatePolicy, final Schema schema) {
        this.trueFilter = trueFilter;
        this.falseFilter = falseFilter;
        this.readOnUpdatePolicy = readOnUpdatePolicy;
        this.schema = schema;
        this.options = new DecodeOptions().setSchema(schema);
    }
    /**
     * Returns the schema which should be used when attribute types and
     * controls.
     *
     * @return The schema which should be used when attribute types and
     *         controls.
     */
    public Schema schema() {
        return schema;
    }
    /**
     * Returns the decoding options which should be used when decoding controls
     * in responses.
     *
     * @return The decoding options which should be used when decoding controls
     *         in responses.
     */
    public DecodeOptions decodeOptions() {
        return options;
    }
    /**
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
@@ -20,6 +20,9 @@
import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -27,8 +30,11 @@
import org.forgerock.json.fluent.JsonPointer;
import org.forgerock.json.fluent.JsonValue;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.ResultHandler;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.Attributes;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.Filter;
@@ -126,7 +132,50 @@
    @Override
    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
        // TODO:
        if (v.isMap()) {
            List<Attribute> result = new ArrayList<Attribute>(v.size());
            for (Map.Entry<String, Object> field : v.asMap().entrySet()) {
                final AttributeDescription ad;
                try {
                    ad = AttributeDescription.valueOf(field.getKey(), c.getConfig().schema());
                } catch (Exception e) {
                    // FIXME: improve error message.
                    h.handleError(new BadRequestException("The field " + field.getKey()
                            + " is invalid"));
                    return;
                }
                Object value = field.getValue();
                if (isJSONPrimitive(value)) {
                    result.add(Attributes.singletonAttribute(ad, value));
                } else if (value instanceof Collection<?>) {
                    Attribute a =
                            c.getConfig().decodeOptions().getAttributeFactory().newAttribute(ad);
                    for (Object o : (Collection<?>) value) {
                        if (isJSONPrimitive(o)) {
                            a.add(o);
                        } else {
                            // FIXME: improve error message.
                            h.handleError(new BadRequestException("The field " + field.getKey()
                                    + " is invalid"));
                            return;
                        }
                    }
                    result.add(a);
                } else {
                    // FIXME: improve error message.
                    h.handleError(new BadRequestException("The field " + field.getKey()
                            + " is invalid"));
                    return;
                }
            }
            h.handleResult(result);
        } else {
            h.handleResult(Collections.<Attribute> emptyList());
        }
    }
    private boolean isJSONPrimitive(Object value) {
        return value instanceof String || value instanceof Boolean || value instanceof Number;
    }
    private boolean isIncludedAttribute(final String name) {
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java
File was renamed from opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
@@ -32,20 +32,11 @@
/**
 * An attribute mapper which maps a single JSON attribute to a fixed value.
 */
final class ConstantAttributeMapper extends AttributeMapper {
final class JSONConstantAttributeMapper extends AttributeMapper {
    private final String jsonAttributeName;
    private final Object jsonAttributeValue;
    /**
     * Creates a new constant attribute mapper which maps a single JSON
     * attribute to a fixed value.
     *
     * @param attributeName
     *            The name of the simple JSON attribute.
     * @param attributeValue
     *            The value of the simple JSON attribute.
     */
    ConstantAttributeMapper(final String attributeName, final Object attributeValue) {
    JSONConstantAttributeMapper(final String attributeName, final Object attributeValue) {
        this.jsonAttributeName = attributeName;
        this.jsonAttributeValue = attributeValue;
    }
@@ -109,7 +100,7 @@
    @Override
    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
        // TODO Auto-generated method stub
        h.handleResult(Collections.<Attribute>emptyList());
    }
    private <T extends Comparable<T>> Filter compare(final Context c, final FilterType type,
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -15,10 +15,12 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS;
import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
import static org.forgerock.opendj.rest2ldap.Utils.transform;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
@@ -56,6 +58,8 @@
import org.forgerock.opendj.ldap.ConnectionException;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.EntryNotFoundException;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.Filter;
@@ -64,7 +68,12 @@
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.TimeoutResultException;
import org.forgerock.opendj.ldap.controls.PostReadRequestControl;
import org.forgerock.opendj.ldap.controls.PostReadResponseControl;
import org.forgerock.opendj.ldap.controls.PreReadResponseControl;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Request;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.Result;
@@ -161,8 +170,8 @@
    private final NameStrategy nameStrategy;
    LDAPCollectionResourceProvider(final DN baseDN, final AttributeMapper mapper,
            final ConnectionFactory factory, final Config config, final NameStrategy nameStrategy,
            final MVCCStrategy mvccStrategy) {
            final ConnectionFactory factory, final NameStrategy nameStrategy,
            final MVCCStrategy mvccStrategy, final Config config) {
        this.baseDN = baseDN;
        this.attributeMapper = mapper;
        this.factory = factory;
@@ -186,25 +195,7 @@
    @Override
    public void createInstance(final ServerContext context, final CreateRequest request,
            final ResultHandler<Resource> handler) {
        // We will support three use-cases:
        //
        // 1) client provided: the RDN is derived from the ID
        // 2) client provided: the RDN is derived from a JSON attribute, the ID maps to a user attribute
        // 3) server provided: the RDN is derived from a JSON attribute
        //
        // Procedure:
        //
        // 1) Generate LDAP attributes and create entry
        // 2) Apply ID mapper: create RDN from entry/ID, store ID in entry
        // 3) Create add request
        // 4) Add post read control if policy rfc
        // 5) Do add request
        // 6) If add failed then return error
        // 7) If policy is rfc then return entry
        // 8) If policy is search then read entry
        //
        final Context c = wrap(context);
        final AddRequest addRequest = Requests.newAddRequest(DN.rootDN());
        attributeMapper.toLDAP(c, request.getContent(), new ResultHandler<List<Attribute>>() {
            @Override
            public void handleError(final ResourceException error) {
@@ -213,10 +204,16 @@
            @Override
            public void handleResult(final List<Attribute> result) {
                final AddRequest addRequest = Requests.newAddRequest(DN.rootDN());
                for (final Attribute attribute : result) {
                    addRequest.addAttribute(attribute);
                }
                nameStrategy.setResourceId(c, baseDN, request.getNewResourceId(), addRequest);
                nameStrategy.setResourceId(c, getBaseDN(c), request.getNewResourceId(), addRequest);
                if (config.readOnUpdatePolicy() == USE_READ_ENTRY_CONTROLS) {
                    final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
                    addRequest.addControl(PostReadRequestControl.newControl(false, attributes));
                }
                applyUpdate(c, addRequest, handler);
            }
        });
    }
@@ -374,24 +371,9 @@
                    @Override
                    public void handleResult(final SearchResultEntry entry) {
                        final String revision = mvccStrategy.getRevisionFromEntry(c, entry);
                        final ResultHandler<Map<String, Object>> mapHandler =
                                new ResultHandler<Map<String, Object>>() {
                                    @Override
                                    public void handleError(final ResourceException e) {
                                        handler.handleError(e);
                                    }
                                    @Override
                                    public void handleResult(final Map<String, Object> result) {
                                        final Resource resource =
                                                new Resource(resourceId, revision, new JsonValue(
                                                        result));
                                        handler.handleResult(resource);
                                    }
                                };
                        attributeMapper.toJSON(c, entry, mapHandler);
                        adaptEntry(c, entry, handler);
                    }
                };
        // The handler which will be invoked
@@ -421,6 +403,27 @@
        handler.handleError(new NotSupportedException("Not yet implemented"));
    }
    private void adaptEntry(final Context c, final Entry entry,
            final ResultHandler<Resource> handler) {
        final String actualResourceId = nameStrategy.getResourceId(c, entry);
        final String revision = mvccStrategy.getRevisionFromEntry(c, entry);
        final ResultHandler<Map<String, Object>> mapHandler =
                new ResultHandler<Map<String, Object>>() {
                    @Override
                    public void handleError(final ResourceException e) {
                        handler.handleError(e);
                    }
                    @Override
                    public void handleResult(final Map<String, Object> result) {
                        final Resource resource =
                                new Resource(actualResourceId, revision, new JsonValue(result));
                        handler.handleResult(resource);
                    }
                };
        attributeMapper.toJSON(c, entry, mapHandler);
    }
    /**
     * Adapts an LDAP result code to a resource exception.
     *
@@ -676,4 +679,79 @@
    private Context wrap(final ServerContext context) {
        return new Context(config, context);
    }
    private org.forgerock.opendj.ldap.ResultHandler<Result> postUpdateHandler(final Context c,
            final ResultHandler<Resource> handler) {
        // The handler which will be invoked for the LDAP add result.
        final org.forgerock.opendj.ldap.ResultHandler<Result> resultHandler =
                new org.forgerock.opendj.ldap.ResultHandler<Result>() {
                    @Override
                    public void handleErrorResult(final ErrorResultException error) {
                        handler.handleError(adaptErrorResult(error));
                    }
                    @Override
                    public void handleResult(final Result result) {
                        // FIXME: handle USE_SEARCH policy.
                        Entry entry;
                        try {
                            PostReadResponseControl postReadControl =
                                    result.getControl(PostReadResponseControl.DECODER, config
                                            .decodeOptions());
                            if (postReadControl != null) {
                                entry = postReadControl.getEntry();
                            } else {
                                PreReadResponseControl preReadControl =
                                        result.getControl(PreReadResponseControl.DECODER, config
                                                .decodeOptions());
                                if (preReadControl != null) {
                                    entry = preReadControl.getEntry();
                                } else {
                                    entry = null;
                                }
                            }
                        } catch (DecodeException e) {
                            // FIXME: log something?
                            entry = null;
                        }
                        if (entry != null) {
                            adaptEntry(c, entry, handler);
                        } else {
                            final Resource resource =
                                    new Resource(null, null, new JsonValue(Collections.emptyMap()));
                            handler.handleResult(resource);
                        }
                    }
                };
        return resultHandler;
    }
    private void applyUpdate(final Context c, final Request request,
            final ResultHandler<Resource> handler) {
        final org.forgerock.opendj.ldap.ResultHandler<Result> resultHandler =
                postUpdateHandler(c, handler);
        final ConnectionCompletionHandler<Result> outerHandler =
                new ConnectionCompletionHandler<Result>(resultHandler) {
                    @Override
                    public void handleResult(final Connection connection) {
                        final RequestCompletionHandler<Result> innerHandler =
                                new RequestCompletionHandler<Result>(connection, resultHandler);
                        // FIXME: simplify this once we have Connection#applyChange()
                        if (request instanceof AddRequest) {
                            connection.addAsync((AddRequest) request, null, innerHandler);
                        } else if (request instanceof org.forgerock.opendj.ldap.requests.DeleteRequest) {
                            connection.deleteAsync(
                                    (org.forgerock.opendj.ldap.requests.DeleteRequest) request,
                                    null, innerHandler);
                        } else if (request instanceof ModifyRequest) {
                            connection.modifyAsync((ModifyRequest) request, null, innerHandler);
                        } else {
                            throw new IllegalStateException();
                        }
                    }
                };
        factory.getConnectionAsync(outerHandler);
    }
}
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java
New file
@@ -0,0 +1,67 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2012-2013 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.ldap.Attributes.singletonAttribute;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.forgerock.json.fluent.JsonPointer;
import org.forgerock.json.fluent.JsonValue;
import org.forgerock.json.resource.ResultHandler;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.Filter;
/**
 * An attribute mapper which maps a single LDAP attribute to a fixed value.
 */
final class LDAPConstantAttributeMapper extends AttributeMapper {
    private final List<Attribute> attributes;
    LDAPConstantAttributeMapper(final AttributeDescription attributeName,
            final Object attributeValue) {
        attributes = Collections.singletonList(singletonAttribute(attributeName, attributeValue));
    }
    @Override
    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
            final Set<String> ldapAttributes) {
        // Nothing to do.
    }
    @Override
    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
        // This attribute mapper cannot handle the provided filter component.
        h.handleResult(null);
    }
    @Override
    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
        h.handleResult(Collections.<String, Object> emptyMap());
    }
    @Override
    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
        h.handleResult(attributes);
    }
}
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java
New file
@@ -0,0 +1,48 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2013 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap;
/**
 * The policy which should be used in order to read an entry before it is
 * deleted, or after it is added or modified.
 */
public enum ReadOnUpdatePolicy {
    /**
     * The LDAP entry will not be read when an update is performed. More
     * specifically, the REST resource will not be returned as part of a create,
     * delete, patch, or update request.
     */
    DISABLED,
    /**
     * The LDAP entry will be read atomically using the RFC 4527 read-entry
     * controls. More specifically, the REST resource will be returned as part
     * of a create, delete, patch, or update request, and it will reflect the
     * state of the resource at the time the update was performed. This policy
     * requires that the LDAP server supports RFC 4527.
     */
    USE_READ_ENTRY_CONTROLS,
    /**
     * The LDAP entry will be read non-atomically using an LDAP search when an
     * update is performed. More specifically, the REST resource will be
     * returned as part of a create, delete, patch, or update request, but it
     * may not reflect the state of the resource at the time the update was
     * performed.
     */
    USE_SEARCH;
}
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -18,6 +18,7 @@
import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.ldap.schema.CoreSchema.getEntryUUIDAttributeType;
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
import java.util.Arrays;
@@ -52,16 +53,73 @@
     */
    public static final class Builder {
        private DN baseDN; // TODO: support template variables.
        private Config config = Config.defaultConfig();
        private ConnectionFactory factory;
        private final List<AttributeMapper> mappers = new LinkedList<AttributeMapper>();
        private MVCCStrategy mvccStrategy = mvccUsingEtag();
        private NameStrategy nameStrategy = nameByEntryUUID("uid");
        private Filter falseFilter = Filter.present("1.1");
        private Schema schema = Schema.getDefaultSchema();
        private ReadOnUpdatePolicy readOnUpdatePolicy = USE_READ_ENTRY_CONTROLS;
        private Filter trueFilter = Filter.objectClassPresent();
        Builder() {
            // No implementation required.
        }
        /**
         * Sets the schema which should be used when attribute types and
         * controls.
         *
         * @param schema
         *            The schema which should be used when attribute types and
         *            controls.
         * @return A reference to this builder.
         */
        public Builder schema(final Schema schema) {
            this.schema = ensureNotNull(schema);
            return this;
        }
        /**
         * Sets the absolute false filter which should be used when querying the
         * LDAP server.
         *
         * @param filter
         *            The absolute false filter.
         * @return A reference to this builder.
         */
        public Builder falseFilter(final Filter filter) {
            this.trueFilter = ensureNotNull(filter);
            return this;
        }
        /**
         * Sets the policy which should be used in order to read an entry before
         * it is deleted, or after it is added or modified.
         *
         * @param policy
         *            The policy which should be used in order to read an entry
         *            before it is deleted, or after it is added or modified.
         * @return A reference to this builder.
         */
        public Builder readOnUpdatePolicy(final ReadOnUpdatePolicy policy) {
            this.readOnUpdatePolicy = ensureNotNull(policy);
            return this;
        }
        /**
         * Sets the absolute true filter which should be used when querying the
         * LDAP server.
         *
         * @param filter
         *            The absolute true filter.
         * @return A reference to this builder.
         */
        public Builder trueFilter(final Filter filter) {
            this.trueFilter = ensureNotNull(filter);
            return this;
        }
        public Builder baseDN(final DN dn) {
            ensureNotNull(dn);
            this.baseDN = dn;
@@ -80,14 +138,9 @@
            if (mappers.isEmpty()) {
                throw new IllegalStateException("No mappings provided");
            }
            return new LDAPCollectionResourceProvider(baseDN, mapOf(mappers), factory, config,
                    nameStrategy, mvccStrategy);
        }
        public Builder config(final Config config) {
            ensureNotNull(config);
            this.config = config;
            return this;
            return new LDAPCollectionResourceProvider(baseDN, mapOf(mappers), factory,
                    nameStrategy, mvccStrategy, new Config(trueFilter, falseFilter,
                            readOnUpdatePolicy, schema));
        }
        public Builder factory(final ConnectionFactory factory) {
@@ -254,8 +307,19 @@
        return new ComplexAttributeMapper(jsonAttribute, mapOf(mappers));
    }
    public static AttributeMapper mapConstant(final String attribute, final Object attributeValue) {
        return new ConstantAttributeMapper(attribute, attributeValue);
    public static AttributeMapper mapJSONConstant(final String attribute,
            final Object attributeValue) {
        return new JSONConstantAttributeMapper(attribute, attributeValue);
    }
    public static AttributeMapper mapLDAPConstant(final String attribute,
            final Object attributeValue) {
        return mapLDAPConstant(AttributeDescription.valueOf(attribute), attributeValue);
    }
    public static AttributeMapper mapLDAPConstant(final AttributeDescription attribute,
            final Object attributeValue) {
        return new LDAPConstantAttributeMapper(attribute, attributeValue);
    }
    public static MVCCStrategy mvccUsingAttribute(final AttributeDescription attribute) {