From b09a9d843c32f9d4127d6a8db31a98cfdec9cbad Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 08 Feb 2013 11:37:59 +0000
Subject: [PATCH] Checkpoint changes: clean up APIs, separate out MVCC and naming strategies.

---
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java          |   96 ++---
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java                        |    2 
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java         |   29 -
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java                          |   15 
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java         |   33 -
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java                |   20 
 opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java                        |   54 +-
 /dev/null                                                                                                            |   73 ----
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java        |   37 -
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java       |   45 -
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java                      |  321 +++++++++++++++++
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java                   |   60 +++
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java                   |   95 +++++
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java |  162 +++-----
 14 files changed, 660 insertions(+), 382 deletions(-)

diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
index 9240bac..cdb9c5c 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
@@ -30,7 +30,15 @@
  * An attribute mapper is responsible for converting JSON values to and from
  * LDAP attributes.
  */
-public interface AttributeMapper {
+public abstract class AttributeMapper {
+    /*
+     * This interface is an abstract class so that methods can be made package
+     * private until API is finalized.
+     */
+
+    AttributeMapper() {
+        // Nothing to do.
+    }
 
     /**
      * Adds the names of the LDAP attributes required by this attribute mapper
@@ -48,7 +56,7 @@
      *            The set into which the required LDAP attribute names should be
      *            put.
      */
-    void getLDAPAttributes(Context c, JsonPointer jsonAttribute, Set<String> ldapAttributes);
+    abstract void getLDAPAttributes(Context c, JsonPointer jsonAttribute, Set<String> ldapAttributes);
 
     /**
      * Transforms the provided REST comparison filter parameters to an LDAP
@@ -80,8 +88,8 @@
      * @param h
      *            The result handler.
      */
-    void getLDAPFilter(Context c, FilterType type, JsonPointer jsonAttribute, String operator,
-            Object valueAssertion, ResultHandler<Filter> h);
+    abstract void getLDAPFilter(Context c, FilterType type, JsonPointer jsonAttribute,
+            String operator, Object valueAssertion, ResultHandler<Filter> h);
 
     /**
      * Transforms attributes contained in the provided LDAP entry to JSON
@@ -99,7 +107,7 @@
      * @param h
      *            The result handler.
      */
-    void toJSON(Context c, Entry e, ResultHandler<Map<String, Object>> h);
+    abstract void toJSON(Context c, Entry e, ResultHandler<Map<String, Object>> h);
 
     /**
      * Transforms JSON content in the provided JSON value to LDAP attributes,
@@ -116,7 +124,7 @@
      * @param h
      *            The result handler.
      */
-    void toLDAP(Context c, JsonValue v, ResultHandler<List<Attribute>> h);
+    abstract void toLDAP(Context c, JsonValue v, ResultHandler<List<Attribute>> h);
 
     // TODO: methods for obtaining schema information (e.g. name, description,
     // type information).
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 a5e5705..78a6672 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
@@ -34,7 +34,7 @@
  * An attribute mapper which maps a single JSON attribute to the result of
  * another attribute mapper.
  */
-public class ComplexAttributeMapper implements AttributeMapper {
+final class ComplexAttributeMapper extends AttributeMapper {
 
     private final String jsonAttributeName;
     private final AttributeMapper mapper;
@@ -50,17 +50,14 @@
      *            The mapper which should be used to provide the contents of the
      *            complex attribute.
      */
-    public ComplexAttributeMapper(final String jsonAttributeName, final AttributeMapper mapper) {
+    ComplexAttributeMapper(final String jsonAttributeName, final AttributeMapper mapper) {
         this.jsonAttributeName = jsonAttributeName;
         this.mapper = mapper;
         this.normalizedJsonAttributeName = toLowerCase(jsonAttributeName);
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
             final Set<String> ldapAttributes) {
         if (jsonAttribute.isEmpty() || matches(jsonAttribute)) {
             final JsonPointer relativePointer = jsonAttribute.relativePointer();
@@ -68,13 +65,9 @@
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void getLDAPFilter(final Context c, final FilterType type,
-            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
-            final ResultHandler<Filter> h) {
+    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
+            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
         if (matches(jsonAttribute)) {
             final JsonPointer relativePointer = jsonAttribute.relativePointer();
             mapper.getLDAPFilter(c, type, relativePointer, operator, valueAssertion, h);
@@ -84,12 +77,8 @@
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void toJSON(final Context c, final Entry e,
-            final ResultHandler<Map<String, Object>> h) {
+    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
         final ResultHandler<Map<String, Object>> wrapper =
                 new ResultHandler<Map<String, Object>>() {
 
@@ -108,12 +97,8 @@
         mapper.toJSON(c, e, wrapper);
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void toLDAP(final Context c, final JsonValue v,
-            final ResultHandler<List<Attribute>> h) {
+    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
     }
 
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 c5dfa31..ca359ed 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
@@ -19,10 +19,10 @@
 import static org.forgerock.opendj.rest2ldap.Utils.transform;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -39,46 +39,27 @@
  * An attribute mapper which combines the results of a set of subordinate
  * attribute mappers into a single JSON object.
  */
-public final class CompositeAttributeMapper implements AttributeMapper {
-    private final List<AttributeMapper> attributeMappers = new LinkedList<AttributeMapper>();
+final class CompositeAttributeMapper extends AttributeMapper {
+    private final List<AttributeMapper> attributeMappers;
 
     /**
      * Creates a new composite attribute mapper.
      */
-    public CompositeAttributeMapper() {
-        // No implementation required.
+    CompositeAttributeMapper(final Collection<AttributeMapper> mappers) {
+        this.attributeMappers = new ArrayList<AttributeMapper>(mappers);
     }
 
-    /**
-     * Adds a subordinate attribute mapper to this composite attribute mapper.
-     *
-     * @param mapper
-     *            The subordinate attribute mapper.
-     * @return This composite attribute mapper.
-     */
-    public CompositeAttributeMapper addMapper(final AttributeMapper mapper) {
-        attributeMappers.add(mapper);
-        return this;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
             final Set<String> ldapAttributes) {
         for (final AttributeMapper attribute : attributeMappers) {
             attribute.getLDAPAttributes(c, jsonAttribute, ldapAttributes);
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void getLDAPFilter(final Context c, final FilterType type,
-            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
-            final ResultHandler<Filter> h) {
+    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
+            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
         final ResultHandler<Filter> handler =
                 accumulate(attributeMappers.size(), transform(
                         new Function<List<Filter>, Filter, Void>() {
@@ -113,11 +94,8 @@
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
+    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
         final ResultHandler<Map<String, Object>> handler =
                 accumulate(attributeMappers.size(), transform(
                         new Function<List<Map<String, Object>>, Map<String, Object>, Void>() {
@@ -141,11 +119,8 @@
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
+    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
     }
 
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/ConstantAttributeMapper.java
index fec587e..6484101 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/ConstantAttributeMapper.java
@@ -32,7 +32,7 @@
 /**
  * An attribute mapper which maps a single JSON attribute to a fixed value.
  */
-public class ConstantAttributeMapper implements AttributeMapper {
+final class ConstantAttributeMapper extends AttributeMapper {
     private final String jsonAttributeName;
     private final Object jsonAttributeValue;
 
@@ -45,27 +45,20 @@
      * @param attributeValue
      *            The value of the simple JSON attribute.
      */
-    public ConstantAttributeMapper(final String attributeName, final Object attributeValue) {
+    ConstantAttributeMapper(final String attributeName, final Object attributeValue) {
         this.jsonAttributeName = attributeName;
         this.jsonAttributeValue = attributeValue;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
             final Set<String> ldapAttributes) {
         // Nothing to do.
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void getLDAPFilter(final Context c, final FilterType type,
-            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
-            final ResultHandler<Filter> h) {
+    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
+            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
         if (jsonAttribute.size() == 1 && jsonAttribute.get(0).equalsIgnoreCase(jsonAttributeName)) {
             final Filter filter;
             if (type == FilterType.PRESENT) {
@@ -107,26 +100,20 @@
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
+    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
         // FIXME: how do we know if the user requested it???
         h.handleResult(Collections.singletonMap(jsonAttributeName, jsonAttributeValue));
 
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
+    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
     }
 
-    private <T extends Comparable<T>> Filter compare(Context c, final FilterType type, final T v1,
-            final T v2) {
+    private <T extends Comparable<T>> Filter compare(final Context c, final FilterType type,
+            final T v1, final T v2) {
         final Filter filter;
         switch (type) {
         case EQUAL_TO:
@@ -134,8 +121,7 @@
             break;
         case GREATER_THAN:
             filter =
-                    v1.compareTo(v2) > 0 ? c.getConfig().trueFilter() : c.getConfig()
-                            .falseFilter();
+                    v1.compareTo(v2) > 0 ? c.getConfig().trueFilter() : c.getConfig().falseFilter();
             break;
         case GREATER_THAN_OR_EQUAL_TO:
             filter =
@@ -144,8 +130,7 @@
             break;
         case LESS_THAN:
             filter =
-                    v1.compareTo(v2) < 0 ? c.getConfig().trueFilter() : c.getConfig()
-                            .falseFilter();
+                    v1.compareTo(v2) < 0 ? c.getConfig().trueFilter() : c.getConfig().falseFilter();
             break;
         case LESS_THAN_OR_EQUAL_TO:
             filter =
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
index 5faf14f..cee7d06 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
@@ -24,7 +24,7 @@
     private final Config config;
     private final ServerContext context;
 
-    Context(Config config, ServerContext context) {
+    Context(final Config config, final ServerContext context) {
         this.config = config;
         this.context = context;
     }
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 8314c1b..7c8fe43 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
@@ -36,7 +36,7 @@
  * An attribute mapper that directly maps a configurable selection of attributes
  * to and from LDAP without any transformation.
  */
-public final class DefaultAttributeMapper implements AttributeMapper {
+final class DefaultAttributeMapper extends AttributeMapper {
     // All user attributes by default.
     private final Map<String, String> excludedAttributes = new LinkedHashMap<String, String>();
     private final Map<String, String> includedAttributes = new LinkedHashMap<String, String>();
@@ -45,7 +45,7 @@
      * Creates a new default attribute mapper which will map all user attributes
      * to JSON by default.
      */
-    public DefaultAttributeMapper() {
+    DefaultAttributeMapper() {
         // No implementation required.
     }
 
@@ -56,18 +56,15 @@
      *            The attributes to be excluded.
      * @return This attribute mapper.
      */
-    public DefaultAttributeMapper excludeAttribute(final String... attributes) {
+    DefaultAttributeMapper excludeAttribute(final String... attributes) {
         for (final String attribute : attributes) {
             excludedAttributes.put(toLowerCase(attribute), attribute);
         }
         return this;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
             final Set<String> ldapAttributes) {
         switch (jsonAttribute.size()) {
         case 0:
@@ -88,13 +85,9 @@
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void getLDAPFilter(final Context c, final FilterType type,
-            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
-            final ResultHandler<Filter> h) {
+    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
+            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
         if (jsonAttribute.size() == 1 && isIncludedAttribute(jsonAttribute.get(0))) {
             h.handleResult(toFilter(c, type, jsonAttribute.get(0), valueAssertion));
         } else {
@@ -110,18 +103,17 @@
      *            The attributes to be included.
      * @return This attribute mapper.
      */
-    public DefaultAttributeMapper includeAttribute(final String... attributes) {
+    DefaultAttributeMapper includeAttribute(final String... attributes) {
         for (final String attribute : attributes) {
             includedAttributes.put(toLowerCase(attribute), attribute);
         }
         return this;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
+    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
+        // FIXME: this will leave out attributes which were not included in the LDAP entry. We should
+        // ensure that JSON attributes are present, even if they are null or an empty array.
         final Map<String, Object> result = new LinkedHashMap<String, Object>(e.getAttributeCount());
         for (final Attribute a : e.getAllAttributes()) {
             final String name = getAttributeName(a);
@@ -132,11 +124,8 @@
         h.handleResult(result);
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
+    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO:
     }
 
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 dd3b78f..8c4200b 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
@@ -49,13 +49,13 @@
 import org.forgerock.json.resource.UncategorizedException;
 import org.forgerock.json.resource.UpdateRequest;
 import org.forgerock.opendj.ldap.AssertionFailureException;
+import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.AuthenticationException;
 import org.forgerock.opendj.ldap.AuthorizationException;
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.ConnectionException;
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.DN;
-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,6 +64,7 @@
 import org.forgerock.opendj.ldap.SearchResultHandler;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.TimeoutResultException;
+import org.forgerock.opendj.ldap.requests.AddRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
 import org.forgerock.opendj.ldap.requests.SearchRequest;
 import org.forgerock.opendj.ldap.responses.Result;
@@ -74,7 +75,7 @@
  * A {@code CollectionResourceProvider} implementation which maps a JSON
  * resource collection to LDAP entries beneath a base DN.
  */
-public class LDAPCollectionResourceProvider implements CollectionResourceProvider {
+final class LDAPCollectionResourceProvider implements CollectionResourceProvider {
 
     private abstract class AbstractRequestCompletionHandler<R,
             H extends org.forgerock.opendj.ldap.ResultHandler<? super R>>
@@ -137,17 +138,11 @@
             super(connection, resultHandler);
         }
 
-        /**
-         * {@inheritDoc}
-         */
         @Override
         public final boolean handleEntry(final SearchResultEntry entry) {
             return resultHandler.handleEntry(entry);
         }
 
-        /**
-         * {@inheritDoc}
-         */
         @Override
         public final boolean handleReference(final SearchResultReference reference) {
             return resultHandler.handleReference(reference);
@@ -155,95 +150,93 @@
 
     }
 
-    // FIXME: make this configurable.
-    private static final String ETAG_ATTRIBUTE = "etag";
-
     // Dummy exception used for signalling search success.
     private static final ResourceException SUCCESS = new UncategorizedException(0, null, null);
 
-    // FIXME: make this configurable, also allow use of DN.
-    private static final String UUID_ATTRIBUTE = "entryUUID";
-
     private final AttributeMapper attributeMapper;
     private final DN baseDN; // TODO: support template variables.
     private final Config config;
     private final ConnectionFactory factory;
+    private final MVCCStrategy mvccStrategy;
+    private final NameStrategy nameStrategy;
 
-    /**
-     * Creates a new LDAP resource.
-     *
-     * @param baseDN
-     *            The parent of all entries contained in this LDAP collection.
-     * @param mapper
-     *            The attribute mapper which will be used for mapping LDAP
-     *            attributes to JSON attributes.
-     * @param factory
-     *            The LDAP connection factory which will be used for performing
-     *            LDAP operations.
-     * @param config
-     *            Common configuration options.
-     */
-    public LDAPCollectionResourceProvider(final DN baseDN, final AttributeMapper mapper,
-            final ConnectionFactory factory, final Config config) {
+    LDAPCollectionResourceProvider(final DN baseDN, final AttributeMapper mapper,
+            final ConnectionFactory factory, final Config config, final NameStrategy nameStrategy,
+            final MVCCStrategy mvccStrategy) {
         this.baseDN = baseDN;
         this.attributeMapper = mapper;
         this.factory = factory;
         this.config = config;
+        this.nameStrategy = nameStrategy;
+        this.mvccStrategy = mvccStrategy;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void actionCollection(final ServerContext context, final ActionRequest request,
             final ResultHandler<JsonValue> handler) {
         handler.handleError(new NotSupportedException("Not yet implemented"));
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void actionInstance(final ServerContext context, final String resourceId,
             final ActionRequest request, final ResultHandler<JsonValue> handler) {
         handler.handleError(new NotSupportedException("Not yet implemented"));
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void createInstance(final ServerContext context, final CreateRequest request,
             final ResultHandler<Resource> handler) {
-        handler.handleError(new NotSupportedException("Not yet implemented"));
+        // 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) {
+                handler.handleError(error);
+            }
+
+            @Override
+            public void handleResult(final List<Attribute> result) {
+                for (final Attribute attribute : result) {
+                    addRequest.addAttribute(attribute);
+                }
+                nameStrategy.setResourceId(c, baseDN, request.getNewResourceId(), addRequest);
+            }
+        });
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void deleteInstance(final ServerContext context, final String resourceId,
             final DeleteRequest request, final ResultHandler<Resource> handler) {
         handler.handleError(new NotSupportedException("Not yet implemented"));
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void patchInstance(final ServerContext context, final String resourceId,
             final PatchRequest request, final ResultHandler<Resource> handler) {
         handler.handleError(new NotSupportedException("Not yet implemented"));
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void queryCollection(final ServerContext context, final QueryRequest request,
             final QueryResultHandler handler) {
         final Context c = wrap(context);
-        final Collection<String> ldapAttributes = getLDAPAttributes(c, request.getFieldFilters());
 
         // The handler which will be invoked for each LDAP search result.
         final SearchResultHandler searchHandler = new SearchResultHandler() {
@@ -263,10 +256,8 @@
                     return false;
                 }
 
-                // TODO: should the resource or the container define the ID
-                // mapping?
-                final String id = getIDFromEntry(entry);
-                final String revision = getEtagFromEntry(entry);
+                final String id = nameStrategy.getResourceId(c, entry);
+                final String revision = mvccStrategy.getRevisionFromEntry(c, entry);
                 final ResultHandler<Map<String, Object>> mapHandler =
                         new ResultHandler<Map<String, Object>>() {
                             @Override
@@ -341,7 +332,6 @@
                 if (ldapFilter == null || ldapFilter == c.getConfig().falseFilter()) {
                     handler.handleResult(new QueryResult());
                 } else {
-                    final String[] tmp = getSearchAttributes(ldapAttributes);
                     final ConnectionCompletionHandler<Result> outerHandler =
                             new ConnectionCompletionHandler<Result>(searchHandler) {
 
@@ -350,9 +340,12 @@
                                     final SearchRequestCompletionHandler innerHandler =
                                             new SearchRequestCompletionHandler(connection,
                                                     searchHandler);
+                                    final String[] attributes =
+                                            getLDAPAttributes(c, request.getFieldFilters());
                                     final SearchRequest request =
-                                            Requests.newSearchRequest(baseDN,
-                                                    SearchScope.SINGLE_LEVEL, ldapFilter, tmp);
+                                            Requests.newSearchRequest(getBaseDN(c),
+                                                    SearchScope.SINGLE_LEVEL, ldapFilter,
+                                                    attributes);
                                     connection.searchAsync(request, null, innerHandler);
                                 }
 
@@ -366,15 +359,10 @@
         getLDAPFilter(c, request.getQueryFilter(), filterHandler);
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void readInstance(final ServerContext context, final String resourceId,
             final ReadRequest request, final ResultHandler<Resource> handler) {
         final Context c = wrap(context);
-        final Collection<String> ldapAttributes = getLDAPAttributes(c, request.getFieldFilters());
-        final String[] tmp = getSearchAttributes(ldapAttributes);
 
         // The handler which will be invoked for the LDAP search result.
         final org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry> searchHandler =
@@ -386,7 +374,7 @@
 
                     @Override
                     public void handleResult(final SearchResultEntry entry) {
-                        final String revision = getEtagFromEntry(entry);
+                        final String revision = mvccStrategy.getRevisionFromEntry(c, entry);
                         final ResultHandler<Map<String, Object>> mapHandler =
                                 new ResultHandler<Map<String, Object>>() {
                                     @Override
@@ -415,9 +403,10 @@
                         final RequestCompletionHandler<SearchResultEntry> innerHandler =
                                 new RequestCompletionHandler<SearchResultEntry>(connection,
                                         searchHandler);
+                        final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
                         final SearchRequest request =
-                                Requests.newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter
-                                        .equality(UUID_ATTRIBUTE, resourceId), tmp);
+                                nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId)
+                                        .addAttribute(attributes);
                         connection.searchSingleEntryAsync(request, innerHandler);
                     }
 
@@ -426,9 +415,6 @@
         factory.getConnectionAsync(outerHandler);
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
     public void updateInstance(final ServerContext context, final String resourceId,
             final UpdateRequest request, final ResultHandler<Resource> handler) {
@@ -466,26 +452,8 @@
         return ResourceException.getException(resourceResultCode, null, error.getMessage(), error);
     }
 
-    /**
-     * Returns the ETag for the provided entry.
-     *
-     * @param entry
-     *            The entry.
-     * @return The ETag.
-     */
-    private String getEtagFromEntry(final Entry entry) {
-        return entry.parseAttribute(ETAG_ATTRIBUTE).asString();
-    }
-
-    /**
-     * Returns the resource ID for the provided entry.
-     *
-     * @param entry
-     *            The entry.
-     * @return The resource ID.
-     */
-    private String getIDFromEntry(final Entry entry) {
-        return entry.parseAttribute(UUID_ATTRIBUTE).asString();
+    private DN getBaseDN(final Context context) {
+        return baseDN;
     }
 
     /**
@@ -497,8 +465,9 @@
      * @return The set of LDAP attributes associated with the resource
      *         attributes.
      */
-    private Collection<String> getLDAPAttributes(final Context c,
+    private String[] getLDAPAttributes(final Context c,
             final Collection<JsonPointer> requestedAttributes) {
+        // Get all the LDAP attributes required by the attribute mappers.
         final Set<String> requestedLDAPAttributes;
         if (requestedAttributes.isEmpty()) {
             // Full read.
@@ -511,7 +480,11 @@
                 attributeMapper.getLDAPAttributes(c, requestedAttribute, requestedLDAPAttributes);
             }
         }
-        return requestedLDAPAttributes;
+
+        // Get the LDAP attributes required by the Etag and name stategies.
+        nameStrategy.getLDAPAttributes(c, requestedLDAPAttributes);
+        mvccStrategy.getLDAPAttributes(c, requestedLDAPAttributes);
+        return requestedLDAPAttributes.toArray(new String[requestedLDAPAttributes.size()]);
     }
 
     private void getLDAPFilter(final Context c, final QueryFilter queryFilter,
@@ -700,15 +673,6 @@
         queryFilter.accept(visitor, h);
     }
 
-    private String[] getSearchAttributes(final Collection<String> attributes) {
-        // FIXME: who is responsible for adding the UUID and etag attributes to
-        // this search?
-        final String[] tmp = attributes.toArray(new String[attributes.size() + 2]);
-        tmp[tmp.length - 2] = UUID_ATTRIBUTE;
-        tmp[tmp.length - 1] = ETAG_ATTRIBUTE;
-        return tmp;
-    }
-
     private Context wrap(final ServerContext context) {
         return new Context(config, context);
     }
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java
new file mode 100644
index 0000000..8e04aaf
--- /dev/null
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import org.forgerock.opendj.ldap.Entry;
+
+/**
+ * A multi-version concurrency control strategy is responsible for ensuring that
+ * clients can perform atomic updates to LDAP resources.
+ */
+public abstract class MVCCStrategy {
+    /*
+     * This interface is an abstract class so that methods can be made package
+     * private until API is finalized.
+     */
+
+    MVCCStrategy() {
+        // Nothing to do.
+    }
+
+    /**
+     * Retrieves the revision value (etag) from the provided LDAP entry.
+     *
+     * @param c
+     *            The context.
+     * @param entry
+     *            The LDAP entry.
+     * @return The revision value.
+     */
+    abstract String getRevisionFromEntry(Context c, Entry entry);
+
+    /**
+     * Adds the name of any LDAP attribute required by this MVCC strategy to the
+     * provided set.
+     *
+     * @param c
+     *            The context.
+     * @param ldapAttributes
+     *            The set into which any required LDAP attribute name should be
+     *            put.
+     */
+    abstract void getLDAPAttributes(Context c, Set<String> ldapAttributes);
+
+}
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java
new file mode 100644
index 0000000..3010cc3
--- /dev/null
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java
@@ -0,0 +1,95 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+
+/**
+ * A name strategy is responsible for naming REST resources and LDAP entries.
+ */
+public abstract class NameStrategy {
+    /*
+     * This interface is an abstract class so that methods can be made package
+     * private until API is finalized.
+     */
+
+    NameStrategy() {
+        // Nothing to do.
+    }
+
+    /**
+     * Returns a search request which can be used to obtain the specified REST
+     * resource.
+     *
+     * @param c
+     *            The context.
+     * @param baseDN
+     *            The search base DN.
+     * @param resourceId
+     *            The resource ID.
+     * @return A search request which can be used to obtain the specified REST
+     *         resource.
+     */
+    abstract SearchRequest createSearchRequest(Context c, DN baseDN, String resourceId);
+
+    /**
+     * Adds the name of any LDAP attribute required by this name strategy to the
+     * provided set.
+     *
+     * @param c
+     *            The context.
+     * @param ldapAttributes
+     *            The set into which any required LDAP attribute name should be
+     *            put.
+     */
+    abstract void getLDAPAttributes(Context c, Set<String> ldapAttributes);
+
+    /**
+     * Retrieves the resource ID from the provided LDAP entry. Implementations
+     * may use the entry DN as well as any attributes in order to determine the
+     * resource ID.
+     *
+     * @param c
+     *            The context.
+     * @param entry
+     *            The LDAP entry from which the resource ID should be obtained.
+     * @return The resource ID.
+     */
+    abstract String getResourceId(Context c, Entry entry);
+
+    /**
+     * Sets the resource ID in the provided LDAP entry. Implementations are
+     * responsible for setting the entry DN as well as any attributes associated
+     * with the resource ID.
+     *
+     * @param c
+     *            The context.
+     * @param baseDN
+     *            The baseDN to use when constructing the entry's DN.
+     * @param resourceId
+     *            The resource ID.
+     * @param entry
+     *            The LDAP entry whose DN and resource ID attributes are to be
+     *            set.
+     */
+    abstract void setResourceId(Context c, DN baseDN, String resourceId, Entry entry);
+
+}
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
deleted file mode 100644
index 645c14c..0000000
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 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.Entry;
-import org.forgerock.opendj.ldap.Filter;
-
-/**
- * An attribute mapper which maps DN valued LDAP attributes to resource IDs.
- */
-public class ReferenceAttributeMapper implements AttributeMapper {
-
-    // private final EntryContainer referencedContainer;
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
-            final Set<String> ldapAttributes) {
-        // TODO Auto-generated method stub
-
-    }
-
-    @Override
-    public void getLDAPFilter(final Context c, final FilterType type,
-            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
-            final ResultHandler<Filter> h) {
-        // TODO Auto-generated method stub
-
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
-        // TODO Auto-generated method stub
-
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
-        // TODO Auto-generated method stub
-    }
-
-}
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
new file mode 100644
index 0000000..46ad0f4
--- /dev/null
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -0,0 +1,321 @@
+/*
+ * 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;
+
+import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
+import static org.forgerock.opendj.ldap.schema.CoreSchema.getEntryUUIDAttributeType;
+import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.forgerock.json.resource.CollectionResourceProvider;
+import org.forgerock.opendj.ldap.AttributeDescription;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.Filter;
+import org.forgerock.opendj.ldap.LinkedAttribute;
+import org.forgerock.opendj.ldap.RDN;
+import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
+import org.forgerock.opendj.ldap.schema.AttributeType;
+import org.forgerock.opendj.ldap.schema.Schema;
+
+/**
+ * Provides core factory methods and builders for constructing LDAP resource
+ * collections.
+ */
+public final class Rest2LDAP {
+    // @Checkstyle:off
+
+    /**
+     * A builder for incrementally constructing LDAP resource collections.
+     */
+    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");
+
+        Builder() {
+            // No implementation required.
+        }
+
+        public Builder baseDN(final DN dn) {
+            ensureNotNull(dn);
+            this.baseDN = dn;
+            return this;
+        }
+
+        public Builder baseDN(final String dn) {
+            ensureNotNull(dn);
+            this.baseDN = DN.valueOf(dn);
+            return this;
+        }
+
+        public CollectionResourceProvider build() {
+            ensureNotNull(factory);
+            ensureNotNull(baseDN);
+            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;
+        }
+
+        public Builder factory(final ConnectionFactory factory) {
+            ensureNotNull(factory);
+            this.factory = factory;
+            return this;
+        }
+
+        public Builder map(final AttributeMapper... mappers) {
+            ensureNotNull(mappers);
+            this.mappers.addAll(Arrays.asList(mappers));
+            return this;
+        }
+
+        public Builder map(final Collection<AttributeMapper> mappers) {
+            ensureNotNull(mappers);
+            this.mappers.addAll(mappers);
+            return this;
+        }
+
+        public Builder with(final MVCCStrategy strategy) {
+            ensureNotNull(strategy);
+            this.mvccStrategy = strategy;
+            return this;
+        }
+
+        public Builder with(final NameStrategy strategy) {
+            ensureNotNull(strategy);
+            this.nameStrategy = strategy;
+            return this;
+        }
+    }
+
+    private static final class AttributeMVCCStrategy extends MVCCStrategy {
+        private final AttributeDescription ldapAttribute;
+
+        private AttributeMVCCStrategy(final AttributeDescription ldapAttribute) {
+            this.ldapAttribute = ldapAttribute;
+        }
+
+        @Override
+        String getRevisionFromEntry(final Context c, final Entry entry) {
+            return entry.parseAttribute(ldapAttribute).asString();
+        }
+
+        @Override
+        void getLDAPAttributes(final Context c, final Set<String> ldapAttributes) {
+            ldapAttributes.add(ldapAttribute.toString());
+        }
+    }
+
+    private static final class AttributeNameStrategy extends NameStrategy {
+        private final AttributeDescription dnAttribute;
+        private final AttributeDescription idAttribute;
+        private final boolean isServerProvided;
+
+        private AttributeNameStrategy(final AttributeType dnAttribute,
+                final AttributeDescription idAttribute, final boolean isServerProvided) {
+            this.dnAttribute = AttributeDescription.create(dnAttribute);
+            if (dnAttribute.equals(idAttribute)) {
+                throw new IllegalArgumentException("DN and ID attributes must be different");
+            }
+            this.idAttribute = ensureNotNull(idAttribute);
+            this.isServerProvided = isServerProvided;
+        }
+
+        @Override
+        SearchRequest createSearchRequest(final Context c, final DN baseDN, final String resourceId) {
+            return newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter.equality(idAttribute
+                    .toString(), resourceId));
+        }
+
+        @Override
+        void getLDAPAttributes(final Context c, final Set<String> ldapAttributes) {
+            ldapAttributes.add(idAttribute.toString());
+        }
+
+        @Override
+        String getResourceId(final Context c, final Entry entry) {
+            return entry.parseAttribute(idAttribute).asString();
+        }
+
+        @Override
+        void setResourceId(final Context c, final DN baseDN, final String resourceId,
+                final Entry entry) {
+            if (!isServerProvided) {
+                entry.addAttribute(new LinkedAttribute(idAttribute, ByteString.valueOf(resourceId)));
+            }
+            final String rdnValue = entry.parseAttribute(dnAttribute).asString();
+            final RDN rdn = new RDN(dnAttribute.getAttributeType(), rdnValue);
+            entry.setName(baseDN.child(rdn));
+
+        }
+    }
+
+    private static final class DNNameStrategy extends NameStrategy {
+        private final AttributeDescription attribute;
+
+        private DNNameStrategy(final AttributeType attribute) {
+            this.attribute = AttributeDescription.create(attribute);
+        }
+
+        @Override
+        SearchRequest createSearchRequest(final Context c, final DN baseDN, final String resourceId) {
+            return newSearchRequest(baseDN.child(rdn(resourceId)), SearchScope.BASE_OBJECT, c
+                    .getConfig().trueFilter());
+        }
+
+        @Override
+        void getLDAPAttributes(final Context c, final Set<String> ldapAttributes) {
+            ldapAttributes.add(attribute.toString());
+        }
+
+        @Override
+        String getResourceId(final Context c, final Entry entry) {
+            return entry.parseAttribute(attribute).asString();
+        }
+
+        @Override
+        void setResourceId(final Context c, final DN baseDN, final String resourceId,
+                final Entry entry) {
+            entry.setName(baseDN.child(rdn(resourceId)));
+            entry.addAttribute(new LinkedAttribute(attribute, ByteString.valueOf(resourceId)));
+        }
+
+        private RDN rdn(final String resourceId) {
+            return new RDN(attribute.getAttributeType(), resourceId);
+        }
+
+    }
+
+    public static SimpleAttributeMapper map(final AttributeDescription attribute) {
+        return map(attribute.toString(), attribute);
+    }
+
+    public static SimpleAttributeMapper map(final String attribute) {
+        return map(attribute, attribute);
+    }
+
+    public static SimpleAttributeMapper map(final String jsonAttribute,
+            final AttributeDescription ldapAttribute) {
+        return new SimpleAttributeMapper(jsonAttribute, ldapAttribute);
+    }
+
+    public static SimpleAttributeMapper map(final String jsonAttribute, final String ldapAttribute) {
+        return map(jsonAttribute, AttributeDescription.valueOf(ldapAttribute));
+    }
+
+    public static AttributeMapper mapAllExcept(final String... attributes) {
+        return new DefaultAttributeMapper().excludeAttribute(attributes);
+    }
+
+    public static AttributeMapper mapAllOf(final String... attributes) {
+        return new DefaultAttributeMapper().includeAttribute(attributes);
+    }
+
+    public static AttributeMapper mapComplex(final String jsonAttribute,
+            final AttributeMapper... mappers) {
+        return mapComplex(jsonAttribute, Arrays.asList(mappers));
+    }
+
+    public static AttributeMapper mapComplex(final String jsonAttribute,
+            final Collection<AttributeMapper> mappers) {
+        return new ComplexAttributeMapper(jsonAttribute, mapOf(mappers));
+    }
+
+    public static AttributeMapper mapConstant(final String attribute, final Object attributeValue) {
+        return new ConstantAttributeMapper(attribute, attributeValue);
+    }
+
+    public static MVCCStrategy mvccUsingAttribute(final AttributeDescription attribute) {
+        return new AttributeMVCCStrategy(attribute);
+    }
+
+    public static MVCCStrategy mvccUsingAttribute(final String attribute) {
+        return mvccUsingAttribute(AttributeDescription.valueOf(attribute));
+    }
+
+    public static MVCCStrategy mvccUsingEtag() {
+        return mvccUsingAttribute("etag");
+    }
+
+    public static NameStrategy nameByClient(final AttributeType dnAttribute,
+            final AttributeDescription idAttribute) {
+        return new AttributeNameStrategy(dnAttribute, idAttribute, false);
+    }
+
+    public static NameStrategy nameByClient(final String dnAttribute, final String idAttribute) {
+        return nameByClient(Schema.getDefaultSchema().getAttributeType(dnAttribute),
+                AttributeDescription.valueOf(idAttribute));
+    }
+
+    public static NameStrategy nameByDN(final AttributeType attribute) {
+        return new DNNameStrategy(attribute);
+    }
+
+    public static NameStrategy nameByDN(final String attribute) {
+        return nameByDN(Schema.getDefaultSchema().getAttributeType(attribute));
+    }
+
+    public static NameStrategy nameByEntryUUID(final AttributeType dnAttribute) {
+        return nameByServer(dnAttribute, AttributeDescription.create(getEntryUUIDAttributeType()));
+    }
+
+    public static NameStrategy nameByEntryUUID(final String dnAttribute) {
+        return nameByEntryUUID(Schema.getDefaultSchema().getAttributeType(dnAttribute));
+    }
+
+    public static NameStrategy nameByServer(final AttributeType dnAttribute,
+            final AttributeDescription idAttribute) {
+        return new AttributeNameStrategy(dnAttribute, idAttribute, true);
+    }
+
+    public static NameStrategy nameByServer(final String dnAttribute, final String idAttribute) {
+        return nameByServer(Schema.getDefaultSchema().getAttributeType(dnAttribute),
+                AttributeDescription.valueOf(idAttribute));
+    }
+
+    public static Builder collection() {
+        return new Builder();
+    }
+
+    private static AttributeMapper mapOf(final Collection<AttributeMapper> mappers) {
+        return new CompositeAttributeMapper(mappers);
+    }
+
+    private Rest2LDAP() {
+        // Prevent instantiation.
+    }
+
+}
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
index 8d6a1c5..dabc613 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -19,6 +19,7 @@
 import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
 import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -28,6 +29,7 @@
 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.ByteString;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.Filter;
@@ -38,38 +40,29 @@
  * An attribute mapper which maps a single JSON attribute to a single LDAP
  * attribute.
  */
-public class SimpleAttributeMapper implements AttributeMapper {
+public final class SimpleAttributeMapper extends AttributeMapper {
 
     private Function<ByteString, ?, Void> decoder = null;
     private Object defaultValue = null;
+    private Collection<Object> defaultValues = Collections.emptySet();
     private boolean forceSingleValued = false;
 
     // private boolean isReadOnly = false;
     private final String jsonAttributeName;
-    private final String ldapAttributeName;
+    private final AttributeDescription ldapAttributeName;
     private final String normalizedJsonAttributeName;
 
     /**
      * Creates a new simple attribute mapper which maps a single LDAP attribute
      * to an entry.
      *
-     * @param attributeName
-     *            The name of the simple JSON and LDAP attribute.
-     */
-    public SimpleAttributeMapper(final String attributeName) {
-        this(attributeName, attributeName);
-    }
-
-    /**
-     * Creates a new simple attribute mapper which maps a single LDAP attribute
-     * to an entry.
-     *
      * @param jsonAttributeName
      *            The name of the simple JSON attribute.
      * @param ldapAttributeName
      *            The name of the LDAP attribute.
      */
-    public SimpleAttributeMapper(final String jsonAttributeName, final String ldapAttributeName) {
+    SimpleAttributeMapper(final String jsonAttributeName,
+            final AttributeDescription ldapAttributeName) {
         this.jsonAttributeName = jsonAttributeName;
         this.ldapAttributeName = ldapAttributeName;
         this.normalizedJsonAttributeName = toLowerCase(jsonAttributeName);
@@ -98,36 +91,12 @@
      */
     public SimpleAttributeMapper defaultJSONValue(final Object defaultValue) {
         this.defaultValue = defaultValue;
+        this.defaultValues =
+                defaultValue != null ? Collections.singleton(defaultValue) : Collections.emptySet();
         return this;
     }
 
     /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
-            final Set<String> ldapAttributes) {
-        if (jsonAttribute.isEmpty() || matches(jsonAttribute)) {
-            ldapAttributes.add(ldapAttributeName);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void getLDAPFilter(final Context c, final FilterType type,
-            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
-            final ResultHandler<Filter> h) {
-        if (matches(jsonAttribute)) {
-            h.handleResult(toFilter(c, type, ldapAttributeName, valueAssertion));
-        } else {
-            // This attribute mapper cannot handle the provided filter component.
-            h.handleResult(null);
-        }
-    }
-
-    /**
      * Prevents the LDAP attribute from being updated.
      *
      * @param readOnly
@@ -154,32 +123,41 @@
         return this;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
-        final Attribute a = e.getAttribute(ldapAttributeName);
-        if (a != null) {
-            final Function<ByteString, ?, Void> f =
-                    decoder == null ? Functions.fixedFunction(byteStringToJson(), a) : decoder;
-            final Object value;
-            if (forceSingleValued || a.getAttributeDescription().getAttributeType().isSingleValue()) {
-                value = a.parse().as(f, defaultValue);
-            } else {
-                value = a.parse().asSetOf(f, defaultValue);
-            }
-            h.handleResult(Collections.singletonMap(jsonAttributeName, value));
+    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+            final Set<String> ldapAttributes) {
+        if (jsonAttribute.isEmpty() || matches(jsonAttribute)) {
+            ldapAttributes.add(ldapAttributeName.toString());
+        }
+    }
+
+    @Override
+    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
+            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
+        if (matches(jsonAttribute)) {
+            h.handleResult(toFilter(c, type, ldapAttributeName.toString(), valueAssertion));
         } else {
+            // This attribute mapper cannot handle the provided filter component.
             h.handleResult(null);
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
     @Override
-    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
+    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
+        final Function<ByteString, ?, Void> f =
+                decoder == null ? Functions.fixedFunction(byteStringToJson(), ldapAttributeName)
+                        : decoder;
+        final Object value;
+        if (forceSingleValued || ldapAttributeName.getAttributeType().isSingleValue()) {
+            value = e.parseAttribute(ldapAttributeName).as(f, defaultValue);
+        } else {
+            value = e.parseAttribute(ldapAttributeName).asSetOf(f, defaultValues);
+        }
+        h.handleResult(Collections.singletonMap(jsonAttributeName, value));
+    }
+
+    @Override
+    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
 
     }
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java
deleted file mode 100644
index 5b5de74..0000000
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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 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.Entry;
-import org.forgerock.opendj.ldap.Filter;
-
-/**
- * An attribute mapper which inlines LDAP attributes from subordinate LDAP
- * entries.
- */
-public class SubContainerAttributeMapper implements AttributeMapper {
-
-    // private final EntryContainer referencedContainer;
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
-            final Set<String> ldapAttributes) {
-        // TODO Auto-generated method stub
-
-    }
-
-    @Override
-    public void getLDAPFilter(final Context c, final FilterType type,
-            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
-            final ResultHandler<Filter> h) {
-        // TODO Auto-generated method stub
-
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
-        // TODO Auto-generated method stub
-
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
-        // TODO Auto-generated method stub
-
-    }
-
-}
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
index 7fa9bac..43c47a5 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -31,6 +31,7 @@
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.ResultHandler;
 import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.AttributeDescription;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.Function;
@@ -85,12 +86,11 @@
     }
 
     // @Checkstyle:off
-    private static final Function<ByteString, Object, Attribute> BYTESTRING_TO_JSON =
-            new Function<ByteString, Object, Attribute>() {
+    private static final Function<ByteString, Object, AttributeDescription> BYTESTRING_TO_JSON =
+            new Function<ByteString, Object, AttributeDescription>() {
                 @Override
-                public Object apply(final ByteString value, final Attribute a) {
-                    final Syntax syntax =
-                            a.getAttributeDescription().getAttributeType().getSyntax();
+                public Object apply(final ByteString value, final AttributeDescription ad) {
+                    final Syntax syntax = ad.getAttributeType().getSyntax();
                     if (syntax.equals(getBooleanSyntax())) {
                         return Functions.byteStringToBoolean().apply(value, null);
                     } else if (syntax.equals(getIntegerSyntax())) {
@@ -112,7 +112,8 @@
     }
 
     static Object attributeToJson(final Attribute a) {
-        final Function<ByteString, Object, Void> f = Functions.fixedFunction(BYTESTRING_TO_JSON, a);
+        final Function<ByteString, Object, Void> f =
+                Functions.fixedFunction(BYTESTRING_TO_JSON, a.getAttributeDescription());
         final boolean isSingleValued =
                 a.getAttributeDescription().getAttributeType().isSingleValue();
         return isSingleValued ? a.parse().as(f) : asList(a.parse().asSetOf(f));
@@ -120,7 +121,7 @@
 
     // @Checkstyle:on
 
-    static Function<ByteString, Object, Attribute> byteStringToJson() {
+    static Function<ByteString, Object, AttributeDescription> byteStringToJson() {
         return BYTESTRING_TO_JSON;
     }
 
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
index 720ea47..0b06eeb 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -18,13 +18,17 @@
 
 import static org.forgerock.json.resource.Resources.newInternalConnectionFactory;
 import static org.forgerock.opendj.ldap.Connections.newAuthenticatedConnectionFactory;
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.collection;
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.map;
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.mapAllOf;
+import static org.forgerock.opendj.rest2ldap.Rest2LDAP.mapComplex;
 
 import java.util.logging.Logger;
 
+import org.forgerock.json.resource.CollectionResourceProvider;
 import org.forgerock.json.resource.Router;
 import org.forgerock.json.resource.servlet.HttpServlet;
 import org.forgerock.opendj.ldap.ConnectionFactory;
-import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.Functions;
 import org.forgerock.opendj.ldap.LDAPConnectionFactory;
 import org.forgerock.opendj.ldap.requests.Requests;
@@ -55,39 +59,25 @@
                         Requests.newSimpleBindRequest("cn=directory manager", "password"
                                 .toCharArray()));
 
-        // Create user resource.
-        final AttributeMapper userMapper =
-                new CompositeAttributeMapper().addMapper(
-                        new SimpleAttributeMapper("id", "entryUUID").singleValued(true)).addMapper(
-                        new DefaultAttributeMapper().includeAttribute("uid", "isMemberOf",
-                                "modifyTimestamp")).addMapper(
-                        new ComplexAttributeMapper("name", new DefaultAttributeMapper()
-                                .includeAttribute("cn", "sn", "givenName"))).addMapper(
-                        new ComplexAttributeMapper("contactInformation",
-                                new CompositeAttributeMapper().addMapper(
-                                        new SimpleAttributeMapper("telephoneNumber").decoder(
-                                                Functions.byteStringToString()).singleValued(true))
-                                        .addMapper(
-                                                new SimpleAttributeMapper("emailAddress", "mail")
-                                                        .singleValued(true))));
-
-        final LDAPCollectionResourceProvider userResource =
-                new LDAPCollectionResourceProvider(DN.valueOf("ou=people,dc=example,dc=com"),
-                        userMapper, ldapFactory, Config.defaultConfig());
-
-        // Create group resource.
-        final AttributeMapper groupMapper =
-                new DefaultAttributeMapper().includeAttribute("cn", "ou", "description",
-                        "uniquemember");
-
-        final LDAPCollectionResourceProvider groupResource =
-                new LDAPCollectionResourceProvider(DN.valueOf("ou=groups,dc=example,dc=com"),
-                        groupMapper, ldapFactory, Config.defaultConfig());
-
         // Create the router.
         final Router router = new Router();
-        router.addRoute("/users", userResource);
-        router.addRoute("/groups", groupResource);
+
+        // Create user resource.
+        CollectionResourceProvider users =
+                collection().factory(ldapFactory).baseDN("ou=people,dc=example,dc=com").map(
+                        map("id", "entryUUID").singleValued(true),
+                        mapAllOf("uid", "isMemberOf", "modifyTimestamp"),
+                        mapComplex("name", mapAllOf("cn", "sn", "givenName")),
+                        mapComplex("contactInformation", map("telephoneNumber").decoder(
+                                Functions.byteStringToString()).singleValued(true), map(
+                                "emailAddress", "mail").singleValued(true))).build();
+        router.addRoute("/users", users);
+
+        // Create group resource.
+        CollectionResourceProvider groups =
+                collection().factory(ldapFactory).baseDN("ou=groups,dc=example,dc=com").map(
+                        mapAllOf("cn", "ou", "description", "uniquemember")).build();
+        router.addRoute("/groups", groups);
 
         final org.forgerock.json.resource.ConnectionFactory resourceFactory =
                 newInternalConnectionFactory(router);

--
Gitblit v1.10.0