/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive.concurrent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.ldaptive.Connection;
import org.ldaptive.ConnectionFactory;
import org.ldaptive.LdapException;
import org.ldaptive.Request;
import org.ldaptive.Response;
import org.ldaptive.SearchFilter;
import org.ldaptive.SearchOperation;
import org.ldaptive.SearchRequest;
import org.ldaptive.SearchResult;
import org.ldaptive.handler.SearchEntryHandler;
/**
* Executes a list of search filters in parallel over a list of connection factories. This implementation executes each
* search on the same connection in separate threads. If you need parallel searches over a pool of connections see
* {@link AggregatePooledSearchExecutor}. A cached thread pool is used by default.
*
* @author Middleware Services
*/
public class AggregateSearchExecutor extends AbstractAggregateSearchExecutor<ConnectionFactory>
{
/** Default constructor. */
public AggregateSearchExecutor()
{
this(Executors.newCachedThreadPool());
}
/**
* Creates a new aggregate search executor.
*
* @param es executor service
*/
public AggregateSearchExecutor(final ExecutorService es)
{
super(es);
}
@Override
public Collection<Response<SearchResult>> search(
final ConnectionFactory[] factories,
final SearchFilter[] filters,
final String[] attrs,
final SearchEntryHandler... handlers)
throws LdapException
{
final CompletionService<Collection<Response<SearchResult>>> searches = new ExecutorCompletionService<>(
getExecutorService());
final SearchRequest[] requests = new SearchRequest[filters.length];
for (int i = 0; i < filters.length; i++) {
final SearchRequest sr = newSearchRequest(this);
if (filters[i] != null) {
sr.setSearchFilter(filters[i]);
}
if (attrs != null) {
sr.setReturnAttributes(attrs);
}
if (handlers != null) {
sr.setSearchEntryHandlers(handlers);
}
requests[i] = sr;
}
final List<Future<Collection<Response<SearchResult>>>> futures = new ArrayList<>(factories.length * filters.length);
for (ConnectionFactory factory : factories) {
final Connection conn = factory.getConnection();
final SearchOperation op = createSearchOperation(conn);
final SearchOperationWorker worker = new SearchOperationWorker(op, getExecutorService());
futures.add(searches.submit(createCallable(conn, worker, requests)));
}
final List<Response<SearchResult>> responses = new ArrayList<>(factories.length * filters.length);
for (Future<Collection<Response<SearchResult>>> future : futures) {
try {
responses.addAll(future.get());
} catch (ExecutionException e) {
logger.debug("ExecutionException thrown, ignoring", e);
} catch (InterruptedException e) {
logger.warn("InterruptedException thrown, ignoring", e);
}
}
return responses;
}
/**
* Returns a {@link Callable} that executes the supplied request with the supplied worker in a try-finally block that
* opens and closes the connection.
*
* @param <Q> type of ldap request
* @param <S> type of ldap response
* @param conn connection that the worker will execute on
* @param worker to execute
* @param requests to pass to the worker
*
* @return callable for the supplied operation and request
*/
protected static <Q extends Request, S> Callable<Collection<Response<S>>> createCallable(
final Connection conn,
final OperationWorker<Q, S> worker,
final Q[] requests)
{
return
() -> {
try {
conn.open();
return worker.executeToCompletion(requests);
} finally {
conn.close();
}
};
}
}