/*
* 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 2013 ForgeRock AS.
*/
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.StaticUtils.closeSilently;
import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.opendj.ldap.TestCaseUtils.findFreeSocketAddress;
import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.forgerock.opendj.ldap.requests.AbandonRequest;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.requests.UnbindRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.mockito.stubbing.Stubber;
import org.testng.annotations.Test;
/**
* Tests the {@link LDAPConnectionFactory} class.
*/
@SuppressWarnings({ "javadoc", "unchecked" })
public class LDAPConnectionFactoryTestCase extends SdkTestCase {
// Test timeout for tests which need to wait for network events.
private static final long TEST_TIMEOUT = 30L;
/**
* Unit test for OPENDJ-1247: a locally timed out bind request will leave a
* connection in an invalid state since a bind (or startTLS) is in progress
* and no other operations can be performed. Therefore, a timeout should
* cause the connection to become invalid and an appropriate connection
* event sent. In addition, no abandon request should be sent.
*
* This test is failing because the connection remains valid.
*/
@Test(enabled = false)
public void testClientSideTimeoutForBindRequest() throws Exception {
final AtomicReference context = new AtomicReference();
final Semaphore latch = new Semaphore(0);
// The server connection should receive a bind, but no abandon request.
final ServerConnection serverConnection = mock(ServerConnection.class);
release(latch).when(serverConnection).handleBind(any(Integer.class), anyInt(),
any(BindRequest.class), any(IntermediateResponseHandler.class),
any(ResultHandler.class));
release(latch).when(serverConnection).handleConnectionClosed(any(Integer.class),
any(UnbindRequest.class));
final LDAPListener server = createServer(latch, context, serverConnection);
final ConnectionFactory factory =
new LDAPConnectionFactory(server.getSocketAddress(), new LDAPOptions().setTimeout(
1, TimeUnit.MILLISECONDS));
Connection connection = null;
try {
// Connect to the server.
connection = factory.getConnection();
// Wait for the server to accept the connection.
assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
/*
* A bind request timeout should cause the connection to fail, so
* ensure that event listeners are fired.
*/
final ConnectionEventListener listener = mock(ConnectionEventListener.class);
connection.addConnectionEventListener(listener);
final ResultHandler handler = mock(ResultHandler.class);
final FutureResult future =
connection.bindAsync(newSimpleBindRequest(), null, handler);
// Wait for the server to receive the bind request.
assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
// Wait for the request to timeout.
try {
future.get(TEST_TIMEOUT, TimeUnit.SECONDS);
} catch (TimeoutResultException e) {
assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_TIMEOUT);
verify(handler).handleErrorResult(same(e));
// The connection should no longer be valid.
verify(listener).handleConnectionError(eq(false), same(e));
assertThat(connection.isValid()).isFalse();
connection.close();
// Wait for the server to receive the close request.
assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
/*
* Check that the only interactions were the bind and the close
* and specifically there was no abandon request.
*/
verify(serverConnection).handleBind(any(Integer.class), eq(3),
any(BindRequest.class), any(IntermediateResponseHandler.class),
any(ResultHandler.class));
verify(serverConnection).handleConnectionClosed(any(Integer.class),
any(UnbindRequest.class));
verifyNoMoreInteractions(serverConnection);
}
} finally {
closeSilently(connection);
factory.close();
server.close();
}
}
/**
* Unit test for OPENDJ-1247: a locally timed out request which is not a
* bind or startTLS should result in a client side timeout error, but the
* connection should remain valid. In addition, no abandon request should be
* sent.
*
* This test is failing because no abandon request is received.
*/
@Test(enabled = false)
public void testClientSideTimeoutForSearchRequest() throws Exception {
final AtomicReference context = new AtomicReference();
final Semaphore latch = new Semaphore(0);
// The server connection should receive a search and then an abandon.
final ServerConnection serverConnection = mock(ServerConnection.class);
release(latch).when(serverConnection).handleSearch(any(Integer.class),
any(SearchRequest.class), any(IntermediateResponseHandler.class),
any(SearchResultHandler.class));
release(latch).when(serverConnection).handleAbandon(any(Integer.class),
any(AbandonRequest.class));
final LDAPListener server = createServer(latch, context, serverConnection);
final ConnectionFactory factory =
new LDAPConnectionFactory(server.getSocketAddress(), new LDAPOptions().setTimeout(
1, TimeUnit.MILLISECONDS));
Connection connection = null;
try {
// Connect to the server.
connection = factory.getConnection();
// Wait for the server to accept the connection.
assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
/*
* A search request timeout should not cause the connection to fail,
* so ensure that event listeners are not fired.
*/
final ConnectionEventListener listener = mock(ConnectionEventListener.class);
connection.addConnectionEventListener(listener);
final ResultHandler handler = mock(ResultHandler.class);
final FutureResult future =
connection.readEntryAsync(DN.valueOf("cn=test"), null, handler);
// Wait for the server to receive the search request.
assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
// Wait for the request to timeout.
try {
future.get(TEST_TIMEOUT, TimeUnit.SECONDS);
} catch (TimeoutResultException e) {
assertThat(e.getResult().getResultCode()).isEqualTo(ResultCode.CLIENT_SIDE_TIMEOUT);
verify(handler).handleErrorResult(same(e));
// The connection should still be valid.
verifyZeroInteractions(listener);
assertThat(connection.isValid()).isTrue();
// Wait for the server to receive the abandon request.
assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
}
} finally {
closeSilently(connection);
factory.close();
server.close();
}
}
/**
* This unit test exposes the bug raised in issue OPENDJ-1156: NPE in
* ReferenceCountedObject after shutting down directory.
*/
@Test
public void testResourceManagement() throws Exception {
final AtomicReference context = new AtomicReference();
final Semaphore latch = new Semaphore(0);
final LDAPListener server = createServer(latch, context, mock(ServerConnection.class));
final ConnectionFactory factory = new LDAPConnectionFactory(server.getSocketAddress());
try {
for (int i = 0; i < 100; i++) {
// Connect to the server.
final Connection connection = factory.getConnection();
try {
// Wait for the server to accept the connection.
assertThat(latch.tryAcquire(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue();
final MockConnectionEventListener listener = new MockConnectionEventListener();
connection.addConnectionEventListener(listener);
// Perform remote disconnect which will trigger a client side connection error.
context.get().disconnect();
// Wait for the error notification to reach the client.
listener.awaitError(TEST_TIMEOUT, TimeUnit.SECONDS);
} finally {
connection.close();
}
}
} finally {
factory.close();
server.close();
}
}
private LDAPListener createServer(final Semaphore latch,
final AtomicReference context,
final ServerConnection serverConnection) throws IOException {
return new LDAPListener(findFreeSocketAddress(),
new ServerConnectionFactory() {
@Override
public ServerConnection handleAccept(
final LDAPClientContext clientContext) throws ErrorResultException {
context.set(clientContext);
latch.release();
return serverConnection;
}
});
}
private Stubber release(final Semaphore latch) {
return doAnswer(new Answer() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
latch.release();
return null;
}
});
}
}