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}