/*
* ====================
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License("CDDL") (the "License"). You may not use this file
* except in compliance with the License.
*
* You can obtain a copy of the License at
* http://IdentityConnectors.dev.java.net/legal/license.txt
* See the License for the specific language governing permissions and limitations
* under the License.
*
* When distributing the Covered Code, include this CDDL Header Notice in each file
* and include the License file at identityconnectors/legal/license.txt.
* If applicable, add the following below this CDDL Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
* ====================
* "Portions Copyrighted 2014 ForgeRock AS"
* Portions Copyrighted 2014 Evolveum
*/
package org.identityconnectors.ldap.search;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import javax.naming.LimitExceededException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.PartialResultException;
import javax.naming.SizeLimitExceededException;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.PagedResultsControl;
import javax.naming.ldap.PagedResultsResponseControl;
import javax.naming.ldap.SortControl;
import org.identityconnectors.common.Base64;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.SortKey;
import org.identityconnectors.ldap.LdapConnection;
public class SimplePagedSearchStrategy extends LdapSearchStrategy {
private static final Log log = Log.getLog(SimplePagedSearchStrategy.class);
private OperationOptions options;
private final int defaultPageSize;
private final SortKey[] sortKeys;
private byte[] cookie = null;
private int lastListSize = -1;
public SimplePagedSearchStrategy(int defaultPageSize) {
this.defaultPageSize = defaultPageSize;
this.sortKeys = null;
}
public SimplePagedSearchStrategy(OperationOptions options, int defaultPageSize, SortKey[] sortKeys) {
this.options = options;
this.defaultPageSize = defaultPageSize;
this.sortKeys = sortKeys;
}
@Override
public void doSearch(LdapConnection conn, List<String> baseDNs, String query, SearchControls searchControls, LdapSearchResultsHandler handler) throws IOException, NamingException {
log.ok("Searching in {0} with filter {1} and {2}", baseDNs, query, searchControlsToString(searchControls));
LdapContext ctx = conn.getInitialContext().newInstance(null);
SortControl sortControl = null;
if (sortKeys != null && sortKeys.length > 0){
javax.naming.ldap.SortKey[] skis = new javax.naming.ldap.SortKey[sortKeys.length];
for(int i = 0; i < sortKeys.length; i++){
skis[i] = new javax.naming.ldap.SortKey(sortKeys[i].getField(),sortKeys[i].isAscendingOrder(),null);
}
// We don't want to make this critical... better return unsorted results than nothing.
sortControl = new SortControl(skis, Control.NONCRITICAL);
}
int pageSize = defaultPageSize;
int offset = 0;
if (options != null && options.getPagedResultsOffset() != null) {
offset = options.getPagedResultsOffset();
if (offset != 0) {
log.info("Inefficient search using SimplePaged control and offset {0}",offset);
}
}
try {
Iterator<String> baseDNIter = baseDNs.iterator();
boolean proceed = true;
while (baseDNIter.hasNext() && proceed) {
String baseDN = baseDNIter.next();
cookie = null;
if (options != null && options.getPagedResultsCookie() != null) {
cookie = Base64.decode(options.getPagedResultsCookie());
}
pageSize = defaultPageSize;
int numberOfResutlsHandled = 0;
int numberOfResultsSkipped = 0;
do {
if (options != null && options.getPageSize() != null &&
((numberOfResutlsHandled + numberOfResultsSkipped + pageSize) > offset + options.getPageSize())) {
pageSize = offset + options.getPageSize() - (numberOfResutlsHandled + numberOfResultsSkipped);
}
if (sortControl != null) {
ctx.setRequestControls(new Control[]{new PagedResultsControl(pageSize, cookie, Control.CRITICAL), sortControl});
} else {
ctx.setRequestControls(new Control[]{new PagedResultsControl(pageSize, cookie, Control.CRITICAL)});
}
if (log.isOk()) {
log.ok("LDAP search request: PagedResults( pageSize = {0}, cookie = {1} )",
pageSize, Base64.encode(cookie));
}
int responseResultCount = 0;
NamingEnumeration<SearchResult> results = null;
try {
results = ctx.search(baseDN, query, searchControls);
while (proceed && results.hasMore()) {
responseResultCount++;
SearchResult searchResult = results.next();
if (offset > numberOfResultsSkipped) {
numberOfResultsSkipped++;
} else {
numberOfResutlsHandled++;
proceed = handler.handle(baseDN, searchResult);
if (!proceed) {
log.ok("Ending search because handler returned false");
}
}
}
} catch (PartialResultException e) {
log.ok("PartialResultException caught: {0}",e.getRemainingName());
if (results != null) {
results.close();
}
} catch (NamingException e) {
log.error("Unexpected search exception: {0}", e.getMessage(), e);
throw e;
} catch (RuntimeException e) {
log.error("Unexpected search exception: {0}", e.getMessage(), e);
throw e;
}
PagedResultsResponseControl pagedControlResponse = getPagedControlResponse(ctx.getResponseControls());
if (pagedControlResponse != null) {
cookie = pagedControlResponse.getCookie();
lastListSize = pagedControlResponse.getResultSize();
} else {
cookie = null;
lastListSize = -1;
}
if (log.isOk()) {
log.ok("LDAP search response: {0} results returned, PagedResults( resultSize = {1}, cookie = {2} )", responseResultCount,
pagedControlResponse==null?"noControl":pagedControlResponse.getResultSize(),
pagedControlResponse==null?"noControl":Base64.encode(pagedControlResponse.getCookie()));
}
if (responseResultCount == 0) {
// Zero results returned. This is either a hidded error or end of search.
log.warn("Zero results returned from paged search");
break;
}
if (!proceed) {
break;
}
if (options != null && options.getPageSize() != null &&
((numberOfResutlsHandled + numberOfResultsSkipped) >= offset + options.getPageSize())) {
break;
}
} while (cookie != null);
}
} catch (LimitExceededException e) {
// This should not happen!
log.error("Server size limit exceeded even though SimplePaged control was used (page size {0})", pageSize, e);
throw e;
} finally {
ctx.close();
}
}
private PagedResultsResponseControl getPagedControlResponse(Control[] controls) {
if (controls != null) {
for (Control control : controls) {
if (control instanceof PagedResultsResponseControl) {
return (PagedResultsResponseControl) control;
}
}
}
return null;
}
@Override
public int getRemainingPagedResults() {
return lastListSize;
}
@Override
public String getPagedResultsCookie() {
if (cookie == null) {
return null;
}
return Base64.encode(cookie);
}
}