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

Nicolas Capponi
18.52.2013 5f06b4f9165af37f28859b3db4aa1c5a8d69d51a
Fix OPENDJ-41: Expose LDAP Grizzly filter APIs
This a a part of OPENDJ-175 - Decouple OpenDJ LDAP SDK from Grizzly
CR2491

* In GrizzlyLDAPListener and GrizzlyLDAPConnectionFactory classes :
- Default filter chain used for processing is built from transport instead of building it from scratch.
- Additional Grizzly filters can be provided by using transport argument passed to constructors (no change implied)
- Additional Grizzly filters are added between Transport filter and LDAP filter

* Add a new utility class GrizzlyUtils that provides buildFilterChain and addFilterToConnection methods
Use the utility class to factorize code to add a filter to the filter chain in LDAPServerFilter and GrizzlyLDAPConnection classes

* Add test case for the new GrizzlyUtils class
2 files added
4 files modified
302 ■■■■ changed files
opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java 21 ●●●●● patch | view | raw | blame | history
opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java 5 ●●●●● patch | view | raw | blame | history
opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPListener.java 16 ●●●● patch | view | raw | blame | history
opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyUtils.java 126 ●●●●● patch | view | raw | blame | history
opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPServerFilter.java 21 ●●●●● patch | view | raw | blame | history
opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyUtilsTestCase.java 113 ●●●●● patch | view | raw | blame | history
opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnection.java
@@ -84,7 +84,6 @@
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;
@@ -706,25 +705,7 @@
     */
    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);
            GrizzlyUtils.addFilterToConnection(filter, connection);
        }
    }
opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPConnectionFactory.java
@@ -53,8 +53,6 @@
import org.glassfish.grizzly.EmptyCompletionHandler;
import org.glassfish.grizzly.SocketConnectorHandler;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
import org.glassfish.grizzly.filterchain.TransportFilter;
import org.glassfish.grizzly.nio.transport.TCPNIOConnectorHandler;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
@@ -257,7 +255,8 @@
        this.socketAddress = address;
        this.options = new LDAPOptions(options);
        this.clientFilter = new LDAPClientFilter(new LDAPReader(this.options.getDecodeOptions()), 0);
        this.defaultFilterChain = FilterChainBuilder.stateless().add(new TransportFilter()).add(clientFilter).build();
        this.defaultFilterChain = GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), clientFilter);
    }
    @Override
opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyLDAPListener.java
@@ -40,8 +40,6 @@
import org.forgerock.opendj.ldap.ServerConnectionFactory;
import org.forgerock.opendj.ldap.spi.LDAPListenerImpl;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
import org.glassfish.grizzly.filterchain.TransportFilter;
import org.glassfish.grizzly.nio.transport.TCPNIOBindingHandler;
import org.glassfish.grizzly.nio.transport.TCPNIOServerConnection;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
@@ -53,7 +51,6 @@
 */
public final class GrizzlyLDAPListener implements LDAPListenerImpl {
    private final ReferenceCountedObject<TCPNIOTransport>.Reference transport;
    private final FilterChain defaultFilterChain;
    private final ServerConnectionFactory<LDAPClientContext, Integer> connectionFactory;
    private final TCPNIOServerConnection serverConnection;
    private final AtomicBoolean isClosed = new AtomicBoolean();
@@ -105,12 +102,11 @@
        this.connectionFactory = factory;
        final DecodeOptions decodeOptions = new DecodeOptions(options.getDecodeOptions());
        this.defaultFilterChain =
                FilterChainBuilder.stateless().add(new TransportFilter()).add(
                        new LDAPServerFilter(this, new LDAPReader(decodeOptions), options
                                .getMaxRequestSize())).build();
        final LDAPServerFilter serverFilter = new LDAPServerFilter(this, new LDAPReader(decodeOptions), options
                .getMaxRequestSize());
        final FilterChain ldapChain = GrizzlyUtils.buildFilterChain(this.transport.get().getProcessor(), serverFilter);
        final TCPNIOBindingHandler bindingHandler =
                TCPNIOBindingHandler.builder(this.transport.get()).processor(defaultFilterChain).build();
                TCPNIOBindingHandler.builder(this.transport.get()).processor(ldapChain).build();
        this.serverConnection = bindingHandler.bind(address, options.getBacklog());
    }
@@ -148,8 +144,4 @@
    ServerConnectionFactory<LDAPClientContext, Integer> getConnectionFactory() {
        return connectionFactory;
    }
    FilterChain getDefaultFilterChain() {
        return defaultFilterChain;
    }
}
opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/GrizzlyUtils.java
New file
@@ -0,0 +1,126 @@
/*
 * 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 com.forgerock.opendj.grizzly;
import org.glassfish.grizzly.Processor;
import org.glassfish.grizzly.filterchain.Filter;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
import org.glassfish.grizzly.filterchain.TransportFilter;
import org.glassfish.grizzly.ssl.SSLFilter;
/**
 * Common utility methods.
 */
final class GrizzlyUtils {
    /**
     * Build a filter chain from the provided processor if possible and the
     * provided filter.
     * <p>
     * If the provided processor can't be used for building the new filter
     * chain, then a chain with only a {@code TransportFilter} is used as a base
     * chain.
     *
     * @param processor
     *            processor to build the filter chain from. If the processor is
     *            not a filter chain (for example, it can be a
     *            {@code StandaloneProcessor} then it is ignored to build the
     *            returned filter chain
     * @param filter
     *            filter to add at the end of the filter chain
     * @return a new filter chain, based on the provided processor if processor
     *         is a {@code FilterChain}, and having the provided filter as the
     *         last filter
     */
    public static FilterChain buildFilterChain(Processor processor, Filter filter) {
        if (processor instanceof FilterChain) {
            return FilterChainBuilder.stateless().addAll((FilterChain) processor).add(filter).build();
        } else {
            return FilterChainBuilder.stateless().add(new TransportFilter()).add(filter).build();
        }
    }
    /**
     * Adds a filter to filter chain registered with the given connection.
     * <p>
     * For a non-SSL filter, filter is added at the last position before the
     * LDAP filter.
     * <p>
     * For a SSL filter, filter is added before any
     * {@code ConnectionSecurityLayerFilter} which is already present in the
     * filter chain.
     *
     * @param filter
     *            filter to add
     * @param connection
     *            connection to update with the new filter chain containing the
     *            provided filter
     */
    public static void addFilterToConnection(final Filter filter, org.glassfish.grizzly.Connection<?> connection) {
        final FilterChain currentChain = (FilterChain) connection.getProcessor();
        final FilterChain newChain = addFilterToChain(filter, currentChain);
        connection.setProcessor(newChain);
    }
    /**
     * Adds a filter to a filter chain.
     * <p>
     * For a non-SSL filter, filter is added at the last position before the
     * LDAP filter.
     * <p>
     * For a SSL filter, filter is added before any
     * {@code ConnectionSecurityLayerFilter} which is already present in the
     * filter chain.
     *
     * @param filter
     *            filter to add
     * @param chain
     *            initial filter chain
     * @return a new filter chain which includes the provided filter
     */
    public static FilterChain addFilterToChain(final Filter filter, final FilterChain chain) {
        // By default, before LDAP filter which is the last one
        int indexToAddFilter = chain.size() - 1;
        if (filter instanceof SSLFilter) {
            // Before any ConnectionSecurityLayerFilters if present
            for (int i = chain.size() - 2; i >= 0; i--) {
                if (!(chain.get(i) instanceof ConnectionSecurityLayerFilter)) {
                    indexToAddFilter = i + 1;
                    break;
                }
            }
        }
        return FilterChainBuilder.stateless().addAll(chain).add(indexToAddFilter, filter).build();
    }
    // Prevent instantiation.
    private GrizzlyUtils() {
        // No implementation required.
    }
}
opendj3/opendj-grizzly/src/main/java/com/forgerock/opendj/grizzly/LDAPServerFilter.java
@@ -76,7 +76,6 @@
import org.glassfish.grizzly.filterchain.BaseFilter;
import org.glassfish.grizzly.filterchain.Filter;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
@@ -360,25 +359,7 @@
         *            The filter to be installed.
         */
        private void installFilter(final Filter filter) {
            // 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);
            GrizzlyUtils.addFilterToConnection(filter, connection);
        }
        /**
opendj3/opendj-grizzly/src/test/java/com/forgerock/opendj/grizzly/GrizzlyUtilsTestCase.java
New file
@@ -0,0 +1,113 @@
/*
 * 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 com.forgerock.opendj.grizzly;
import static org.fest.assertions.Assertions.*;
import org.forgerock.opendj.ldap.SdkTestCase;
import org.glassfish.grizzly.StandaloneProcessor;
import org.glassfish.grizzly.filterchain.BaseFilter;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
import org.glassfish.grizzly.filterchain.TransportFilter;
import org.glassfish.grizzly.ssl.SSLFilter;
import org.testng.annotations.Test;
@SuppressWarnings("javadoc")
public class GrizzlyUtilsTestCase extends SdkTestCase {
    private static final class DummyLDAPFilter extends BaseFilter { // only need type
    }
    private static final class FilterOne extends BaseFilter { // only need type
    }
    private static final class DummySSLFilter extends SSLFilter { // only need type
    }
    /**
     * Default filter chain contains a transport filter and a ldap filter.
     */
    private FilterChain getDefaultFilterChain() {
        return FilterChainBuilder.stateless().
                add(new TransportFilter()).add(new DummyLDAPFilter()).build();
    }
    @Test
    public void addFilterToChain() throws Exception {
        final FilterChain chain = GrizzlyUtils.addFilterToChain(new FilterOne(), getDefaultFilterChain());
        assertThat(chain.indexOfType(TransportFilter.class)).isEqualTo(0);
        assertThat(chain.indexOfType(FilterOne.class)).isEqualTo(1);
        assertThat(chain.indexOfType(DummyLDAPFilter.class)).isEqualTo(2);
        assertThat(chain.size()).isEqualTo(3);
    }
    @Test
    public void addSSLFilterToChain() throws Exception {
        final FilterChain chain = GrizzlyUtils.addFilterToChain(new DummySSLFilter(), getDefaultFilterChain());
        assertThat(chain.indexOfType(TransportFilter.class)).isEqualTo(0);
        assertThat(chain.indexOfType(DummySSLFilter.class)).isEqualTo(1);
        assertThat(chain.indexOfType(DummyLDAPFilter.class)).isEqualTo(2);
        assertThat(chain.size()).isEqualTo(3);
    }
    @Test
    public void addConnectionSecurityLayerAndSSLFilterToChain() throws Exception {
        final FilterChain chain = GrizzlyUtils.addFilterToChain(new ConnectionSecurityLayerFilter(null, null),
                getDefaultFilterChain());
        final FilterChain sslChain = GrizzlyUtils.addFilterToChain(new DummySSLFilter(), chain);
        // SSLFilter must be beneath ConnectionSecurityLayerFilter
        assertThat(sslChain.indexOfType(TransportFilter.class)).isEqualTo(0);
        assertThat(sslChain.indexOfType(DummySSLFilter.class)).isEqualTo(1);
        assertThat(sslChain.indexOfType(ConnectionSecurityLayerFilter.class)).isEqualTo(2);
        assertThat(sslChain.indexOfType(DummyLDAPFilter.class)).isEqualTo(3);
        assertThat(sslChain.size()).isEqualTo(4);
    }
    @Test
    public void buildFilterChainFromFilterChainProcessor() throws Exception {
        final FilterChain chain = GrizzlyUtils.buildFilterChain(
                FilterChainBuilder.stateless().add(new TransportFilter()).build(), new DummyLDAPFilter());
        assertThat(chain.indexOfType(TransportFilter.class)).isEqualTo(0);
        assertThat(chain.indexOfType(DummyLDAPFilter.class)).isEqualTo(1);
        assertThat(chain.size()).isEqualTo(2);
    }
    @Test
    public void buildFilterChainFromNonFilterChainProcessor() throws Exception {
        final FilterChain chain = GrizzlyUtils.buildFilterChain(new StandaloneProcessor(), new DummyLDAPFilter());
        assertThat(chain.indexOfType(TransportFilter.class)).isEqualTo(0);
        assertThat(chain.indexOfType(DummyLDAPFilter.class)).isEqualTo(1);
        assertThat(chain.size()).isEqualTo(2);
    }
}