From a95e7c21293142583e48517c9b30adbf2d4ace73 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Wed, 06 Mar 2013 11:57:52 +0000
Subject: [PATCH] Partial fix for OPENDJ-699: Implement DN reference mapping

---
 opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json                            |    3 
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java                      |   15 
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java |  494 +++++++++++++++++++++-----------------------
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java       |  127 ++++++++++
 4 files changed, 372 insertions(+), 267 deletions(-)

diff --git a/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json b/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
index 457b52b..57af90a 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
+++ b/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
@@ -46,6 +46,7 @@
                 } },
                 "manager"     : { "reference" : {
                     "ldapAttribute" : "manager",
+                    "baseDN"        : "ou=people,dc=example,dc=com",
                     "mapper"        : { "object" : {
                         "id"          : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true } },
                         "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
@@ -53,6 +54,7 @@
                 } },
                 "groups"     : { "reference" : {
                     "ldapAttribute" : "isMemberOf",
+                    "baseDN"        : "ou=groups,dc=example,dc=com",
                     "writability"   : "readOnly",
                     "mapper"        : { "object" : {
                         "id"          : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
@@ -92,6 +94,7 @@
                 "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "readOnly" } },
                 "members"    : { "reference" : {
                     "ldapAttribute" : "uniqueMember",
+                    "baseDN"        : "dc=example,dc=com",
                     "mapper"        : { "object" : {
                         "id"          : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true } },
                         "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
index 05e7764..d2aa3fb 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -73,81 +73,43 @@
 import org.forgerock.opendj.ldap.responses.Result;
 import org.forgerock.opendj.ldap.responses.SearchResultEntry;
 import org.forgerock.opendj.ldap.responses.SearchResultReference;
-import org.forgerock.opendj.ldif.ChangeRecord;
 
 /**
  * A {@code CollectionResourceProvider} implementation which maps a JSON
  * resource collection to LDAP entries beneath a base DN.
  */
 final class LDAPCollectionResourceProvider implements CollectionResourceProvider {
-    private <V> ResultHandler<V> wrap(final Context c, final ResultHandler<V> handler) {
-        return new ResultHandler<V>() {
-            @Override
-            public void handleError(ResourceException error) {
-                c.close();
-                handler.handleError(error);
-            }
-
-            @Override
-            public void handleResult(V result) {
-                c.close();
-                handler.handleResult(result);
-            }
-        };
-    }
-
-    private QueryResultHandler wrap(final Context c, final QueryResultHandler handler) {
-        return new QueryResultHandler() {
-            @Override
-            public void handleError(ResourceException error) {
-                c.close();
-                handler.handleError(error);
-            }
-
-            @Override
-            public boolean handleResource(Resource resource) {
-                return handler.handleResource(resource);
-            }
-
-            @Override
-            public void handleResult(QueryResult result) {
-                c.close();
-                handler.handleResult(result);
-            }
-        };
-    }
-
-    private abstract class ConnectionCompletionHandler<R> implements
+    private abstract class ConnectionCompletionHandler implements
             org.forgerock.opendj.ldap.ResultHandler<Connection> {
-        private final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler;
         private final Context c;
+        private final ResultHandler<?> handler;
 
-        ConnectionCompletionHandler(final Context c,
-                final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler) {
+        ConnectionCompletionHandler(final Context c, final ResultHandler<?> handler) {
             this.c = c;
-            this.resultHandler = resultHandler;
+            this.handler = handler;
         }
 
         @Override
         public final void handleErrorResult(final ErrorResultException error) {
-            resultHandler.handleErrorResult(error);
+            handler.handleError(adapt(error));
         }
 
         @Override
-        public final void handleResult(Connection connection) {
+        public final void handleResult(final Connection connection) {
             c.setConnection(connection);
             chain();
         }
 
         abstract void chain();
-
     }
 
     // Dummy exception used for signalling search success.
     private static final ResourceException SUCCESS = new UncategorizedException(0, null, null);
 
     private final List<Attribute> additionalLDAPAttributes;
+
     private final AttributeMapper attributeMapper;
+
     private final DN baseDN; // TODO: support template variables.
     private final Config config;
     private final ConnectionFactory factory;
@@ -185,33 +147,45 @@
         final Context c = wrap(context);
         final ResultHandler<Resource> h = wrap(c, handler);
 
-        attributeMapper.toLDAP(c, request.getContent(), new ResultHandler<List<Attribute>>() {
+        // Get the connection, then determine entry content, then perform add.
+        factory.getConnectionAsync(new ConnectionCompletionHandler(c, h) {
             @Override
-            public void handleError(final ResourceException error) {
-                h.handleError(error);
-            }
+            void chain() {
+                // Calculate entry content.
+                attributeMapper.toLDAP(c, request.getContent(),
+                        new ResultHandler<List<Attribute>>() {
+                            @Override
+                            public void handleError(final ResourceException error) {
+                                h.handleError(error);
+                            }
 
-            @Override
-            public void handleResult(final List<Attribute> result) {
-                final AddRequest addRequest = Requests.newAddRequest(DN.rootDN());
-                for (final Attribute attribute : additionalLDAPAttributes) {
-                    addRequest.addAttribute(attribute);
-                }
-                for (final Attribute attribute : result) {
-                    addRequest.addAttribute(attribute);
-                }
-                try {
-                    nameStrategy.setResourceId(c, getBaseDN(c), request.getNewResourceId(),
-                            addRequest);
-                } catch (ResourceException e) {
-                    h.handleError(e);
-                    return;
-                }
-                if (config.readOnUpdatePolicy() == CONTROLS) {
-                    final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
-                    addRequest.addControl(PostReadRequestControl.newControl(false, attributes));
-                }
-                applyUpdate(c, addRequest, h);
+                            @Override
+                            public void handleResult(final List<Attribute> result) {
+                                // Perform add operation.
+                                final AddRequest addRequest = Requests.newAddRequest(DN.rootDN());
+                                for (final Attribute attribute : additionalLDAPAttributes) {
+                                    addRequest.addAttribute(attribute);
+                                }
+                                for (final Attribute attribute : result) {
+                                    addRequest.addAttribute(attribute);
+                                }
+                                try {
+                                    nameStrategy.setResourceId(c, getBaseDN(c), request
+                                            .getNewResourceId(), addRequest);
+                                } catch (final ResourceException e) {
+                                    h.handleError(e);
+                                    return;
+                                }
+                                if (config.readOnUpdatePolicy() == CONTROLS) {
+                                    final String[] attributes =
+                                            getLDAPAttributes(c, request.getFieldFilters());
+                                    addRequest.addControl(PostReadRequestControl.newControl(false,
+                                            attributes));
+                                }
+                                c.getConnection().applyChangeAsync(addRequest, null,
+                                        postUpdateHandler(c, h));
+                            }
+                        });
             }
         });
     }
@@ -234,122 +208,120 @@
         final Context c = wrap(context);
         final QueryResultHandler h = wrap(c, handler);
 
-        // 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 =
-                    new AtomicReference<ResourceException>();
-            private final AtomicBoolean resultSent = new AtomicBoolean();
-
+        // Get the connection, then calculate the search filter, then perform the search.
+        factory.getConnectionAsync(new ConnectionCompletionHandler(c, h) {
             @Override
-            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;
-                }
-
-                final String id = nameStrategy.getResourceId(c, entry);
-                final String revision = mvccStrategy.getRevisionFromEntry(c, entry);
-                final ResultHandler<JsonValue> mapHandler = new ResultHandler<JsonValue>() {
+            void chain() {
+                // Calculate the filter (this may require the connection).
+                getLDAPFilter(c, request.getQueryFilter(), new ResultHandler<Filter>() {
                     @Override
-                    public void handleError(final ResourceException e) {
-                        pendingResult.compareAndSet(null, e);
-                        pendingResourceCount.decrementAndGet();
-                        completeIfNecessary();
+                    public void handleError(final ResourceException error) {
+                        h.handleError(error);
                     }
 
                     @Override
-                    public void handleResult(final JsonValue result) {
-                        final Resource resource = new Resource(id, revision, result);
-                        h.handleResource(resource);
-                        pendingResourceCount.decrementAndGet();
-                        completeIfNecessary();
-                    }
-                };
-
-                pendingResourceCount.incrementAndGet();
-                attributeMapper.toJSON(c, entry, mapHandler);
-                return true;
-            }
-
-            @Override
-            public void handleErrorResult(final ErrorResultException error) {
-                pendingResult.compareAndSet(null, adapt(error));
-                completeIfNecessary();
-            }
-
-            @Override
-            public boolean handleReference(final SearchResultReference reference) {
-                // TODO: should this be classed as an error since rest2ldap
-                // assumes entries are all colocated?
-                return true;
-            }
-
-            @Override
-            public void handleResult(final Result result) {
-                pendingResult.compareAndSet(null, SUCCESS);
-                completeIfNecessary();
-            }
-
-            /*
-             * 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) {
+                    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 == alwaysFalse()) {
                             h.handleResult(new QueryResult());
                         } else {
-                            h.handleError(result);
-                        }
-                    }
-                }
-            }
-        };
-
-        // 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) {
-                h.handleError(error);
-            }
-
-            @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 == alwaysFalse()) {
-                    h.handleResult(new QueryResult());
-                } else {
-                    final ConnectionCompletionHandler<Result> outerHandler =
-                            new ConnectionCompletionHandler<Result>(c, searchHandler) {
+                            // Perform the search.
+                            final String[] attributes =
+                                    getLDAPAttributes(c, request.getFieldFilters());
+                            final SearchRequest request =
+                                    Requests.newSearchRequest(getBaseDN(c),
+                                            SearchScope.SINGLE_LEVEL, ldapFilter == Filter
+                                                    .alwaysTrue() ? Filter.objectClassPresent()
+                                                    : ldapFilter, attributes);
+                            c.getConnection().searchAsync(request, null, new SearchResultHandler() {
+                                private final AtomicInteger pendingResourceCount =
+                                        new AtomicInteger();
+                                private final AtomicReference<ResourceException> pendingResult =
+                                        new AtomicReference<ResourceException>();
+                                private final AtomicBoolean resultSent = new AtomicBoolean();
 
                                 @Override
-                                void chain() {
-                                    final String[] attributes =
-                                            getLDAPAttributes(c, request.getFieldFilters());
-                                    final SearchRequest request =
-                                            Requests.newSearchRequest(getBaseDN(c),
-                                                    SearchScope.SINGLE_LEVEL, ldapFilter == Filter
-                                                            .alwaysTrue() ? Filter
-                                                            .objectClassPresent() : ldapFilter,
-                                                    attributes);
-                                    c.getConnection().searchAsync(request, null, searchHandler);
+                                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;
+                                    }
+
+                                    final String id = nameStrategy.getResourceId(c, entry);
+                                    final String revision =
+                                            mvccStrategy.getRevisionFromEntry(c, entry);
+                                    final ResultHandler<JsonValue> mapHandler =
+                                            new ResultHandler<JsonValue>() {
+                                                @Override
+                                                public void handleError(final ResourceException e) {
+                                                    pendingResult.compareAndSet(null, e);
+                                                    pendingResourceCount.decrementAndGet();
+                                                    completeIfNecessary();
+                                                }
+
+                                                @Override
+                                                public void handleResult(final JsonValue result) {
+                                                    final Resource resource =
+                                                            new Resource(id, revision, result);
+                                                    h.handleResource(resource);
+                                                    pendingResourceCount.decrementAndGet();
+                                                    completeIfNecessary();
+                                                }
+                                            };
+
+                                    pendingResourceCount.incrementAndGet();
+                                    attributeMapper.toJSON(c, entry, mapHandler);
+                                    return true;
                                 }
 
-                            };
+                                @Override
+                                public void handleErrorResult(final ErrorResultException error) {
+                                    pendingResult.compareAndSet(null, adapt(error));
+                                    completeIfNecessary();
+                                }
 
-                    factory.getConnectionAsync(outerHandler);
-                }
+                                @Override
+                                public boolean handleReference(final SearchResultReference reference) {
+                                    // TODO: should this be classed as an error since rest2ldap
+                                    // assumes entries are all colocated?
+                                    return true;
+                                }
+
+                                @Override
+                                public void handleResult(final Result result) {
+                                    pendingResult.compareAndSet(null, SUCCESS);
+                                    completeIfNecessary();
+                                }
+
+                                /*
+                                 * 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) {
+                                                h.handleResult(new QueryResult());
+                                            } else {
+                                                h.handleError(result);
+                                            }
+                                        }
+                                    }
+                                }
+                            });
+                        }
+                    }
+                });
             }
-        };
-
-        getLDAPFilter(c, request.getQueryFilter(), filterHandler);
+        });
     }
 
     @Override
@@ -358,36 +330,29 @@
         final Context c = wrap(context);
         final ResultHandler<Resource> h = wrap(c, handler);
 
-        // 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
-                    public void handleErrorResult(final ErrorResultException error) {
-                        h.handleError(adapt(error));
-                    }
+        // Get connection then perform the search.
+        factory.getConnectionAsync(new ConnectionCompletionHandler(c, h) {
+            @Override
+            void chain() {
+                // Do the search.
+                final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
+                final SearchRequest request =
+                        nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId).addAttribute(
+                                attributes);
+                c.getConnection().searchSingleEntryAsync(request,
+                        new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
+                            @Override
+                            public void handleErrorResult(final ErrorResultException error) {
+                                h.handleError(adapt(error));
+                            }
 
-                    @Override
-                    public void handleResult(final SearchResultEntry entry) {
-                        adaptEntry(c, entry, h);
-                    }
-
-                };
-
-        // The handler which will be invoked
-        final ConnectionCompletionHandler<SearchResultEntry> outerHandler =
-                new ConnectionCompletionHandler<SearchResultEntry>(c, searchHandler) {
-                    @Override
-                    void chain() {
-                        final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
-                        final SearchRequest request =
-                                nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId)
-                                        .addAttribute(attributes);
-                        c.getConnection().searchSingleEntryAsync(request, searchHandler);
-                    }
-
-                };
-
-        factory.getConnectionAsync(outerHandler);
+                            @Override
+                            public void handleResult(final SearchResultEntry entry) {
+                                adaptEntry(c, entry, h);
+                            }
+                        });
+            }
+        });
     }
 
     @Override
@@ -408,20 +373,6 @@
         }, handler));
     }
 
-    private void applyUpdate(final Context c, final ChangeRecord request,
-            final ResultHandler<Resource> handler) {
-        final org.forgerock.opendj.ldap.ResultHandler<Result> resultHandler =
-                postUpdateHandler(c, handler);
-        final ConnectionCompletionHandler<Result> outerHandler =
-                new ConnectionCompletionHandler<Result>(c, resultHandler) {
-                    @Override
-                    void chain() {
-                        c.getConnection().applyChangeAsync(request, null, resultHandler);
-                    }
-                };
-        factory.getConnectionAsync(outerHandler);
-    }
-
     private DN getBaseDN(final Context context) {
         return baseDN;
     }
@@ -636,48 +587,83 @@
     private org.forgerock.opendj.ldap.ResultHandler<Result> postUpdateHandler(final Context c,
             final ResultHandler<Resource> handler) {
         // The handler which will be invoked for the LDAP add result.
-        final org.forgerock.opendj.ldap.ResultHandler<Result> resultHandler =
-                new org.forgerock.opendj.ldap.ResultHandler<Result>() {
-                    @Override
-                    public void handleErrorResult(final ErrorResultException error) {
-                        handler.handleError(adapt(error));
-                    }
+        return new org.forgerock.opendj.ldap.ResultHandler<Result>() {
+            @Override
+            public void handleErrorResult(final ErrorResultException error) {
+                handler.handleError(adapt(error));
+            }
 
-                    @Override
-                    public void handleResult(final Result result) {
-                        // FIXME: handle USE_SEARCH policy.
-                        Entry entry;
-                        try {
-                            final PostReadResponseControl postReadControl =
-                                    result.getControl(PostReadResponseControl.DECODER, config
-                                            .decodeOptions());
-                            if (postReadControl != null) {
-                                entry = postReadControl.getEntry();
-                            } else {
-                                final PreReadResponseControl preReadControl =
-                                        result.getControl(PreReadResponseControl.DECODER, config
-                                                .decodeOptions());
-                                if (preReadControl != null) {
-                                    entry = preReadControl.getEntry();
-                                } else {
-                                    entry = null;
-                                }
-                            }
-                        } catch (final DecodeException e) {
-                            // FIXME: log something?
+            @Override
+            public void handleResult(final Result result) {
+                // FIXME: handle USE_SEARCH policy.
+                Entry entry;
+                try {
+                    final PostReadResponseControl postReadControl =
+                            result.getControl(PostReadResponseControl.DECODER, config
+                                    .decodeOptions());
+                    if (postReadControl != null) {
+                        entry = postReadControl.getEntry();
+                    } else {
+                        final PreReadResponseControl preReadControl =
+                                result.getControl(PreReadResponseControl.DECODER, config
+                                        .decodeOptions());
+                        if (preReadControl != null) {
+                            entry = preReadControl.getEntry();
+                        } else {
                             entry = null;
                         }
-                        if (entry != null) {
-                            adaptEntry(c, entry, handler);
-                        } else {
-                            final Resource resource =
-                                    new Resource(null, null, new JsonValue(Collections.emptyMap()));
-                            handler.handleResult(resource);
-                        }
                     }
+                } catch (final DecodeException e) {
+                    // FIXME: log something?
+                    entry = null;
+                }
+                if (entry != null) {
+                    adaptEntry(c, entry, handler);
+                } else {
+                    final Resource resource =
+                            new Resource(null, null, new JsonValue(Collections.emptyMap()));
+                    handler.handleResult(resource);
+                }
+            }
 
-                };
-        return resultHandler;
+        };
+    }
+
+    private QueryResultHandler wrap(final Context c, final QueryResultHandler handler) {
+        return new QueryResultHandler() {
+            @Override
+            public void handleError(final ResourceException error) {
+                c.close();
+                handler.handleError(error);
+            }
+
+            @Override
+            public boolean handleResource(final Resource resource) {
+                return handler.handleResource(resource);
+            }
+
+            @Override
+            public void handleResult(final QueryResult result) {
+                c.close();
+                handler.handleResult(result);
+            }
+        };
+    }
+
+    private <V> ResultHandler<V> wrap(final Context c, final ResultHandler<V> handler) {
+        return new ResultHandler<V>() {
+            @Override
+            public void handleError(final ResourceException error) {
+                c.close();
+                handler.handleError(error);
+            }
+
+            @Override
+            public void handleResult(final V result) {
+                c.close();
+                handler.handleResult(result);
+            }
+        };
     }
 
     private Context wrap(final ServerContext context) {
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
index 7b52566..132e76e 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
@@ -15,20 +15,23 @@
  */
 package org.forgerock.opendj.rest2ldap;
 
-import static org.forgerock.opendj.ldap.Filter.alwaysFalse;
+import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
 import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
 import static org.forgerock.opendj.rest2ldap.Utils.adapt;
+import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
 import static org.forgerock.opendj.rest2ldap.Utils.transform;
 import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
+import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.ResultHandler;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.AttributeDescription;
@@ -37,23 +40,38 @@
 import org.forgerock.opendj.ldap.ErrorResultException;
 import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.Function;
+import org.forgerock.opendj.ldap.ResultCode;
+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 attribute mapper which provides a mapping from a JSON value to a single DN
  * valued LDAP attribute.
  */
 public final class ReferenceAttributeMapper extends AttributeMapper {
+    /**
+     * The maximum number of candidate references to allow in search filters.
+     */
+    private static final int SEARCH_MAX_CANDIDATES = 1000;
 
+    private final DN baseDN;
+    private Filter filter = null;
     private boolean isRequired = false;
     private boolean isSingleValued = false;
     private final AttributeDescription ldapAttributeName;
     private final AttributeMapper mapper;
+    private SearchScope scope = SearchScope.WHOLE_SUBTREE;
     private WritabilityPolicy writabilityPolicy = READ_WRITE;
 
-    ReferenceAttributeMapper(final AttributeDescription ldapAttributeName,
+    ReferenceAttributeMapper(final AttributeDescription ldapAttributeName, final DN baseDN,
             final AttributeMapper mapper) {
         this.ldapAttributeName = ldapAttributeName;
+        this.baseDN = baseDN;
         this.mapper = mapper;
     }
 
@@ -80,6 +98,47 @@
     }
 
     /**
+     * Sets the filter which should be used when searching for referenced LDAP
+     * entries. The default is {@code (objectClass=*)}.
+     *
+     * @param filter
+     *            The filter which should be used when searching for referenced
+     *            LDAP entries.
+     * @return This attribute mapper.
+     */
+    public ReferenceAttributeMapper searchFilter(final Filter filter) {
+        this.filter = ensureNotNull(filter);
+        return this;
+    }
+
+    /**
+     * Sets the filter which should be used when searching for referenced LDAP
+     * entries. The default is {@code (objectClass=*)}.
+     *
+     * @param filter
+     *            The filter which should be used when searching for referenced
+     *            LDAP entries.
+     * @return This attribute mapper.
+     */
+    public ReferenceAttributeMapper searchFilter(final String filter) {
+        return searchFilter(Filter.valueOf(filter));
+    }
+
+    /**
+     * Sets the search scope which should be used when searching for referenced
+     * LDAP entries. The default is {@link SearchScope#WHOLE_SUBTREE}.
+     *
+     * @param scope
+     *            The search scope which should be used when searching for
+     *            referenced LDAP entries.
+     * @return This attribute mapper.
+     */
+    public ReferenceAttributeMapper searchScope(final SearchScope scope) {
+        this.scope = ensureNotNull(scope);
+        return this;
+    }
+
+    /**
      * Indicates whether or not the LDAP attribute supports updates. The default
      * is {@link WritabilityPolicy#READ_WRITE}.
      *
@@ -101,10 +160,62 @@
     @Override
     void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
             final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
-        // TODO: only presence and equality matching will be supported. Equality matching will
-        // only work for the primary key (whatever that is) by performing a reverse look up to
-        // convert the primary key to a DN.
-        h.handleResult(alwaysFalse());
+        // First construct a filter which can be used to find referenced resources.
+        final ResultHandler<Filter> filterHandler = new ResultHandler<Filter>() {
+            @Override
+            public void handleError(final ResourceException error) {
+                h.handleError(error); // Propagate.
+            }
+
+            @Override
+            public void handleResult(final Filter result) {
+                // Now construct a search to find candidate DNs.
+                final Filter searchFilter = filter != null ? Filter.and(filter, result) : result;
+                final SearchRequest request =
+                        Requests.newSearchRequest(baseDN, scope, searchFilter, "1.1");
+
+                // Create a result handler which will collect the returned entries and construct a search filter.
+                final SearchResultHandler searchHandler = new SearchResultHandler() {
+                    final List<Filter> subFilters = new LinkedList<Filter>();
+
+                    @Override
+                    public boolean handleEntry(final SearchResultEntry entry) {
+                        if (subFilters.size() < SEARCH_MAX_CANDIDATES) {
+                            subFilters.add(Filter.equality(ldapAttributeName.toString(), entry
+                                    .getName()));
+                            return true;
+                        } else {
+                            // No point in continuing - maximum candidates reached.
+                            return false;
+                        }
+                    }
+
+                    @Override
+                    public void handleErrorResult(final ErrorResultException error) {
+                        h.handleError(adapt(error)); // Propagate.
+                    }
+
+                    @Override
+                    public boolean handleReference(final SearchResultReference reference) {
+                        // Ignore references.
+                        return true;
+                    }
+
+                    @Override
+                    public void handleResult(final Result result) {
+                        if (subFilters.size() >= SEARCH_MAX_CANDIDATES) {
+                            handleErrorResult(newErrorResult(ResultCode.ADMIN_LIMIT_EXCEEDED));
+                        } else if (subFilters.size() == 1) {
+                            h.handleResult(subFilters.get(0));
+                        } else {
+                            h.handleResult(Filter.or(subFilters));
+                        }
+                    }
+                };
+                c.getConnection().searchAsync(request, null, searchHandler);
+            }
+        };
+        mapper.getLDAPFilter(c, type, jsonAttribute, operator, valueAssertion, filterHandler);
     }
 
     @Override
@@ -116,7 +227,7 @@
             try {
                 final DN dn = attribute.parse().usingSchema(c.getConfig().schema()).asDN();
                 readEntry(c, dn, h);
-            } catch (Exception ex) {
+            } catch (final Exception ex) {
                 // The LDAP attribute could not be decoded.
                 h.handleError(adapt(ex));
             }
@@ -146,7 +257,7 @@
                 for (final DN dn : dns) {
                     readEntry(c, dn, handler);
                 }
-            } catch (Exception ex) {
+            } catch (final Exception ex) {
                 // The LDAP attribute could not be decoded.
                 h.handleError(adapt(ex));
             }
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 6a9d558..9964e7f 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -184,6 +184,7 @@
          *         },
          *         "manager"     : { "reference" : {
          *             "ldapAttribute" : "manager",
+         *             "baseDN"        : "ou=people,dc=example,dc=com",
          *             "mapper"        : { "object" : {
          *                 "id"          : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true } },
          *                 "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
@@ -367,14 +368,18 @@
                 final JsonValue config = mapper.get("reference");
                 final AttributeDescription ldapAttribute =
                         ad(config.get("ldapAttribute").required().asString());
+                final DN baseDN = DN.valueOf(config.get("baseDN").required().asString(), schema);
                 final AttributeMapper m = configureMapper(config.get("mapper").required());
-                final ReferenceAttributeMapper r = reference(ldapAttribute, m);
+                final ReferenceAttributeMapper r = reference(ldapAttribute, baseDN, m);
                 if (config.get("isRequired").defaultTo(false).asBoolean()) {
                     r.isRequired();
                 }
                 if (config.get("isSingleValued").defaultTo(false).asBoolean()) {
                     r.isSingleValued();
                 }
+                if (config.isDefined("searchFilter")) {
+                    r.searchFilter(config.get("searchFilter").asString());
+                }
                 r.writability(parseWritability(mapper, config));
                 return r;
             } else if (mapper.isDefined("object")) {
@@ -644,13 +649,13 @@
     }
 
     public static ReferenceAttributeMapper reference(final AttributeDescription attribute,
-            final AttributeMapper mapper) {
-        return new ReferenceAttributeMapper(attribute, mapper);
+            final DN baseDN, final AttributeMapper mapper) {
+        return new ReferenceAttributeMapper(attribute, baseDN, mapper);
     }
 
-    public static ReferenceAttributeMapper reference(final String attribute,
+    public static ReferenceAttributeMapper reference(final String attribute, final String baseDN,
             final AttributeMapper mapper) {
-        return reference(AttributeDescription.valueOf(attribute), mapper);
+        return reference(AttributeDescription.valueOf(attribute), DN.valueOf(baseDN), mapper);
     }
 
     public static SimpleAttributeMapper simple(final AttributeDescription attribute) {

--
Gitblit v1.10.0