From bb7775ad525637d45e5192175b9b806dfff69e82 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 08 Feb 2013 18:23:37 +0000
Subject: [PATCH] Partial fix for OPENDJ-691: Implement add/create support

---
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java    |   15 -
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java         |    6 
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java       |   26 ++
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java                      |   86 ++++++-
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java         |   51 ++++
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java    |   67 ++++++
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java |  154 ++++++++++---
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java                         |  177 ++-------------
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java             |   48 ++++
 9 files changed, 420 insertions(+), 210 deletions(-)

diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
index 78a6672..a1ad982 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
+++ b/opendj-sdk/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) {
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
index ca359ed..cf77ea8 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
+++ b/opendj-sdk/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")
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
index 41f16eb..53076d2 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
+++ b/opendj-sdk/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;
     }
 
     /**
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
index 7c8fe43..030055b 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
+++ b/opendj-sdk/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) {
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java
similarity index 91%
rename from opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
rename to opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.java
index 6484101..fac2ae1 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/JSONConstantAttributeMapper.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,
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
index 8c4200b..1c6b825 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
+++ b/opendj-sdk/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);
+    }
 }
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java
new file mode 100644
index 0000000..87272b6
--- /dev/null
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPConstantAttributeMapper.java
@@ -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);
+    }
+
+}
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java
new file mode 100644
index 0000000..1d68304
--- /dev/null
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReadOnUpdatePolicy.java
@@ -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;
+}
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 46ad0f4..8b7583a 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj-sdk/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) {

--
Gitblit v1.10.0