From 16869da731710f4c3754c7b229fa7346211a25a4 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Fri, 01 Feb 2013 14:57:43 +0000
Subject: [PATCH] Pull EntryContainer into LDAPCollectionResourceProvider until layering is better understood.

---
 /dev/null                                                                                                 |  238 -----------------
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java                          |    2 
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java       |    8 
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java |  247 +++++++++++++++--
 opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java                        |   64 ++--
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java                         |  197 +++++++++++++
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java        |   32 +-
 7 files changed, 452 insertions(+), 336 deletions(-)

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 9d7f367..c5dfa31 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
@@ -91,10 +91,10 @@
                                     if (f == null) {
                                         // No mapping so remove.
                                         i.remove();
-                                    } else if (f == c.getConfig().getFalseFilter()) {
-                                        return c.getConfig().getFalseFilter();
-                                    } else if (f == c.getConfig().getTrueFilter()) {
-                                        return c.getConfig().getTrueFilter();
+                                    } else if (f == c.getConfig().falseFilter()) {
+                                        return c.getConfig().falseFilter();
+                                    } else if (f == c.getConfig().trueFilter()) {
+                                        return c.getConfig().trueFilter();
                                     }
                                 }
                                 switch (value.size()) {
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
index b36b195..41f16eb 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
@@ -15,6 +15,9 @@
  */
 package org.forgerock.opendj.rest2ldap;
 
+import static org.forgerock.opendj.rest2ldap.Config.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS;
+import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
+
 import org.forgerock.opendj.ldap.Filter;
 
 /**
@@ -22,24 +25,200 @@
  */
 public final class Config {
 
-    private final Filter trueFilter = Filter.objectClassPresent();
-    private final Filter falseFilter = Filter.present("1.1");
+    /**
+     * An interface for incrementally constructing common configuration options.
+     */
+    public static final class Builder {
+        private Filter falseFilter;
+        private ReadOnUpdatePolicy readOnUpdatePolicy;
+        private Filter trueFilter;
+
+        private Builder() {
+            // Nothing to do.
+        }
+
+        /**
+         * Returns a new configuration based on the current state of this
+         * builder.
+         *
+         * @return A new configuration based on the current state of this
+         *         builder.
+         */
+        public Config build() {
+            return new Config(trueFilter, falseFilter, readOnUpdatePolicy);
+        }
+
+        /**
+         * Sets the absolute false filter which should be used when querying the
+         * LDAP server.
+         *
+         * @param filter
+         *            The absolute false filter.
+         * @return A reference to this builder.
+         */
+        public Builder falseFilter(final Filter filter) {
+            this.trueFilter = ensureNotNull(filter);
+            return this;
+        }
+
+        /**
+         * Sets the policy which should be used in order to read an entry before
+         * it is deleted, or after it is added or modified.
+         *
+         * @param policy
+         *            The policy which should be used in order to read an entry
+         *            before it is deleted, or after it is added or modified.
+         * @return A reference to this builder.
+         */
+        public Builder readOnUpdatePolicy(final ReadOnUpdatePolicy policy) {
+            this.readOnUpdatePolicy = ensureNotNull(policy);
+            return this;
+        }
+
+        /**
+         * Sets the absolute true filter which should be used when querying the
+         * LDAP server.
+         *
+         * @param filter
+         *            The absolute true filter.
+         * @return A reference to this builder.
+         */
+        public Builder trueFilter(final Filter filter) {
+            this.trueFilter = ensureNotNull(filter);
+            return this;
+        }
+    };
 
     /**
-     * Returns the absolute true filter.
-     *
-     * @return The absolute true filter.
+     * The policy which should be used in order to read an entry before it is
+     * deleted, or after it is added or modified.
      */
-    public Filter getTrueFilter() {
-        return trueFilter;
+    public static enum ReadOnUpdatePolicy {
+        /**
+         * The LDAP entry will not be read when an update is performed. More
+         * specifically, the REST resource will not be returned as part of a
+         * create, delete, patch, or update request.
+         */
+        DISABLED,
+
+        /**
+         * The LDAP entry will be read atomically using the RFC 4527 read-entry
+         * controls. More specifically, the REST resource will be returned as
+         * part of a create, delete, patch, or update request, and it will
+         * reflect the state of the resource at the time the update was
+         * performed. This policy requires that the LDAP server supports RFC
+         * 4527.
+         */
+        USE_READ_ENTRY_CONTROLS,
+
+        /**
+         * The LDAP entry will be read non-atomically using an LDAP search when
+         * an update is performed. More specifically, the REST resource will be
+         * returned as part of a create, delete, patch, or update request, but
+         * it may not reflect the state of the resource at the time the update
+         * was performed.
+         */
+        USE_SEARCH;
+    }
+
+    private static final Config DEFAULT = new Builder().trueFilter(Filter.objectClassPresent())
+            .falseFilter(Filter.present("1.1")).readOnUpdatePolicy(USE_READ_ENTRY_CONTROLS).build();
+
+    /**
+     * Returns a new builder which can be used for incrementally constructing
+     * common configuration options. The builder will initially have
+     * {@link #defaultConfig() default} settings.
+     *
+     * @return The new builder.
+     */
+    public static Builder builder() {
+        return builder(DEFAULT);
     }
 
     /**
-     * Returns the absolute false filter.
+     * Returns a new builder which can be used for incrementally constructing
+     * common configuration options. The builder will initially have the same
+     * settings as the provided configuration.
+     *
+     * @param config
+     *            The initial settings.
+     * @return The new builder.
+     */
+    public static Builder builder(final Config config) {
+        return new Builder().trueFilter(config.trueFilter()).falseFilter(config.falseFilter())
+                .readOnUpdatePolicy(config.readOnUpdatePolicy());
+    }
+
+    /**
+     * Returns the default configuration having the following settings:
+     * <ul>
+     * <li>the absolute true filter {@code (objectClass=*)}
+     * <li>the absolute false filter {@code (1.1=*)}
+     * <li>the read on update policy
+     * {@link ReadOnUpdatePolicy#USE_READ_ENTRY_CONTROLS}.
+     * </ul>
+     *
+     * @return The default configuration.
+     */
+    public static Config defaultConfig() {
+        return DEFAULT;
+    }
+
+    private final Filter falseFilter;
+
+    private final ReadOnUpdatePolicy readOnUpdatePolicy;
+    private final Filter trueFilter;
+
+    private Config(final Filter trueFilter, final Filter falseFilter,
+            final ReadOnUpdatePolicy readOnUpdatePolicy) {
+        this.trueFilter = trueFilter;
+        this.falseFilter = falseFilter;
+        this.readOnUpdatePolicy = readOnUpdatePolicy;
+    }
+
+    /**
+     * Returns the absolute false filter which should be used when querying the
+     * LDAP server.
      *
      * @return The absolute false filter.
      */
-    public Filter getFalseFilter() {
+    public Filter falseFilter() {
         return falseFilter;
     }
+
+    /**
+     * Returns the policy which should be used in order to read an entry before
+     * it is deleted, or after it is added or modified.
+     *
+     * @return The policy which should be used in order to read an entry before
+     *         it is deleted, or after it is added or modified.
+     */
+    public ReadOnUpdatePolicy readOnUpdatePolicy() {
+        return readOnUpdatePolicy;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("trueFilter=");
+        builder.append(trueFilter);
+        builder.append(", falseFilter=");
+        builder.append(falseFilter);
+        builder.append(", readOnUpdatePolicy=");
+        builder.append(readOnUpdatePolicy);
+        return builder.toString();
+    }
+
+    /**
+     * Returns the absolute true filter which should be used when querying the
+     * LDAP server.
+     *
+     * @return The absolute true filter.
+     */
+    public Filter trueFilter() {
+        return trueFilter;
+    }
 }
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 5fc4644..fec587e 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
@@ -69,20 +69,20 @@
         if (jsonAttribute.size() == 1 && jsonAttribute.get(0).equalsIgnoreCase(jsonAttributeName)) {
             final Filter filter;
             if (type == FilterType.PRESENT) {
-                filter = c.getConfig().getTrueFilter();
+                filter = c.getConfig().trueFilter();
             } else if (jsonAttributeValue instanceof String && valueAssertion instanceof String) {
                 final String v1 = toLowerCase((String) jsonAttributeValue);
                 final String v2 = toLowerCase((String) valueAssertion);
                 switch (type) {
                 case CONTAINS:
                     filter =
-                            v1.contains(v2) ? c.getConfig().getTrueFilter() : c.getConfig()
-                                    .getFalseFilter();
+                            v1.contains(v2) ? c.getConfig().trueFilter() : c.getConfig()
+                                    .falseFilter();
                     break;
                 case STARTS_WITH:
                     filter =
-                            v1.startsWith(v2) ? c.getConfig().getTrueFilter() : c.getConfig()
-                                    .getFalseFilter();
+                            v1.startsWith(v2) ? c.getConfig().trueFilter() : c.getConfig()
+                                    .falseFilter();
                     break;
                 default:
                     filter = compare(c, type, v1, v2);
@@ -98,7 +98,7 @@
                 filter = compare(c, type, v1, v2);
             } else {
                 // This attribute mapper is a candidate but it does not match.
-                filter = c.getConfig().getFalseFilter();
+                filter = c.getConfig().falseFilter();
             }
             h.handleResult(filter);
         } else {
@@ -130,30 +130,30 @@
         final Filter filter;
         switch (type) {
         case EQUAL_TO:
-            filter = v1.equals(v2) ? c.getConfig().getTrueFilter() : c.getConfig().getFalseFilter();
+            filter = v1.equals(v2) ? c.getConfig().trueFilter() : c.getConfig().falseFilter();
             break;
         case GREATER_THAN:
             filter =
-                    v1.compareTo(v2) > 0 ? c.getConfig().getTrueFilter() : c.getConfig()
-                            .getFalseFilter();
+                    v1.compareTo(v2) > 0 ? c.getConfig().trueFilter() : c.getConfig()
+                            .falseFilter();
             break;
         case GREATER_THAN_OR_EQUAL_TO:
             filter =
-                    v1.compareTo(v2) >= 0 ? c.getConfig().getTrueFilter() : c.getConfig()
-                            .getFalseFilter();
+                    v1.compareTo(v2) >= 0 ? c.getConfig().trueFilter() : c.getConfig()
+                            .falseFilter();
             break;
         case LESS_THAN:
             filter =
-                    v1.compareTo(v2) < 0 ? c.getConfig().getTrueFilter() : c.getConfig()
-                            .getFalseFilter();
+                    v1.compareTo(v2) < 0 ? c.getConfig().trueFilter() : c.getConfig()
+                            .falseFilter();
             break;
         case LESS_THAN_OR_EQUAL_TO:
             filter =
-                    v1.compareTo(v2) <= 0 ? c.getConfig().getTrueFilter() : c.getConfig()
-                            .getFalseFilter();
+                    v1.compareTo(v2) <= 0 ? c.getConfig().trueFilter() : c.getConfig()
+                            .falseFilter();
             break;
         default:
-            filter = c.getConfig().getFalseFilter(); // Not supported.
+            filter = c.getConfig().falseFilter(); // Not supported.
             break;
         }
         return filter;
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
deleted file mode 100644
index f6916de..0000000
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java
+++ /dev/null
@@ -1,238 +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.Collection;
-
-import org.forgerock.opendj.ldap.Connection;
-import org.forgerock.opendj.ldap.ConnectionFactory;
-import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.ErrorResultException;
-import org.forgerock.opendj.ldap.Filter;
-import org.forgerock.opendj.ldap.ResultHandler;
-import org.forgerock.opendj.ldap.SearchResultHandler;
-import org.forgerock.opendj.ldap.SearchScope;
-import org.forgerock.opendj.ldap.requests.Requests;
-import org.forgerock.opendj.ldap.requests.SearchRequest;
-import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.opendj.ldap.responses.SearchResultEntry;
-import org.forgerock.opendj.ldap.responses.SearchResultReference;
-
-/**
- * An entry container.
- */
-public final class EntryContainer {
-    private abstract class AbstractRequestCompletionHandler<R, H extends ResultHandler<? super R>>
-            implements ResultHandler<R> {
-        final Connection connection;
-        final H resultHandler;
-
-        AbstractRequestCompletionHandler(final Connection connection, final H resultHandler) {
-            this.connection = connection;
-            this.resultHandler = resultHandler;
-        }
-
-        @Override
-        public final void handleErrorResult(final ErrorResultException error) {
-            connection.close();
-            resultHandler.handleErrorResult(error);
-        }
-
-        @Override
-        public final void handleResult(final R result) {
-            connection.close();
-            resultHandler.handleResult(result);
-        }
-
-    }
-
-    private abstract class ConnectionCompletionHandler<R> implements ResultHandler<Connection> {
-        private final ResultHandler<? super R> resultHandler;
-
-        ConnectionCompletionHandler(final ResultHandler<? super R> resultHandler) {
-            this.resultHandler = resultHandler;
-        }
-
-        @Override
-        public final void handleErrorResult(final ErrorResultException error) {
-            resultHandler.handleErrorResult(error);
-        }
-
-        @Override
-        public abstract void handleResult(Connection connection);
-
-    }
-
-    private final class RequestCompletionHandler<R> extends
-            AbstractRequestCompletionHandler<R, ResultHandler<? super R>> {
-        RequestCompletionHandler(final Connection connection,
-                final ResultHandler<? super R> resultHandler) {
-            super(connection, resultHandler);
-        }
-    }
-
-    private final class SearchRequestCompletionHandler extends
-            AbstractRequestCompletionHandler<Result, SearchResultHandler> implements
-            SearchResultHandler {
-
-        SearchRequestCompletionHandler(final Connection connection,
-                final SearchResultHandler resultHandler) {
-            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);
-        }
-
-    }
-
-    // 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";
-
-    // TODO: support template variables.
-    private final DN baseDN;
-
-    private final ConnectionFactory factory;
-
-    /**
-     * Creates a new entry container for the provided base DN and LDAP
-     * connection factory.
-     *
-     * @param baseDN
-     *            The base DN.
-     * @param factory
-     *            The LDAP connection factory.
-     */
-    public EntryContainer(final DN baseDN, final ConnectionFactory factory) {
-        this.baseDN = baseDN;
-        this.factory = factory;
-    }
-
-    /**
-     * Returns the ETag for the provided entry.
-     *
-     * @param entry
-     *            The entry.
-     * @return The ETag.
-     */
-    public 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.
-     */
-    public String getIDFromEntry(final Entry entry) {
-        return entry.parseAttribute(UUID_ATTRIBUTE).asString();
-    }
-
-    /**
-     * Lists the entries contained in this container.
-     *
-     * @param context
-     *            The request context.
-     * @param filter
-     *            The LDAP search filter.
-     * @param attributes
-     *            The list of LDAP attributes to be returned.
-     * @param handler
-     *            The search result handler.
-     */
-    public void listEntries(final Context context, final Filter filter,
-            final Collection<String> attributes, final SearchResultHandler handler) {
-        final String[] tmp = getSearchAttributes(attributes);
-        final ConnectionCompletionHandler<Result> outerHandler =
-                new ConnectionCompletionHandler<Result>(handler) {
-
-                    @Override
-                    public void handleResult(final Connection connection) {
-                        final SearchRequestCompletionHandler innerHandler =
-                                new SearchRequestCompletionHandler(connection, handler);
-                        final SearchRequest request =
-                                Requests.newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, filter,
-                                        tmp);
-                        connection.searchAsync(request, null, innerHandler);
-                    }
-
-                };
-
-        factory.getConnectionAsync(outerHandler);
-    }
-
-    /**
-     * Reads the entry having the specified resource ID.
-     *
-     * @param c
-     *            The request context.
-     * @param id
-     *            The resource ID.
-     * @param attributes
-     *            The list of LDAP attributes to be returned.
-     * @param handler
-     *            The search result handler.
-     */
-    public void readEntry(final Context c, final String id, final Collection<String> attributes,
-            final ResultHandler<SearchResultEntry> handler) {
-        final String[] tmp = getSearchAttributes(attributes);
-        // @Checkstyle:off
-        final ConnectionCompletionHandler<SearchResultEntry> outerHandler =
-                new ConnectionCompletionHandler<SearchResultEntry>(handler) {
-
-                    @Override
-                    public void handleResult(final Connection connection) {
-                        final RequestCompletionHandler<SearchResultEntry> innerHandler =
-                                new RequestCompletionHandler<SearchResultEntry>(connection, handler);
-                        final SearchRequest request =
-                                Requests.newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter
-                                        .equality(UUID_ATTRIBUTE, id), tmp);
-                        connection.searchSingleEntryAsync(request, innerHandler);
-                    }
-
-                };
-        // @Checkstyle:on
-        factory.getConnectionAsync(outerHandler);
-    }
-
-    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/LDAPCollectionResourceProvider.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
index 5ef4c76..dd3b78f 100644
--- 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
@@ -51,14 +51,21 @@
 import org.forgerock.opendj.ldap.AssertionFailureException;
 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;
 import org.forgerock.opendj.ldap.Function;
 import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
 import org.forgerock.opendj.ldap.SearchResultHandler;
+import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.TimeoutResultException;
+import org.forgerock.opendj.ldap.requests.Requests;
+import org.forgerock.opendj.ldap.requests.SearchRequest;
 import org.forgerock.opendj.ldap.responses.Result;
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 import org.forgerock.opendj.ldap.responses.SearchResultReference;
@@ -68,25 +75,120 @@
  * resource collection to LDAP entries beneath a base DN.
  */
 public class LDAPCollectionResourceProvider implements CollectionResourceProvider {
+
+    private abstract class AbstractRequestCompletionHandler<R,
+            H extends org.forgerock.opendj.ldap.ResultHandler<? super R>>
+            implements org.forgerock.opendj.ldap.ResultHandler<R> {
+        final Connection connection;
+        final H resultHandler;
+
+        AbstractRequestCompletionHandler(final Connection connection, final H resultHandler) {
+            this.connection = connection;
+            this.resultHandler = resultHandler;
+        }
+
+        @Override
+        public final void handleErrorResult(final ErrorResultException error) {
+            connection.close();
+            resultHandler.handleErrorResult(error);
+        }
+
+        @Override
+        public final void handleResult(final R result) {
+            connection.close();
+            resultHandler.handleResult(result);
+        }
+
+    }
+
+    private abstract class ConnectionCompletionHandler<R> implements
+            org.forgerock.opendj.ldap.ResultHandler<Connection> {
+        private final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler;
+
+        ConnectionCompletionHandler(
+                final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler) {
+            this.resultHandler = resultHandler;
+        }
+
+        @Override
+        public final void handleErrorResult(final ErrorResultException error) {
+            resultHandler.handleErrorResult(error);
+        }
+
+        @Override
+        public abstract void handleResult(Connection connection);
+
+    }
+
+    private final class RequestCompletionHandler<R> extends
+            AbstractRequestCompletionHandler<R, org.forgerock.opendj.ldap.ResultHandler<? super R>> {
+        RequestCompletionHandler(final Connection connection,
+                final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler) {
+            super(connection, resultHandler);
+        }
+    }
+
+    private final class SearchRequestCompletionHandler extends
+            AbstractRequestCompletionHandler<Result, SearchResultHandler> implements
+            SearchResultHandler {
+
+        SearchRequestCompletionHandler(final Connection connection,
+                final SearchResultHandler resultHandler) {
+            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);
+        }
+
+    }
+
+    // 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 EntryContainer entryContainer;
-    private final Config config = new Config();
+    private final DN baseDN; // TODO: support template variables.
+    private final Config config;
+    private final ConnectionFactory factory;
 
     /**
      * Creates a new LDAP resource.
      *
-     * @param container
-     *            The LDAP entry container.
+     * @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 EntryContainer container,
-            final AttributeMapper mapper) {
-        this.entryContainer = container;
+    public LDAPCollectionResourceProvider(final DN baseDN, final AttributeMapper mapper,
+            final ConnectionFactory factory, final Config config) {
+        this.baseDN = baseDN;
         this.attributeMapper = mapper;
+        this.factory = factory;
+        this.config = config;
     }
 
     /**
@@ -140,8 +242,10 @@
     @Override
     public void queryCollection(final ServerContext context, final QueryRequest request,
             final QueryResultHandler handler) {
-        // List the entries.
         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() {
             private final AtomicInteger pendingResourceCount = new AtomicInteger();
             private final AtomicReference<ResourceException> pendingResult =
@@ -161,8 +265,8 @@
 
                 // TODO: should the resource or the container define the ID
                 // mapping?
-                final String id = entryContainer.getIDFromEntry(entry);
-                final String revision = entryContainer.getEtagFromEntry(entry);
+                final String id = getIDFromEntry(entry);
+                final String revision = getEtagFromEntry(entry);
                 final ResultHandler<Map<String, Object>> mapHandler =
                         new ResultHandler<Map<String, Object>>() {
                             @Override
@@ -224,8 +328,8 @@
             }
         };
 
-        final Collection<String> ldapAttributes = getLDAPAttributes(c, request.getFieldFilters());
-        getLDAPFilter(c, request.getQueryFilter(), new ResultHandler<Filter>() {
+        // The handler which will be invoked once the LDAP filter has been transformed.
+        final ResultHandler<Filter> filterHandler = new ResultHandler<Filter>() {
             @Override
             public void handleError(final ResourceException error) {
                 handler.handleError(error);
@@ -234,13 +338,32 @@
             @Override
             public void handleResult(final Filter ldapFilter) {
                 // Avoid performing a search if the filter could not be mapped or if it will never match.
-                if (ldapFilter == null || ldapFilter == c.getConfig().getFalseFilter()) {
+                if (ldapFilter == null || ldapFilter == c.getConfig().falseFilter()) {
                     handler.handleResult(new QueryResult());
                 } else {
-                    entryContainer.listEntries(c, ldapFilter, ldapAttributes, searchHandler);
+                    final String[] tmp = getSearchAttributes(ldapAttributes);
+                    final ConnectionCompletionHandler<Result> outerHandler =
+                            new ConnectionCompletionHandler<Result>(searchHandler) {
+
+                                @Override
+                                public void handleResult(final Connection connection) {
+                                    final SearchRequestCompletionHandler innerHandler =
+                                            new SearchRequestCompletionHandler(connection,
+                                                    searchHandler);
+                                    final SearchRequest request =
+                                            Requests.newSearchRequest(baseDN,
+                                                    SearchScope.SINGLE_LEVEL, ldapFilter, tmp);
+                                    connection.searchAsync(request, null, innerHandler);
+                                }
+
+                            };
+
+                    factory.getConnectionAsync(outerHandler);
                 }
             }
-        });
+        };
+
+        getLDAPFilter(c, request.getQueryFilter(), filterHandler);
     }
 
     /**
@@ -250,7 +373,10 @@
     public void readInstance(final ServerContext context, final String resourceId,
             final ReadRequest request, final ResultHandler<Resource> handler) {
         final Context c = wrap(context);
-        // @Checkstyle:off
+        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 =
                 new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
                     @Override
@@ -260,7 +386,7 @@
 
                     @Override
                     public void handleResult(final SearchResultEntry entry) {
-                        final String revision = entryContainer.getEtagFromEntry(entry);
+                        final String revision = getEtagFromEntry(entry);
                         final ResultHandler<Map<String, Object>> mapHandler =
                                 new ResultHandler<Map<String, Object>>() {
                                     @Override
@@ -279,9 +405,25 @@
                         attributeMapper.toJSON(c, entry, mapHandler);
                     }
                 };
-        // @Checkstyle:on
-        final Collection<String> ldapAttributes = getLDAPAttributes(c, request.getFieldFilters());
-        entryContainer.readEntry(c, resourceId, ldapAttributes, searchHandler);
+
+        // The handler which will be invoked
+        final ConnectionCompletionHandler<SearchResultEntry> outerHandler =
+                new ConnectionCompletionHandler<SearchResultEntry>(searchHandler) {
+
+                    @Override
+                    public void handleResult(final Connection connection) {
+                        final RequestCompletionHandler<SearchResultEntry> innerHandler =
+                                new RequestCompletionHandler<SearchResultEntry>(connection,
+                                        searchHandler);
+                        final SearchRequest request =
+                                Requests.newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter
+                                        .equality(UUID_ATTRIBUTE, resourceId), tmp);
+                        connection.searchSingleEntryAsync(request, innerHandler);
+                    }
+
+                };
+
+        factory.getConnectionAsync(outerHandler);
     }
 
     /**
@@ -325,6 +467,28 @@
     }
 
     /**
+     * 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();
+    }
+
+    /**
      * Determines the set of LDAP attributes to request in an LDAP read (search,
      * post-read), based on the provided list of JSON pointers.
      *
@@ -369,16 +533,16 @@
                                                     final Filter f = i.next();
                                                     if (f == null) {
                                                         // Filter component did not match any attribute mappers.
-                                                        return c.getConfig().getFalseFilter();
-                                                    } else if (f == c.getConfig().getFalseFilter()) {
-                                                        return c.getConfig().getFalseFilter();
-                                                    } else if (f == c.getConfig().getTrueFilter()) {
+                                                        return c.getConfig().falseFilter();
+                                                    } else if (f == c.getConfig().falseFilter()) {
+                                                        return c.getConfig().falseFilter();
+                                                    } else if (f == c.getConfig().trueFilter()) {
                                                         i.remove();
                                                     }
                                                 }
                                                 switch (value.size()) {
                                                 case 0:
-                                                    return c.getConfig().getTrueFilter();
+                                                    return c.getConfig().trueFilter();
                                                 case 1:
                                                     return value.get(0);
                                                 default:
@@ -395,8 +559,8 @@
                     @Override
                     public Void visitBooleanLiteralFilter(final ResultHandler<Filter> p,
                             final boolean value) {
-                        p.handleResult(value ? c.getConfig().getTrueFilter() : c.getConfig()
-                                .getFalseFilter());
+                        p.handleResult(value ? c.getConfig().trueFilter() : c.getConfig()
+                                .falseFilter());
                         return null;
                     }
 
@@ -465,11 +629,11 @@
                             public Filter apply(final Filter value, final Void p) {
                                 if (value == null) {
                                     // Filter component did not match any attribute mappers.
-                                    return c.getConfig().getTrueFilter();
-                                } else if (value == c.getConfig().getFalseFilter()) {
-                                    return c.getConfig().getTrueFilter();
-                                } else if (value == c.getConfig().getTrueFilter()) {
-                                    return c.getConfig().getFalseFilter();
+                                    return c.getConfig().trueFilter();
+                                } else if (value == c.getConfig().falseFilter()) {
+                                    return c.getConfig().trueFilter();
+                                } else if (value == c.getConfig().trueFilter()) {
+                                    return c.getConfig().falseFilter();
                                 } else {
                                     return Filter.not(value);
                                 }
@@ -494,15 +658,15 @@
                                                     if (f == null) {
                                                         // Filter component did not match any attribute mappers.
                                                         i.remove();
-                                                    } else if (f == c.getConfig().getFalseFilter()) {
+                                                    } else if (f == c.getConfig().falseFilter()) {
                                                         i.remove();
-                                                    } else if (f == c.getConfig().getTrueFilter()) {
-                                                        return c.getConfig().getTrueFilter();
+                                                    } else if (f == c.getConfig().trueFilter()) {
+                                                        return c.getConfig().trueFilter();
                                                     }
                                                 }
                                                 switch (value.size()) {
                                                 case 0:
-                                                    return c.getConfig().getFalseFilter();
+                                                    return c.getConfig().falseFilter();
                                                 case 1:
                                                     return value.get(0);
                                                 default:
@@ -536,7 +700,16 @@
         queryFilter.accept(visitor, h);
     }
 
-    private Context wrap(ServerContext context) {
+    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/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 9a66c9c..7fa9bac 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
@@ -173,7 +173,7 @@
             break;
         case EXTENDED:
         default:
-            filter = c.getConfig().getFalseFilter(); // Not supported.
+            filter = c.getConfig().falseFilter(); // Not supported.
             break;
         }
         return filter;
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
index 0ec2721..720ea47 100644
--- 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
@@ -50,50 +50,52 @@
      */
     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);
+        final ConnectionFactory ldapFactory =
+                newAuthenticatedConnectionFactory(new LDAPConnectionFactory("localhost", 1389),
+                        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(
-                userContainer, userMapper);
+        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(
-                groupContainer, groupMapper);
+        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);
 
-        final org.forgerock.json.resource.ConnectionFactory resourceFactory = newInternalConnectionFactory(router);
+        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));
+            final ServletRegistration reg =
+                    ctx.addServlet("managed", new HttpServlet(resourceFactory));
             reg.addMapping("/managed/*");
             ctx.deploy(httpServer);
 

--
Gitblit v1.10.0