/* See LICENSE for licensing and NOTICE for copyright. */ package org.ldaptive; import java.util.Arrays; import org.ldaptive.handler.AbstractRetryOperationExceptionHandler; import org.ldaptive.handler.Handler; import org.ldaptive.handler.HandlerResult; import org.ldaptive.handler.OperationExceptionHandler; import org.ldaptive.handler.OperationResponseHandler; import org.ldaptive.referral.ReferralHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides common implementation for ldap operations. * * @param <Q> type of ldap request * @param <S> type of ldap response * * @author Middleware Services */ public abstract class AbstractOperation<Q extends Request, S> implements Operation<Q, S> { /** Logger for this class. */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** Connection to perform operation. */ private final Connection connection; /** Handler to handle operation exceptions. */ private OperationExceptionHandler<Q, S> operationExceptionHandler = new ReopenOperationExceptionHandler(); /** Handlers to handle operation responses. */ private OperationResponseHandler<Q, S>[] operationResponseHandlers; /** * Creates a new abstract operation. * * @param conn to use for this operation */ public AbstractOperation(final Connection conn) { connection = conn; } /** * Returns the connection used for this operation. * * @return connection */ protected Connection getConnection() { return connection; } /** * Returns the operation exception handler. * * @return operation exception handler */ public OperationExceptionHandler<Q, S> getOperationExceptionHandler() { return operationExceptionHandler; } /** * Sets the operation exception handler. * * @param handler operation exception handler */ public void setOperationExceptionHandler(final OperationExceptionHandler<Q, S> handler) { operationExceptionHandler = handler; } /** * Returns the operation response handlers. * * @return operation response handlers */ public OperationResponseHandler<Q, S>[] getOperationResponseHandlers() { return operationResponseHandlers; } /** * Sets the operation response handlers. * * @param handlers operation response handlers */ @SuppressWarnings("unchecked") public void setOperationResponseHandlers(final OperationResponseHandler<Q, S>... handlers) { operationResponseHandlers = handlers; } /** * Call the provider specific implementation of this ldap operation. * * @param request ldap request * * @return ldap response * * @throws LdapException if the invocation fails */ protected abstract Response<S> invoke(final Q request) throws LdapException; @Override public Response<S> execute(final Q request) throws LdapException { logger.debug("execute request={} with connection={}", request, connection); Response<S> response = null; try { response = invoke(request); } catch (OperationException e) { if (operationExceptionHandler == null) { throw e; } logger.debug("Error performing LDAP operation, invoking exception handler: {}", operationExceptionHandler, e); final HandlerResult<Response<S>> hr = operationExceptionHandler.handle(connection, request, response); if (hr.getAbort()) { throw e; } response = hr.getResult(); } if (ResultCode.REFERRAL == response.getResultCode()) { @SuppressWarnings("unchecked") final ReferralHandler<Q, S> handler = request.getReferralHandler(); if (handler != null) { logger.debug("Encountered referral, invoking referral handler: {}", handler); final HandlerResult<Response<S>> hr = handler.handle(connection, request, response); response = hr.getResult(); } } // execute response handlers final HandlerResult<Response<S>> hr = executeHandlers(getOperationResponseHandlers(), request, response); logger.debug("execute response={} for request={} with connection={}", hr.getResult(), request, connection); return hr.getResult(); } /** * Processes each handler and returns a handler result containing a result processed by all handlers. If any handler * indicates that the operation should be aborted, that flag is returned to the operation after all handlers have been * invoked. * * @param <Q> type of request * @param <S> type of response * @param handlers to invoke * @param request the operation was performed with * @param result from the operation * * @return handler result * * @throws LdapException if an error occurs processing a handler */ protected <Q extends Request, S> HandlerResult<S> executeHandlers( final Handler<Q, S>[] handlers, final Q request, final S result) throws LdapException { S processed = result; boolean abort = false; if (handlers != null && handlers.length > 0) { for (Handler<Q, S> handler : handlers) { if (handler != null) { try { final HandlerResult<S> hr = handler.handle(getConnection(), request, processed); if (hr != null) { if (hr.getAbort()) { abort = true; } processed = hr.getResult(); } } catch (Exception e) { logger.warn("{} threw unexpected exception", handler, e); } } } } return new HandlerResult<>(processed, abort); } @Override public String toString() { return String.format( "[%s@%d::connection=%s, operationExceptionHandler=%s, " + "operationResponseHandlers=%s]", getClass().getName(), hashCode(), connection, operationExceptionHandler, Arrays.toString(operationResponseHandlers)); } /** * Exception handler that invokes {@link Connection#reopen(BindRequest)} when an operation exception occurs and then * invokes the operation again. */ public class ReopenOperationExceptionHandler extends AbstractRetryOperationExceptionHandler<Q, S> { /** Bind request to use when reopening a connection. */ private final BindRequest bindRequest; /** Default constructor. */ public ReopenOperationExceptionHandler() { bindRequest = null; } /** * Creates a new reopen operation exception handler. * * @param request to bind with on reopen */ public ReopenOperationExceptionHandler(final BindRequest request) { bindRequest = request; } @Override protected void handleInternal(final Connection conn, final Q request, final Response<S> response) throws LdapException { logger.warn("Operation exception encountered, reopening connection"); if (bindRequest != null) { conn.reopen(bindRequest); } else { conn.reopen(); } } @Override protected HandlerResult<Response<S>> createResult( final Connection conn, final Q request, final Response<S> response) throws LdapException { return new HandlerResult<>(invoke(request)); } @Override public String toString() { return String.format( "[%s@%d::retry=%s, retryWait=%s, retryBackoff=%s, bindRequest=%s]", getClass().getName(), hashCode(), getRetry(), getRetryWait(), getRetryBackoff(), bindRequest); } } }