mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
17.50.2013 060013ad7b5e6d9f4ca1e2db098ad6d762109588
Fix OPENDJ-701: Implement paged results support

* added support for paged results in rest2ldap.

2 files modified
170 ■■■■■ changed files
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java 58 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java 112 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -60,8 +60,10 @@
import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.Filter;
@@ -75,6 +77,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.ModifyRequest;
@@ -92,6 +95,9 @@
    // Dummy exception used for signalling search success.
    private static final ResourceException SUCCESS = new UncategorizedException(0, null, null);
    // Empty decode options required for decoding response controls.
    private static final DecodeOptions DECODE_OPTIONS = new DecodeOptions();
    private final List<Attribute> additionalLDAPAttributes;
    private final AttributeMapper attributeMapper;
    private final DN baseDN; // TODO: support template variables.
@@ -342,11 +348,40 @@
                        } else {
                            // Perform the search.
                            final String[] attributes = getLDAPAttributes(c, request.getFields());
                            final SearchRequest request =
                            final SearchRequest searchRequest =
                                    newSearchRequest(getBaseDN(c), SearchScope.SINGLE_LEVEL,
                                            ldapFilter == Filter.alwaysTrue() ? Filter
                                                    .objectClassPresent() : ldapFilter, attributes);
                            c.getConnection().searchAsync(request, null, new SearchResultHandler() {
                            /*
                             * Add the page results control. We can support the
                             * page offset by reading the next offset pages, or
                             * offset x page size resources.
                             */
                            final int pageResultStartIndex;
                            final int pageSize = request.getPageSize();
                            if (request.getPageSize() > 0) {
                                final int pageResultEndIndex;
                                if (request.getPagedResultsOffset() > 0) {
                                    pageResultStartIndex = request.getPagedResultsOffset() * pageSize;
                                    pageResultEndIndex = pageResultStartIndex + pageSize;
                                } else {
                                    pageResultStartIndex = 0;
                                    pageResultEndIndex = pageSize;
                                }
                                final ByteString cookie =
                                        request.getPagedResultsCookie() != null ? ByteString
                                                .valueOfBase64(request.getPagedResultsCookie())
                                                : ByteString.empty();
                                final SimplePagedResultsControl control =
                                        SimplePagedResultsControl.newControl(true,
                                                pageResultEndIndex, cookie);
                                searchRequest.addControl(control);
                            } else {
                                pageResultStartIndex = 0;
                            }
                            c.getConnection().searchAsync(searchRequest, null, new SearchResultHandler() {
                                /*
                                 * The following fields are guarded by
                                 * sequenceLock. In addition, the sequenceLock
@@ -357,6 +392,8 @@
                                private int pendingResourceCount = 0;
                                private ResourceException pendingResult = null;
                                private boolean resultSent = false;
                                private int totalResourceCount = 0;
                                private String cookie = null;
                                @Override
                                public boolean handleEntry(final SearchResultEntry entry) {
@@ -371,6 +408,10 @@
                                        if (pendingResult != null) {
                                            return false;
                                        }
                                        if (totalResourceCount++ < pageResultStartIndex) {
                                            // Haven't reached paged results threshold yet.
                                            return true;
                                        }
                                        pendingResourceCount++;
                                    }
@@ -440,6 +481,17 @@
                                @Override
                                public void handleResult(final Result result) {
                                    synchronized (sequenceLock) {
                                        if (request.getPageSize() > 0) {
                                            try {
                                                final SimplePagedResultsControl control = result.getControl(
                                                    SimplePagedResultsControl.DECODER, DECODE_OPTIONS);
                                                if (control != null && !control.getCookie().isEmpty()) {
                                                    cookie = control.getCookie().toBase64String();
                                                }
                                            } catch (final DecodeException e) {
                                                // FIXME: need some logging.
                                            }
                                        }
                                        completeIfNecessary(SUCCESS);
                                    }
                                }
@@ -465,7 +517,7 @@
                                    if (pendingResourceCount == 0 && pendingResult != null
                                            && !resultSent) {
                                        if (pendingResult == SUCCESS) {
                                            h.handleResult(new QueryResult());
                                            h.handleResult(new QueryResult(cookie, -1));
                                        } else {
                                            h.handleError(pendingResult);
                                        }
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicRequestsTest.java
@@ -28,6 +28,7 @@
import static org.forgerock.json.resource.PatchOperation.replace;
import static org.forgerock.json.resource.Requests.newDeleteRequest;
import static org.forgerock.json.resource.Requests.newPatchRequest;
import static org.forgerock.json.resource.Requests.newQueryRequest;
import static org.forgerock.json.resource.Requests.newReadRequest;
import static org.forgerock.json.resource.Requests.newUpdateRequest;
import static org.forgerock.json.resource.Resources.newCollection;
@@ -42,6 +43,7 @@
import static org.forgerock.opendj.rest2ldap.TestUtils.ctx;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@@ -51,6 +53,9 @@
import org.forgerock.json.resource.NotFoundException;
import org.forgerock.json.resource.NotSupportedException;
import org.forgerock.json.resource.PreconditionFailedException;
import org.forgerock.json.resource.QueryFilter;
import org.forgerock.json.resource.QueryResult;
import org.forgerock.json.resource.Requests;
import org.forgerock.json.resource.Resource;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
@@ -88,6 +93,84 @@
    // FIXME: factor out test for re-use as common test suite (e.g. for InMemoryBackend).
    @Test
    public void testQueryAll() throws Exception {
        final Connection connection = newConnection();
        final List<Resource> resources = new LinkedList<Resource>();
        final QueryResult result =
                connection.query(ctx(), Requests.newQueryRequest("").setQueryFilter(
                        QueryFilter.alwaysTrue()), resources);
        assertThat(resources).hasSize(5);
        assertThat(result.getPagedResultsCookie()).isNull();
        assertThat(result.getRemainingPagedResults()).isEqualTo(-1);
    }
    @Test
    public void testQueryNone() throws Exception {
        final Connection connection = newConnection();
        final List<Resource> resources = new LinkedList<Resource>();
        final QueryResult result =
                connection.query(ctx(), Requests.newQueryRequest("").setQueryFilter(
                        QueryFilter.alwaysFalse()), resources);
        assertThat(resources).hasSize(0);
        assertThat(result.getPagedResultsCookie()).isNull();
        assertThat(result.getRemainingPagedResults()).isEqualTo(-1);
    }
    @Test
    public void testQueryPageResultsCookie() throws Exception {
        final Connection connection = newConnection();
        final List<Resource> resources = new ArrayList<Resource>();
        // Read first page.
        QueryResult result =
                connection.query(ctx(), newQueryRequest("")
                        .setQueryFilter(QueryFilter.alwaysTrue()).setPageSize(2), resources);
        assertThat(result.getPagedResultsCookie()).isNotNull();
        assertThat(resources).hasSize(2);
        assertThat(resources.get(0).getId()).isEqualTo("test1");
        assertThat(resources.get(1).getId()).isEqualTo("test2");
        String cookie = result.getPagedResultsCookie();
        resources.clear();
        // Read second page.
        result =
                connection.query(ctx(), newQueryRequest("")
                        .setQueryFilter(QueryFilter.alwaysTrue()).setPageSize(2)
                        .setPagedResultsCookie(cookie), resources);
        assertThat(result.getPagedResultsCookie()).isNotNull();
        assertThat(resources).hasSize(2);
        assertThat(resources.get(0).getId()).isEqualTo("test3");
        assertThat(resources.get(1).getId()).isEqualTo("test4");
        cookie = result.getPagedResultsCookie();
        resources.clear();
        // Read third page.
        result =
                connection.query(ctx(), newQueryRequest("")
                        .setQueryFilter(QueryFilter.alwaysTrue()).setPageSize(2)
                        .setPagedResultsCookie(cookie), resources);
        assertThat(result.getPagedResultsCookie()).isNull();
        assertThat(resources).hasSize(1);
        assertThat(resources.get(0).getId()).isEqualTo("test5");
    }
    @Test
    public void testQueryPageResultsIndexed() throws Exception {
        final Connection connection = newConnection();
        final List<Resource> resources = new ArrayList<Resource>();
        QueryResult result =
                connection.query(ctx(), newQueryRequest("")
                        .setQueryFilter(QueryFilter.alwaysTrue()).setPageSize(2)
                        .setPagedResultsOffset(1), resources);
        assertThat(result.getPagedResultsCookie()).isNotNull();
        assertThat(resources).hasSize(2);
        assertThat(resources.get(0).getId()).isEqualTo("test3");
        assertThat(resources.get(1).getId()).isEqualTo("test4");
    }
    @Test
    public void testDelete() throws Exception {
        final Connection connection = newConnection();
        final Resource resource = connection.delete(ctx(), newDeleteRequest("/test1"));
@@ -601,7 +684,34 @@
                        "userpassword: password",
                        "cn: test user 2",
                        "sn: user 2",
                        "etag: 67890"
                        "etag: 67890",
                        "",
                        "dn: uid=test3,dc=test",
                        "objectClass: top",
                        "objectClass: person",
                        "uid: test3",
                        "userpassword: password",
                        "cn: test user 3",
                        "sn: user 3",
                        "etag: 33333",
                        "",
                        "dn: uid=test4,dc=test",
                        "objectClass: top",
                        "objectClass: person",
                        "uid: test4",
                        "userpassword: password",
                        "cn: test user 4",
                        "sn: user 4",
                        "etag: 44444",
                        "",
                        "dn: uid=test5,dc=test",
                        "objectClass: top",
                        "objectClass: person",
                        "uid: test5",
                        "userpassword: password",
                        "cn: test user 5",
                        "sn: user 5",
                        "etag: 55555"
                ));
        // @formatter:on