From a37627c2a86f954cec0b359ad483ff42a0a33d9b Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Fri, 27 Sep 2013 14:28:00 +0000
Subject: [PATCH] Fix OPENDJ-972 - OpenDJ should set size limit to 1 when performing single entry searches Review CR-2378
---
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/com/forgerock/opendj/ldap/core_fr.properties | 4
opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/SdkTestCase.java | 13 +
opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/AbstractAsynchronousConnectionTestCase.java | 162 ++++++++++++++++-
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/SearchRequestImpl.java | 7
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java | 154 +++++++++++------
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/com/forgerock/opendj/ldap/core.properties | 2
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/Requests.java | 56 ++++++
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableSearchRequestImpl.java | 7
opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/requests/SearchRequestTestCase.java | 82 +++++++++
opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/SearchRequest.java | 17 +
10 files changed, 429 insertions(+), 75 deletions(-)
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java
index e4a5349..1ae4d82 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractConnection.java
@@ -29,7 +29,9 @@
import static com.forgerock.opendj.ldap.CoreMessages.ERR_NO_SEARCH_RESULT_ENTRIES;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES;
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES_NO_COUNT;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_UNEXPECTED_SEARCH_RESULT_REFERENCES;
+
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import java.util.Collection;
@@ -67,11 +69,7 @@
SearchResultHandler {
private final ResultHandler<? super SearchResultEntry> handler;
- private volatile SearchResultEntry firstEntry = null;
-
- private volatile SearchResultReference firstReference = null;
-
- private volatile int entryCount = 0;
+ private final SingleEntryHandler singleEntryHandler = new SingleEntryHandler();
private volatile FutureResult<Result> future = null;
@@ -86,14 +84,22 @@
@Override
public SearchResultEntry get() throws ErrorResultException, InterruptedException {
- future.get();
+ try {
+ future.get();
+ } catch (ErrorResultException e) {
+ throw singleEntryHandler.filterError(e);
+ }
return get0();
}
@Override
- public SearchResultEntry get(final long timeout, final TimeUnit unit)
- throws ErrorResultException, TimeoutException, InterruptedException {
- future.get(timeout, unit);
+ public SearchResultEntry get(final long timeout, final TimeUnit unit) throws ErrorResultException,
+ TimeoutException, InterruptedException {
+ try {
+ future.get(timeout, unit);
+ } catch (ErrorResultException e) {
+ throw singleEntryHandler.filterError(e);
+ }
return get0();
}
@@ -104,26 +110,20 @@
@Override
public boolean handleEntry(final SearchResultEntry entry) {
- if (firstEntry == null) {
- firstEntry = entry;
- }
- entryCount++;
- return true;
+ return singleEntryHandler.handleEntry(entry);
}
@Override
public void handleErrorResult(final ErrorResultException error) {
if (handler != null) {
- handler.handleErrorResult(error);
+ ErrorResultException finalError = singleEntryHandler.filterError(error);
+ handler.handleErrorResult(finalError);
}
}
@Override
public boolean handleReference(final SearchResultReference reference) {
- if (firstReference == null) {
- firstReference = reference;
- }
- return true;
+ return singleEntryHandler.handleReference(reference);
}
@Override
@@ -148,21 +148,11 @@
}
private SearchResultEntry get0() throws ErrorResultException {
- if (entryCount == 0) {
- // Did not find any entries.
- throw newErrorResult(ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED,
- ERR_NO_SEARCH_RESULT_ENTRIES.get().toString());
- } else if (entryCount > 1) {
- // Got more entries than expected.
- throw newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
- ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES.get(entryCount).toString());
- } else if (firstReference != null) {
- // Got an unexpected search result reference.
- throw newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
- ERR_UNEXPECTED_SEARCH_RESULT_REFERENCES.get(
- firstReference.getURIs().iterator().next()).toString());
+ ErrorResultException exception = singleEntryHandler.checkForClientSideError();
+ if (exception == null) {
+ return singleEntryHandler.firstEntry;
} else {
- return firstEntry;
+ throw exception;
}
}
@@ -192,7 +182,7 @@
*/
@Override
public void handleErrorResult(final ErrorResultException error) {
- // Ignore.
+ // Ignore
}
@Override
@@ -211,6 +201,50 @@
// Ignore.
}
+ /**
+ * Filter the provided error in order to transform size limit exceeded error to a client side error,
+ * or leave it as is for any other error.
+ *
+ * @param error to filter
+ * @return provided error in most case, or <code>ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED</code>
+ * error if provided error is <code>ResultCode.SIZE_LIMIT_EXCEEDED</code>
+ */
+ public ErrorResultException filterError(final ErrorResultException error) {
+ if (error.getResult().getResultCode().equals(ResultCode.SIZE_LIMIT_EXCEEDED)) {
+ return newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
+ ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES_NO_COUNT.get().toString());
+ } else {
+ return error;
+ }
+ }
+
+ /**
+ * Check for any error related to number of search result at client-side level: no result,
+ * too many result, search result reference.
+ *
+ * This method should be called only after search operation is finished.
+ *
+ * @return an <code>ErrorResultException</code> if an error is detected, <code>null</code> otherwise
+ */
+ public ErrorResultException checkForClientSideError() {
+ ErrorResultException exception = null;
+ if (entryCount == 0) {
+ // Did not find any entries.
+ exception = newErrorResult(ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED, ERR_NO_SEARCH_RESULT_ENTRIES
+ .get().toString());
+ } else if (entryCount > 1) {
+ // Got more entries than expected.
+ exception = newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
+ ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES.get(entryCount).toString());
+ } else if (firstReference != null) {
+ // Got an unexpected search result reference.
+ exception = newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
+ ERR_UNEXPECTED_SEARCH_RESULT_REFERENCES.get(firstReference.getURIs().iterator().next())
+ .toString());
+ }
+ return exception;
+ }
+
}
// Visitor used for processing synchronous change requests.
@@ -410,7 +444,7 @@
public SearchResultEntry readEntry(final DN baseObject, final String... attributeDescriptions)
throws ErrorResultException {
final SearchRequest request =
- Requests.newSearchRequest(baseObject, SearchScope.BASE_OBJECT, Filter
+ Requests.newSingleEntrySearchRequest(baseObject, SearchScope.BASE_OBJECT, Filter
.objectClassPresent(), attributeDescriptions);
return searchSingleEntry(request);
}
@@ -432,8 +466,9 @@
final Collection<String> attributeDescriptions,
final ResultHandler<? super SearchResultEntry> handler) {
final SearchRequest request =
- Requests.newSearchRequest(name, SearchScope.BASE_OBJECT, Filter
- .objectClassPresent());
+ Requests.newSingleEntrySearchRequest(
+ name, SearchScope.BASE_OBJECT,
+ Filter.objectClassPresent());
if (attributeDescriptions != null) {
request.getAttributes().addAll(attributeDescriptions);
}
@@ -521,23 +556,16 @@
public SearchResultEntry searchSingleEntry(final SearchRequest request)
throws ErrorResultException {
final SingleEntryHandler handler = new SingleEntryHandler();
- search(request, handler);
-
- if (handler.entryCount == 0) {
- // Did not find any entries.
- throw newErrorResult(ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED,
- ERR_NO_SEARCH_RESULT_ENTRIES.get().toString());
- } else if (handler.entryCount > 1) {
- // Got more entries than expected.
- throw newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
- ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES.get(handler.entryCount).toString());
- } else if (handler.firstReference != null) {
- // Got an unexpected search result reference.
- throw newErrorResult(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED,
- ERR_UNEXPECTED_SEARCH_RESULT_REFERENCES.get(
- handler.firstReference.getURIs().iterator().next()).toString());
- } else {
+ try {
+ search(enforceSingleEntrySearchRequest(request), handler);
+ } catch (ErrorResultException e) {
+ throw handler.filterError(e);
+ }
+ ErrorResultException error = handler.checkForClientSideError();
+ if (error == null) {
return handler.firstEntry;
+ } else {
+ throw error;
}
}
@@ -548,7 +576,7 @@
public SearchResultEntry searchSingleEntry(final String baseObject, final SearchScope scope,
final String filter, final String... attributeDescriptions) throws ErrorResultException {
final SearchRequest request =
- Requests.newSearchRequest(baseObject, scope, filter, attributeDescriptions);
+ Requests.newSingleEntrySearchRequest(baseObject, scope, filter, attributeDescriptions);
return searchSingleEntry(request);
}
@@ -559,12 +587,28 @@
public FutureResult<SearchResultEntry> searchSingleEntryAsync(final SearchRequest request,
final ResultHandler<? super SearchResultEntry> handler) {
final SingleEntryFuture innerFuture = new SingleEntryFuture(handler);
- final FutureResult<Result> future = searchAsync(request, null, innerFuture);
+ final FutureResult<Result> future =
+ searchAsync(enforceSingleEntrySearchRequest(request), null, innerFuture);
innerFuture.setResultFuture(future);
return innerFuture;
}
/**
+ * Ensure that a single entry search request is returned, based on provided request.
+ *
+ * @param request
+ * to be checked
+ * @return a single entry search request, equal to or based on the provided request
+ */
+ private SearchRequest enforceSingleEntrySearchRequest(final SearchRequest request) {
+ if (request.isSingleEntrySearch()) {
+ return request;
+ } else {
+ return Requests.copyOfSearchRequest(request).setSizeLimit(1);
+ }
+ }
+
+ /**
* {@inheritDoc}
* <p>
* Sub-classes should provide an implementation which returns an appropriate
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/Requests.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/Requests.java
index 3381a35..fadf1f3 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/Requests.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/Requests.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2011-2012 ForgeRock AS
+ * Portions copyright 2011-2013 ForgeRock AS
*/
package org.forgerock.opendj.ldap.requests;
@@ -1017,7 +1017,7 @@
/**
* Creates a new search request using the provided distinguished name,
- * scope, and filter, decoded using the default schema.
+ * scope, and filter.
*
* @param name
* The distinguished name of the base entry relative to which the
@@ -1079,6 +1079,58 @@
}
/**
+ * Creates a new search request for a single entry, using the provided distinguished name,
+ * scope, and filter.
+ *
+ * @param name
+ * The distinguished name of the base entry relative to which the
+ * search is to be performed.
+ * @param scope
+ * The scope of the search.
+ * @param filter
+ * The filter that defines the conditions that must be fulfilled
+ * in order for an entry to be returned.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each entry.
+ * @return The new search request.
+ * @throws NullPointerException
+ * If the {@code name}, {@code scope}, or {@code filter} were
+ * {@code null}.
+ */
+ public static SearchRequest newSingleEntrySearchRequest(final DN name, final SearchScope scope,
+ final Filter filter, final String... attributeDescriptions) {
+ return newSearchRequest(name, scope, filter, attributeDescriptions).setSizeLimit(1);
+ }
+
+ /**
+ * Creates a new search request for a single entry, using the provided distinguished name,
+ * scope, and filter, decoded using the default schema.
+ *
+ * @param name
+ * The distinguished name of the base entry relative to which the
+ * search is to be performed.
+ * @param scope
+ * The scope of the search.
+ * @param filter
+ * The filter that defines the conditions that must be fulfilled
+ * in order for an entry to be returned.
+ * @param attributeDescriptions
+ * The names of the attributes to be included with each entry.
+ * @return The new search request.
+ * @throws LocalizedIllegalArgumentException
+ * If {@code name} could not be decoded using the default
+ * schema, or if {@code filter} is not a valid LDAP string
+ * representation of a filter.
+ * @throws NullPointerException
+ * If the {@code name}, {@code scope}, or {@code filter} were
+ * {@code null}.
+ */
+ public static SearchRequest newSingleEntrySearchRequest(final String name, final SearchScope scope,
+ final String filter, final String... attributeDescriptions) {
+ return newSearchRequest(name, scope, filter, attributeDescriptions).setSizeLimit(1);
+ }
+
+ /**
* Creates a new simple bind request having an empty name and password
* suitable for anonymous authentication.
*
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/SearchRequest.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/SearchRequest.java
index ce830bf..9de33bd 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/SearchRequest.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/SearchRequest.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2009-2010 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.requests;
@@ -147,6 +147,19 @@
int getSizeLimit();
/**
+ * Indicates whether search result is expected to be limited to a single entry.
+ * <p>
+ * It is the case if size limit is equal to 1 or if scope is equal to <code>SearchScope.BASE_OBJECT</code>.
+ * <p>
+ * If search results contain more than one entry, the search operation will throw
+ * a <code>MultipleEntriesFoundException</code>.
+ *
+ * @return {@code true} if the search is limited to a single entry result,
+ * or {@code false} (the default) otherwise.
+ */
+ boolean isSingleEntrySearch();
+
+ /**
* Returns the time limit that should be used in order to restrict the
* maximum time (in seconds) allowed for the search.
* <p>
@@ -270,6 +283,8 @@
* A value of zero (the default) in this field indicates that no
* client-requested size limit restrictions are in effect. Servers may also
* enforce a maximum number of entries to return.
+ * <p>
+ * This method overrides the size limit set using a previous call to {@link #setSingleEntrySearch()}.
*
* @param limit
* The size limit that should be used in order to restrict the
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/SearchRequestImpl.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/SearchRequestImpl.java
index 7df07e6..4b00351 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/SearchRequestImpl.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/SearchRequestImpl.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.requests;
@@ -107,6 +107,11 @@
}
@Override
+ public boolean isSingleEntrySearch() {
+ return sizeLimit == 1 || SearchScope.BASE_OBJECT.equals(scope);
+ }
+
+ @Override
public int getTimeLimit() {
return timeLimit;
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableSearchRequestImpl.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableSearchRequestImpl.java
index 6388047..fd3bd76 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableSearchRequestImpl.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableSearchRequestImpl.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap.requests;
@@ -80,6 +80,11 @@
}
@Override
+ public boolean isSingleEntrySearch() {
+ return impl.isSingleEntrySearch();
+ }
+
+ @Override
public int getTimeLimit() {
return impl.getTimeLimit();
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/com/forgerock/opendj/ldap/core.properties b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/com/forgerock/opendj/ldap/core.properties
index 1057f00..3103d70 100755
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/com/forgerock/opendj/ldap/core.properties
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/com/forgerock/opendj/ldap/core.properties
@@ -844,6 +844,8 @@
but did not return any search result entries when one was expected
ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES=The search request succeeded \
but returned %d search result entry when only one was expected
+ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES_NO_COUNT=The search request succeeded \
+ but returned more than one search result entry when only one was expected
ERR_UNEXPECTED_SEARCH_RESULT_REFERENCES=The search request succeeded \
but returned a search result reference containing the following URI: %s
#
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/com/forgerock/opendj/ldap/core_fr.properties b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/com/forgerock/opendj/ldap/core_fr.properties
index aa9bf60..2c6f2ce 100755
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/com/forgerock/opendj/ldap/core_fr.properties
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/main/resources/com/forgerock/opendj/ldap/core_fr.properties
@@ -20,6 +20,7 @@
# CDDL HEADER END
#
# Copyright 2009 Sun Microsystems, Inc.
+# Portions copyright 2013 ForgeRock AS
#
ERR_ATTR_SYNTAX_COUNTRY_STRING_INVALID_LENGTH_9=La valeur indiqu\u00e9e "%s" n'est pas une cha\u00eene de pays valide car elle n'a pas une longueur de deux caract\u00e8res exactement
ERR_ATTR_SYNTAX_DELIVERY_METHOD_NO_ELEMENTS_11=La valeur indiqu\u00e9e "%s" n'est pas une m\u00e9thode de distribution valide car elle ne contient aucun \u00e9l\u00e9ment
@@ -94,3 +95,6 @@
ERR_ATTR_SYNTAX_DCR_PROHIBITED_REQUIRED_BY_STRUCTURAL_271=La r\u00e8gle de contenu DIT "%s" n'est pas valide car elle interdit l'utilisation du type d'attribut %s requis par la classe d'objet structurelle associ\u00e9e %s
ERR_ATTR_SYNTAX_DCR_PROHIBITED_REQUIRED_BY_AUXILIARY_272=La r\u00e8gle de contenu DIT "%s" n'est pas valide car elle interdit l'utilisation du type d'attribut %s requis par la classe d'objet structurelle auxiliaire %s
ERR_ATTR_SYNTAX_DN_INVALID_REQUIRES_ESCAPE_CHAR_282=Impossible d'analyser la valeur fournie "%s" en tant que nom valide distinctif car la valeur de l'attribut commence avec un caract\u00e8re en position %d qui doit \u00eatre \u00e9vit\u00e9
+ERR_NO_SEARCH_RESULT_ENTRIES=La recherche a \u00e9t\u00e9 effectu\u00e9e avec succ\u00e8s mais n'a retourn\u00e9 aucune entr\u00e9e alors qu'une entr\u00e9e \u00e9tait attendue
+ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES=La recherche a \u00e9t\u00e9 effectu\u00e9e avec succ\u00e8s mais a retourn\u00e9 %s entr\u00e9es alors qu'une seule entr\u00e9e \u00e9tait attendue
+ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES_NO_COUNT=La recherche a \u00e9t\u00e9 effectu\u00e9e avec succ\u00e8s mais a retourn\u00e9 plusieurs entr\u00e9es alors qu'une seule entr\u00e9e \u00e9tait attendue
\ No newline at end of file
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/AbstractAsynchronousConnectionTestCase.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/AbstractAsynchronousConnectionTestCase.java
index 6b3fc0f..6966aa4 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/AbstractAsynchronousConnectionTestCase.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/AbstractAsynchronousConnectionTestCase.java
@@ -22,7 +22,7 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2011 ForgeRock AS.
+ * Portions copyright 2011-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap;
@@ -30,6 +30,7 @@
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.Fail.fail;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
+import static org.mockito.Mockito.*;
import java.util.LinkedList;
import java.util.List;
@@ -65,15 +66,11 @@
private final class MockConnection extends AbstractAsynchronousConnection {
private final ResultCode resultCode;
- private final SearchResultEntry entry;
+ private final SearchResultEntry[] entries;
- private MockConnection(ResultCode resultCode) {
- this(resultCode, null);
- }
-
- private MockConnection(ResultCode resultCode, SearchResultEntry entry) {
+ private MockConnection(ResultCode resultCode, SearchResultEntry...entries) {
this.resultCode = resultCode;
- this.entry = entry;
+ this.entries = entries;
}
/**
@@ -222,14 +219,17 @@
public FutureResult<Result> searchAsync(SearchRequest request,
IntermediateResponseHandler intermediateResponseHandler,
SearchResultHandler resultHandler) {
- if (entry != null) {
+ for (SearchResultEntry entry : entries) {
resultHandler.handleEntry(entry);
}
-
- if (!resultCode.isExceptional()) {
- return new CompletedFutureResult<Result>(Responses.newResult(resultCode));
+ if (resultCode.isExceptional()) {
+ ErrorResultException errorResult = newErrorResult(resultCode);
+ resultHandler.handleErrorResult(errorResult);
+ return new CompletedFutureResult<Result>(errorResult);
} else {
- return new CompletedFutureResult<Result>(newErrorResult(resultCode));
+ Result result = Responses.newResult(resultCode);
+ resultHandler.handleResult(result);
+ return new CompletedFutureResult<Result>(result);
}
}
@@ -401,10 +401,144 @@
List<SearchResultEntry> entries = new LinkedList<SearchResultEntry>();
try {
mockConnection.search(searchRequest, entries);
- fail();
+ failWasExpected(ErrorResultException.class);
} catch (ErrorResultException e) {
assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.UNWILLING_TO_PERFORM);
assertThat(entries.isEmpty());
}
}
+
+ @Test()
+ public void testSingleEntrySearchRequestSuccess() throws Exception {
+ final SearchResultEntry entry = Responses.newSearchResultEntry("cn=test");
+ final Connection mockConnection = new MockConnection(ResultCode.SUCCESS, entry);
+ final SearchRequest request =
+ Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
+ assertThat(mockConnection.searchSingleEntry(request)).isEqualTo(entry);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test()
+ public void testSingleEntrySearchAsyncRequestSuccess() throws Exception {
+ final SearchResultEntry entry = Responses.newSearchResultEntry("cn=test");
+ final Connection mockConnection = new MockConnection(ResultCode.SUCCESS, entry);
+ final SearchRequest request =
+ Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
+ ResultHandler<SearchResultEntry> handler = mock(ResultHandler.class);
+
+ FutureResult<SearchResultEntry> futureResult = mockConnection.searchSingleEntryAsync(request, handler);
+
+ assertThat(futureResult.get()).isEqualTo(entry);
+ verify(handler).handleResult(any(SearchResultEntry.class));
+ }
+
+ @Test()
+ public void testSingleEntrySearchRequestNoEntryReturned() throws Exception {
+ final Connection mockConnection = new MockConnection(ResultCode.SUCCESS);
+ final SearchRequest request =
+ Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
+ try {
+ mockConnection.searchSingleEntry(request);
+ failWasExpected(EntryNotFoundException.class);
+ } catch (EntryNotFoundException e) {
+ assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_NO_RESULTS_RETURNED);
+ }
+ }
+
+ @Test()
+ public void testSingleEntrySearchRequestMultipleEntriesToReturn() throws Exception {
+ final Connection mockConnection = new MockConnection(ResultCode.SIZE_LIMIT_EXCEEDED,
+ Responses.newSearchResultEntry("cn=test"));
+ final SearchRequest request =
+ Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
+ try {
+ mockConnection.searchSingleEntry(request);
+ failWasExpected(MultipleEntriesFoundException.class);
+ } catch (MultipleEntriesFoundException e) {
+ assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED);
+ }
+ }
+
+ @Test()
+ public void testSingleEntrySearchRequestMultipleEntriesReturnedByServer() throws Exception {
+ // could happen if server does not enforce size limit
+ final Connection mockConnection = new MockConnection(ResultCode.SUCCESS,
+ Responses.newSearchResultEntry("cn=test"),
+ Responses.newSearchResultEntry("cn=test,ou=org"));
+ final SearchRequest request =
+ Requests.newSingleEntrySearchRequest("cn=test", SearchScope.WHOLE_SUBTREE, "(objectClass=*)");
+ try {
+ mockConnection.searchSingleEntry(request);
+ failWasExpected(MultipleEntriesFoundException.class);
+ } catch (MultipleEntriesFoundException e) {
+ assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test()
+ public void testSingleEntrySearchAsyncRequestMultipleEntriesToReturn() throws Exception {
+ final Connection mockConnection = new MockConnection(ResultCode.SIZE_LIMIT_EXCEEDED,
+ Responses.newSearchResultEntry("cn=test"));
+ final SearchRequest request =
+ Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
+ ResultHandler<SearchResultEntry> handler = mock(ResultHandler.class);
+
+ try {
+ mockConnection.searchSingleEntryAsync(request, handler).get();
+ failWasExpected(MultipleEntriesFoundException.class);
+ } catch (MultipleEntriesFoundException e) {
+ assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED);
+ verify(handler).handleErrorResult(any(ErrorResultException.class));
+ }
+ }
+
+ @Test()
+ public void testSingleEntrySearchAsyncRequestMultipleEntriesReturnedByServer() throws Exception {
+ // could happen if server does not enfore size limit
+ final Connection mockConnection = new MockConnection(ResultCode.SUCCESS,
+ Responses.newSearchResultEntry("cn=test"),
+ Responses.newSearchResultEntry("cn=test,ou=org"));
+ final SearchRequest request = Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT,
+ "(objectClass=*)");
+ ResultHandler<SearchResultEntry> handler = mock(ResultHandler.class);
+
+ try {
+ mockConnection.searchSingleEntryAsync(request, handler).get();
+ failWasExpected(MultipleEntriesFoundException.class);
+ } catch (MultipleEntriesFoundException e) {
+ assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_UNEXPECTED_RESULTS_RETURNED);
+ verify(handler).handleErrorResult(any(ErrorResultException.class));
+ }
+ }
+
+ @Test()
+ public void testSingleEntrySearchRequestFail() throws Exception {
+ final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
+ final SearchRequest request =
+ Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
+ try {
+ mockConnection.searchSingleEntry(request);
+ failWasExpected(ErrorResultException.class);
+ } catch (ErrorResultException e) {
+ assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.UNWILLING_TO_PERFORM);
+ }
+ }
+
+ @Test()
+ public void testSingleEntrySearchAsyncRequestFail() throws Exception {
+ final Connection mockConnection = new MockConnection(ResultCode.UNWILLING_TO_PERFORM);
+ final SearchRequest request =
+ Requests.newSingleEntrySearchRequest("cn=test", SearchScope.BASE_OBJECT, "(objectClass=*)");
+ ResultHandler<SearchResultEntry> handler = mock(ResultHandler.class);
+
+ try {
+ mockConnection.searchSingleEntryAsync(request, handler).get();
+ failWasExpected(ErrorResultException.class);
+ } catch (ErrorResultException e) {
+ assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.UNWILLING_TO_PERFORM);
+ verify(handler).handleErrorResult(any(ErrorResultException.class));
+ }
+ }
+
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/SdkTestCase.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/SdkTestCase.java
index 708776d..8cdf183 100644
--- a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/SdkTestCase.java
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/SdkTestCase.java
@@ -22,10 +22,12 @@
*
*
* Copyright 2010 Sun Microsystems, Inc.
- * Portions copyright 2012 ForgeRock AS.
+ * Portions copyright 2012-2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap;
+import static org.fest.assertions.Fail.*;
+
import org.forgerock.testng.ForgeRockTestCase;
import org.testng.annotations.Test;
@@ -35,4 +37,13 @@
*/
@Test(groups = { "precommit", "types", "sdk" })
public abstract class SdkTestCase extends ForgeRockTestCase {
+
+ /**
+ * Fail with precise message giving the exception that was expected.
+ *
+ * @param exceptionClass expected exception
+ */
+ void failWasExpected(Class<? extends Throwable> exceptionClass) {
+ fail("should throw an exception " + exceptionClass.getSimpleName());
+ }
}
diff --git a/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/requests/SearchRequestTestCase.java b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/requests/SearchRequestTestCase.java
new file mode 100644
index 0000000..2d92da6
--- /dev/null
+++ b/opendj-sdk/opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/requests/SearchRequestTestCase.java
@@ -0,0 +1,82 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ * Copyright 2013 ForgeRock AS.
+ */
+
+package org.forgerock.opendj.ldap.requests;
+
+import static org.fest.assertions.Assertions.*;
+
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Filter;
+import org.forgerock.opendj.ldap.SearchScope;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@SuppressWarnings("javadoc")
+public class SearchRequestTestCase extends RequestTestCase {
+
+ @DataProvider(name = "SearchRequests")
+ public Object[][] getSearchRequests() throws Exception {
+ return getTestRequests();
+ }
+
+ @Override
+ protected SearchRequest[] createTestRequests() throws Exception {
+ return new SearchRequest[] {
+ Requests.newSearchRequest(
+ "uid=user.0,ou=people,o=test",
+ SearchScope.BASE_OBJECT, "(uid=user)", "uid", "ou"),
+ Requests.newSearchRequest("uid=user.0,ou=people,o=test",
+ SearchScope.SINGLE_LEVEL, "(uid=user)", "uid", "ou") };
+ }
+
+ @Test
+ public void createRequestForSingleEntrySearch() throws Exception {
+ SearchRequest request = Requests.newSingleEntrySearchRequest(
+ DN.valueOf("uid=user.0,ou=people,o=test"),
+ SearchScope.BASE_OBJECT, Filter.equality("uid", "user"), "uid");
+
+ assertThat(request.getSizeLimit()).isEqualTo(1);
+ assertThat(request.isSingleEntrySearch()).isTrue();
+ }
+
+ @Test
+ public void createRequestForSingleEntrySearchWithStrings() throws Exception {
+ SearchRequest request = Requests.newSingleEntrySearchRequest(
+ "uid=user.0,ou=people,o=test",
+ SearchScope.BASE_OBJECT, "(uid=user)", "uid");
+
+ assertThat(request.getSizeLimit()).isEqualTo(1);
+ assertThat(request.isSingleEntrySearch()).isTrue();
+ }
+
+ @Test
+ public void createRequestWithBaseObjectScope() throws Exception {
+ SearchRequest request = Requests.newSearchRequest(
+ DN.valueOf("uid=user.0,ou=people,o=test"),
+ SearchScope.BASE_OBJECT, Filter.equality("uid", "user"), "uid");
+
+ assertThat(request.isSingleEntrySearch()).isTrue();
+ }
+}
--
Gitblit v1.10.0