mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
12.46.2012 7983705a0ce4de2fd6e7a4061fe4afa0f9e8c66a
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/ConnectionFactoryTestCase.java
@@ -22,13 +22,15 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS
 *      Portions copyright 2011-2012 ForgeRock AS
 */
package org.forgerock.opendj.ldap;
import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -37,16 +39,22 @@
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import javax.net.ssl.SSLContext;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.DigestMD5SASLBindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.BindResult;
import org.forgerock.opendj.ldap.responses.Responses;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.SchemaBuilder;
@@ -65,6 +73,10 @@
 */
@SuppressWarnings("javadoc")
public class ConnectionFactoryTestCase extends SdkTestCase {
    // Test timeout in ms for tests which need to wait for network events.
    private static final long TEST_TIMEOUT = 30L;
    private static final long TEST_TIMEOUT_MS = TEST_TIMEOUT * 1000L;
    class MyResultHandler implements ResultHandler<Connection> {
        // latch.
        private final CountDownLatch latch;
@@ -114,8 +126,8 @@
        StaticUtils.DEBUG_LOG.setLevel(Level.INFO);
    }
    @DataProvider(name = "connectionFactories")
    public Object[][] getConnectionFactories() throws Exception {
    @DataProvider
    Object[][] connectionFactories() throws Exception {
        Object[][] factories = new Object[21][1];
        // HeartBeatConnectionFactory
@@ -429,4 +441,228 @@
        assertThat(realConnectionIsClosed[2]).isTrue();
        assertThat(realConnectionIsClosed[3]).isTrue();
    }
    private static final class CloseNotify {
        private boolean closeOnAccept;
        private boolean doBindFirst;
        private boolean useEventListener;
        private boolean sendDisconnectNotification;
        private CloseNotify(boolean closeOnAccept, boolean doBindFirst, boolean useEventListener,
                boolean sendDisconnectNotification) {
            this.closeOnAccept = closeOnAccept;
            this.doBindFirst = doBindFirst;
            this.useEventListener = useEventListener;
            this.sendDisconnectNotification = sendDisconnectNotification;
        }
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("[");
            if (closeOnAccept) {
                builder.append(" closeOnAccept");
            }
            if (doBindFirst) {
                builder.append(" doBindFirst");
            }
            if (useEventListener) {
                builder.append(" useEventListener");
            }
            if (sendDisconnectNotification) {
                builder.append(" sendDisconnectNotification");
            }
            builder.append(" ]");
            return builder.toString();
        }
    }
    @DataProvider
    Object[][] closeNotifyConfig() {
        // @formatter:off
        return new Object[][] {
            // closeOnAccept, doBindFirst, useEventListener, sendDisconnectNotification
            // Close on accept.
            { new CloseNotify(true,  false, false, false) },
            { new CloseNotify(true,  false, true,  false) },
            // Use disconnect.
            { new CloseNotify(false, false, false, false) },
            { new CloseNotify(false, false, false, true) },
            { new CloseNotify(false, false, true,  false) },
            { new CloseNotify(false, false, true,  true) },
            { new CloseNotify(false, true,  false, false) },
            { new CloseNotify(false, true,  false, true) },
            { new CloseNotify(false, true,  true,  false) },
            { new CloseNotify(false, true,  true,  true) },
        };
        // @formatter:on
    }
    @SuppressWarnings("unchecked")
    @Test(dataProvider = "closeNotifyConfig")
    public void testCloseNotify(final CloseNotify config) throws Exception {
        final CountDownLatch connectLatch = new CountDownLatch(1);
        final AtomicReference<LDAPClientContext> contextHolder =
                new AtomicReference<LDAPClientContext>();
        final ServerConnectionFactory<LDAPClientContext, Integer> mockServer =
                mock(ServerConnectionFactory.class);
        when(mockServer.handleAccept(any(LDAPClientContext.class))).thenAnswer(
                new Answer<ServerConnection<Integer>>() {
                    public ServerConnection<Integer> answer(InvocationOnMock invocation)
                            throws Throwable {
                        // Allow the context to be accessed from outside the mock.
                        contextHolder.set((LDAPClientContext) invocation.getArguments()[0]);
                        connectLatch.countDown(); /* is this needed? */
                        if (config.closeOnAccept) {
                            throw newErrorResult(ResultCode.UNAVAILABLE);
                        } else {
                            // Return a mock connection which always succeeds for binds.
                            ServerConnection<Integer> mockConnection = mock(ServerConnection.class);
                            doAnswer(new Answer<Void>() {
                                @Override
                                public Void answer(InvocationOnMock invocation) throws Throwable {
                                    ResultHandler<? super BindResult> resultHandler =
                                            (ResultHandler<? super BindResult>) invocation
                                                    .getArguments()[4];
                                    resultHandler.handleResult(Responses
                                            .newBindResult(ResultCode.SUCCESS));
                                    return null;
                                }
                            }).when(mockConnection).handleBind(anyInt(), anyInt(),
                                    any(BindRequest.class), any(IntermediateResponseHandler.class),
                                    any(ResultHandler.class));
                            return mockConnection;
                        }
                    }
                });
        final int port = TestCaseUtils.findFreePort();
        LDAPListener listener = new LDAPListener(port, mockServer);
        try {
            LDAPConnectionFactory clientFactory = new LDAPConnectionFactory("localhost", port);
            final Connection client = clientFactory.getConnection();
            connectLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
            MockConnectionEventListener mockListener = null;
            try {
                if (config.useEventListener) {
                    mockListener = new MockConnectionEventListener();
                    client.addConnectionEventListener(mockListener);
                }
                if (config.doBindFirst) {
                    client.bind("cn=test", "password".toCharArray());
                }
                if (!config.closeOnAccept) {
                    // Disconnect using client context.
                    LDAPClientContext context = contextHolder.get();
                    assertThat(context).isNotNull();
                    assertThat(context.isClosed()).isFalse();
                    if (config.sendDisconnectNotification) {
                        context.disconnect(ResultCode.BUSY, "busy");
                    } else {
                        context.disconnect();
                    }
                    assertThat(context.isClosed()).isTrue();
                }
                // Block until remote close is signalled.
                if (mockListener != null) {
                    // Block using listener.
                    mockListener.awaitError(TEST_TIMEOUT, TimeUnit.SECONDS);
                    assertThat(mockListener.getInvocationCount()).isEqualTo(1);
                    assertThat(mockListener.isDisconnectNotification()).isEqualTo(
                            config.sendDisconnectNotification);
                    assertThat(mockListener.getError()).isNotNull();
                } else {
                    // Block by spinning on isValid.
                    waitForCondition(new Callable<Boolean>() {
                        @Override
                        public Boolean call() throws Exception {
                            return !client.isValid();
                        }
                    });
                }
                assertThat(client.isValid()).isFalse();
                assertThat(client.isClosed()).isFalse();
            } finally {
                client.close();
            }
            // Check state after remote close and local close.
            assertThat(client.isValid()).isFalse();
            assertThat(client.isClosed()).isTrue();
            if (mockListener != null) {
                mockListener.awaitClose(TEST_TIMEOUT, TimeUnit.SECONDS);
                assertThat(mockListener.getInvocationCount()).isEqualTo(2);
            }
        } finally {
            listener.close();
        }
    }
    @SuppressWarnings("unchecked")
    @Test
    public void testUnsolicitedNotifications() throws Exception {
        final CountDownLatch connectLatch = new CountDownLatch(1);
        final AtomicReference<LDAPClientContext> contextHolder =
                new AtomicReference<LDAPClientContext>();
        final ServerConnectionFactory<LDAPClientContext, Integer> mockServer =
                mock(ServerConnectionFactory.class);
        when(mockServer.handleAccept(any(LDAPClientContext.class))).thenAnswer(
                new Answer<ServerConnection<Integer>>() {
                    public ServerConnection<Integer> answer(InvocationOnMock invocation)
                            throws Throwable {
                        // Allow the context to be accessed from outside the mock.
                        contextHolder.set((LDAPClientContext) invocation.getArguments()[0]);
                        connectLatch.countDown(); /* is this needed? */
                        return mock(ServerConnection.class);
                    }
                });
        final int port = TestCaseUtils.findFreePort();
        LDAPListener listener = new LDAPListener(port, mockServer);
        try {
            LDAPConnectionFactory clientFactory = new LDAPConnectionFactory("localhost", port);
            final Connection client = clientFactory.getConnection();
            connectLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
            try {
                MockConnectionEventListener mockListener = new MockConnectionEventListener();
                client.addConnectionEventListener(mockListener);
                // Send notification.
                LDAPClientContext context = contextHolder.get();
                assertThat(context).isNotNull();
                context.sendUnsolicitedNotification(Responses.newGenericExtendedResult(
                        ResultCode.OTHER).setOID("1.2.3.4"));
                assertThat(context.isClosed()).isFalse();
                // Block using listener.
                mockListener.awaitNotification(TEST_TIMEOUT, TimeUnit.SECONDS);
                assertThat(mockListener.getInvocationCount()).isEqualTo(1);
                assertThat(mockListener.getNotification()).isNotNull();
                assertThat(mockListener.getNotification().getResultCode()).isEqualTo(
                        ResultCode.OTHER);
                assertThat(mockListener.getNotification().getOID()).isEqualTo("1.2.3.4");
                assertThat(client.isValid()).isTrue();
                assertThat(client.isClosed()).isFalse();
            } finally {
                client.close();
            }
        } finally {
            listener.close();
        }
    }
    private void waitForCondition(Callable<Boolean> condition) throws Exception {
        long timeout = System.currentTimeMillis() + TEST_TIMEOUT_MS;
        while (!condition.call()) {
            Thread.yield();
            if (System.currentTimeMillis() > timeout) {
                throw new TimeoutException("Test timed out after " + TEST_TIMEOUT + " seconds");
            }
        }
    }
}