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

Mark Craig
18.07.2012 bf5f8327f7e22b03cf1807aac9fbd0845346c8bb
Draft example that does very basic DN and attribute rewriting
1 files added
1 files modified
762 ■■■■■ changed files
opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java 684 ●●●●● patch | view | raw | blame | history
opendj3/src/main/docbkx/dev-guide/chap-simple-proxy.xml 78 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
New file
@@ -0,0 +1,684 @@
/*
 * 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 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2011-2012 ForgeRock AS
 */
package org.forgerock.opendj.examples;
import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
import java.io.IOException;
import java.util.List;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.Connections;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.IntermediateResponseHandler;
import org.forgerock.opendj.ldap.LDAPClientContext;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPListener;
import org.forgerock.opendj.ldap.LDAPListenerOptions;
import org.forgerock.opendj.ldap.LinkedAttribute;
import org.forgerock.opendj.ldap.Modification;
import org.forgerock.opendj.ldap.RequestContext;
import org.forgerock.opendj.ldap.RequestHandler;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.ResultHandler;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.ServerConnectionFactory;
import org.forgerock.opendj.ldap.controls.Control;
import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.CancelExtendedRequest;
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.ModifyDNRequest;
import org.forgerock.opendj.ldap.requests.ModifyRequest;
import org.forgerock.opendj.ldap.requests.Request;
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.responses.BindResult;
import org.forgerock.opendj.ldap.responses.CompareResult;
import org.forgerock.opendj.ldap.responses.ExtendedResult;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.responses.SearchResultReference;
/**
 * This example is based on the {@link Proxy}. This example does no load
 * balancing, but instead rewrites attribute descriptions and DN suffixes in
 * requests to and responses from a directory server using hard coded
 * configuration.
 * <ul>
 * <li>It transforms DNs ending in {@code o=example} on the client side to end
 * in {@code dc=example,dc=com} on the server side and vice versa.
 * <li>It transforms the attribute description {@code fullname} on the client
 * side to {@code cn} on the server side and vice versa.
 * </ul>
 *
 * This example has a number of restrictions.
 * <ul>
 * <li>It does not support SSL connections.
 * <li>It does not support StartTLS.
 * <li>It does not support Abandon or Cancel requests.
 * <li>It has very basic authentication and authorization support.
 * <li>It does not rewrite bind DNs.
 * <li>It uses proxied authorization, so if you use OpenDJ directory server, you
 * must set the {@code proxied-auth} privilege for the proxy user.
 * <li>It does not touch matched DNs in results.
 * <li>It does not rewrite attributes with options in search result entries.
 * <li>It does not touch search result references.
 * <li>It does not rewrite LDAP Controls or Extended Operations, except to
 * rewrite the DN used as authorization ID for proxied authorization.
 * </ul>
 * This example takes the following command line parameters:
 *
 * <pre>
 *  &lt;localAddress> &lt;localPort> &lt;proxyDN> &lt;proxyPassword> &lt;serverAddress> &lt;serverPort>
 * </pre>
 *
 * If you have imported the users from <a
 * href="http://opendj.forgerock.org/Example.ldif">Example.ldif</a>, then you
 * can set {@code proxyUserDN} to {@code cn=My App,ou=Apps,dc=example,dc=com}
 * and {@code proxyUserPassword} to {@code password}.
 */
public final class RewriterProxy {
    private static final class ProxyBackend implements RequestHandler<RequestContext> {
        // This example hard codes the attribute...
        private final String clientAttributeTypeName = "fullname";
        private final String serverAttributeTypeName = "cn";
        private final AttributeDescription clientAttributeDescription =
                AttributeDescription.valueOf(clientAttributeTypeName);
        private final AttributeDescription serverAttributeDescription =
                AttributeDescription.valueOf(serverAttributeTypeName);
        // ...and DN rewriting configuration.
        private final CharSequence clientSuffix = "o=example";
        private final CharSequence serverSuffix = "dc=example,dc=com";
        private final ConnectionFactory factory;
        private final ConnectionFactory bindFactory;
        private ProxyBackend(final ConnectionFactory factory, final ConnectionFactory bindFactory) {
            this.factory = factory;
            this.bindFactory = bindFactory;
        }
        private abstract class AbstractRequestCompletionHandler
                <R extends Result, H extends ResultHandler<? super R>>
                implements ResultHandler<R> {
            final H resultHandler;
            final Connection connection;
            AbstractRequestCompletionHandler(final Connection connection, final H resultHandler) {
                this.connection = connection;
                this.resultHandler = resultHandler;
            }
            @Override
            public final void handleErrorResult(final ErrorResultException error) {
                connection.close();
                resultHandler.handleErrorResult(error);
            }
            @Override
            public final void handleResult(final R result) {
                connection.close();
                resultHandler.handleResult(result);
            }
        }
        private abstract class ConnectionCompletionHandler<R extends Result> implements
                ResultHandler<Connection> {
            private final ResultHandler<? super R> resultHandler;
            ConnectionCompletionHandler(final ResultHandler<? super R> resultHandler) {
                this.resultHandler = resultHandler;
            }
            @Override
            public final void handleErrorResult(final ErrorResultException error) {
                resultHandler.handleErrorResult(error);
            }
            @Override
            public abstract void handleResult(Connection connection);
        }
        private final class RequestCompletionHandler<R extends Result> extends
                AbstractRequestCompletionHandler<R, ResultHandler<? super R>> {
            RequestCompletionHandler(final Connection connection,
                    final ResultHandler<? super R> resultHandler) {
                super(connection, resultHandler);
            }
        }
        private final class SearchRequestCompletionHandler extends
                AbstractRequestCompletionHandler<Result, SearchResultHandler> implements
                SearchResultHandler {
            SearchRequestCompletionHandler(final Connection connection,
                    final SearchResultHandler resultHandler) {
                super(connection, resultHandler);
            }
            /**
             * {@inheritDoc}
             */
            @Override
            public final boolean handleEntry(SearchResultEntry entry) {
                return resultHandler.handleEntry(rewrite(entry));
            }
            private SearchResultEntry rewrite(SearchResultEntry entry) {
                // Replace server attributes with client attributes.
                // TODO: Handle attributes with options
                Attribute serverAttribute = entry.getAttribute(
                        serverAttributeDescription);
                Attribute clientAttribute = new LinkedAttribute(
                        clientAttributeDescription, serverAttribute.toArray());
                entry.addAttribute(clientAttribute);
                entry.removeAttribute(serverAttributeDescription);
                // Transform the server DN suffix into a client DN suffix.
                return entry.setName(entry.getName().toString()
                        .replace(serverSuffix, clientSuffix));
            }
            /**
             * {@inheritDoc}
             */
            @Override
            public final boolean handleReference(final SearchResultReference reference) {
                return resultHandler.handleReference(reference);
            }
        }
        private volatile ProxiedAuthV2RequestControl proxiedAuthControl = null;
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleAdd(final RequestContext requestContext, final AddRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            addProxiedAuthControl(request);
            final ConnectionCompletionHandler<Result> outerHandler =
                    new ConnectionCompletionHandler<Result>(resultHandler) {
                        @Override
                        public void handleResult(final Connection connection) {
                            final RequestCompletionHandler<Result> innerHandler =
                                    new RequestCompletionHandler<Result>(connection, resultHandler);
                            connection.addAsync(rewrite(request), intermediateResponseHandler, innerHandler);
                        }
                        private AddRequest rewrite(final AddRequest request) {
                            // Transform the client DN into a server DN.
                            AddRequest rewrittenRequest =
                                    Requests.copyOfAddRequest(request);
                            rewrittenRequest.setName(request.getName().toString()
                                    .replace(clientSuffix, serverSuffix));
                            // Transform the client attribute names into server
                            // attribute names, fullname;lang-fr ==> cn;lang-fr.
                            for (Attribute clientAttribute
                                    : request.getAllAttributes(clientAttributeDescription)) {
                                if (clientAttribute != null) {
                                    String attrDesc = clientAttribute
                                            .getAttributeDescriptionAsString()
                                            .replaceFirst(clientAttributeTypeName,
                                                          serverAttributeTypeName);
                                    Attribute serverAttribute =
                                            new LinkedAttribute(
                                                    AttributeDescription.valueOf(attrDesc),
                                                    clientAttribute.toArray());
                                    rewrittenRequest.addAttribute(serverAttribute);
                                    rewrittenRequest.removeAttribute(
                                            clientAttribute.getAttributeDescription());
                                }
                            }
                            return rewrittenRequest;
                        }
                    };
            factory.getConnectionAsync(outerHandler);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleBind(final RequestContext requestContext, final int version,
                final BindRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super BindResult> resultHandler) {
            if (request.getAuthenticationType() != ((byte) 0x80)) {
                // TODO: SASL authentication not implemented.
                resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
                        "non-SIMPLE authentication not supported: "
                                + request.getAuthenticationType()));
            } else {
                // Authenticate using a separate bind connection pool, because
                // we don't want to change the state of the pooled connection.
                final ConnectionCompletionHandler<BindResult> outerHandler =
                        new ConnectionCompletionHandler<BindResult>(resultHandler) {
                            @Override
                            public void handleResult(final Connection connection) {
                                final ResultHandler<BindResult> innerHandler =
                                        new ResultHandler<BindResult>() {
                                            @Override
                                            public final void handleErrorResult(
                                                    final ErrorResultException error) {
                                                connection.close();
                                                resultHandler.handleErrorResult(error);
                                            }
                                            @Override
                                            public final void handleResult(final BindResult result) {
                                                connection.close();
                                                proxiedAuthControl =
                                                        ProxiedAuthV2RequestControl
                                                                .newControl("dn:"
                                                                        + request.getName());
                                                resultHandler.handleResult(result);
                                            }
                                        };
                                connection.bindAsync(rewrite(request), intermediateResponseHandler,
                                        innerHandler);
                            }
                            private BindRequest rewrite(final BindRequest request) {
                                // TODO: Transform client DN into server DN.
                                return request;
                            }
                        };
                proxiedAuthControl = null;
                bindFactory.getConnectionAsync(outerHandler);
            }
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleCompare(final RequestContext requestContext,
                final CompareRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super CompareResult> resultHandler) {
            addProxiedAuthControl(request);
            final ConnectionCompletionHandler<CompareResult> outerHandler =
                    new ConnectionCompletionHandler<CompareResult>(resultHandler) {
                        @Override
                        public void handleResult(final Connection connection) {
                            final RequestCompletionHandler<CompareResult> innerHandler =
                                    new RequestCompletionHandler<CompareResult>(connection,
                                            resultHandler);
                            connection.compareAsync(rewrite(request), intermediateResponseHandler,
                                    innerHandler);
                        }
                        private CompareRequest rewrite(CompareRequest request) {
                            // Transform the client attribute name into a server
                            // attribute name, fullname;lang-fr ==> cn;lang-fr.
                            String attrName = request.getAttributeDescription().toString();
                            if (attrName.toLowerCase().startsWith(
                                    clientAttributeTypeName.toLowerCase())) {
                                String rewrittenAttrName = attrName
                                        .replaceFirst(clientAttributeTypeName,
                                                      serverAttributeTypeName);
                                request.setAttributeDescription(
                                        AttributeDescription.valueOf(
                                                rewrittenAttrName));
                            }
                            // Transform the client DN into a server DN.
                            return request.setName(request.getName().toString()
                                    .replace(clientSuffix, serverSuffix));
                        }
                    };
            factory.getConnectionAsync(outerHandler);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleDelete(final RequestContext requestContext, final DeleteRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            addProxiedAuthControl(request);
            final ConnectionCompletionHandler<Result> outerHandler =
                    new ConnectionCompletionHandler<Result>(resultHandler) {
                        @Override
                        public void handleResult(final Connection connection) {
                            final RequestCompletionHandler<Result> innerHandler =
                                    new RequestCompletionHandler<Result>(connection, resultHandler);
                            connection.deleteAsync(rewrite(request), intermediateResponseHandler,
                                    innerHandler);
                        }
                        private DeleteRequest rewrite(DeleteRequest request) {
                            // Transform the client DN into a server DN.
                            return request.setName(request.getName().toString()
                                    .replace(clientSuffix, serverSuffix));
                        }
                    };
            factory.getConnectionAsync(outerHandler);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public <R extends ExtendedResult> void handleExtendedRequest(
                final RequestContext requestContext, final ExtendedRequest<R> request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super R> resultHandler) {
            if (request.getOID().equals(CancelExtendedRequest.OID)) {
                // TODO: not implemented.
                resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
                        "Cancel extended request operation not supported"));
            } else if (request.getOID().equals(StartTLSExtendedRequest.OID)) {
                // TODO: not implemented.
                resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
                        "StartTLS extended request operation not supported"));
            } else {
                // Forward all other extended operations.
                addProxiedAuthControl(request);
                final ConnectionCompletionHandler<R> outerHandler =
                        new ConnectionCompletionHandler<R>(resultHandler) {
                            @Override
                            public void handleResult(final Connection connection) {
                                final RequestCompletionHandler<R> innerHandler =
                                        new RequestCompletionHandler<R>(connection, resultHandler);
                                connection.extendedRequestAsync(request,
                                        intermediateResponseHandler, innerHandler);
                            }
                            // TODO: Rewrite PasswordModifyExtendedRequest,
                            //       WhoAmIExtendedResult
                        };
                factory.getConnectionAsync(outerHandler);
            }
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleModify(final RequestContext requestContext, final ModifyRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            addProxiedAuthControl(request);
            final ConnectionCompletionHandler<Result> outerHandler =
                    new ConnectionCompletionHandler<Result>(resultHandler) {
                        @Override
                        public void handleResult(final Connection connection) {
                            final RequestCompletionHandler<Result> innerHandler =
                                    new RequestCompletionHandler<Result>(connection, resultHandler);
                            connection.modifyAsync(rewrite(request), intermediateResponseHandler,
                                    innerHandler);
                        }
                        private ModifyRequest rewrite(final ModifyRequest request) {
                            // Transform the client DN into a server DN.
                            ModifyRequest rewrittenRequest =
                                    Requests.newModifyRequest(request.getName().toString()
                                            .replace(clientSuffix, serverSuffix));
                            // Transform the client attribute names into server
                            // attribute names, fullname;lang-fr ==> cn;lang-fr.
                            List<Modification> mods = request.getModifications();
                            for (Modification mod : mods) {
                                AttributeDescription attrDesc =
                                        mod.getAttribute().getAttributeDescription();
                                if (attrDesc.equals(clientAttributeDescription)) {
                                    String rewrittenAttrName =
                                            attrDesc.toString()
                                                .replaceFirst(clientAttributeTypeName,
                                                              serverAttributeTypeName);
                                    Attribute serverAttribute = new LinkedAttribute(
                                            AttributeDescription.valueOf(rewrittenAttrName),
                                            mod.getAttribute().toArray());
                                    rewrittenRequest.addModification(new Modification(
                                            mod.getModificationType(),
                                            serverAttribute));
                                } else {
                                    rewrittenRequest.addModification(mod);
                                }
                            }
                            for (Control control : request.getControls()) {
                                rewrittenRequest.addControl(control);
                            }
                            return rewrittenRequest;
                        }
                    };
            factory.getConnectionAsync(outerHandler);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleModifyDN(final RequestContext requestContext,
                final ModifyDNRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final ResultHandler<? super Result> resultHandler) {
            addProxiedAuthControl(request);
            final ConnectionCompletionHandler<Result> outerHandler =
                    new ConnectionCompletionHandler<Result>(resultHandler) {
                        @Override
                        public void handleResult(final Connection connection) {
                            final RequestCompletionHandler<Result> innerHandler =
                                    new RequestCompletionHandler<Result>(connection, resultHandler);
                            connection.modifyDNAsync(rewrite(request), intermediateResponseHandler,
                                    innerHandler);
                        }
                        private ModifyDNRequest rewrite(ModifyDNRequest request) {
                            // Transform the client DNs into server DNs.
                            if (request.getNewSuperior() != null) {
                                return request
                                        .setName(request.getName().toString()
                                                .replace(clientSuffix, serverSuffix))
                                        .setNewSuperior(request.getNewSuperior().toString()
                                                .replace(clientSuffix, serverSuffix));
                            } else {
                                return request
                                        .setName(request.getName().toString()
                                                .replace(clientSuffix, serverSuffix));
                            }
                        }
                    };
            factory.getConnectionAsync(outerHandler);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public void handleSearch(final RequestContext requestContext, final SearchRequest request,
                final IntermediateResponseHandler intermediateResponseHandler,
                final SearchResultHandler resultHandler) {
            addProxiedAuthControl(request);
            final ConnectionCompletionHandler<Result> outerHandler =
                    new ConnectionCompletionHandler<Result>(resultHandler) {
                        @Override
                        public void handleResult(final Connection connection) {
                            final SearchRequestCompletionHandler innerHandler =
                                    new SearchRequestCompletionHandler(connection, resultHandler);
                            connection.searchAsync(rewrite(request), intermediateResponseHandler,
                                    innerHandler);
                        }
                        private SearchRequest rewrite(final SearchRequest request) {
                            // Transform the client attribute names to a server
                            // attribute names, fullname;lang-fr ==> cn;lang-fr.
                            String[] attrNames =
                                    new String[request.getAttributes().size()];
                            int count = 0;
                            for (String attrName : request.getAttributes()) {
                                if (attrName.equalsIgnoreCase(clientAttributeTypeName)) {
                                    attrNames[count] = serverAttributeTypeName;
                                } else {
                                    attrNames[count] = attrName;
                                }
                                ++count;
                            }
                            // Rewrite the baseDN, and rewrite the Filter in
                            // dangerously lazy fashion. All the filter rewrite
                            // does is a string replace, so if the client
                            // attribute name appears in the value part of the
                            // AVA, this implementation will not work.
                            return Requests.newSearchRequest(
                                    DN.valueOf(request.getName().toString()
                                            .replace(clientSuffix, serverSuffix)),
                                    request.getScope(),
                                    Filter.valueOf(request.getFilter().toString()
                                            .replace(clientAttributeTypeName,
                                                     serverAttributeTypeName)),
                                    attrNames);
                        }
                    };
            factory.getConnectionAsync(outerHandler);
        }
        private void addProxiedAuthControl(final Request request) {
            final ProxiedAuthV2RequestControl control = proxiedAuthControl;
            if (control != null) {
                request.addControl(control);
            }
        }
    }
    /**
     * Main method.
     *
     * @param args
     *            The command line arguments: local address, local port, proxy
     *            user DN, proxy user password, server address, server port
     */
    public static void main(final String[] args) {
        if (args.length != 6) {
            System.err.println("Usage:"
                    + "\tlocalAddress localPort proxyDN proxyPassword "
                    + "serverAddress serverPort");
            System.exit(1);
        }
        final String localAddress = args[0];
        final int localPort = Integer.parseInt(args[1]);
        final String proxyDN = args[2];
        final String proxyPassword = args[3];
        final String remoteAddress = args[4];
        final int remotePort = Integer.parseInt(args[5]);
        // Create connection factories.
        final ConnectionFactory factory =
                Connections.newFixedConnectionPool(
                        Connections.newAuthenticatedConnectionFactory(
                                new LDAPConnectionFactory(remoteAddress, remotePort),
                                Requests.newSimpleBindRequest(
                                        proxyDN, proxyPassword.toCharArray())),
                        Integer.MAX_VALUE);
        final ConnectionFactory bindFactory =
                Connections.newFixedConnectionPool(new LDAPConnectionFactory(
                        remoteAddress, remotePort), Integer.MAX_VALUE);
        // Create a server connection adapter.
        final ProxyBackend backend = new ProxyBackend(factory, bindFactory);
        final ServerConnectionFactory<LDAPClientContext, Integer> connectionHandler =
                Connections.newServerConnectionFactory(backend);
        // Create listener.
        final LDAPListenerOptions options = new LDAPListenerOptions().setBacklog(4096);
        LDAPListener listener = null;
        try {
            listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
            System.out.println("Press any key to stop the server...");
            System.in.read();
        } catch (final IOException e) {
            System.out.println("Error listening on " + localAddress + ":" + localPort);
            e.printStackTrace();
        } finally {
            if (listener != null) {
                listener.close();
            }
        }
    }
    private RewriterProxy() {
        // Not used.
    }
}
opendj3/src/main/docbkx/dev-guide/chap-simple-proxy.xml
@@ -114,9 +114,85 @@
  and so forth.</para>
 </section>
 <section xml:id="handling-client-connections">
  <title>Listening For &amp; Handling Client Connections</title>
  <para>You create an <literal>LDAPListener</literal> to handle incoming client
  connections. The <literal>LDAPListener</literal> takes a connection handler
  that deals with the connections, in this case connections back to the
  directory servers handling client requests.</para>
  <programlisting language="java">
final LDAPListenerOptions options = new LDAPListenerOptions().setBacklog(4096);
LDAPListener listener = null;
try {
    listener = new LDAPListener(localAddress, localPort, connectionHandler,
            options);
    System.out.println("Press any key to stop the server...");
    System.in.read();
} catch (final IOException e) {
    System.out.println("Error listening on " + localAddress + ":" + localPort);
    e.printStackTrace();
} finally {
    if (listener != null) {
        listener.close();
    }
}
</programlisting>
  <para>You get a <literal>ServerConnectionFactory</literal> to handle requests
  coming from clients. The <literal>ServerConnectionFactory</literal> takes a
  request handler that deals with the incoming client requests. The request
  handler implements handlers for all supported operations. The Proxy example
  implements a <literal>ProxyBackend</literal> to handle requests. The
  <literal>ProxyBackend</literal> sends the requests on to the backend
  directory servers and routes the results returned back to client
  applications.</para>
  <programlisting language="java">
final ProxyBackend backend = new ProxyBackend(factory, bindFactory);
final ServerConnectionFactory&lt;LDAPClientContext, Integer&gt; connectionHandler =
        Connections.newServerConnectionFactory(backend);
</programlisting>
  <para>See the Proxy example code for details about the
  <literal>ProxyBackend</literal> implementation.</para>
 </section>
 <section xml:id="dn-attr-rewriting">
  <title>DN &amp; Attribute Rewriting</title>
  <para>TODO</para>
  <para>Suppose you have a client application that expects a different
  attribute name, such as <literal>fullname</literal> for a standard attribute
  like <literal>cn</literal> (common name), and that expects a distinguished
  name (DN) suffix different from what is stored in the directory. If you
  cannot change the application, one possible alternative is a proxy layer
  that does DN and attribute rewriting.<footnote><para>Some servers, such as
  OpenDJ directory server, can do attribute rewriting without a proxy layer.
  See your directory server's documentation for details.</para></footnote></para>
  <screen># A search accessing the directory server
$ ldapsearch -b dc=example,dc=com -p 1389 "(cn=Babs Jensen)" cn
dn: uid=bjensen,ou=People,dc=example,dc=com
cn: Barbara Jensen
cn: Babs Jensen
# The same search search accessing a proxy that rewrites requests and responses
$ ldapsearch -b o=example -p 8389 "(fullname=Babs Jensen)" fullname
dn: uid=bjensen,ou=People,o=example
fullname: Barbara Jensen
fullname: Babs Jensen
</screen>
  <para>The OpenDJ LDAP SDK <link xlink:show="new"
  xlink:href="http://opendj.forgerock.org/opendj-ldap-sdk-examples/xref/org/forgerock/opendj/examples/RewriterProxy.html"
  >RewriterProxy</link> example builds on the Proxy example, rewriting requests
  and search result entries. When you read the example, look for the
  <literal>rewrite()</literal> methods.</para>
  <para>In the above output, the rewriter proxy listens on port 8389,
  connecting to a directory server listening on 1389. The directory server
  contains data from <link xlink:href="http://opendj.forgerock.org/Example.ldif"
  xlink:show="new"><filename>Example.ldif</filename></link>.</para>
 </section>
</chapter>