/*
|
* CDDL HEADER START
|
*
|
* The contents of this file are subject to the terms of the
|
* Common Development and Distribution License, Version 1.0 only
|
* (the "License"). You may not use this file except in compliance
|
* with the License.
|
*
|
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* See the License for the specific language governing permissions
|
* and limitations under the License.
|
*
|
* When distributing Covered Code, include this CDDL HEADER in each
|
* file and include the License file at legal-notices/CDDLv1_0.txt.
|
* If applicable, add the following below this CDDL HEADER, with the
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
* information:
|
* Portions Copyright [yyyy] [name of copyright owner]
|
*
|
* CDDL HEADER END
|
*
|
*
|
* Copyright 2010 Sun Microsystems, Inc.
|
* Portions Copyright 2011-2012 ForgeRock AS
|
*/
|
|
package com.forgerock.opendj.ldap;
|
|
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
|
|
import java.io.IOException;
|
import java.net.InetSocketAddress;
|
import java.util.List;
|
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLEngine;
|
|
import org.forgerock.opendj.ldap.AbstractAsynchronousConnection;
|
import org.forgerock.opendj.ldap.Connection;
|
import org.forgerock.opendj.ldap.ConnectionEventListener;
|
import org.forgerock.opendj.ldap.ErrorResultException;
|
import org.forgerock.opendj.ldap.FutureResult;
|
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
|
import org.forgerock.opendj.ldap.LDAPOptions;
|
import org.forgerock.opendj.ldap.ResultCode;
|
import org.forgerock.opendj.ldap.ResultHandler;
|
import org.forgerock.opendj.ldap.SearchResultHandler;
|
import org.forgerock.opendj.ldap.requests.AbandonRequest;
|
import org.forgerock.opendj.ldap.requests.AddRequest;
|
import org.forgerock.opendj.ldap.requests.BindClient;
|
import org.forgerock.opendj.ldap.requests.BindRequest;
|
import org.forgerock.opendj.ldap.requests.CompareRequest;
|
import org.forgerock.opendj.ldap.requests.DeleteRequest;
|
import org.forgerock.opendj.ldap.requests.ExtendedRequest;
|
import org.forgerock.opendj.ldap.requests.GenericBindRequest;
|
import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
|
import org.forgerock.opendj.ldap.requests.ModifyRequest;
|
import org.forgerock.opendj.ldap.requests.Requests;
|
import org.forgerock.opendj.ldap.requests.SearchRequest;
|
import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
|
import org.forgerock.opendj.ldap.requests.UnbindRequest;
|
import org.forgerock.opendj.ldap.responses.BindResult;
|
import org.forgerock.opendj.ldap.responses.CompareResult;
|
import org.forgerock.opendj.ldap.responses.ExtendedResult;
|
import org.forgerock.opendj.ldap.responses.Responses;
|
import org.forgerock.opendj.ldap.responses.Result;
|
import org.glassfish.grizzly.CompletionHandler;
|
import org.glassfish.grizzly.filterchain.Filter;
|
import org.glassfish.grizzly.filterchain.FilterChain;
|
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
|
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
|
import org.glassfish.grizzly.ssl.SSLFilter;
|
|
import com.forgerock.opendj.util.CompletedFutureResult;
|
import com.forgerock.opendj.util.StaticUtils;
|
import com.forgerock.opendj.util.Validator;
|
|
/**
|
* LDAP connection implementation.
|
*/
|
final class LDAPConnection extends AbstractAsynchronousConnection implements Connection {
|
private final AtomicBoolean bindOrStartTLSInProgress = new AtomicBoolean(false);
|
private final org.glassfish.grizzly.Connection<?> connection;
|
private final LDAPWriter ldapWriter = new LDAPWriter();
|
private final AtomicInteger nextMsgID = new AtomicInteger(1);
|
private final LDAPOptions options;
|
private final ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>> pendingRequests =
|
new ConcurrentHashMap<Integer, AbstractLDAPFutureResultImpl<?>>();
|
private final Object stateLock = new Object();
|
// Guarded by stateLock
|
private Result connectionInvalidReason;
|
private boolean failedDueToDisconnect = false;
|
private boolean isClosed = false;
|
private boolean isFailed = false;
|
private List<ConnectionEventListener> listeners = null;
|
|
/**
|
* Creates a new LDAP connection.
|
*
|
* @param connection
|
* The Grizzly connection.
|
* @param options
|
* The LDAP client options.
|
*/
|
LDAPConnection(final org.glassfish.grizzly.Connection<?> connection, final LDAPOptions options) {
|
this.connection = connection;
|
this.options = options;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FutureResult<Void> abandonAsync(final AbandonRequest request) {
|
final AbstractLDAPFutureResultImpl<?> pendingRequest;
|
final int messageID = nextMsgID.getAndIncrement();
|
try {
|
synchronized (stateLock) {
|
checkConnectionIsValid();
|
checkBindOrStartTLSInProgress();
|
// Remove the future associated with the request to be abandoned.
|
pendingRequest = pendingRequests.remove(request.getRequestID());
|
}
|
if (pendingRequest == null) {
|
// There has never been a request with the specified message ID or
|
// the response has already been received and handled. We can ignore
|
// this abandon request.
|
|
// Message ID will be -1 since no request was sent.
|
return new CompletedFutureResult<Void>((Void) null);
|
}
|
pendingRequest.cancel(false);
|
try {
|
final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
|
try {
|
ldapWriter.abandonRequest(asn1Writer, messageID, request);
|
connection.write(asn1Writer.getBuffer(), null);
|
return new CompletedFutureResult<Void>((Void) null, messageID);
|
} finally {
|
asn1Writer.recycle();
|
}
|
} catch (final IOException e) {
|
throw adaptRequestIOException(e);
|
}
|
} catch (final ErrorResultException e) {
|
return new CompletedFutureResult<Void>(e, messageID);
|
}
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FutureResult<Result> addAsync(final AddRequest request,
|
final IntermediateResponseHandler intermediateResponseHandler,
|
final ResultHandler<? super Result> resultHandler) {
|
final int messageID = nextMsgID.getAndIncrement();
|
final LDAPFutureResultImpl future =
|
new LDAPFutureResultImpl(messageID, request, resultHandler,
|
intermediateResponseHandler, this);
|
try {
|
synchronized (stateLock) {
|
checkConnectionIsValid();
|
checkBindOrStartTLSInProgress();
|
pendingRequests.put(messageID, future);
|
}
|
try {
|
final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
|
try {
|
ldapWriter.addRequest(asn1Writer, messageID, request);
|
connection.write(asn1Writer.getBuffer(), null);
|
} finally {
|
asn1Writer.recycle();
|
}
|
} catch (final IOException e) {
|
pendingRequests.remove(messageID);
|
throw adaptRequestIOException(e);
|
}
|
} catch (final ErrorResultException e) {
|
future.adaptErrorResult(e.getResult());
|
}
|
return future;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public void addConnectionEventListener(final ConnectionEventListener listener) {
|
Validator.ensureNotNull(listener);
|
final boolean notifyClose;
|
final boolean notifyErrorOccurred;
|
synchronized (stateLock) {
|
notifyClose = isClosed;
|
notifyErrorOccurred = isFailed;
|
if (!isClosed) {
|
if (listeners == null) {
|
listeners = new CopyOnWriteArrayList<ConnectionEventListener>();
|
}
|
listeners.add(listener);
|
}
|
}
|
if (notifyErrorOccurred) {
|
// Use the reason provided in the disconnect notification.
|
listener.handleConnectionError(failedDueToDisconnect,
|
newErrorResult(connectionInvalidReason));
|
}
|
if (notifyClose) {
|
listener.handleConnectionClosed();
|
}
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FutureResult<BindResult> bindAsync(final BindRequest request,
|
final IntermediateResponseHandler intermediateResponseHandler,
|
final ResultHandler<? super BindResult> resultHandler) {
|
final int messageID = nextMsgID.getAndIncrement();
|
final BindClient context;
|
try {
|
context =
|
request.createBindClient(
|
connection.getPeerAddress() instanceof InetSocketAddress
|
? ((InetSocketAddress) connection.getPeerAddress()).getHostName()
|
: connection.getPeerAddress().toString());
|
} catch (final Exception e) {
|
// FIXME: I18N need to have a better error message.
|
// FIXME: Is this the best result code?
|
final Result errorResult =
|
Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
|
"An error occurred while creating a bind context").setCause(e);
|
final ErrorResultException error = ErrorResultException.newErrorResult(errorResult);
|
if (resultHandler != null) {
|
resultHandler.handleErrorResult(error);
|
}
|
return new CompletedFutureResult<BindResult>(error, messageID);
|
}
|
|
final LDAPBindFutureResultImpl future =
|
new LDAPBindFutureResultImpl(messageID, context, resultHandler,
|
intermediateResponseHandler, this);
|
|
try {
|
synchronized (stateLock) {
|
checkConnectionIsValid();
|
if (!pendingRequests.isEmpty()) {
|
future.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR)
|
.setDiagnosticMessage(
|
"There are other operations pending on this connection"));
|
return future;
|
}
|
if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
|
future.setResultOrError(Responses.newBindResult(ResultCode.OPERATIONS_ERROR)
|
.setDiagnosticMessage("Bind or Start TLS operation in progress"));
|
return future;
|
}
|
pendingRequests.put(messageID, future);
|
}
|
|
try {
|
final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
|
try {
|
// Use the bind client to get the initial request instead of
|
// using the bind request passed to this method.
|
final GenericBindRequest initialRequest = context.nextBindRequest();
|
ldapWriter.bindRequest(asn1Writer, messageID, 3, initialRequest);
|
connection.write(asn1Writer.getBuffer(), null);
|
} finally {
|
asn1Writer.recycle();
|
}
|
} catch (final IOException e) {
|
pendingRequests.remove(messageID);
|
bindOrStartTLSInProgress.set(false);
|
throw adaptRequestIOException(e);
|
}
|
} catch (final ErrorResultException e) {
|
future.adaptErrorResult(e.getResult());
|
}
|
|
return future;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public void close(final UnbindRequest request, final String reason) {
|
// FIXME: I18N need to internationalize this message.
|
Validator.ensureNotNull(request);
|
|
close(request, false, Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED)
|
.setDiagnosticMessage(
|
"Connection closed by client" + (reason != null ? ": " + reason : "")));
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FutureResult<CompareResult> compareAsync(final CompareRequest request,
|
final IntermediateResponseHandler intermediateResponseHandler,
|
final ResultHandler<? super CompareResult> resultHandler) {
|
final int messageID = nextMsgID.getAndIncrement();
|
final LDAPCompareFutureResultImpl future =
|
new LDAPCompareFutureResultImpl(messageID, request, resultHandler,
|
intermediateResponseHandler, this);
|
try {
|
synchronized (stateLock) {
|
checkConnectionIsValid();
|
checkBindOrStartTLSInProgress();
|
pendingRequests.put(messageID, future);
|
}
|
try {
|
final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
|
try {
|
ldapWriter.compareRequest(asn1Writer, messageID, request);
|
connection.write(asn1Writer.getBuffer(), null);
|
} finally {
|
asn1Writer.recycle();
|
}
|
} catch (final IOException e) {
|
pendingRequests.remove(messageID);
|
throw adaptRequestIOException(e);
|
}
|
} catch (final ErrorResultException e) {
|
future.adaptErrorResult(e.getResult());
|
}
|
return future;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FutureResult<Result> deleteAsync(final DeleteRequest request,
|
final IntermediateResponseHandler intermediateResponseHandler,
|
final ResultHandler<? super Result> resultHandler) {
|
final int messageID = nextMsgID.getAndIncrement();
|
final LDAPFutureResultImpl future =
|
new LDAPFutureResultImpl(messageID, request, resultHandler,
|
intermediateResponseHandler, this);
|
try {
|
synchronized (stateLock) {
|
checkConnectionIsValid();
|
checkBindOrStartTLSInProgress();
|
pendingRequests.put(messageID, future);
|
}
|
try {
|
final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
|
try {
|
ldapWriter.deleteRequest(asn1Writer, messageID, request);
|
connection.write(asn1Writer.getBuffer(), null);
|
} finally {
|
asn1Writer.recycle();
|
}
|
} catch (final IOException e) {
|
pendingRequests.remove(messageID);
|
throw adaptRequestIOException(e);
|
}
|
} catch (final ErrorResultException e) {
|
future.adaptErrorResult(e.getResult());
|
}
|
return future;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public <R extends ExtendedResult> FutureResult<R> extendedRequestAsync(
|
final ExtendedRequest<R> request,
|
final IntermediateResponseHandler intermediateResponseHandler,
|
final ResultHandler<? super R> resultHandler) {
|
final int messageID = nextMsgID.getAndIncrement();
|
final LDAPExtendedFutureResultImpl<R> future =
|
new LDAPExtendedFutureResultImpl<R>(messageID, request, resultHandler,
|
intermediateResponseHandler, this);
|
try {
|
synchronized (stateLock) {
|
checkConnectionIsValid();
|
if (request.getOID().equals(StartTLSExtendedRequest.OID)) {
|
if (!pendingRequests.isEmpty()) {
|
future.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
|
ResultCode.OPERATIONS_ERROR, "",
|
"There are pending operations on this connection"));
|
return future;
|
} else if (isTLSEnabled()) {
|
future.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
|
ResultCode.OPERATIONS_ERROR, "",
|
"This connection is already TLS enabled"));
|
return future;
|
} else if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
|
future.setResultOrError(request.getResultDecoder().newExtendedErrorResult(
|
ResultCode.OPERATIONS_ERROR, "",
|
"Bind or Start TLS operation in progress"));
|
return future;
|
}
|
} else {
|
checkBindOrStartTLSInProgress();
|
}
|
pendingRequests.put(messageID, future);
|
}
|
try {
|
final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
|
try {
|
ldapWriter.extendedRequest(asn1Writer, messageID, request);
|
connection.write(asn1Writer.getBuffer(), null);
|
} finally {
|
asn1Writer.recycle();
|
}
|
} catch (final IOException e) {
|
pendingRequests.remove(messageID);
|
bindOrStartTLSInProgress.set(false);
|
throw adaptRequestIOException(e);
|
}
|
} catch (final ErrorResultException e) {
|
future.adaptErrorResult(e.getResult());
|
}
|
return future;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public boolean isClosed() {
|
synchronized (stateLock) {
|
return isClosed;
|
}
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public boolean isValid() {
|
synchronized (stateLock) {
|
return isValid0();
|
}
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FutureResult<Result> modifyAsync(final ModifyRequest request,
|
final IntermediateResponseHandler intermediateResponseHandler,
|
final ResultHandler<? super Result> resultHandler) {
|
final int messageID = nextMsgID.getAndIncrement();
|
final LDAPFutureResultImpl future =
|
new LDAPFutureResultImpl(messageID, request, resultHandler,
|
intermediateResponseHandler, this);
|
try {
|
synchronized (stateLock) {
|
checkConnectionIsValid();
|
checkBindOrStartTLSInProgress();
|
pendingRequests.put(messageID, future);
|
}
|
try {
|
final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
|
try {
|
ldapWriter.modifyRequest(asn1Writer, messageID, request);
|
connection.write(asn1Writer.getBuffer(), null);
|
} finally {
|
asn1Writer.recycle();
|
}
|
} catch (final IOException e) {
|
pendingRequests.remove(messageID);
|
throw adaptRequestIOException(e);
|
}
|
} catch (final ErrorResultException e) {
|
future.adaptErrorResult(e.getResult());
|
}
|
return future;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FutureResult<Result> modifyDNAsync(final ModifyDNRequest request,
|
final IntermediateResponseHandler intermediateResponseHandler,
|
final ResultHandler<? super Result> resultHandler) {
|
final int messageID = nextMsgID.getAndIncrement();
|
final LDAPFutureResultImpl future =
|
new LDAPFutureResultImpl(messageID, request, resultHandler,
|
intermediateResponseHandler, this);
|
try {
|
synchronized (stateLock) {
|
checkConnectionIsValid();
|
checkBindOrStartTLSInProgress();
|
pendingRequests.put(messageID, future);
|
}
|
try {
|
final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
|
try {
|
ldapWriter.modifyDNRequest(asn1Writer, messageID, request);
|
connection.write(asn1Writer.getBuffer(), null);
|
} finally {
|
asn1Writer.recycle();
|
}
|
} catch (final IOException e) {
|
pendingRequests.remove(messageID);
|
throw adaptRequestIOException(e);
|
}
|
} catch (final ErrorResultException e) {
|
future.adaptErrorResult(e.getResult());
|
}
|
return future;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public void removeConnectionEventListener(final ConnectionEventListener listener) {
|
Validator.ensureNotNull(listener);
|
synchronized (stateLock) {
|
if (listeners != null) {
|
listeners.remove(listener);
|
}
|
}
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public FutureResult<Result> searchAsync(final SearchRequest request,
|
final IntermediateResponseHandler intermediateResponseHandler,
|
final SearchResultHandler resultHandler) {
|
final int messageID = nextMsgID.getAndIncrement();
|
final LDAPSearchFutureResultImpl future =
|
new LDAPSearchFutureResultImpl(messageID, request, resultHandler,
|
intermediateResponseHandler, this);
|
try {
|
synchronized (stateLock) {
|
checkConnectionIsValid();
|
checkBindOrStartTLSInProgress();
|
pendingRequests.put(messageID, future);
|
}
|
try {
|
final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
|
try {
|
ldapWriter.searchRequest(asn1Writer, messageID, request);
|
connection.write(asn1Writer.getBuffer(), null);
|
} finally {
|
asn1Writer.recycle();
|
}
|
} catch (final IOException e) {
|
pendingRequests.remove(messageID);
|
throw adaptRequestIOException(e);
|
}
|
} catch (final ErrorResultException e) {
|
future.adaptErrorResult(e.getResult());
|
}
|
return future;
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public String toString() {
|
final StringBuilder builder = new StringBuilder();
|
builder.append("LDAPConnection(");
|
builder.append(connection.getLocalAddress());
|
builder.append(',');
|
builder.append(connection.getPeerAddress());
|
builder.append(')');
|
return builder.toString();
|
}
|
|
long cancelExpiredRequests(final long currentTime) {
|
final long timeout = options.getTimeout(TimeUnit.MILLISECONDS);
|
long delay = timeout;
|
if (timeout > 0) {
|
for (final int requestID : pendingRequests.keySet()) {
|
final AbstractLDAPFutureResultImpl<?> future = pendingRequests.get(requestID);
|
if (future != null) {
|
final long diff = (future.getTimestamp() + timeout) - currentTime;
|
if (diff <= 0 && pendingRequests.remove(requestID) != null) {
|
StaticUtils.DEBUG_LOG.fine("Cancelling expired future result: " + future);
|
final Result result = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT);
|
future.adaptErrorResult(result);
|
|
abandonAsync(Requests.newAbandonRequest(future.getRequestID()));
|
} else {
|
delay = Math.min(delay, diff);
|
}
|
}
|
}
|
}
|
return delay;
|
}
|
|
/**
|
* Closes this connection, invoking event listeners as needed.
|
*
|
* @param unbindRequest
|
* The client provided unbind request if this is a client
|
* initiated close, or {@code null} if the connection has failed.
|
* @param isDisconnectNotification
|
* {@code true} if this is a connection failure signalled by a
|
* server disconnect notification.
|
* @param reason
|
* The result indicating why the connection was closed.
|
*/
|
void close(final UnbindRequest unbindRequest, final boolean isDisconnectNotification,
|
final Result reason) {
|
final boolean notifyClose;
|
final boolean notifyErrorOccurred;
|
final List<ConnectionEventListener> tmpListeners;
|
synchronized (stateLock) {
|
if (isClosed) {
|
// Already closed locally.
|
return;
|
} else if (unbindRequest != null) {
|
// Local close.
|
notifyClose = true;
|
notifyErrorOccurred = false;
|
isClosed = true;
|
tmpListeners = listeners;
|
listeners = null; // Prevent future invocations.
|
if (connectionInvalidReason == null) {
|
connectionInvalidReason = reason;
|
}
|
} else if (isFailed) {
|
// Already failed.
|
return;
|
} else {
|
// Connection has failed and this is the first indication.
|
notifyClose = false;
|
notifyErrorOccurred = true;
|
isFailed = true;
|
failedDueToDisconnect = isDisconnectNotification;
|
connectionInvalidReason = reason;
|
tmpListeners = listeners; // Keep list for client close.
|
}
|
}
|
|
// First abort all outstanding requests.
|
for (final int requestID : pendingRequests.keySet()) {
|
final AbstractLDAPFutureResultImpl<?> future = pendingRequests.remove(requestID);
|
if (future != null) {
|
future.adaptErrorResult(connectionInvalidReason);
|
}
|
}
|
|
// Now try cleanly closing the connection if possible.
|
// Only send unbind if specified.
|
if (unbindRequest != null) {
|
try {
|
final ASN1BufferWriter asn1Writer = ASN1BufferWriter.getWriter();
|
try {
|
ldapWriter
|
.unbindRequest(asn1Writer, nextMsgID.getAndIncrement(), unbindRequest);
|
connection.write(asn1Writer.getBuffer(), null);
|
} finally {
|
asn1Writer.recycle();
|
}
|
} catch (final IOException e) {
|
// Underlying channel prob blown up. Just ignore.
|
}
|
}
|
TimeoutChecker.INSTANCE.removeConnection(this);
|
connection.closeSilently();
|
|
// Notify listeners.
|
if (tmpListeners != null) {
|
if (notifyErrorOccurred) {
|
for (final ConnectionEventListener listener : tmpListeners) {
|
// Use the reason provided in the disconnect notification.
|
listener.handleConnectionError(isDisconnectNotification, newErrorResult(reason));
|
}
|
}
|
if (notifyClose) {
|
for (final ConnectionEventListener listener : tmpListeners) {
|
listener.handleConnectionClosed();
|
}
|
}
|
}
|
}
|
|
int continuePendingBindRequest(final LDAPBindFutureResultImpl future)
|
throws ErrorResultException {
|
final int newMsgID = nextMsgID.getAndIncrement();
|
synchronized (stateLock) {
|
checkConnectionIsValid();
|
pendingRequests.put(newMsgID, future);
|
}
|
return newMsgID;
|
}
|
|
LDAPOptions getLDAPOptions() {
|
return options;
|
}
|
|
AbstractLDAPFutureResultImpl<?> getPendingRequest(final Integer messageID) {
|
return pendingRequests.get(messageID);
|
}
|
|
void handleUnsolicitedNotification(final ExtendedResult result) {
|
final List<ConnectionEventListener> tmpListeners;
|
synchronized (stateLock) {
|
tmpListeners = listeners;
|
}
|
if (tmpListeners != null) {
|
for (final ConnectionEventListener listener : tmpListeners) {
|
listener.handleUnsolicitedNotification(result);
|
}
|
}
|
}
|
|
/**
|
* Installs a new Grizzly filter (e.g. SSL/SASL) beneath the top-level LDAP
|
* filter.
|
*
|
* @param filter
|
* The filter to be installed.
|
*/
|
void installFilter(final Filter filter) {
|
synchronized (stateLock) {
|
// Determine the index where the filter should be added.
|
final FilterChain oldFilterChain = (FilterChain) connection.getProcessor();
|
int filterIndex = oldFilterChain.size() - 1;
|
if (filter instanceof SSLFilter) {
|
// Beneath any ConnectionSecurityLayerFilters if present,
|
// otherwise beneath the LDAP filter.
|
for (int i = oldFilterChain.size() - 2; i >= 0; i--) {
|
if (!(oldFilterChain.get(i) instanceof ConnectionSecurityLayerFilter)) {
|
filterIndex = i + 1;
|
break;
|
}
|
}
|
}
|
|
// Create the new filter chain.
|
final FilterChain newFilterChain =
|
FilterChainBuilder.stateless().addAll(oldFilterChain).add(filterIndex, filter)
|
.build();
|
connection.setProcessor(newFilterChain);
|
}
|
}
|
|
/**
|
* Indicates whether or not TLS is enabled on this connection.
|
*
|
* @return {@code true} if TLS is enabled on this connection, otherwise
|
* {@code false}.
|
*/
|
boolean isTLSEnabled() {
|
synchronized (stateLock) {
|
final FilterChain currentFilterChain = (FilterChain) connection.getProcessor();
|
for (final Filter filter : currentFilterChain) {
|
if (filter instanceof SSLFilter) {
|
return true;
|
}
|
}
|
return false;
|
}
|
}
|
|
AbstractLDAPFutureResultImpl<?> removePendingRequest(final Integer messageID) {
|
return pendingRequests.remove(messageID);
|
}
|
|
void setBindOrStartTLSInProgress(final boolean state) {
|
bindOrStartTLSInProgress.set(state);
|
}
|
|
void startTLS(final SSLContext sslContext, final List<String> protocols,
|
final List<String> cipherSuites, final CompletionHandler<SSLEngine> completionHandler)
|
throws IOException {
|
synchronized (stateLock) {
|
if (isTLSEnabled()) {
|
throw new IllegalStateException("TLS already enabled");
|
}
|
|
final SSLEngineConfigurator sslEngineConfigurator =
|
new SSLEngineConfigurator(sslContext, true, false, false);
|
sslEngineConfigurator.setEnabledProtocols(protocols.isEmpty() ? null : protocols
|
.toArray(new String[protocols.size()]));
|
sslEngineConfigurator.setEnabledCipherSuites(cipherSuites.isEmpty() ? null
|
: cipherSuites.toArray(new String[cipherSuites.size()]));
|
final SSLFilter sslFilter = new SSLFilter(null, sslEngineConfigurator);
|
installFilter(sslFilter);
|
sslFilter.handshake(connection, completionHandler);
|
}
|
}
|
|
private ErrorResultException adaptRequestIOException(final IOException e) {
|
// FIXME: what other sort of IOExceptions can be thrown?
|
// FIXME: Is this the best result code?
|
final Result errorResult =
|
Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e);
|
connectionErrorOccurred(errorResult);
|
return newErrorResult(errorResult);
|
}
|
|
private void checkBindOrStartTLSInProgress() throws ErrorResultException {
|
if (bindOrStartTLSInProgress.get()) {
|
throw newErrorResult(ResultCode.OPERATIONS_ERROR,
|
"Bind or Start TLS operation in progress");
|
}
|
}
|
|
private void checkConnectionIsValid() throws ErrorResultException {
|
if (!isValid0()) {
|
if (failedDueToDisconnect) {
|
// Connection termination was triggered remotely. We don't want
|
// to blindly pass on the result code to requests since it could
|
// be confused for a genuine response. For example, if the
|
// disconnect contained the invalidCredentials result code then
|
// this could be misinterpreted as a genuine authentication
|
// failure for subsequent bind requests.
|
throw newErrorResult(ResultCode.CLIENT_SIDE_SERVER_DOWN,
|
"Connection closed by server");
|
} else {
|
throw newErrorResult(connectionInvalidReason);
|
}
|
}
|
}
|
|
private void connectionErrorOccurred(final Result reason) {
|
close(null, false, reason);
|
}
|
|
private boolean isValid0() {
|
return !isFailed && !isClosed;
|
}
|
}
|