From db786032bf45be89c4a893281911364d158cfb6e Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Sat, 20 Oct 2012 10:10:53 +0000
Subject: [PATCH] Update to use new json-resource 2.0 APIs.

---
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java         |   46 +-
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java         |   69 +-
 opendj3/opendj-rest2ldap/pom.xml                                                                          |   57 +-
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java       |  146 +++---
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java                 |   63 +-
 opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java                        |  105 ++++
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java        |   24 
 /dev/null                                                                                                 |  244 -----------
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java    |   61 ++
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java          |   74 +-
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java                          |   56 +-
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java                |   17 
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java |  290 +++++++++++++
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java       |    9 
 14 files changed, 747 insertions(+), 514 deletions(-)

diff --git a/opendj3/opendj-rest2ldap/pom.xml b/opendj3/opendj-rest2ldap/pom.xml
index 67ae69a..0c5d7ed 100644
--- a/opendj3/opendj-rest2ldap/pom.xml
+++ b/opendj3/opendj-rest2ldap/pom.xml
@@ -61,45 +61,32 @@
         <dependency>
             <groupId>org.forgerock.commons</groupId>
             <artifactId>json-fluent</artifactId>
-            <version>1.2.0-SNAPSHOT</version>
+            <version>2.0.0-SNAPSHOT</version>
             <scope>compile</scope>
         </dependency>
         <dependency>
+            <groupId>org.forgerock.commons</groupId>
+            <artifactId>json-resource</artifactId>
+            <version>2.0.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.forgerock.commons</groupId>
+            <artifactId>json-resource-servlet</artifactId>
+            <version>2.0.0-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.grizzly</groupId>
+            <artifactId>grizzly-http-servlet</artifactId>
+            <version>2.3-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>javax.servlet</groupId>
-            <artifactId>servlet-api</artifactId>
-            <version>2.5</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.forgerock.commons</groupId>
-            <artifactId>org.forgerock.json.resource</artifactId>
-            <version>1.3.0-SNAPSHOT</version>
-            <scope>compile</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.forgerock.commons</groupId>
-            <artifactId>org.forgerock.json.resource.restlet</artifactId>
-            <version>1.3.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.forgerock.commons</groupId>
-            <artifactId>org.forgerock.restlet</artifactId>
-            <version>1.1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.forgerock.commons</groupId>
-            <artifactId>json-patch</artifactId>
-            <version>1.0.0</version>
-        </dependency>
-        <dependency>
-            <groupId>org.restlet.jse</groupId>
-            <artifactId>org.restlet</artifactId>
-            <version>2.0.9</version>
-        </dependency>
-        <dependency>
-            <groupId>org.restlet.jse</groupId>
-            <artifactId>org.restlet.ext.jackson</artifactId>
-            <version>2.0.9</version>
+            <artifactId>javax.servlet-api</artifactId>
+            <version>3.0.1</version>
+            <scope>test</scope>
         </dependency>
     </dependencies>
     <build>
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
index e906357..3590234 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
@@ -22,12 +22,14 @@
 
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.ResultHandler;
+import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.resource.provider.Context;
 
 /**
- *
+ * An attribute mapper is responsible for converting JSON values to and from
+ * LDAP attributes.
  */
 public interface AttributeMapper {
 
@@ -38,6 +40,7 @@
      * <p>
      * Implementations should only add the names of attributes found in the LDAP
      * entry directly associated with the resource.
+     *
      * @param jsonAttribute
      *            The name of the resource attribute requested by the client.
      * @param ldapAttributes
@@ -56,10 +59,13 @@
      * requests.
      *
      * @param c
+     *            The server context.
      * @param e
+     *            The LDAP entry to be converted to JSON.
      * @param h
+     *            The result handler.
      */
-    void toJson(Context c, Entry e, AttributeMapperCompletionHandler<Map<String, Object>> h);
+    void toJson(ServerContext c, Entry e, ResultHandler<Map<String, Object>> h);
 
     /**
      * Transforms JSON content in the provided JSON value to LDAP attributes,
@@ -70,10 +76,13 @@
      * requests.
      *
      * @param c
+     *            The server context.
      * @param v
+     *            The JSON value to be converted to LDAP attributes.
      * @param h
+     *            The result handler.
      */
-    void toLDAP(Context c, JsonValue v, AttributeMapperCompletionHandler<List<Attribute>> h);
+    void toLDAP(ServerContext c, JsonValue v, ResultHandler<List<Attribute>> h);
 
     // TODO: methods for obtaining schema information (e.g. name, description,
     // type information).
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapperCompletionHandler.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapperCompletionHandler.java
deleted file mode 100644
index a6e92c9..0000000
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapperCompletionHandler.java
+++ /dev/null
@@ -1,29 +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 Copyrighted [year] [name of copyright owner]".
- *
- * Copyright 2012 ForgeRock AS. All rights reserved.
- */
-
-package org.forgerock.opendj.rest2ldap;
-
-import org.forgerock.resource.exception.ResourceException;
-
-/**
- *
- */
-public interface AttributeMapperCompletionHandler<T> {
-
-    void onSuccess(T result);
-
-    void onFailure(ResourceException e);
-}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
index 3120ffc..0babf85 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
@@ -25,19 +25,21 @@
 
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.ResultHandler;
+import org.forgerock.json.resource.ServerContext;
+import org.forgerock.json.resource.ResourceException;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.resource.exception.ResourceException;
-import org.forgerock.resource.provider.Context;
 
 /**
- *
+ * An attribute mapper which will wrap the results of the provided mapper as a
+ * complex JSON object.
  */
 public class ComplexAttributeMapper implements AttributeMapper {
 
-    private final String normalizedJsonAttributeName;
     private final String jsonAttributeName;
     private final AttributeMapper mapper;
+    private final String normalizedJsonAttributeName;
 
     /**
      * Creates a new complex attribute mapper which will wrap the results of the
@@ -49,7 +51,7 @@
      *            The mapper which should be used to provide the contents of the
      *            complex attribute.
      */
-    public ComplexAttributeMapper(String jsonAttributeName, AttributeMapper mapper) {
+    public ComplexAttributeMapper(final String jsonAttributeName, final AttributeMapper mapper) {
         this.jsonAttributeName = jsonAttributeName;
         this.mapper = mapper;
         this.normalizedJsonAttributeName = toLowerCase(jsonAttributeName);
@@ -58,9 +60,9 @@
     /**
      * {@inheritDoc}
      */
-    public void getLDAPAttributes(JsonPointer jsonAttribute, Set<String> ldapAttributes) {
+    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
         if (attributeMatchesPointer(jsonAttribute)) {
-            JsonPointer relativePointer = jsonAttribute.relativePointer();
+            final JsonPointer relativePointer = jsonAttribute.relativePointer();
             mapper.getLDAPAttributes(relativePointer, ldapAttributes);
         }
     }
@@ -68,33 +70,33 @@
     /**
      * {@inheritDoc}
      */
-    public void toJson(Context c, Entry e,
-            final AttributeMapperCompletionHandler<Map<String, Object>> h) {
-        AttributeMapperCompletionHandler<Map<String, Object>> wrapper =
-                new AttributeMapperCompletionHandler<Map<String, Object>>() {
+    public void toJson(final ServerContext c, final Entry e,
+            final ResultHandler<Map<String, Object>> h) {
+        final ResultHandler<Map<String, Object>> wrapper = new ResultHandler<Map<String, Object>>() {
 
-                    public void onSuccess(Map<String, Object> result) {
-                        Map<String, Object> complexResult =
-                                Collections.singletonMap(jsonAttributeName, (Object) result);
-                        h.onSuccess(complexResult);
-                    }
+            public void handleError(final ResourceException e) {
+                h.handleError(e);
+            }
 
-                    public void onFailure(ResourceException e) {
-                        h.onFailure(e);
-                    }
-                };
+            public void handleResult(final Map<String, Object> result) {
+                final Map<String, Object> complexResult = Collections.singletonMap(
+                        jsonAttributeName, (Object) result);
+                h.handleResult(complexResult);
+            }
+        };
         mapper.toJson(c, e, wrapper);
     }
 
     /**
      * {@inheritDoc}
      */
-    public void toLDAP(Context c, JsonValue v, AttributeMapperCompletionHandler<List<Attribute>> h) {
+    public void toLDAP(final ServerContext c, final JsonValue v,
+            final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
 
     }
 
-    private boolean attributeMatchesPointer(JsonPointer resourceAttribute) {
+    private boolean attributeMatchesPointer(final JsonPointer resourceAttribute) {
         return resourceAttribute.isEmpty()
                 || toLowerCase(resourceAttribute.get(0)).equals(normalizedJsonAttributeName);
     }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
index 2d53f3e..bef1872 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
@@ -27,26 +27,34 @@
 
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.ResultHandler;
+import org.forgerock.json.resource.ServerContext;
+import org.forgerock.json.resource.ResourceException;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.resource.exception.ResourceException;
-import org.forgerock.resource.provider.Context;
 
 /**
- *
+ * A collection of one or more attribute mappers whose content will be combined
+ * into a single JSON array.
  */
 public final class CompositeAttributeMapper implements AttributeMapper {
     private final List<AttributeMapper> attributeMappers = new LinkedList<AttributeMapper>();
 
     /**
      * Creates a new composite attribute mapper.
-     *
      */
     public CompositeAttributeMapper() {
         // No implementation required.
     }
 
-    public CompositeAttributeMapper addMapper(AttributeMapper mapper) {
+    /**
+     * 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;
     }
@@ -54,8 +62,8 @@
     /**
      * {@inheritDoc}
      */
-    public void getLDAPAttributes(JsonPointer jsonAttribute, Set<String> ldapAttributes) {
-        for (AttributeMapper attribute : attributeMappers) {
+    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
+        for (final AttributeMapper attribute : attributeMappers) {
             attribute.getLDAPAttributes(jsonAttribute, ldapAttributes);
         }
     }
@@ -63,45 +71,44 @@
     /**
      * {@inheritDoc}
      */
-    public void toJson(Context c, Entry e,
-            final AttributeMapperCompletionHandler<Map<String, Object>> h) {
-        AttributeMapperCompletionHandler<Map<String, Object>> resultAccumulater =
-                new AttributeMapperCompletionHandler<Map<String, Object>>() {
-                    private final AtomicInteger latch = new AtomicInteger(attributeMappers.size());
-                    private final List<Map<String, Object>> results =
-                            new ArrayList<Map<String, Object>>(latch.get());
+    public void toJson(final ServerContext c, final Entry e,
+            final ResultHandler<Map<String, Object>> h) {
+        final ResultHandler<Map<String, Object>> resultAccumulater = new ResultHandler<Map<String, Object>>() {
+            private final AtomicInteger latch = new AtomicInteger(attributeMappers.size());
+            private final List<Map<String, Object>> results = new ArrayList<Map<String, Object>>(
+                    latch.get());
 
-                    public void onFailure(ResourceException e) {
-                        // Ensure that handler is only invoked once.
-                        if (latch.getAndSet(0) > 0) {
-                            h.onFailure(e);
-                        }
+            public void handleError(final ResourceException e) {
+                // Ensure that handler is only invoked once.
+                if (latch.getAndSet(0) > 0) {
+                    h.handleError(e);
+                }
+            }
+
+            public void handleResult(final Map<String, Object> result) {
+                synchronized (this) {
+                    results.add(result);
+                }
+                if (latch.decrementAndGet() == 0) {
+                    final Map<String, Object> mergeResult;
+                    switch (results.size()) {
+                    case 0:
+                        mergeResult = Collections.<String, Object> emptyMap();
+                        break;
+                    case 1:
+                        mergeResult = results.get(0);
+                        break;
+                    default:
+                        mergeResult = new LinkedHashMap<String, Object>();
+                        mergeJsonValues(results, mergeResult);
+                        break;
                     }
+                    h.handleResult(mergeResult);
+                }
+            }
+        };
 
-                    public void onSuccess(Map<String, Object> result) {
-                        synchronized (this) {
-                            results.add(result);
-                        }
-                        if (latch.decrementAndGet() == 0) {
-                            final Map<String, Object> mergeResult;
-                            switch (results.size()) {
-                            case 0:
-                                mergeResult = Collections.<String, Object> emptyMap();
-                                break;
-                            case 1:
-                                mergeResult = results.get(0);
-                                break;
-                            default:
-                                mergeResult = new LinkedHashMap<String, Object>();
-                                mergeJsonValues(results, mergeResult);
-                                break;
-                            }
-                            h.onSuccess(mergeResult);
-                        }
-                    }
-                };
-
-        for (AttributeMapper mapper : attributeMappers) {
+        for (final AttributeMapper mapper : attributeMappers) {
             mapper.toJson(c, e, resultAccumulater);
         }
     }
@@ -109,27 +116,13 @@
     /**
      * {@inheritDoc}
      */
-    public void toLDAP(Context c, JsonValue v, AttributeMapperCompletionHandler<List<Attribute>> h) {
+    public void toLDAP(final ServerContext c, final JsonValue v,
+            final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
 
     }
 
     /**
-     * Merge the provided list of JSON values into a single value.
-     *
-     * @param srcValues
-     *            The source values.
-     * @param dstValue
-     *            The destination value, into which which the values should be
-     *            merged.
-     */
-    private void mergeJsonValues(List<Map<String, Object>> srcValues, Map<String, Object> dstValue) {
-        for (Map<String, Object> value : srcValues) {
-            mergeJsonValue(value, dstValue);
-        }
-    }
-
-    /**
      * Merge one JSON value into another.
      *
      * @param srcValue
@@ -139,24 +132,25 @@
      *            merged.
      */
     @SuppressWarnings("unchecked")
-    private void mergeJsonValue(Map<String, Object> srcValue, Map<String, Object> dstValue) {
-        for (Map.Entry<String, Object> record : srcValue.entrySet()) {
-            String key = record.getKey();
-            Object newValue = record.getValue();
+    private void mergeJsonValue(final Map<String, Object> srcValue,
+            final Map<String, Object> dstValue) {
+        for (final Map.Entry<String, Object> record : srcValue.entrySet()) {
+            final String key = record.getKey();
+            final Object newValue = record.getValue();
             Object existingValue = dstValue.get(key);
             if (existingValue == null) {
                 // Value is new, so just add it.
                 dstValue.put(key, newValue);
-            } else if (existingValue instanceof Map && newValue instanceof Map) {
+            } else if ((existingValue instanceof Map) && (newValue instanceof Map)) {
                 // Merge two maps - create a new Map, in case the existing one
                 // is unmodifiable.
-                existingValue =
-                        new LinkedHashMap<String, Object>((Map<String, Object>) existingValue);
+                existingValue = new LinkedHashMap<String, Object>(
+                        (Map<String, Object>) existingValue);
                 mergeJsonValue((Map<String, Object>) newValue, (Map<String, Object>) existingValue);
-            } else if (existingValue instanceof List && newValue instanceof List) {
+            } else if ((existingValue instanceof List) && (newValue instanceof List)) {
                 // Merge two lists- create a new List, in case the existing one
                 // is unmodifiable.
-                List<Object> tmp = new ArrayList<Object>((List<Object>) existingValue);
+                final List<Object> tmp = new ArrayList<Object>((List<Object>) existingValue);
                 tmp.addAll((List<Object>) newValue);
                 existingValue = tmp;
             }
@@ -165,4 +159,20 @@
             dstValue.put(key, newValue);
         }
     }
+
+    /**
+     * Merge the provided list of JSON values into a single value.
+     *
+     * @param srcValues
+     *            The source values.
+     * @param dstValue
+     *            The destination value, into which which the values should be
+     *            merged.
+     */
+    private void mergeJsonValues(final List<Map<String, Object>> srcValues,
+            final Map<String, Object> dstValue) {
+        for (final Map<String, Object> value : srcValues) {
+            mergeJsonValue(value, dstValue);
+        }
+    }
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
index 2d29263..5a2b251 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
@@ -23,12 +23,13 @@
 
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.ResultHandler;
+import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.resource.provider.Context;
 
 /**
- *
+ * An attribute mapper which maps a single JSON attribute to a fixed value.
  */
 public class ConstantAttributeMapper implements AttributeMapper {
 
@@ -36,7 +37,7 @@
     private final Object jsonAttributeValue;
 
     /**
-     * Creates a new constant attribute mapper which maps a single LDAP
+     * Creates a new constant attribute mapper which maps a single JSON
      * attribute to a fixed value.
      *
      * @param attributeName
@@ -44,7 +45,7 @@
      * @param attributeValue
      *            The value of the simple JSON attribute.
      */
-    public ConstantAttributeMapper(String attributeName, Object attributeValue) {
+    public ConstantAttributeMapper(final String attributeName, final Object attributeValue) {
         this.jsonAttributeName = attributeName;
         this.jsonAttributeValue = attributeValue;
     }
@@ -52,26 +53,27 @@
     /**
      * {@inheritDoc}
      */
-    public void getLDAPAttributes(JsonPointer jsonAttribute, Set<String> ldapAttributes) {
+    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
         // Nothing to do.
     }
 
     /**
      * {@inheritDoc}
      */
-    public void toJson(Context c, Entry e,
-            final AttributeMapperCompletionHandler<Map<String, Object>> h) {
+    public void toJson(final ServerContext c, final Entry e,
+            final ResultHandler<Map<String, Object>> h) {
         // FIXME: how do we know if the user requested it???
-        Map<String, Object> result =
-                Collections.singletonMap(jsonAttributeName, jsonAttributeValue);
-        h.onSuccess(result);
+        final Map<String, Object> result = Collections.singletonMap(jsonAttributeName,
+                jsonAttributeValue);
+        h.handleResult(result);
 
     }
 
     /**
      * {@inheritDoc}
      */
-    public void toLDAP(Context c, JsonValue v, AttributeMapperCompletionHandler<List<Attribute>> h) {
+    public void toLDAP(final ServerContext c, final JsonValue v,
+            final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
 
     }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
index 45f5ead..36e2c57 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
@@ -27,47 +27,32 @@
 
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.ResultHandler;
+import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.resource.provider.Context;
 
 /**
  *
  */
 public final class DefaultAttributeMapper implements AttributeMapper {
 
+    private final Map<String, String> excludedAttributes = new LinkedHashMap<String, String>();
     // All user attributes by default.
     private final Map<String, String> includedAttributes = new LinkedHashMap<String, String>();
-    private final Map<String, String> excludedAttributes = new LinkedHashMap<String, String>();
 
     public DefaultAttributeMapper() {
         // No implementation required.
     }
 
-    public DefaultAttributeMapper includeAttribute(String... attributes) {
-        for (String attribute : attributes) {
-            includedAttributes.put(toLowerCase(attribute), attribute);
-        }
-        return this;
-    }
-
-    public DefaultAttributeMapper excludeAttribute(String... attributes) {
-        for (String attribute : attributes) {
+    public DefaultAttributeMapper excludeAttribute(final String... attributes) {
+        for (final String attribute : attributes) {
             excludedAttributes.put(toLowerCase(attribute), attribute);
         }
         return this;
     }
 
-    public void getLDAPAttributes(Set<String> ldapAttributes) {
-        if (!includedAttributes.isEmpty()) {
-            ldapAttributes.addAll(includedAttributes.values());
-        } else {
-            // All user attributes.
-            ldapAttributes.add("*");
-        }
-    }
-
-    public void getLDAPAttributes(JsonPointer jsonAttribute, Set<String> ldapAttributes) {
+    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
         switch (jsonAttribute.size()) {
         case 0:
             // Requested everything.
@@ -79,7 +64,7 @@
             }
             break;
         default:
-            String name = jsonAttribute.get(0);
+            final String name = jsonAttribute.get(0);
             if (isIncludedAttribute(name)) {
                 ldapAttributes.add(name);
             }
@@ -87,19 +72,41 @@
         }
     }
 
-    public void toJson(Context c, Entry e, AttributeMapperCompletionHandler<Map<String, Object>> h) {
-        Map<String, Object> result = new LinkedHashMap<String, Object>(e.getAttributeCount());
-        for (Attribute a : e.getAllAttributes()) {
-            String name = getAttributeName(a);
+    public void getLDAPAttributes(final Set<String> ldapAttributes) {
+        if (!includedAttributes.isEmpty()) {
+            ldapAttributes.addAll(includedAttributes.values());
+        } else {
+            // All user attributes.
+            ldapAttributes.add("*");
+        }
+    }
+
+    public DefaultAttributeMapper includeAttribute(final String... attributes) {
+        for (final String attribute : attributes) {
+            includedAttributes.put(toLowerCase(attribute), attribute);
+        }
+        return this;
+    }
+
+    public void toJson(final ServerContext c, final Entry e,
+            final ResultHandler<Map<String, Object>> h) {
+        final Map<String, Object> result = new LinkedHashMap<String, Object>(e.getAttributeCount());
+        for (final Attribute a : e.getAllAttributes()) {
+            final String name = getAttributeName(a);
             if (isIncludedAttribute(name)) {
                 result.put(name, attributeToJson(a));
             }
         }
-        h.onSuccess(result);
+        h.handleResult(result);
     }
 
-    private boolean isIncludedAttribute(String name) {
-        String lowerName = toLowerCase(name);
+    public void toLDAP(final ServerContext c, final JsonValue v,
+            final ResultHandler<List<Attribute>> h) {
+        // TODO:
+    }
+
+    private boolean isIncludedAttribute(final String name) {
+        final String lowerName = toLowerCase(name);
 
         // Ignore the requested attribute if it has been excluded.
         if (excludedAttributes.containsKey(lowerName)) {
@@ -113,8 +120,4 @@
 
         return false;
     }
-
-    public void toLDAP(Context c, JsonValue v, AttributeMapperCompletionHandler<List<Attribute>> h) {
-        // TODO:
-    }
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java
index b2ff886..a40b006 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java
@@ -18,6 +18,7 @@
 
 import java.util.Collection;
 
+import org.forgerock.json.resource.Context;
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.DN;
@@ -32,25 +33,15 @@
 import org.forgerock.opendj.ldap.responses.Result;
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 import org.forgerock.opendj.ldap.responses.SearchResultReference;
-import org.forgerock.resource.provider.Context;
 
 /**
  *
  */
 public final class EntryContainer {
-    // FIXME: make this configurable, also allow use of DN.
-    private static final String UUID_ATTRIBUTE = "entryUUID";
-
-    // FIXME: make this configurable.
-    private static final String ETAG_ATTRIBUTE = "etag";
-
-    private final ConnectionFactory factory;
-    private final DN baseDN;
-
     private abstract class AbstractRequestCompletionHandler<R, H extends ResultHandler<? super R>>
             implements ResultHandler<R> {
-        final H resultHandler;
         final Connection connection;
+        final H resultHandler;
 
         AbstractRequestCompletionHandler(final Connection connection, final H resultHandler) {
             this.connection = connection;
@@ -123,12 +114,32 @@
 
     }
 
-    public EntryContainer(DN baseDN, ConnectionFactory factory) {
+    // FIXME: make this configurable.
+    private static final String ETAG_ATTRIBUTE = "etag";
+
+    // FIXME: make this configurable, also allow use of DN.
+    private static final String UUID_ATTRIBUTE = "entryUUID";
+
+    private final DN baseDN;
+
+    private final ConnectionFactory factory;
+
+    public EntryContainer(final DN baseDN, final ConnectionFactory factory) {
         this.baseDN = baseDN;
         this.factory = factory;
     }
 
-    public void listEntries(final Context context, final SearchResultHandler handler) {
+    public String getEtagFromEntry(final Entry entry) {
+        return entry.parseAttribute(ETAG_ATTRIBUTE).asString();
+    }
+
+    public String getIDFromEntry(final Entry entry) {
+        return entry.parseAttribute(UUID_ATTRIBUTE).asString();
+    }
+
+    public void listEntries(final Context context, final Collection<String> attributes,
+            final SearchResultHandler handler) {
+        final String[] tmp = getSearchAttributes(attributes);
         final ConnectionCompletionHandler<Result> outerHandler =
                 new ConnectionCompletionHandler<Result>(handler) {
 
@@ -136,9 +147,9 @@
                     public void handleResult(final Connection connection) {
                         final SearchRequestCompletionHandler innerHandler =
                                 new SearchRequestCompletionHandler(connection, handler);
-                        SearchRequest request =
+                        final SearchRequest request =
                                 Requests.newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter
-                                        .objectClassPresent(), UUID_ATTRIBUTE, ETAG_ATTRIBUTE);
+                                        .objectClassPresent(), tmp);
                         connection.searchAsync(request, null, innerHandler);
                     }
 
@@ -149,6 +160,7 @@
 
     public void readEntry(final Context c, final String id, final Collection<String> attributes,
             final ResultHandler<SearchResultEntry> handler) {
+        final String[] tmp = getSearchAttributes(attributes);
         final ConnectionCompletionHandler<SearchResultEntry> outerHandler =
                 new ConnectionCompletionHandler<SearchResultEntry>(handler) {
 
@@ -156,13 +168,7 @@
                     public void handleResult(final Connection connection) {
                         final RequestCompletionHandler<SearchResultEntry> innerHandler =
                                 new RequestCompletionHandler<SearchResultEntry>(connection, handler);
-                        // FIXME: who is responsible for adding the UUID and
-                        // etag attributes to this search?
-                        String[] tmp = attributes.toArray(new String[attributes.size() + 2]);
-                        tmp[tmp.length - 2] = UUID_ATTRIBUTE;
-                        tmp[tmp.length - 1] = ETAG_ATTRIBUTE;
-
-                        SearchRequest request =
+                        final SearchRequest request =
                                 Requests.newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter
                                         .equality(UUID_ATTRIBUTE, id), tmp);
                         connection.searchSingleEntryAsync(request, innerHandler);
@@ -173,12 +179,13 @@
         factory.getConnectionAsync(outerHandler);
     }
 
-    public String getIDFromEntry(final Entry entry) {
-        return entry.parseAttribute(UUID_ATTRIBUTE).asString();
-    }
-
-    public String getEtagFromEntry(final Entry entry) {
-        return entry.parseAttribute(ETAG_ATTRIBUTE).asString();
+    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;
     }
 
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Example.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Example.java
deleted file mode 100644
index e807869..0000000
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Example.java
+++ /dev/null
@@ -1,104 +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 Copyrighted [year] [name of copyright owner]".
- *
- * Copyright © 2012 ForgeRock AS. All rights reserved.
- */
-
-package org.forgerock.opendj.rest2ldap;
-
-import org.forgerock.json.resource.restlet.JsonResourceRestlet;
-import org.forgerock.opendj.ldap.ConnectionFactory;
-import org.forgerock.opendj.ldap.Connections;
-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;
-import org.forgerock.resource.framework.JsonResourceProvider;
-import org.forgerock.resource.framework.impl.ResourceInvoker;
-import org.restlet.Application;
-import org.restlet.Component;
-import org.restlet.Restlet;
-import org.restlet.data.Protocol;
-import org.restlet.routing.Router;
-import org.restlet.routing.Template;
-
-/**
- * Example
- */
-public class Example {
-
-    private Application application = new Application();
-    private final Router router = new Router();
-
-    private void bindJsonResource(JsonResourceProvider resource, String path) {
-        Restlet restlet = new JsonResourceRestlet(resource);
-        restlet.setContext(application.getContext());
-        router.attach(path, restlet, Template.MODE_EQUALS);
-        router.attach(path + (path.equals("/") ? "" : "/"), restlet, Template.MODE_STARTS_WITH);
-    }
-
-    public void start() throws Exception {
-        // All LDAP resources will use this connection factory.
-        ConnectionFactory factory =
-                Connections.newAuthenticatedConnectionFactory(new LDAPConnectionFactory(
-                        "localhost", 1389), Requests.newSimpleBindRequest("cn=directory manager",
-                        "password".toCharArray()));
-
-        // Create two entry containers whose members reference each other.
-        EntryContainer userContainer =
-                new EntryContainer(DN.valueOf("ou=people,dc=example,dc=com"), factory);
-        EntryContainer groupContainer =
-                new EntryContainer(DN.valueOf("ou=groups,dc=example,dc=com"), factory);
-
-        // Create user resource.
-        AttributeMapper userMapper =
-                new CompositeAttributeMapper().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").withDecoder(
-                                                Functions.byteStringToString()).forceSingleValued(
-                                                true)).addMapper(
-                                        new SimpleAttributeMapper("emailAddress", "mail")
-                                                .forceSingleValued(true))));
-        LDAPResource userResource = new LDAPResource(userContainer, userMapper);
-        ResourceInvoker userResourceInvoker = new ResourceInvoker();
-        userResourceInvoker.resource = userResource; // FIXME: Yuk!
-        bindJsonResource(userResourceInvoker, "/users");
-
-        // Create group resource.
-        AttributeMapper groupMapper =
-                new DefaultAttributeMapper().includeAttribute("cn", "ou", "description",
-                        "uniquemember");
-        LDAPResource groupResource = new LDAPResource(groupContainer, groupMapper);
-        ResourceInvoker groupResourceInvoker = new ResourceInvoker();
-        groupResourceInvoker.resource = groupResource; // FIXME: Yuk!
-        bindJsonResource(groupResourceInvoker, "/groups");
-
-        // Configure and start the application.
-        application.getTunnelService().setQueryTunnel(false);
-        application.setInboundRoot(router);
-        Component component = new Component();
-        component.getServers().add(Protocol.HTTP, 8080);
-        component.getDefaultHost().attach("", application);
-        component.start();
-    }
-
-    public static void main(String[] args) throws Exception {
-        Example instance = new Example();
-        instance.start();
-    }
-}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
new file mode 100644
index 0000000..d5b2f67
--- /dev/null
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -0,0 +1,290 @@
+/*
+ * 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 Copyrighted [year] [name of copyright owner]".
+ *
+ * Copyright 2012 ForgeRock AS. All rights reserved.
+ */
+
+package org.forgerock.opendj.rest2ldap;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.forgerock.json.fluent.JsonPointer;
+import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.*;
+import org.forgerock.opendj.ldap.AssertionFailureException;
+import org.forgerock.opendj.ldap.AuthenticationException;
+import org.forgerock.opendj.ldap.AuthorizationException;
+import org.forgerock.opendj.ldap.ConnectionException;
+import org.forgerock.opendj.ldap.EntryNotFoundException;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
+import org.forgerock.opendj.ldap.SearchResultHandler;
+import org.forgerock.opendj.ldap.TimeoutResultException;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.forgerock.opendj.ldap.responses.SearchResultReference;
+
+/**
+ *
+ */
+public class LDAPCollectionResourceProvider implements CollectionResourceProvider {
+    private final AttributeMapper attributeMapper;
+    private final EntryContainer entryContainer;
+
+    // Dummy exception used for signalling search success.
+    private static final ResourceException SUCCESS = new UncategorizedException(0, null, null);
+
+    /**
+     * Creates a new LDAP resource.
+     */
+    public LDAPCollectionResourceProvider(final EntryContainer container, final AttributeMapper mapper) {
+        this.entryContainer = container;
+        this.attributeMapper = mapper;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void actionCollection(ServerContext context, ActionRequest request,
+            ResultHandler<JsonValue> handler) {
+        handler.handleError(new NotSupportedException("Not yet implemented"));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void actionInstance(ServerContext context, String resourceId, ActionRequest request,
+            ResultHandler<JsonValue> handler) {
+        handler.handleError(new NotSupportedException("Not yet implemented"));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void createInstance(ServerContext context, CreateRequest request,
+            ResultHandler<Resource> handler) {
+        handler.handleError(new NotSupportedException("Not yet implemented"));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void deleteInstance(ServerContext context, String resourceId, DeleteRequest request,
+            ResultHandler<Resource> handler) {
+        handler.handleError(new NotSupportedException("Not yet implemented"));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void patchInstance(ServerContext context, String resourceId, PatchRequest request,
+            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 Set<JsonPointer> requestedAttributes = new LinkedHashSet<JsonPointer>();
+        final Collection<String> requestedLDAPAttributes = getRequestedLDAPAttributes(requestedAttributes);
+
+        // List the entries.
+        final SearchResultHandler searchHandler = new SearchResultHandler() {
+            private final AtomicInteger pendingResourceCount = new AtomicInteger();
+            private final AtomicReference<ResourceException> pendingResult = new AtomicReference<ResourceException>();
+            private final AtomicBoolean resultSent = new AtomicBoolean();
+
+            /*
+             * Close out the query result set if there are no more pending
+             * resources and the LDAP result has been received.
+             */
+            private void completeIfNecessary() {
+                if (pendingResourceCount.get() == 0) {
+                    final ResourceException result = pendingResult.get();
+                    if (result != null && resultSent.compareAndSet(false, true)) {
+                        if (result == SUCCESS) {
+                            handler.handleResult(null);
+                        } else {
+                            handler.handleError(result);
+                        }
+                    }
+                }
+            }
+
+            public boolean handleEntry(final SearchResultEntry entry) {
+                /*
+                 * Search result entries will be returned before the search
+                 * result/error so the only reason pendingResult will be
+                 * non-null is if a mapping error has occurred.
+                 */
+                if (pendingResult.get() != null) {
+                    return false;
+                }
+
+                // TODO: should the resource or the container define the ID
+                // mapping?
+                final String id = entryContainer.getIDFromEntry(entry);
+                final String revision = entryContainer.getEtagFromEntry(entry);
+                final ResultHandler<Map<String, Object>> mapHandler = new ResultHandler<Map<String, Object>>() {
+                    public void handleError(final ResourceException e) {
+                        pendingResult.compareAndSet(null, e);
+                        pendingResourceCount.decrementAndGet();
+                        completeIfNecessary();
+                    }
+
+                    public void handleResult(final Map<String, Object> result) {
+                        Resource resource = new Resource(id, revision, new JsonValue(result));
+                        handler.handleResource(resource);
+                        pendingResourceCount.decrementAndGet();
+                        completeIfNecessary();
+                    }
+                };
+
+                pendingResourceCount.incrementAndGet();
+                attributeMapper.toJson(context, entry, mapHandler);
+                return true;
+            }
+
+            public void handleErrorResult(final ErrorResultException error) {
+                pendingResult.compareAndSet(null, adaptErrorResult(error));
+                completeIfNecessary();
+            }
+
+            public boolean handleReference(final SearchResultReference reference) {
+                // TODO: should this be classed as an error since rest2ldap
+                // assumes entries are all colocated?
+                return true;
+            }
+
+            public void handleResult(final Result result) {
+                pendingResult.compareAndSet(null, SUCCESS);
+                completeIfNecessary();
+            }
+        };
+        entryContainer.listEntries(context, requestedLDAPAttributes, searchHandler);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void readInstance(final ServerContext context, final String resourceId,
+            ReadRequest request, final ResultHandler<Resource> handler) {
+        // TODO: Determine the set of LDAP attributes that need to be read.
+        final Set<JsonPointer> requestedAttributes = new LinkedHashSet<JsonPointer>();
+        final Collection<String> requestedLDAPAttributes = getRequestedLDAPAttributes(requestedAttributes);
+
+        final org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry> searchHandler = new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
+            public void handleErrorResult(final ErrorResultException error) {
+                handler.handleError(adaptErrorResult(error));
+            }
+
+            public void handleResult(final SearchResultEntry entry) {
+                final String revision = entryContainer.getEtagFromEntry(entry);
+                final ResultHandler<Map<String, Object>> mapHandler = new ResultHandler<Map<String, Object>>() {
+                    public void handleError(final ResourceException e) {
+                        handler.handleError(e);
+                    }
+
+                    public void handleResult(final Map<String, Object> result) {
+                        Resource resource = new Resource(resourceId, revision,
+                                new JsonValue(result));
+                        handler.handleResult(resource);
+                    }
+                };
+                attributeMapper.toJson(context, entry, mapHandler);
+            }
+        };
+        entryContainer.readEntry(context, resourceId, requestedLDAPAttributes, searchHandler);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void updateInstance(ServerContext context, String resourceId, UpdateRequest request,
+            ResultHandler<Resource> handler) {
+        handler.handleError(new NotSupportedException("Not yet implemented"));
+    }
+
+    /**
+     * Adapts an LDAP result code to a resource exception.
+     *
+     * @param error
+     *            The LDAP error that should be adapted.
+     * @return The equivalent resource exception.
+     */
+    private ResourceException adaptErrorResult(final ErrorResultException error) {
+        int resourceResultCode;
+        try {
+            throw error;
+        } catch (final AssertionFailureException e) {
+            resourceResultCode = ResourceException.VERSION_MISMATCH;
+        } catch (final AuthenticationException e) {
+            resourceResultCode = 401;
+        } catch (final AuthorizationException e) {
+            resourceResultCode = ResourceException.FORBIDDEN;
+        } catch (final ConnectionException e) {
+            resourceResultCode = ResourceException.UNAVAILABLE;
+        } catch (final EntryNotFoundException e) {
+            resourceResultCode = ResourceException.NOT_FOUND;
+        } catch (final MultipleEntriesFoundException e) {
+            resourceResultCode = ResourceException.INTERNAL_ERROR;
+        } catch (final TimeoutResultException e) {
+            resourceResultCode = 408;
+        } catch (final ErrorResultException e) {
+            resourceResultCode = ResourceException.INTERNAL_ERROR;
+        }
+        return ResourceException.getException(resourceResultCode, null, error.getMessage(), error);
+    }
+
+    /**
+     * Determines the set of LDAP attributes to request in an LDAP read (search,
+     * post-read), based on the provided set of JSON pointers.
+     *
+     * @param requestedAttributes
+     *            The set of resource attributes to be read.
+     * @return The set of LDAP attributes associated with the resource
+     *         attributes.
+     */
+    private Collection<String> getRequestedLDAPAttributes(final Set<JsonPointer> requestedAttributes) {
+        final Set<String> requestedLDAPAttributes;
+        if (requestedAttributes.isEmpty()) {
+            // Full read.
+            requestedLDAPAttributes = new LinkedHashSet<String>();
+            attributeMapper.getLDAPAttributes(new JsonPointer(), requestedLDAPAttributes);
+        } else {
+            // Partial read.
+            requestedLDAPAttributes = new LinkedHashSet<String>(requestedAttributes.size());
+            for (final JsonPointer requestedAttribute : requestedAttributes) {
+                attributeMapper.getLDAPAttributes(requestedAttribute, requestedLDAPAttributes);
+            }
+        }
+        return requestedLDAPAttributes;
+    }
+
+}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPResource.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPResource.java
deleted file mode 100644
index 890fb00..0000000
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPResource.java
+++ /dev/null
@@ -1,244 +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 Copyrighted [year] [name of copyright owner]".
- *
- * Copyright 2012 ForgeRock AS. All rights reserved.
- */
-
-package org.forgerock.opendj.rest2ldap;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedHashSet;
-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.opendj.ldap.AssertionFailureException;
-import org.forgerock.opendj.ldap.AuthenticationException;
-import org.forgerock.opendj.ldap.AuthorizationException;
-import org.forgerock.opendj.ldap.ConnectionException;
-import org.forgerock.opendj.ldap.EntryNotFoundException;
-import org.forgerock.opendj.ldap.ErrorResultException;
-import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
-import org.forgerock.opendj.ldap.ResultHandler;
-import org.forgerock.opendj.ldap.SearchResultHandler;
-import org.forgerock.opendj.ldap.TimeoutResultException;
-import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.opendj.ldap.responses.SearchResultEntry;
-import org.forgerock.opendj.ldap.responses.SearchResultReference;
-import org.forgerock.resource.exception.NotSupportedException;
-import org.forgerock.resource.exception.ResourceException;
-import org.forgerock.resource.provider.ActionRequest;
-import org.forgerock.resource.provider.ActionResultHandler;
-import org.forgerock.resource.provider.Context;
-import org.forgerock.resource.provider.CreateRequest;
-import org.forgerock.resource.provider.CreateResultHandler;
-import org.forgerock.resource.provider.DeleteRequest;
-import org.forgerock.resource.provider.DeleteResultHandler;
-import org.forgerock.resource.provider.PatchRequest;
-import org.forgerock.resource.provider.PatchResultHandler;
-import org.forgerock.resource.provider.QueryRequest;
-import org.forgerock.resource.provider.QueryResultHandler;
-import org.forgerock.resource.provider.ReadRequest;
-import org.forgerock.resource.provider.ReadResultHandler;
-import org.forgerock.resource.provider.Resource;
-import org.forgerock.resource.provider.UpdateRequest;
-import org.forgerock.resource.provider.UpdateResultHandler;
-
-/**
- *
- */
-public class LDAPResource implements Resource {
-    private final EntryContainer entryContainer;
-    private final AttributeMapper attributeMapper;
-
-    /**
-     * Creates a new LDAP resource.
-     */
-    public LDAPResource(final EntryContainer container, final AttributeMapper mapper) {
-        this.entryContainer = container;
-        this.attributeMapper = mapper;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void action(final ActionRequest request, final Context context,
-            final ActionResultHandler out) {
-        out.setFailure(new NotSupportedException("Not yet implemented"));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void create(final CreateRequest request, final Context context,
-            final CreateResultHandler out) {
-        out.setFailure(new NotSupportedException("Not yet implemented"));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void delete(final DeleteRequest request, final Context context,
-            final DeleteResultHandler out) {
-        out.setFailure(new NotSupportedException("Not yet implemented"));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void patch(final PatchRequest request, final Context context,
-            final PatchResultHandler out) {
-        out.setFailure(new NotSupportedException("Not yet implemented"));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void query(final QueryRequest request, final Context context,
-            final QueryResultHandler out) {
-        out.setFailure(new NotSupportedException("Not yet implemented"));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void read(final ReadRequest request, final Context context, final ReadResultHandler out) {
-        final String id = request.getId();
-        if (id == null) {
-            // List the entries.
-            final SearchResultHandler handler = new SearchResultHandler() {
-                private final List<String> resourceIDs = new ArrayList<String>();
-
-                public boolean handleEntry(final SearchResultEntry entry) {
-                    // TODO: should the resource or the container define the ID
-                    // mapping?
-                    resourceIDs.add(entryContainer.getIDFromEntry(entry));
-                    return true;
-                }
-
-                public void handleErrorResult(final ErrorResultException error) {
-                    out.setFailure(adaptErrorResult(error));
-                }
-
-                public boolean handleReference(final SearchResultReference reference) {
-                    // TODO: should this be classed as an error since rest2ldap
-                    // assumes entries are all colocated?
-                    return true;
-                }
-
-                public void handleResult(final Result result) {
-                    out.setResult(id, null, new JsonValue(resourceIDs));
-                }
-            };
-            entryContainer.listEntries(context, handler);
-        } else {
-            // Read a single entry.
-
-            // TODO: Determine the set of LDAP attributes that need to be read.
-            final Set<JsonPointer> requestedAttributes = new LinkedHashSet<JsonPointer>();
-            final Collection<String> requestedLDAPAttributes =
-                    getRequestedLDAPAttributes(requestedAttributes);
-
-            final ResultHandler<SearchResultEntry> handler =
-                    new ResultHandler<SearchResultEntry>() {
-                        public void handleErrorResult(final ErrorResultException error) {
-                            out.setFailure(adaptErrorResult(error));
-                        }
-
-                        public void handleResult(final SearchResultEntry entry) {
-                            final String revision = entryContainer.getEtagFromEntry(entry);
-                            final AttributeMapperCompletionHandler<Map<String, Object>> mapHandler =
-                                    new AttributeMapperCompletionHandler<Map<String, Object>>() {
-                                        public void onFailure(final ResourceException e) {
-                                            out.setFailure(e);
-                                        }
-
-                                        public void onSuccess(final Map<String, Object> result) {
-                                            out.setResult(id, revision, new JsonValue(result));
-                                        }
-                                    };
-                            attributeMapper.toJson(context, entry, mapHandler);
-                        }
-                    };
-            entryContainer.readEntry(context, id, requestedLDAPAttributes, handler);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void update(final UpdateRequest request, final Context context,
-            final UpdateResultHandler out) {
-        out.setFailure(new NotSupportedException("Not yet implemented"));
-    }
-
-    /**
-     * Adapts an LDAP result code to a resource exception.
-     *
-     * @param error
-     *            The LDAP error that should be adapted.
-     * @return The equivalent resource exception.
-     */
-    private ResourceException adaptErrorResult(final ErrorResultException error) {
-        int resourceResultCode;
-        try {
-            throw error;
-        } catch (AssertionFailureException e) {
-            resourceResultCode = ResourceException.VERSION_MISMATCH;
-        } catch (AuthenticationException e) {
-            resourceResultCode = 401;
-        } catch (AuthorizationException e) {
-            resourceResultCode = ResourceException.FORBIDDEN;
-        } catch (ConnectionException e) {
-            resourceResultCode = ResourceException.UNAVAILABLE;
-        } catch (EntryNotFoundException e) {
-            resourceResultCode = ResourceException.NOT_FOUND;
-        } catch (MultipleEntriesFoundException e) {
-            resourceResultCode = ResourceException.INTERNAL_ERROR;
-        } catch (TimeoutResultException e) {
-            resourceResultCode = 408;
-        } catch (ErrorResultException e) {
-            resourceResultCode = ResourceException.INTERNAL_ERROR;
-        }
-        return ResourceException.getException(resourceResultCode, null, error.getMessage(), error);
-    }
-
-    /**
-     * Determines the set of LDAP attributes to request in an LDAP read (search,
-     * post-read), based on the provided set of JSON pointers.
-     *
-     * @param requestedAttributes
-     *            The set of resource attributes to be read.
-     * @return The set of LDAP attributes associated with the resource
-     *         attributes.
-     */
-    private Collection<String> getRequestedLDAPAttributes(final Set<JsonPointer> requestedAttributes) {
-        final Set<String> requestedLDAPAttributes;
-        if (requestedAttributes.isEmpty()) {
-            // Full read.
-            requestedLDAPAttributes = new LinkedHashSet<String>();
-            attributeMapper.getLDAPAttributes(new JsonPointer(), requestedLDAPAttributes);
-        } else {
-            // Partial read.
-            requestedLDAPAttributes = new LinkedHashSet<String>(requestedAttributes.size());
-            for (final JsonPointer requestedAttribute : requestedAttributes) {
-                attributeMapper.getLDAPAttributes(requestedAttribute, requestedLDAPAttributes);
-            }
-        }
-        return requestedLDAPAttributes;
-    }
-
-}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
index 9e1dcac..a4deee4 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
@@ -22,9 +22,10 @@
 
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.ResultHandler;
+import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.resource.provider.Context;
 
 /**
  *
@@ -36,7 +37,7 @@
     /**
      * {@inheritDoc}
      */
-    public void getLDAPAttributes(JsonPointer jsonAttribute, Set<String> ldapAttributes) {
+    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
         // TODO Auto-generated method stub
 
     }
@@ -44,7 +45,7 @@
     /**
      * {@inheritDoc}
      */
-    public void toJson(Context c, Entry e, AttributeMapperCompletionHandler<Map<String, Object>> h) {
+    public void toJson(final ServerContext c, final Entry e, final ResultHandler<Map<String, Object>> h) {
         // TODO Auto-generated method stub
 
     }
@@ -52,7 +53,7 @@
     /**
      * {@inheritDoc}
      */
-    public void toLDAP(Context c, JsonValue v, AttributeMapperCompletionHandler<List<Attribute>> h) {
+    public void toLDAP(final ServerContext c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
 
     }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
index 0c92b63..214950b 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -26,26 +26,27 @@
 
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.ResultHandler;
+import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.Function;
 import org.forgerock.opendj.ldap.Functions;
-import org.forgerock.resource.provider.Context;
 
 /**
  *
  */
 public class SimpleAttributeMapper implements AttributeMapper {
 
-    private final String ldapAttributeName;
-    private final String jsonAttributeName;
-    private final String normalizedJsonAttributeName;
-
-    private boolean forceSingleValued = false;
-    private Object defaultValue = null;
-    private boolean isReadOnly = false;
     private Function<ByteString, ?, Void> decoder = null;
+    private Object defaultValue = null;
+    private boolean forceSingleValued = false;
+
+    private boolean isReadOnly = false;
+    private final String jsonAttributeName;
+    private final String ldapAttributeName;
+    private final String normalizedJsonAttributeName;
 
     /**
      * Creates a new simple attribute mapper which maps a single LDAP attribute
@@ -54,7 +55,7 @@
      * @param attributeName
      *            The name of the simple JSON and LDAP attribute.
      */
-    public SimpleAttributeMapper(String attributeName) {
+    public SimpleAttributeMapper(final String attributeName) {
         this(attributeName, attributeName);
     }
 
@@ -67,54 +68,38 @@
      * @param ldapAttributeName
      *            The name of the LDAP attribute.
      */
-    public SimpleAttributeMapper(String jsonAttributeName, String ldapAttributeName) {
+    public SimpleAttributeMapper(final String jsonAttributeName, final String ldapAttributeName) {
         this.jsonAttributeName = jsonAttributeName;
         this.ldapAttributeName = ldapAttributeName;
         this.normalizedJsonAttributeName = toLowerCase(jsonAttributeName);
     }
 
-    public SimpleAttributeMapper withDefaultValue(Object defaultValue) {
-        this.defaultValue = defaultValue;
-        return this;
-    }
-
-    public SimpleAttributeMapper isReadOnly(boolean readOnly) {
-        this.isReadOnly = readOnly;
-        return this;
-    }
-
-    public SimpleAttributeMapper forceSingleValued(boolean singleValued) {
+    public SimpleAttributeMapper forceSingleValued(final boolean singleValued) {
         this.forceSingleValued = singleValued;
         return this;
     }
 
-    public SimpleAttributeMapper withDecoder(Function<ByteString, ?, Void> f) {
-        this.decoder = f;
-        return this;
-    }
-
     /**
      * {@inheritDoc}
      */
-    public void getLDAPAttributes(JsonPointer jsonAttribute, Set<String> ldapAttributes) {
+    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
         if (attributeMatchesPointer(jsonAttribute)) {
             ldapAttributes.add(ldapAttributeName);
         }
     }
 
-    private boolean attributeMatchesPointer(JsonPointer resourceAttribute) {
-        return resourceAttribute.isEmpty()
-                || toLowerCase(resourceAttribute.get(0)).equals(normalizedJsonAttributeName);
+    public SimpleAttributeMapper isReadOnly(final boolean readOnly) {
+        this.isReadOnly = readOnly;
+        return this;
     }
 
     /**
      * {@inheritDoc}
      */
-    public void toJson(Context c, Entry e,
-            final AttributeMapperCompletionHandler<Map<String, Object>> h) {
-        Attribute a = e.getAttribute(ldapAttributeName);
+    public void toJson(final ServerContext c, final Entry e, final ResultHandler<Map<String, Object>> h) {
+        final Attribute a = e.getAttribute(ldapAttributeName);
         if (a != null) {
-            Function<ByteString, ?, Void> f =
+            final Function<ByteString, ?, Void> f =
                     decoder == null ? Functions.fixedFunction(byteStringToJson(), a) : decoder;
             final Object value;
             if (forceSingleValued || a.getAttributeDescription().getAttributeType().isSingleValue()) {
@@ -122,17 +107,32 @@
             } else {
                 value = a.parse().asSetOf(f, defaultValue);
             }
-            Map<String, Object> result = Collections.singletonMap(jsonAttributeName, value);
-            h.onSuccess(result);
+            final Map<String, Object> result = Collections.singletonMap(jsonAttributeName, value);
+            h.handleResult(result);
         }
     }
 
     /**
      * {@inheritDoc}
      */
-    public void toLDAP(Context c, JsonValue v, AttributeMapperCompletionHandler<List<Attribute>> h) {
+    public void toLDAP(final ServerContext c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
 
     }
 
+    public SimpleAttributeMapper withDecoder(final Function<ByteString, ?, Void> f) {
+        this.decoder = f;
+        return this;
+    }
+
+    public SimpleAttributeMapper withDefaultValue(final Object defaultValue) {
+        this.defaultValue = defaultValue;
+        return this;
+    }
+
+    private boolean attributeMatchesPointer(final JsonPointer resourceAttribute) {
+        return resourceAttribute.isEmpty()
+                || toLowerCase(resourceAttribute.get(0)).equals(normalizedJsonAttributeName);
+    }
+
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java
new file mode 100644
index 0000000..58bdc72
--- /dev/null
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java
@@ -0,0 +1,61 @@
+/*
+ * 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 Copyrighted [year] [name of copyright owner]".
+ *
+ * Copyright 2012 ForgeRock AS. All rights reserved.
+ */
+
+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.json.resource.ServerContext;
+import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.Entry;
+
+/**
+ *
+ */
+public class SubContainerAttributeMapper implements AttributeMapper {
+
+    // private final EntryContainer referencedContainer;
+
+    /**
+     * {@inheritDoc}
+     */
+    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
+        // TODO Auto-generated method stub
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void toJson(final ServerContext c, final Entry e, final ResultHandler<Map<String, Object>> h) {
+        // TODO Auto-generated method stub
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void toLDAP(final ServerContext c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
index 2446095..df1bb12 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -41,7 +41,8 @@
     private static final Function<ByteString, Object, Attribute> BYTESTRING_TO_JSON =
             new Function<ByteString, Object, Attribute>() {
                 public Object apply(final ByteString value, final Attribute a) {
-                    Syntax syntax = a.getAttributeDescription().getAttributeType().getSyntax();
+                    final Syntax syntax =
+                            a.getAttributeDescription().getAttributeType().getSyntax();
                     if (syntax.equals(getBooleanSyntax())) {
                         return Functions.byteStringToBoolean().apply(value, null);
                     } else if (syntax.equals(getIntegerSyntax())) {
@@ -58,39 +59,17 @@
                 }
             };
 
-    // Prevent instantiation.
-    private Utils() {
-        // No implementation required.
-    }
-
-    private static <T> List<T> asList(Collection<T> c) {
-        if (c instanceof List) {
-            return (List<T>) c;
-        } else {
-            List<T> result = new ArrayList<T>(c.size());
-            result.addAll(c);
-            return result;
-        }
+    static Object attributeToJson(final Attribute a) {
+        final Function<ByteString, Object, Void> f = Functions.fixedFunction(BYTESTRING_TO_JSON, a);
+        final boolean isSingleValued =
+                a.getAttributeDescription().getAttributeType().isSingleValue();
+        return isSingleValued ? a.parse().as(f) : asList(a.parse().asSetOf(f));
     }
 
     static Function<ByteString, Object, Attribute> byteStringToJson() {
         return BYTESTRING_TO_JSON;
     }
 
-    static Object attributeToJson(Attribute a) {
-        Function<ByteString, Object, Void> f = Functions.fixedFunction(BYTESTRING_TO_JSON, a);
-        boolean isSingleValued = a.getAttributeDescription().getAttributeType().isSingleValue();
-        return isSingleValued ? a.parse().as(f) : asList(a.parse().asSetOf(f));
-    }
-
-    static String getAttributeName(Attribute a) {
-        return a.getAttributeDescription().withoutOption("binary").toString();
-    }
-
-    static String toLowerCase(String s) {
-        return s != null ? s.toLowerCase(Locale.ENGLISH) : null;
-    }
-
     static <T> T ensureNotNull(final T object) {
         if (object == null) {
             throw new NullPointerException();
@@ -105,4 +84,25 @@
         return object;
     }
 
+    static String getAttributeName(final Attribute a) {
+        return a.getAttributeDescription().withoutOption("binary").toString();
+    }
+
+    static String toLowerCase(final String s) {
+        return s != null ? s.toLowerCase(Locale.ENGLISH) : null;
+    }
+
+    private static <T> List<T> asList(final Collection<T> c) {
+        if (c instanceof List) {
+            return (List<T>) c;
+        } else {
+            return new ArrayList<T>(c);
+        }
+    }
+
+    // Prevent instantiation.
+    private Utils() {
+        // No implementation required.
+    }
+
 }
diff --git a/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java b/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
new file mode 100644
index 0000000..ccd5a03
--- /dev/null
+++ b/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -0,0 +1,105 @@
+/*
+ * 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 Copyrighted [year] [name of copyright owner]".
+ *
+ * Copyright © 2012 ForgeRock AS. All rights reserved.
+ */
+
+package org.forgerock.opendj.rest2ldap;
+
+import static org.forgerock.json.resource.Resources.newInternalConnectionFactory;
+import static org.forgerock.opendj.ldap.Connections.newAuthenticatedConnectionFactory;
+
+import java.util.logging.Logger;
+
+import org.forgerock.json.resource.Router;
+import org.forgerock.json.resource.RoutingMode;
+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;
+import org.glassfish.grizzly.http.server.HttpServer;
+import org.glassfish.grizzly.servlet.ServletRegistration;
+import org.glassfish.grizzly.servlet.WebappContext;
+
+/**
+ * Example
+ */
+public class Example {
+
+    private static final Logger LOGGER = Logger.getLogger(Example.class.getName());
+    private static final int PORT = 18890;
+
+    public static void main(final String[] args) throws Exception {
+        // All LDAP resources will use this connection factory.
+        final ConnectionFactory ldapFactory = newAuthenticatedConnectionFactory(
+                new LDAPConnectionFactory("localhost", 1389), Requests.newSimpleBindRequest(
+                        "cn=directory manager", "password".toCharArray()));
+
+        // Create two entry containers whose members reference each other.
+        final EntryContainer userContainer = new EntryContainer(DN
+                .valueOf("ou=people,dc=example,dc=com"), ldapFactory);
+        final EntryContainer groupContainer = new EntryContainer(DN
+                .valueOf("ou=groups,dc=example,dc=com"), ldapFactory);
+
+        // Create user resource.
+        final AttributeMapper userMapper = new CompositeAttributeMapper().addMapper(
+                new SimpleAttributeMapper("id", "entryUUID").forceSingleValued(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").withDecoder(
+                                        Functions.byteStringToString()).forceSingleValued(true))
+                        .addMapper(
+                                new SimpleAttributeMapper("emailAddress", "mail")
+                                        .forceSingleValued(true))));
+        final LDAPCollectionResourceProvider userResource = new LDAPCollectionResourceProvider(
+                userContainer, userMapper);
+
+        // Create group resource.
+        final AttributeMapper groupMapper = new DefaultAttributeMapper().includeAttribute("cn",
+                "ou", "description", "uniquemember");
+        final LDAPCollectionResourceProvider groupResource = new LDAPCollectionResourceProvider(
+                groupContainer, groupMapper);
+
+        // Create the router.
+        Router router = new Router();
+        router.addRoute(RoutingMode.EQUALS, "/users", userResource);
+        router.addRoute(RoutingMode.EQUALS, "/groups", groupResource);
+
+        final org.forgerock.json.resource.ConnectionFactory resourceFactory = newInternalConnectionFactory(router);
+        final HttpServer httpServer = HttpServer.createSimpleServer("./", PORT);
+        try {
+            final WebappContext ctx = new WebappContext("example", "/example");
+            final ServletRegistration reg = ctx.addServlet("managed", new HttpServlet(
+                    resourceFactory));
+            reg.addMapping("/managed/*");
+            ctx.deploy(httpServer);
+
+            LOGGER.info("Starting server...");
+            httpServer.start();
+            LOGGER.info("Server started");
+            LOGGER.info("Press any key to stop the server...");
+            System.in.read();
+        } finally {
+            LOGGER.info("Stopping server...");
+            httpServer.stop();
+            LOGGER.info("Server stopped");
+        }
+    }
+
+}

--
Gitblit v1.10.0