/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive.referral;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.ldaptive.Connection;
import org.ldaptive.ConnectionConfig;
import org.ldaptive.ConnectionFactory;
import org.ldaptive.DefaultConnectionFactory;
import org.ldaptive.LdapException;
import org.ldaptive.LdapURL;
import org.ldaptive.Operation;
import org.ldaptive.Request;
import org.ldaptive.Response;
import org.ldaptive.ResultCode;
import org.ldaptive.handler.HandlerResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Common implementation of referral handling.
*
* @param <Q> type of ldap request
* @param <S> type of ldap response
*
* @author Middleware Services
*/
public abstract class AbstractReferralHandler<Q extends Request, S> implements ReferralHandler<Q, S>
{
/** Default referral limit. Value is {@value}. */
protected static final int DEFAULT_REFERRAL_LIMIT = 10;
/** Default referral connection factory. Uses {@link DefaultConnectionFactory}. */
// CheckStyle:JavadocVariable OFF
protected static final ReferralConnectionFactory DEFAULT_CONNECTION_FACTORY = (config, ldapUrl) -> {
final ConnectionConfig cc = ConnectionConfig.newConnectionConfig(config);
cc.setLdapUrl(ldapUrl);
return new DefaultConnectionFactory(cc);
};
// CheckStyle:JavadocVariable ON
/** Logger for this class. */
protected final Logger logger = LoggerFactory.getLogger(getClass());
/** Referral limit. */
private final int referralLimit;
/** Referral depth. */
private final int referralDepth;
/** Referral connection factory. */
private final ReferralConnectionFactory connectionFactory;
/**
* Creates a new abstract referral handler.
*
* @param limit number of referrals to follow
* @param depth number of referrals followed
* @param factory referral connection factory
*/
public AbstractReferralHandler(final int limit, final int depth, final ReferralConnectionFactory factory)
{
referralLimit = limit;
referralDepth = depth;
connectionFactory = factory;
}
/**
* Returns the maximum number of referrals to follow.
*
* @return referral limit
*/
public int getReferralLimit()
{
return referralLimit;
}
/**
* Returns the referral depth of this handler.
*
* @return referral depth
*/
public int getReferralDepth()
{
return referralDepth;
}
/**
* Returns the referral connection factory.
*
* @return referral connection factory
*/
public ReferralConnectionFactory getReferralConnectionFactory()
{
return connectionFactory;
}
/**
* Creates a new request for this type of referral.
*
* @param request of the original operation
* @param url of the referral
*
* @return new request
*/
protected abstract Q createReferralRequest(final Q request, final LdapURL url);
/**
* Creates an operation for this type of referral.
*
* @param conn to execute the operation on
*
* @return new operation
*/
protected abstract Operation<Q, S> createReferralOperation(final Connection conn);
/**
* Follows the supplied referral URLs in order until a SUCCESS or REFERRAL_LIMIT_EXCEEDED occurs. If neither of those
* conditions occurs this method returns null.
*
* @param conn the original operation occurred on
* @param request of the operation that produced a referral
* @param referralUrls produced by the request
*
* @return referral response
*
* @throws LdapException if a REFERRAL_LIMIT_EXCEEDED in encountered
*/
protected Response<S> followReferral(final Connection conn, final Q request, final String[] referralUrls)
throws LdapException
{
Response<S> referralResponse = null;
final List<String> urls = Arrays.asList(referralUrls);
Collections.shuffle(urls);
logger.debug("Following referral with URLs: {}", urls);
for (String url : urls) {
final LdapURL ldapUrl = new LdapURL(url);
if (ldapUrl.getEntry().getHostname() == null) {
continue;
}
final ConnectionFactory cf = connectionFactory.getConnectionFactory(
conn.getConnectionConfig(),
ldapUrl.getEntry().getHostnameWithSchemeAndPort());
try (Connection referralConn = cf.getConnection()) {
referralConn.open();
final Q referralRequest = createReferralRequest(request, ldapUrl);
final Operation<Q, S> op = createReferralOperation(referralConn);
referralResponse = op.execute(referralRequest);
} catch (LdapException e) {
logger.warn("Could not follow referral to " + url, e);
if (e.getResultCode() == ResultCode.REFERRAL_LIMIT_EXCEEDED) {
throw e;
}
}
if (referralResponse != null && referralResponse.getResultCode() == ResultCode.SUCCESS) {
break;
}
}
return referralResponse;
}
@Override
public HandlerResult<Response<S>> handle(final Connection conn, final Q request, final Response<S> response)
throws LdapException
{
HandlerResult<Response<S>> result;
if (referralDepth > referralLimit) {
result = new HandlerResult<>(
new Response<>(
response.getResult(),
ResultCode.REFERRAL_LIMIT_EXCEEDED,
response.getMessage(),
response.getMatchedDn(),
response.getControls(),
response.getReferralURLs(),
response.getMessageId()));
} else {
final Response<S> referralResponse = followReferral(conn, request, response.getReferralURLs());
if (referralResponse != null) {
result = new HandlerResult<>(referralResponse);
} else {
result = new HandlerResult<>(response);
}
}
return result;
}
/**
* Implementation that does not require the response.
*
* @param conn the operation occurred on
* @param request the operation executed
* @param referralUrls encountered in the operation
*
* @return handler result
*
* @throws LdapException if an error occurs following referrals
*/
public HandlerResult<Response<S>> handle(final Connection conn, final Q request, final String[] referralUrls)
throws LdapException
{
HandlerResult<Response<S>> result;
if (referralDepth > referralLimit) {
result = new HandlerResult<>(
new Response<>((S) null, ResultCode.REFERRAL_LIMIT_EXCEEDED, null, null, null, null, -1));
} else {
final Response<S> referralResponse = followReferral(conn, request, referralUrls);
if (referralResponse != null) {
result = new HandlerResult<>(referralResponse);
} else {
result = new HandlerResult<>(null);
}
}
return result;
}
@Override
public void initializeRequest(final Q request) {}
}