/* * 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. *

* TODO: handle illegal state exceptions. */ final class LDAPConnection extends AbstractAsynchronousConnection implements Connection { private final org.glassfish.grizzly.Connection connection; private Result connectionInvalidReason; private boolean isClosed = false; private final List listeners = new CopyOnWriteArrayList(); private final AtomicInteger nextMsgID = new AtomicInteger(1); private final AtomicBoolean bindOrStartTLSInProgress = new AtomicBoolean(false); private final ConcurrentHashMap> pendingRequests = new ConcurrentHashMap>(); private final Object stateLock = new Object(); private final LDAPWriter ldapWriter = new LDAPWriter(); private final LDAPOptions options; /** * 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} */ public FutureResult abandonAsync(final AbandonRequest request) { final AbstractLDAPFutureResultImpl pendingRequest; final int messageID = nextMsgID.getAndIncrement(); synchronized (stateLock) { if (connectionInvalidReason != null) { return new CompletedFutureResult(newErrorResult(connectionInvalidReason), messageID); } if (bindOrStartTLSInProgress.get()) { final Result errorResult = Responses.newResult(ResultCode.OPERATIONS_ERROR).setDiagnosticMessage( "Bind or Start TLS operation in progress"); return new CompletedFutureResult(newErrorResult(errorResult), messageID); } // First 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) 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) null, messageID); } finally { asn1Writer.recycle(); } } catch (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 new CompletedFutureResult(newErrorResult(errorResult), messageID); } } /** * {@inheritDoc} */ public FutureResult addAsync(final AddRequest request, final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler resultHandler) { final int messageID = nextMsgID.getAndIncrement(); final LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request, resultHandler, intermediateResponseHandler, this); synchronized (stateLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (bindOrStartTLSInProgress.get()) { future.setResultOrError(Responses.newResult(ResultCode.OPERATIONS_ERROR) .setDiagnosticMessage("Bind or Start TLS operation in progress")); return future; } 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); // 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); future.adaptErrorResult(errorResult); } return future; } /** * {@inheritDoc} */ public void addConnectionEventListener(final ConnectionEventListener listener) { Validator.ensureNotNull(listener); listeners.add(listener); } /** * {@inheritDoc} */ public FutureResult bindAsync(final BindRequest request, final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler resultHandler) { final int messageID = nextMsgID.getAndIncrement(); 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(error, messageID); } final LDAPBindFutureResultImpl future = new LDAPBindFutureResultImpl(messageID, context, resultHandler, intermediateResponseHandler, this); synchronized (stateLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } 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); // 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); future.adaptErrorResult(errorResult); } return future; } /** * {@inheritDoc} */ 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} */ public FutureResult compareAsync(final CompareRequest request, final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler resultHandler) { final int messageID = nextMsgID.getAndIncrement(); final LDAPCompareFutureResultImpl future = new LDAPCompareFutureResultImpl(messageID, request, resultHandler, intermediateResponseHandler, this); synchronized (stateLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (bindOrStartTLSInProgress.get()) { future.setResultOrError(Responses.newCompareResult(ResultCode.OPERATIONS_ERROR) .setDiagnosticMessage("Bind or Start TLS operation in progress")); return future; } 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); // 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); future.adaptErrorResult(errorResult); } return future; } /** * {@inheritDoc} */ public FutureResult deleteAsync(final DeleteRequest request, final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler resultHandler) { final int messageID = nextMsgID.getAndIncrement(); final LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request, resultHandler, intermediateResponseHandler, this); synchronized (stateLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (bindOrStartTLSInProgress.get()) { future.setResultOrError(Responses.newResult(ResultCode.OPERATIONS_ERROR) .setDiagnosticMessage("Bind or Start TLS operation in progress")); return future; } 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); // 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); future.adaptErrorResult(errorResult); } return future; } /** * {@inheritDoc} */ public FutureResult extendedRequestAsync( final ExtendedRequest request, final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler resultHandler) { final int messageID = nextMsgID.getAndIncrement(); final LDAPExtendedFutureResultImpl future = new LDAPExtendedFutureResultImpl(messageID, request, resultHandler, intermediateResponseHandler, this); synchronized (stateLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } 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; } if (isTLSEnabled()) { future.setResultOrError(request.getResultDecoder().newExtendedErrorResult( ResultCode.OPERATIONS_ERROR, "", "This connection is already TLS enabled")); return future; } if (!bindOrStartTLSInProgress.compareAndSet(false, true)) { future.setResultOrError(request.getResultDecoder().newExtendedErrorResult( ResultCode.OPERATIONS_ERROR, "", "Bind or Start TLS operation in progress")); return future; } } else { if (bindOrStartTLSInProgress.get()) { future.setResultOrError(request.getResultDecoder().newExtendedErrorResult( ResultCode.OPERATIONS_ERROR, "", "Bind or Start TLS operation in progress")); return future; } } 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); // 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); future.adaptErrorResult(errorResult); } return future; } /** * {@inheritDoc} */ public boolean isClosed() { synchronized (stateLock) { return isClosed; } } /** * {@inheritDoc} */ public boolean isValid() { synchronized (stateLock) { return connectionInvalidReason == null && !isClosed; } } /** * {@inheritDoc} */ public FutureResult modifyAsync(final ModifyRequest request, final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler resultHandler) { final int messageID = nextMsgID.getAndIncrement(); final LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request, resultHandler, intermediateResponseHandler, this); synchronized (stateLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (bindOrStartTLSInProgress.get()) { future.setResultOrError(Responses.newResult(ResultCode.OPERATIONS_ERROR) .setDiagnosticMessage("Bind or Start TLS operation in progress")); return future; } 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); // 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); future.adaptErrorResult(errorResult); } return future; } /** * {@inheritDoc} */ public FutureResult modifyDNAsync(final ModifyDNRequest request, final IntermediateResponseHandler intermediateResponseHandler, final ResultHandler resultHandler) { final int messageID = nextMsgID.getAndIncrement(); final LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request, resultHandler, intermediateResponseHandler, this); synchronized (stateLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (bindOrStartTLSInProgress.get()) { future.setResultOrError(Responses.newResult(ResultCode.OPERATIONS_ERROR) .setDiagnosticMessage("Bind or Start TLS operation in progress")); return future; } 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); // 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); future.adaptErrorResult(errorResult); } return future; } /** * {@inheritDoc} */ public void removeConnectionEventListener(final ConnectionEventListener listener) { Validator.ensureNotNull(listener); listeners.remove(listener); } /** * {@inheritDoc} */ public FutureResult 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); synchronized (stateLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (bindOrStartTLSInProgress.get()) { future.setResultOrError(Responses.newResult(ResultCode.OPERATIONS_ERROR) .setDiagnosticMessage("Bind or Start TLS operation in progress")); return future; } 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); // 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); future.adaptErrorResult(errorResult); } return future; } /** * {@inheritDoc} */ public String toString() { StringBuilder builder = new StringBuilder(); builder.append("LDAPConnection("); builder.append(connection.getLocalAddress()); builder.append(','); builder.append(connection.getPeerAddress()); builder.append(')'); return builder.toString(); } int continuePendingBindRequest(final LDAPBindFutureResultImpl future) throws ErrorResultException { final int newMsgID = nextMsgID.getAndIncrement(); synchronized (stateLock) { if (connectionInvalidReason != null) { throw newErrorResult(connectionInvalidReason); } pendingRequests.put(newMsgID, future); } return newMsgID; } long cancelExpiredRequests(final long currentTime) { final long timeout = options.getTimeout(TimeUnit.MILLISECONDS); long delay = timeout; if (timeout > 0) { for (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; } void close(final UnbindRequest unbindRequest, final boolean isDisconnectNotification, final Result reason) { boolean notifyClose = false; boolean notifyErrorOccurred = false; synchronized (stateLock) { if (isClosed) { // Already closed. return; } if (connectionInvalidReason != null) { // Already closed. isClosed = true; return; } if (unbindRequest != null) { // User closed. isClosed = true; notifyClose = true; } else { notifyErrorOccurred = true; } // Mark the connection as invalid. if (!isDisconnectNotification) { // Connection termination was detected locally, so use the // provided // reason for all subsequent requests. connectionInvalidReason = reason; } else { // 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. connectionInvalidReason = Responses.newResult(ResultCode.CLIENT_SIDE_SERVER_DOWN) .setDiagnosticMessage("Connection closed by server"); } } // First abort all outstanding requests. for (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 && !isDisconnectNotification) { 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. } } connection.closeSilently(); // Notify listeners. if (notifyClose) { for (final ConnectionEventListener listener : listeners) { listener.handleConnectionClosed(); } } if (notifyErrorOccurred) { for (final ConnectionEventListener listener : listeners) { // Use the reason provided in the disconnect notification. listener.handleConnectionError(isDisconnectNotification, newErrorResult(reason)); } } } LDAPOptions getLDAPOptions() { return options; } AbstractLDAPFutureResultImpl getPendingRequest(final Integer messageID) { return pendingRequests.get(messageID); } void handleUnsolicitedNotification(final ExtendedResult result) { if (isClosed()) { // Don't notify after connection is closed. return; } for (final ConnectionEventListener listener : listeners) { 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. 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. 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 (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 protocols, final List cipherSuites, final CompletionHandler completionHandler) throws IOException { synchronized (stateLock) { if (isTLSEnabled()) { throw new IllegalStateException("TLS already enabled"); } 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()])); SSLFilter sslFilter = new SSLFilter(null, sslEngineConfigurator); installFilter(sslFilter); sslFilter.handshake(connection, completionHandler); } } private void connectionErrorOccurred(final Result reason) { close(null, false, reason); } }