/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive.control.util;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.ldaptive.Connection;
import org.ldaptive.LdapException;
import org.ldaptive.Request;
import org.ldaptive.Response;
import org.ldaptive.SearchEntry;
import org.ldaptive.SearchRequest;
import org.ldaptive.SearchResult;
import org.ldaptive.async.AsyncRequest;
import org.ldaptive.async.AsyncSearchOperation;
import org.ldaptive.async.handler.AsyncRequestHandler;
import org.ldaptive.control.SyncRequestControl;
import org.ldaptive.extended.CancelOperation;
import org.ldaptive.extended.CancelRequest;
import org.ldaptive.handler.HandlerResult;
import org.ldaptive.handler.IntermediateResponseHandler;
import org.ldaptive.handler.OperationResponseHandler;
import org.ldaptive.handler.SearchEntryHandler;
import org.ldaptive.intermediate.IntermediateResponse;
import org.ldaptive.intermediate.SyncInfoMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Client that simplifies using the sync repl control.
*
* @author Middleware Services
*/
public class SyncReplClient
{
/** Logger for this class. */
protected final Logger logger = LoggerFactory.getLogger(getClass());
/** Connection to invoke the search operation on. */
private final Connection connection;
/** Controls which mode the sync repl control should use. */
private final boolean refreshAndPersist;
/**
* Creates a new sync repl client.
*
* @param conn to execute the async search operation on
* @param persist whether to refresh and persist or just refresh
*/
public SyncReplClient(final Connection conn, final boolean persist)
{
connection = conn;
refreshAndPersist = persist;
}
/**
* Invokes {@link #execute(SearchRequest, CookieManager, int)} with a {@link DefaultCookieManager} and a capacity of
* {@link Integer#MAX_VALUE}.
*
* @param request search request to execute
*
* @return blocking queue to wait for sync repl items
*
* @throws LdapException if the search fails
*/
public BlockingQueue<SyncReplItem> execute(final SearchRequest request)
throws LdapException
{
return execute(request, new DefaultCookieManager(), Integer.MAX_VALUE);
}
/**
* Invokes {@link #execute(SearchRequest, CookieManager, int)} with a capacity of {@link Integer#MAX_VALUE}.
*
* @param request search request to execute
* @param manager for reading and writing cookies
*
* @return blocking queue to wait for sync repl items
*
* @throws LdapException if the search fails
*/
public BlockingQueue<SyncReplItem> execute(final SearchRequest request, final CookieManager manager)
throws LdapException
{
return execute(request, manager, Integer.MAX_VALUE);
}
/**
* Performs an async search operation with the {@link SyncRequestControl}. The supplied request is modified in the
* following way:
*
* <ul>
* <li>{@link SearchRequest#setControls( org.ldaptive.control.RequestControl...)} is invoked with {@link
* SyncRequestControl}</li>
* <li>{@link SearchRequest#setSearchEntryHandlers(SearchEntryHandler...)} is invoked with a custom handler that
* places sync repl data in a blocking queue.</li>
* <li>{@link SearchRequest#setIntermediateResponseHandlers( IntermediateResponseHandler...)} is invoked with a
* custom handler that places sync repl data in a blocking queue.</li>
* <li>{@link AsyncSearchOperation#setOperationResponseHandlers( OperationResponseHandler[])} is invoked with a
* custom handler that places the sync repl response in a blocking queue.</li>
* <li>{@link AsyncSearchOperation#setExceptionHandler(org.ldaptive.async.handler.ExceptionHandler)} is invoked with
* a custom handler that places the exception in a blocking queue.</li>
* </ul>
*
* <p>The search request object should not be reused for any other search operations.</p>
*
* @param request search request to execute
* @param manager for reading and writing cookies
* @param capacity of the returned blocking queue
*
* @return blocking queue to wait for sync repl items
*
* @throws LdapException if the search fails
*/
@SuppressWarnings("unchecked")
public BlockingQueue<SyncReplItem> execute(
final SearchRequest request,
final CookieManager manager,
final int capacity)
throws LdapException
{
final BlockingQueue<SyncReplItem> queue = new LinkedBlockingQueue<>(capacity);
final AsyncSearchOperation search = new AsyncSearchOperation(connection);
search.setOperationResponseHandlers(
new OperationResponseHandler<SearchRequest, SearchResult>() {
@Override
public HandlerResult<Response<SearchResult>> handle(
final Connection conn,
final SearchRequest request,
final Response<SearchResult> response)
throws LdapException
{
try {
logger.debug("received {}", response);
search.shutdown();
final SyncReplItem item = new SyncReplItem(new SyncReplItem.Response(response));
if (item.getResponse().getSyncDoneControl() != null) {
final byte[] cookie = item.getResponse().getSyncDoneControl().getCookie();
if (cookie != null) {
manager.writeCookie(cookie);
}
}
queue.put(item);
} catch (Exception e) {
logger.warn("Unable to enqueue response {}", response);
}
return new HandlerResult<>(response);
}
});
search.setAsyncRequestHandlers(
new AsyncRequestHandler() {
@Override
public HandlerResult<AsyncRequest> handle(
final Connection conn,
final Request request,
final AsyncRequest asyncRequest)
throws LdapException
{
try {
logger.debug("received {}", asyncRequest);
queue.put(new SyncReplItem(asyncRequest));
} catch (Exception e) {
logger.warn("Unable to enqueue async request {}", asyncRequest);
}
return new HandlerResult<>(null);
}
});
search.setExceptionHandler(
(conn, request1, exception) -> {
try {
logger.debug("received exception:", exception);
search.shutdown();
queue.put(new SyncReplItem(exception));
} catch (Exception e) {
logger.warn("Unable to enqueue exception:", exception);
}
return new HandlerResult<>(null);
});
request.setControls(
new SyncRequestControl(
refreshAndPersist ? SyncRequestControl.Mode.REFRESH_AND_PERSIST : SyncRequestControl.Mode.REFRESH_ONLY,
manager.readCookie(),
true));
request.setSearchEntryHandlers(
new SearchEntryHandler() {
@Override
public HandlerResult<SearchEntry> handle(
final Connection conn,
final SearchRequest request,
final SearchEntry entry)
throws LdapException
{
try {
logger.debug("received {}", entry);
final SyncReplItem item = new SyncReplItem(new SyncReplItem.Entry(entry));
if (item.getEntry().getSyncStateControl() != null) {
final byte[] cookie = item.getEntry().getSyncStateControl().getCookie();
if (cookie != null) {
manager.writeCookie(cookie);
}
}
queue.put(item);
} catch (Exception e) {
logger.warn("Unable to enqueue entry {}", entry);
}
return new HandlerResult<>(null);
}
@Override
public void initializeRequest(final SearchRequest request) {}
});
request.setIntermediateResponseHandlers(
new IntermediateResponseHandler() {
@Override
public HandlerResult<IntermediateResponse> handle(
final Connection conn,
final Request request,
final IntermediateResponse response)
throws LdapException
{
if (SyncInfoMessage.OID.equals(response.getOID())) {
try {
logger.debug("received {}", response);
final SyncInfoMessage message = (SyncInfoMessage) response;
if (message.getCookie() != null) {
manager.writeCookie(message.getCookie());
}
queue.put(new SyncReplItem(message));
} catch (Exception e) {
logger.warn("Unable to enqueue intermediate response {}", response);
}
}
return new HandlerResult<>(null);
}
});
search.execute(request);
return queue;
}
/**
* Invokes a cancel operation on the supplied ldap message id. Convenience method supplied to cancel sync repl
* operations.
*
* @param messageId of the operation to cancel
*
* @return cancel operation response
*
* @throws LdapException if the cancel operation fails
*/
public Response<Void> cancel(final int messageId)
throws LdapException
{
final CancelOperation cancel = new CancelOperation(connection);
return cancel.execute(new CancelRequest(messageId));
}
}