001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2009-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017
018package org.forgerock.opendj.examples;
019
020import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
021import static org.forgerock.opendj.ldap.LDAPListener.*;
022import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
023
024import java.io.IOException;
025import java.util.Collection;
026import java.util.LinkedList;
027import java.util.List;
028
029import org.forgerock.opendj.ldap.ConnectionFactory;
030import org.forgerock.opendj.ldap.Connections;
031import org.forgerock.opendj.ldap.ConsistentHashMap;
032import org.forgerock.opendj.ldap.DN;
033import org.forgerock.opendj.ldap.LDAPClientContext;
034import org.forgerock.opendj.ldap.LDAPConnectionFactory;
035import org.forgerock.opendj.ldap.LDAPListener;
036import org.forgerock.opendj.ldap.LdapException;
037import org.forgerock.opendj.ldap.RequestContext;
038import org.forgerock.opendj.ldap.RequestHandlerFactory;
039import org.forgerock.opendj.ldap.ServerConnectionFactory;
040import org.forgerock.opendj.ldap.requests.BindRequest;
041import org.forgerock.util.Options;
042
043import com.forgerock.reactive.ServerConnectionFactoryAdapter;
044
045/**
046 * An LDAP load balancing proxy which forwards requests to one or more remote
047 * Directory Servers. This is implementation is very simple and is only intended
048 * as an example:
049 * <ul>
050 * <li>It does not support SSL connections
051 * <li>It does not support StartTLS
052 * <li>It does not support Abandon or Cancel requests
053 * <li>Very basic authentication and authorization support.
054 * </ul>
055 * This example takes the following command line parameters:
056 *
057 * <pre>
058 *     {@code [--load-balancer <mode>] <listenAddress> <listenPort> <proxyDN> <proxyPassword>
059 *         <remoteAddress1> <remotePort1> [<remoteAddress2> <remotePort2> ...]}
060 * </pre>
061 *
062 * Where {@code <mode>} is one of "least-requests", "fail-over", "affinity", or "distribution". The default is
063 * least-requests.
064 */
065public final class Proxy {
066    /**
067     * Main method.
068     *
069     * @param args
070     *            The command line arguments: [--load-balancer <mode>] listen address, listen port,
071     *            remote address1, remote port1, remote address2, remote port2,
072     *            ...
073     */
074    public static void main(final String[] args) {
075        if (args.length < 6 || args.length % 2 != 0) {
076            System.err.println("Usage: [--load-balancer <mode>] listenAddress listenPort "
077                    + "proxyDN proxyPassword remoteAddress1 remotePort1 remoteAddress2 remotePort2 ...");
078            System.exit(1);
079        }
080
081        // Parse command line arguments.
082        int i = 0;
083
084        final LoadBalancingAlgorithm algorithm;
085        if ("--load-balancer".equals(args[i])) {
086            algorithm = getLoadBalancingAlgorithm(args[i + 1]);
087            i += 2;
088        } else {
089            algorithm = LoadBalancingAlgorithm.LEAST_REQUESTS;
090        }
091
092        final String localAddress = args[i++];
093        final int localPort = Integer.parseInt(args[i++]);
094
095        final String proxyDN = args[i++];
096        final String proxyPassword = args[i++];
097
098        // Create load balancer.
099        // --- JCite pools ---
100        final List<ConnectionFactory> factories = new LinkedList<>();
101        final BindRequest bindRequest = newSimpleBindRequest(proxyDN, proxyPassword.toCharArray());
102        final Options factoryOptions = Options.defaultOptions()
103                                              .set(HEARTBEAT_ENABLED, true)
104                                              .set(AUTHN_BIND_REQUEST, bindRequest);
105
106        final List<ConnectionFactory> bindFactories = new LinkedList<>();
107        final Options bindFactoryOptions = Options.defaultOptions().set(HEARTBEAT_ENABLED, true);
108
109        for (; i < args.length; i += 2) {
110            final String remoteAddress = args[i];
111            final int remotePort = Integer.parseInt(args[i + 1]);
112
113            factories.add(Connections.newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
114                                                                                        remotePort,
115                                                                                        factoryOptions)));
116
117            bindFactories.add(Connections.newCachedConnectionPool(new LDAPConnectionFactory(remoteAddress,
118                                                                                            remotePort,
119                                                                                            bindFactoryOptions)));
120        }
121        // --- JCite pools ---
122
123        final ConnectionFactory factory = algorithm.newLoadBalancer(factories, factoryOptions);
124        final ConnectionFactory bindFactory = algorithm.newLoadBalancer(bindFactories, bindFactoryOptions);
125
126        // --- JCite backend ---
127        /*
128         * Create a server connection adapter which will create a new proxy
129         * backend for each inbound client connection. This is required because
130         * we need to maintain authorization state between client requests.
131         */
132        final RequestHandlerFactory<LDAPClientContext, RequestContext> proxyFactory =
133                new RequestHandlerFactory<LDAPClientContext, RequestContext>() {
134                    @Override
135                    public ProxyBackend handleAccept(LDAPClientContext clientContext)
136                            throws LdapException {
137                        return new ProxyBackend(factory, bindFactory);
138                    }
139                };
140        final ServerConnectionFactory<LDAPClientContext, Integer> connectionHandler =
141                Connections.newServerConnectionFactory(proxyFactory);
142        // --- JCite backend ---
143
144        // --- JCite listener ---
145        // Create listener.
146        final Options options = Options.defaultOptions().set(CONNECT_MAX_BACKLOG, 4096);
147        LDAPListener listener = null;
148        try {
149            listener = new LDAPListener(localAddress, localPort, new ServerConnectionFactoryAdapter(
150                    options.get(LDAP_DECODE_OPTIONS), connectionHandler), options);
151            System.out.println("Press any key to stop the server...");
152            System.in.read();
153        } catch (final IOException e) {
154            System.out.println("Error listening on " + localAddress + ":" + localPort);
155            e.printStackTrace();
156        } finally {
157            if (listener != null) {
158                listener.close();
159            }
160        }
161        // --- JCite listener ---
162    }
163
164    private static LoadBalancingAlgorithm getLoadBalancingAlgorithm(final String algorithmName) {
165        switch (algorithmName) {
166        case "least-requests":
167            return LoadBalancingAlgorithm.LEAST_REQUESTS;
168        case "fail-over":
169            return LoadBalancingAlgorithm.FAIL_OVER;
170        case "affinity":
171            return LoadBalancingAlgorithm.AFFINITY;
172        case "distribution":
173            return LoadBalancingAlgorithm.DISTRIBUTION;
174        default:
175            System.err.println("Unrecognized load-balancing algorithm '" + algorithmName + "'. Should be one of "
176                                       + "'least-requests', 'fail-over', 'affinity', or 'distribution'.");
177            System.exit(1);
178        }
179        return LoadBalancingAlgorithm.LEAST_REQUESTS; // keep compiler happy.
180    }
181
182    private enum LoadBalancingAlgorithm {
183        LEAST_REQUESTS {
184            @Override
185            ConnectionFactory newLoadBalancer(final Collection<ConnectionFactory> factories, final Options options) {
186                // --- JCite load balancer ---
187                return Connections.newLeastRequestsLoadBalancer(factories, options);
188                // --- JCite load balancer ---
189            }
190        },
191        FAIL_OVER {
192            @Override
193            ConnectionFactory newLoadBalancer(final Collection<ConnectionFactory> factories, final Options options) {
194                return Connections.newFailoverLoadBalancer(factories, options);
195            }
196        },
197        AFFINITY {
198            @Override
199            ConnectionFactory newLoadBalancer(final Collection<ConnectionFactory> factories, final Options options) {
200                return Connections.newAffinityRequestLoadBalancer(factories, options);
201            }
202        },
203        DISTRIBUTION {
204            @Override
205            ConnectionFactory newLoadBalancer(final Collection<ConnectionFactory> factories, final Options options) {
206                final ConsistentHashMap<ConnectionFactory> partitions = new ConsistentHashMap<>();
207                int i = 0;
208                for (final ConnectionFactory factory : factories) {
209                    partitions.put("partition-" + i++, factory);
210                }
211                return Connections.newFixedSizeDistributionLoadBalancer(DN.valueOf("ou=people,dc=example,dc=com"),
212                                                                        partitions,
213                                                                        options);
214            }
215        };
216
217        abstract ConnectionFactory newLoadBalancer(Collection<ConnectionFactory> factories, Options options);
218    }
219
220    private Proxy() {
221        // Not used.
222    }
223}