/* 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.Operation; import org.ldaptive.Request; import org.ldaptive.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Base class for worker operations. If no {@link ExecutorService} is provided a cached thread pool is used by default. * * @param <Q> type of ldap request * @param <S> type of ldap response * * @author Middleware Services */ public abstract class AbstractOperationWorker<Q extends Request, S> implements OperationWorker<Q, S> { /** Logger for this class. */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** operation to execute. */ private final Operation<Q, S> operation; /** to submit operations to. */ private final ExecutorService service; /** * Creates a new abstract operation worker. * * @param op operation */ public AbstractOperationWorker(final Operation<Q, S> op) { this(op, Executors.newCachedThreadPool()); } /** * Creates a new abstract operation worker. * * @param op operation * @param es executor service */ public AbstractOperationWorker(final Operation<Q, S> op, final ExecutorService es) { operation = op; service = es; } /** * Execute an ldap operation on a separate thread. * * @param request containing the data required by this operation * * @return future response for this operation */ @Override public Future<Response<S>> execute(final Q request) { return service.submit(createCallable(operation, request)); } /** * Execute an ldap operation for each request on a separate thread. * * @param requests containing the data required by this operation * * @return future responses for this operation */ @Override @SuppressWarnings("unchecked") public Collection<Future<Response<S>>> execute(final Q... requests) { final List<Future<Response<S>>> results = new ArrayList<>(requests.length); for (Q request : requests) { results.add(service.submit(createCallable(operation, request))); } return results; } /** * Execute an ldap operation for each request on a separate thread and waits for all operations to complete. * * @param requests containing the data required by this operation * * @return responses for this operation */ @Override @SuppressWarnings("unchecked") public Collection<Response<S>> executeToCompletion(final Q... requests) { final CompletionService<Response<S>> cs = new ExecutorCompletionService<>(service); final List<Future<Response<S>>> futures = new ArrayList<>(requests.length); for (Q request : requests) { futures.add(cs.submit(createCallable(operation, request))); } final List<Response<S>> responses = new ArrayList<>(requests.length); for (Future<Response<S>> future : futures) { try { responses.add(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 operation. * * @param <Q> type of ldap request * @param <S> type of ldap response * @param operation to execute * @param request to pass to the operation * * @return callable for the supplied operation and request */ public static <Q extends Request, S> Callable<Response<S>> createCallable( final Operation<Q, S> operation, final Q request) { return () -> operation.execute(request); } /** Invokes {@link ExecutorService#shutdown()} on the underlying executor service. */ public void shutdown() { service.shutdown(); } @Override protected void finalize() throws Throwable { try { shutdown(); } finally { super.finalize(); } } }