
org.ldaptive.control.util.SyncReplRunner Maven / Gradle / Ivy
/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive.control.util;
import java.time.Duration;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.ldaptive.AbstractConnectionValidator;
import org.ldaptive.ConnectionConfig;
import org.ldaptive.ConnectionValidator;
import org.ldaptive.LdapEntry;
import org.ldaptive.LdapException;
import org.ldaptive.Result;
import org.ldaptive.SearchConnectionValidator;
import org.ldaptive.SearchRequest;
import org.ldaptive.SearchResultReference;
import org.ldaptive.SingleConnectionFactory;
import org.ldaptive.extended.SyncInfoMessage;
import org.ldaptive.transport.ThreadPoolConfig;
import org.ldaptive.transport.Transport;
import org.ldaptive.transport.TransportFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class that executes a {@link SyncReplClient} and expects to run continuously, reconnecting if the server is
* unavailable. Consumers must be registered to handle entries, results, and messages as they are returned from the
* server. If the connection validator fails, the runner will be stopped and started, then the sync repl search
* will execute again. Consumers cannot execute blocking LDAP operations on the same connection because the next
* incoming message is not read until the consumer has completed.
*
* @author Middleware Services
*/
public final class SyncReplRunner
{
/** Logger for this class. */
private static final Logger LOGGER = LoggerFactory.getLogger(SyncReplRunner.class);
/** Number of I/O worker threads. */
private static final int IO_WORKER_THREADS = 1;
/** Number of message worker threads. */
private static final int MESSAGE_WORKER_THREADS = 4;
/** Sync repl search request. */
private final SearchRequest searchRequest;
/** Sync repl cookie manager. */
private final CookieManager cookieManager;
/** Search operation handle. */
private final SyncReplClient syncReplClient;
/** Invoked when {@link #start()} begins. */
private Supplier onStart;
/** Invoked when an entry is received. */
private Consumer onEntry;
/** Invoked when a reference is received. */
private Consumer onReference;
/** Invoked when a result is received. */
private Consumer onResult;
/** Invoked when a sync info message is received. */
private Consumer onMessage;
/** Invoked when an exception occurs. */
private Consumer onException;
/** Whether the sync repl search is running. */
private boolean started;
/**
* Creates a new sync repl runner. The supplied connection factory is modified to invoke {@link
* SyncReplClient#send(SearchRequest, CookieManager)} when the connection opens and {@link SyncReplClient#cancel()}
* when the connection closes.
*
* @param cf to get a connection from
* @param request sync repl search request
* @param manager sync repl cookie manager
*/
public SyncReplRunner(final SingleConnectionFactory cf, final SearchRequest request, final CookieManager manager)
{
syncReplClient = new SyncReplClient(cf, true);
searchRequest = request;
cookieManager = manager;
cf.setOnOpen(conn -> {
try {
syncReplClient.send(searchRequest, cookieManager);
} catch (LdapException e) {
LOGGER.error("Could not send sync repl request", e);
return false;
}
return true;
});
cf.setOnClose(conn -> {
try {
if (!syncReplClient.isComplete()) {
syncReplClient.cancel();
}
} catch (Exception e) {
LOGGER.debug("Could not cancel sync repl request", e);
return false;
}
return true;
});
}
/**
* Creates a new single connection factory. Uses a {@link SearchConnectionValidator} for connection validation. See
* {@link #createTransport()}
*
* @param config sync repl connection configuration
*
* @return single connection factory for use with a sync repl runner
*/
public static SingleConnectionFactory createConnectionFactory(final ConnectionConfig config)
{
// CheckStyle:MagicNumber OFF
return createConnectionFactory(
config,
SearchConnectionValidator.builder()
.period(Duration.ofMinutes(1))
.timeout(Duration.ofSeconds(5))
.timeoutIsFailure(false)
.build());
// CheckStyle:MagicNumber ON
}
/**
* Creates a new single connection factory. See {@link #createTransport()}.
*
* @param config sync repl connection configuration
* @param validator connection validator
*
* @return single connection factory for use with a sync repl runner
*/
public static SingleConnectionFactory createConnectionFactory(
final ConnectionConfig config, final ConnectionValidator validator)
{
return createConnectionFactory(createTransport(), config, validator);
}
/**
* Creates a new single connection factory.
*
* @param transport sync repl connection transport
* @param config sync repl connection configuration
* @param validator connection validator
*
* @return single connection factory for use with a sync repl runner
*/
public static SingleConnectionFactory createConnectionFactory(
final Transport transport, final ConnectionConfig config, final ConnectionValidator validator)
{
final SingleConnectionFactory factory = new SingleConnectionFactory(config, transport);
factory.setValidator(validator);
configureConnectionFactory(factory);
return factory;
}
/**
* Configures the supplied factory for use with a {@link SyncReplRunner}. The factory's configuration will have the
* following modifications:
*
* - {@link ConnectionConfig#setAutoReconnect(boolean)} to false
* - {@link ConnectionConfig#setAutoReplay(boolean)} to false
* - {@link ConnectionConfig#setAutoRead(boolean)} to false
* - {@link SingleConnectionFactory#setFailFastInitialize(boolean)} to false
* - {@link SingleConnectionFactory#setNonBlockingInitialize(boolean)} to false
* - {@link AbstractConnectionValidator#setOnFailure(Consumer)} to
* {@link SingleConnectionFactory.ReinitializeConnectionConsumer}
*
*
* @param factory to configure
*/
public static void configureConnectionFactory(final SingleConnectionFactory factory)
{
final ConnectionConfig newConfig = ConnectionConfig.copy(factory.getConnectionConfig());
newConfig.setAutoReconnect(false);
newConfig.setAutoReplay(false);
newConfig.setAutoRead(false);
factory.setConnectionConfig(newConfig);
factory.setFailFastInitialize(false);
factory.setNonBlockingInitialize(false);
if (factory.getValidator() instanceof AbstractConnectionValidator) {
final AbstractConnectionValidator validator = (AbstractConnectionValidator) factory.getValidator();
if (validator.getOnFailure() == null) {
validator.setOnFailure(factory.new ReinitializeConnectionConsumer());
}
}
}
/**
* Returns a transport configured to use for sync repl. Use {@link #IO_WORKER_THREADS} number of I/O threads and
* {@link #MESSAGE_WORKER_THREADS} number of message threads. This transport is configured never to be shutdown.
*
* @return transport
*/
private static Transport createTransport()
{
// message thread pool size must be >2 since exceptions are reported on the messages thread pool and flow control
// requires a thread to signal reads and pass user events
// startTLS and connection initializers will require additional threads
return TransportFactory.getTransport(
ThreadPoolConfig.builder()
.threadPoolName("ldaptive-sync-repl-runner")
.ioThreads(IO_WORKER_THREADS)
.messageThreads(MESSAGE_WORKER_THREADS)
.shutdownStrategy(ThreadPoolConfig.ShutdownStrategy.NEVER)
.freeze()
.build());
}
/**
* Sets the onStart supplier.
*
* @param supplier to invoke on start
*/
public void setOnStart(final Supplier supplier)
{
synchronized (syncReplClient) {
if (started) {
throw new IllegalStateException("Cannot set onStart, runner already started");
}
onStart = supplier;
}
}
/**
* Sets the onEntry consumer.
*
* @param consumer to invoke when an entry is received
*/
public void setOnEntry(final Consumer consumer)
{
synchronized (syncReplClient) {
if (started) {
throw new IllegalStateException("Cannot set onEntry, runner already started");
}
onEntry = consumer;
}
}
/**
* Sets the onReference consumer.
*
* @param consumer to invoke when a reference is received
*/
public void setOnReference(final Consumer consumer)
{
synchronized (syncReplClient) {
if (started) {
throw new IllegalStateException("Cannot set onReference, runner already started");
}
onReference = consumer;
}
}
/**
* Sets the onResult consumer.
*
* @param consumer to invoke when a result is received
*/
public void setOnResult(final Consumer consumer)
{
synchronized (syncReplClient) {
if (started) {
throw new IllegalStateException("Cannot set onResult, runner already started");
}
onResult = consumer;
}
}
/**
* Sets the onMessage consumer.
*
* @param consumer to invoke when a sync info message is received
*/
public void setOnMessage(final Consumer consumer)
{
synchronized (syncReplClient) {
if (started) {
throw new IllegalStateException("Cannot set onMessage, runner already started");
}
onMessage = consumer;
}
}
/**
* Sets the onException consumer.
*
* @param consumer to invoke when an exception is received
*/
public void setOnException(final Consumer consumer)
{
synchronized (syncReplClient) {
if (started) {
throw new IllegalStateException("Cannot set onException, runner already started");
}
onException = consumer;
}
}
/**
* Prepare this runner for use.
*/
public void initialize()
{
synchronized (syncReplClient) {
if (started) {
throw new IllegalStateException("Runner has already been started");
}
syncReplClient.setOnEntry(onEntry);
syncReplClient.setOnReference(onReference);
syncReplClient.setOnResult(onResult);
syncReplClient.setOnMessage(onMessage);
syncReplClient.setOnException(onException);
}
}
/**
* Starts this runner.
*/
public void start()
{
synchronized (syncReplClient) {
if (started) {
throw new IllegalStateException("Runner has already been started");
}
try {
if (onStart != null && !onStart.get()) {
throw new RuntimeException("Start aborted from " + onStart);
}
LOGGER.debug("Starting runner {}", this);
// the connection factory may be shared between multiple runners
if (!((SingleConnectionFactory) syncReplClient.getConnectionFactory()).isInitialized()) {
((SingleConnectionFactory) syncReplClient.getConnectionFactory()).initialize();
}
started = true;
LOGGER.info("Runner {} started", this);
} catch (Exception e) {
LOGGER.error("Could not start the runner", e);
}
}
}
/**
* Stops this runner.
*/
public void stop()
{
synchronized (syncReplClient) {
if (!started) {
throw new IllegalStateException("Runner has not been started");
}
LOGGER.debug("Stopping runner {}", this);
syncReplClient.close();
started = false;
LOGGER.info("Runner {} stopped", this);
}
}
/**
* Returns whether this runner is started.
*
* @return whether this runner is started
*/
public boolean isStarted()
{
return started;
}
/**
* Cancels the sync repl search and sends a new search request.
*/
public void restartSearch()
{
synchronized (syncReplClient) {
if (!started) {
throw new IllegalStateException("Cannot restart the search, runner is stopped");
}
try {
if (!syncReplClient.isComplete()) {
syncReplClient.cancel();
}
} catch (Exception e) {
LOGGER.debug("Could not cancel sync repl request", e);
}
try {
syncReplClient.send(searchRequest, cookieManager);
} catch (LdapException e) {
throw new IllegalStateException("Could not send sync repl request", e);
}
}
}
@Override
public String toString()
{
return getClass().getName() + "@" + hashCode() + "::" +
"syncReplClient=" + syncReplClient + ", " +
"searchRequest=" + searchRequest + ", " +
"cookieManager=" + cookieManager + ", " +
"onStart=" + onStart + ", " +
"onEntry=" + onEntry + ", " +
"onReference=" + onReference + ", " +
"onResult=" + onResult + ", " +
"onMessage=" + onMessage + ", " +
"onException=" + onException + ", " +
"started=" + started;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy