View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2009-2010 Sun Microsystems, Inc.
15   * Portions Copyright 2011-2016 ForgeRock AS.
16   */
17  
18  package org.forgerock.opendj.examples;
19  
20  import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
21  import static org.forgerock.opendj.ldap.LDAPListener.*;
22  import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
23  
24  import java.io.IOException;
25  import java.util.Collection;
26  import java.util.LinkedList;
27  import java.util.List;
28  
29  import org.forgerock.opendj.ldap.ConnectionFactory;
30  import org.forgerock.opendj.ldap.Connections;
31  import org.forgerock.opendj.ldap.ConsistentHashMap;
32  import org.forgerock.opendj.ldap.DN;
33  import org.forgerock.opendj.ldap.LDAPClientContext;
34  import org.forgerock.opendj.ldap.LDAPConnectionFactory;
35  import org.forgerock.opendj.ldap.LDAPListener;
36  import org.forgerock.opendj.ldap.LdapException;
37  import org.forgerock.opendj.ldap.RequestContext;
38  import org.forgerock.opendj.ldap.RequestHandlerFactory;
39  import org.forgerock.opendj.ldap.ServerConnectionFactory;
40  import org.forgerock.opendj.ldap.requests.BindRequest;
41  import org.forgerock.util.Options;
42  
43  import com.forgerock.reactive.ServerConnectionFactoryAdapter;
44  
45  /**
46   * An LDAP load balancing proxy which forwards requests to one or more remote
47   * Directory Servers. This is implementation is very simple and is only intended
48   * as an example:
49   * <ul>
50   * <li>It does not support SSL connections
51   * <li>It does not support StartTLS
52   * <li>It does not support Abandon or Cancel requests
53   * <li>Very basic authentication and authorization support.
54   * </ul>
55   * This example takes the following command line parameters:
56   *
57   * <pre>
58   *     {@code [--load-balancer <mode>] <listenAddress> <listenPort> <proxyDN> <proxyPassword>
59   *         <remoteAddress1> <remotePort1> [<remoteAddress2> <remotePort2> ...]}
60   * </pre>
61   *
62   * Where {@code <mode>} is one of "least-requests", "fail-over", "affinity", or "distribution". The default is
63   * least-requests.
64   */
65  public final class Proxy {
66      /**
67       * Main method.
68       *
69       * @param args
70       *            The command line arguments: [--load-balancer <mode>] listen address, listen port,
71       *            remote address1, remote port1, remote address2, remote port2,
72       *            ...
73       */
74      public static void main(final String[] args) {
75          if (args.length < 6 || args.length % 2 != 0) {
76              System.err.println("Usage: [--load-balancer <mode>] listenAddress listenPort "
77                      + "proxyDN proxyPassword remoteAddress1 remotePort1 remoteAddress2 remotePort2 ...");
78              System.exit(1);
79          }
80  
81          // Parse command line arguments.
82          int i = 0;
83  
84          final LoadBalancingAlgorithm algorithm;
85          if ("--load-balancer".equals(args[i])) {
86              algorithm = getLoadBalancingAlgorithm(args[i + 1]);
87              i += 2;
88          } else {
89              algorithm = LoadBalancingAlgorithm.LEAST_REQUESTS;
90          }
91  
92          final String localAddress = args[i++];
93          final int localPort = Integer.parseInt(args[i++]);
94  
95          final String proxyDN = args[i++];
96          final String proxyPassword = args[i++];
97  
98          // Create load balancer.
99          // --- 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 }