From bf5f8327f7e22b03cf1807aac9fbd0845346c8bb Mon Sep 17 00:00:00 2001
From: Mark Craig <mark.craig@forgerock.com>
Date: Fri, 18 May 2012 09:07:46 +0000
Subject: [PATCH] Draft example that does very basic DN and attribute rewriting

---
 opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java |  684 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 opendj3/src/main/docbkx/dev-guide/chap-simple-proxy.xml                                         |   78 +++++
 2 files changed, 761 insertions(+), 1 deletions(-)

diff --git a/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java b/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
new file mode 100644
index 0000000..85426f4
--- /dev/null
+++ b/opendj3/opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/RewriterProxy.java
@@ -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.
+    }
+}
diff --git a/opendj3/src/main/docbkx/dev-guide/chap-simple-proxy.xml b/opendj3/src/main/docbkx/dev-guide/chap-simple-proxy.xml
index 12163ba..d4ed13f 100644
--- a/opendj3/src/main/docbkx/dev-guide/chap-simple-proxy.xml
+++ b/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>

--
Gitblit v1.10.0