From 4b00c9c4997b772b184f6f5bc3b3554e57561ccb Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 17 Oct 2013 12:49:49 +0000
Subject: [PATCH] Preliminary work for OPENDJ-701: Implement paged results support

---
 opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java |   57 +++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 47 insertions(+), 10 deletions(-)

diff --git a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java
index 812fdfc..cbc1c89 100644
--- a/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java
+++ b/opendj3/opendj-core/src/main/java/org/forgerock/opendj/ldap/MemoryBackend.java
@@ -43,6 +43,7 @@
 import org.forgerock.opendj.ldap.controls.PostReadResponseControl;
 import org.forgerock.opendj.ldap.controls.PreReadRequestControl;
 import org.forgerock.opendj.ldap.controls.PreReadResponseControl;
+import org.forgerock.opendj.ldap.controls.SimplePagedResultsControl;
 import org.forgerock.opendj.ldap.controls.SubtreeDeleteRequestControl;
 import org.forgerock.opendj.ldap.requests.AddRequest;
 import org.forgerock.opendj.ldap.requests.BindRequest;
@@ -396,15 +397,18 @@
                 if (matcher.matches(baseEntry).toBoolean()) {
                     sendEntry(attributeFilter, resultHandler, baseEntry);
                 }
+                resultHandler.handleResult(newResult(ResultCode.SUCCESS));
             } else if (scope.equals(SearchScope.SINGLE_LEVEL) || scope.equals(SearchScope.WHOLE_SUBTREE)) {
-                searchWithSubordinates(
-                        requestContext, resultHandler, dn, matcher,
-                        attributeFilter, request.getSizeLimit(), scope);
+                searchWithSubordinates(requestContext, resultHandler, dn, matcher, attributeFilter,
+                        request.getSizeLimit(), scope, request.getControl(
+                                SimplePagedResultsControl.DECODER, new DecodeOptions()));
             } else {
                 throw newErrorResult(ResultCode.PROTOCOL_ERROR,
                         "Search request contains an unsupported search scope");
             }
-            resultHandler.handleResult(newResult(ResultCode.SUCCESS));
+        } catch (DecodeException e) {
+            resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
+                    ResultCode.PROTOCOL_ERROR, e.getMessage(), e));
         } catch (final ErrorResultException e) {
             resultHandler.handleErrorResult(e);
         }
@@ -478,33 +482,66 @@
      * @param attributeFilter to select attributes to return in search results
      * @param sizeLimit maximum number of entries to return. A value of zero indicates no restriction
      *          on number of entries.
+     * @param pagedResults The simple paged results control, if present.
      * @throws CancelledResultException
      *           If a cancellation request has been received and processing of
      *           the request should be aborted if possible.
      * @throws ErrorResultException
      *           If the request is unsuccessful.
      */
-    private void searchWithSubordinates(final RequestContext requestContext, final SearchResultHandler resultHandler,
-            final DN dn, final Matcher matcher, final AttributeFilter attributeFilter, final int sizeLimit,
-            SearchScope scope) throws CancelledResultException, ErrorResultException {
-
+    private void searchWithSubordinates(final RequestContext requestContext,
+            final SearchResultHandler resultHandler, final DN dn, final Matcher matcher,
+            final AttributeFilter attributeFilter, final int sizeLimit, SearchScope scope,
+            SimplePagedResultsControl pagedResults) throws CancelledResultException,
+            ErrorResultException {
+        final int pageSize = pagedResults != null ? pagedResults.getSize() : 0;
+        final int offset = (pagedResults != null && !pagedResults.getCookie().isEmpty())
+                ? Integer.valueOf(pagedResults.getCookie().toString()) : 0;
         final Map<DN, Entry> subtree = entries.subMap(dn, dn.child(RDN.maxValue()));
         int numberOfResults = 0;
+        int position = 0;
         for (final Entry entry : subtree.values()) {
             requestContext.checkIfCancelled(false);
             if (scope.equals(SearchScope.WHOLE_SUBTREE) || entry.getName().isChildOf(dn)) {
                 if (matcher.matches(entry).toBoolean()) {
+                    /*
+                     * This entry is going to be returned to the client so it
+                     * counts towards the size limit and any paging criteria.
+                     */
+
+                    // Check size limit.
                     if (sizeLimit > 0 && numberOfResults >= sizeLimit) {
                         throw newErrorResult(newResult(ResultCode.SIZE_LIMIT_EXCEEDED));
                     }
+
+                    // Ignore this entry if we haven't reached the first page yet.
+                    if (pageSize > 0 && position++ < offset) {
+                        continue;
+                    }
+
+                    // Send the entry back to the client.
+                    if (!sendEntry(attributeFilter, resultHandler, entry)) {
+                        // Client has disconnected or cancelled.
+                        break;
+                    }
+
                     numberOfResults++;
-                    boolean acceptMoreResults = sendEntry(attributeFilter, resultHandler, entry);
-                    if (!acceptMoreResults) {
+
+                    // Stop if we've reached the end of the page.
+                    if (pageSize > 0 && numberOfResults == pageSize) {
                         break;
                     }
                 }
             }
         }
+        final Result result = newResult(ResultCode.SUCCESS);
+        if (pageSize > 0) {
+            final ByteString cookie =
+                    numberOfResults == pageSize ? ByteString.valueOf(String.valueOf(position))
+                            : ByteString.empty();
+            result.addControl(SimplePagedResultsControl.newControl(true, 0, cookie));
+        }
+        resultHandler.handleResult(result);
     }
 
     private <R extends Result> R addResultControls(final Request request, final Entry before,

--
Gitblit v1.10.0