GrizzlyLDAPConnection.java revision 16bdb19cdda5201d272cd6ca5bf876c88493327c
/*
* 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
* 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-2015 ForgeRock AS
*/
/**
* LDAP connection implementation.
*/
final class GrizzlyLDAPConnection extends AbstractAsynchronousConnection implements TimeoutEventListener {
/**
* A dummy SSL client engine configurator as SSLFilter only needs client
* config. This prevents Grizzly from needlessly using JVM defaults which
* may be incorrectly configured.
*/
private static final SSLEngineConfigurator DUMMY_SSL_ENGINE_CONFIGURATOR;
static {
try {
} catch (GeneralSecurityException e) {
// This should never happen.
throw new IllegalStateException("Unable to create Dummy SSL Engine Configurator", e);
}
}
private final GrizzlyLDAPConnectionFactory factory;
private final ConcurrentHashMap<Integer, ResultLdapPromiseImpl<?, ?>> pendingRequests = new ConcurrentHashMap<>();
/** Guarded by stateLock. */
private Result connectionInvalidReason;
private boolean failedDueToDisconnect;
private boolean isClosed;
private boolean isFailed;
/**
* Create a LDAP Connection with provided Grizzly connection and LDAP
* connection factory.
*
* @param connection
* actual connection
* @param factory
* factory that provides LDAP connections
*/
final GrizzlyLDAPConnectionFactory factory) {
this.connection = connection;
}
/*
* Need to be careful here since both abandonAsync and Promise.cancel can
* be called separately by the client application. Therefore
* promise.cancel() should abandon the request, and abandonAsync should
* cancel the promise. In addition, bind or StartTLS requests cannot be
* abandoned.
*/
try {
synchronized (stateLock) {
/*
* If there is a bind or startTLS in progress then it must be
* this request which is being abandoned. The following check
* will prevent it from happening.
*/
}
} catch (final LdapException e) {
return newFailedLdapPromise(e);
}
// Remove the promise associated with the request to be abandoned.
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.
*/
}
/*
* This will cancel the promise, but will also recursively invoke this
* method. Since the pending request has been removed, there is no risk
* of an infinite loop.
*/
pendingRequest.cancel(false);
/*
* FIXME: there's a potential race condition here if a bind or startTLS
* is initiated just after we removed the pending request.
*/
return sendAbandonRequest(request);
}
try {
} catch (final IOException e) {
return newFailedLdapPromise(adaptRequestIOException(e));
} finally {
}
}
try {
synchronized (stateLock) {
}
try {
try {
} finally {
}
} catch (final IOException e) {
throw adaptRequestIOException(e);
}
} catch (final LdapException e) {
}
return promise;
}
final boolean notifyClose;
final boolean notifyErrorOccurred;
synchronized (stateLock) {
if (!isClosed) {
listeners = new CopyOnWriteArrayList<>();
}
}
}
if (notifyErrorOccurred) {
// Use the reason provided in the disconnect notification.
}
if (notifyClose) {
}
}
final BindClient context;
try {
} catch (final LdapException e) {
return newFailedLdapPromise(e, messageID);
}
final BindResultLdapPromiseImpl promise =
try {
synchronized (stateLock) {
if (!pendingRequests.isEmpty()) {
"There are other operations pending on this connection"));
return promise;
}
if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
"Bind or Start TLS operation in progress"));
return promise;
}
}
try {
try {
// Use the bind client to get the initial request instead of
// using the bind request passed to this method.
} finally {
}
} catch (final IOException e) {
bindOrStartTLSInProgress.set(false);
throw adaptRequestIOException(e);
}
} catch (final LdapException e) {
}
return promise;
}
// FIXME: I18N need to internationalize this message.
}
try {
synchronized (stateLock) {
}
try {
try {
} finally {
}
} catch (final IOException e) {
throw adaptRequestIOException(e);
}
} catch (final LdapException e) {
}
return promise;
}
try {
synchronized (stateLock) {
}
try {
try {
} finally {
}
} catch (final IOException e) {
throw adaptRequestIOException(e);
}
} catch (final LdapException e) {
}
return promise;
}
public <R extends ExtendedResult> LdapPromise<R> extendedRequestAsync(final ExtendedRequest<R> request,
final ExtendedResultLdapPromiseImpl<R> promise =
try {
synchronized (stateLock) {
if (!pendingRequests.isEmpty()) {
return promise;
} else if (isTLSEnabled()) {
return promise;
} else if (!bindOrStartTLSInProgress.compareAndSet(false, true)) {
return promise;
}
} else {
}
}
try {
try {
} finally {
}
} catch (final IOException e) {
bindOrStartTLSInProgress.set(false);
throw adaptRequestIOException(e);
}
} catch (final LdapException e) {
}
return promise;
}
public boolean isClosed() {
synchronized (stateLock) {
return isClosed;
}
}
public boolean isValid() {
synchronized (stateLock) {
return isValid0();
}
}
try {
synchronized (stateLock) {
}
try {
try {
} finally {
}
} catch (final IOException e) {
throw adaptRequestIOException(e);
}
} catch (final LdapException e) {
}
return promise;
}
try {
synchronized (stateLock) {
}
try {
try {
} finally {
}
} catch (final IOException e) {
throw adaptRequestIOException(e);
}
} catch (final LdapException e) {
}
return promise;
}
synchronized (stateLock) {
}
}
}
/** {@inheritDoc} */
final IntermediateResponseHandler intermediateResponseHandler, final SearchResultHandler entryHandler) {
final SearchResultLdapPromiseImpl promise =
try {
synchronized (stateLock) {
}
try {
try {
} finally {
}
} catch (final IOException e) {
throw adaptRequestIOException(e);
}
} catch (final LdapException e) {
}
return promise;
}
}
public long handleTimeout(final long currentTime) {
if (timeout <= 0) {
return 0;
}
continue;
}
if (diff > 0) {
// Will expire in diff milliseconds.
// Result arrived at the same time.
continue;
} else if (promise.isBindOrStartTLS()) {
/*
* No other operations can be performed while a bind or StartTLS
* request is active, so we cannot time out the request. We
* therefore have a choice: either ignore timeouts for these
* operations, or enforce them but doing so requires
* invalidating the connection. We'll do the latter, since
* ignoring timeouts could cause the application to hang.
*/
+ "(connection will be invalidated): ", promise));
// Fail the connection.
final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_TIMEOUT).setDiagnosticMessage(
} else {
/*
* FIXME: there's a potential race condition here if a bind or
* startTLS is initiated just after we check the boolean. It
* seems potentially even more dangerous to send the abandon
* request while holding the state lock, since a blocking write
* could hang the application.
*/
// if (!bindOrStartTLSInProgress.get()) {
// sendAbandonRequest(newAbandonRequest(promise.getRequestID()));
// }
}
}
return delay;
}
public long getTimeout() {
}
/**
* 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.
*/
final boolean notifyClose;
final boolean notifyErrorOccurred;
synchronized (stateLock) {
if (isClosed) {
// Already closed locally.
return;
} else if (unbindRequest != null) {
// Local close.
notifyClose = true;
notifyErrorOccurred = false;
isClosed = true;
if (connectionInvalidReason == null) {
}
} else if (isFailed) {
// Already failed.
return;
} else {
// Connection has failed and this is the first indication.
notifyClose = false;
notifyErrorOccurred = true;
isFailed = true;
}
}
// First abort all outstanding requests.
}
}
/*
* If this is the final client initiated close then release close the
* connection and release resources.
*/
if (notifyClose) {
try {
/*
* Underlying channel probably blown up. Ignore all errors,
* including possibly runtime exceptions (see OPENDJ-672).
*/
} finally {
}
}
// Notify listeners.
if (tmpListeners != null) {
if (notifyErrorOccurred) {
// Use the reason provided in the disconnect notification.
}
}
if (notifyClose) {
}
}
}
}
synchronized (stateLock) {
}
return newMsgID;
}
return factory.getLDAPOptions();
}
}
synchronized (stateLock) {
}
if (tmpListeners != null) {
}
}
}
/**
* filter.
*
* @param filter
* The filter to be installed.
*/
synchronized (stateLock) {
}
}
/**
* 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) {
return true;
}
}
return false;
}
}
}
void setBindOrStartTLSInProgress(final boolean state) {
}
void startTLS(final SSLContext sslContext, final List<String> protocols, final List<String> cipherSuites,
synchronized (stateLock) {
if (isTLSEnabled()) {
throw new IllegalStateException("TLS already enabled");
}
final SSLEngineConfigurator sslEngineConfigurator = new SSLEngineConfigurator(sslContext, true, false,
false);
}
}
// FIXME: what other sort of IOExceptions can be thrown?
// FIXME: Is this the best result code?
return newLdapException(errorResult);
}
private void checkBindOrStartTLSInProgress() throws LdapException {
if (bindOrStartTLSInProgress.get()) {
}
}
private void checkConnectionIsValid() throws LdapException {
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.
*/
} else {
}
}
}
}
private boolean isValid0() {
}
}