/*
* 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
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* 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
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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 2010 Sun Microsystems, Inc.
*/
package org.opends.sdk.examples.server.proxy;
import static org.opends.sdk.ErrorResultException.newErrorResult;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import org.opends.sdk.*;
import org.opends.sdk.controls.ProxiedAuthV2RequestControl;
import org.opends.sdk.requests.*;
import org.opends.sdk.responses.*;
/**
* An LDAP load balancing proxy which forwards requests to one or more remote
* Directory Servers. This is implementation is very simple and is only intended
* as an example:
*
* - It does not support SSL connections
*
- It does not support StartTLS
*
- It does not support Abandon or Cancel requests
*
- Very basic authentication and authorization support.
*
* This example takes the following command line parameters:
*
*
* <listenAddress> <listenPort> <remoteAddress1> <remotePort1>
* [<remoteAddress2> <remotePort2> ...]
*
*/
public final class Main
{
/**
* Proxy server connection factory implementation.
*/
private static final class Proxy implements
ServerConnectionFactory
{
private final class ServerConnectionImpl implements
ServerConnection
{
private abstract class AbstractRequestCompletionHandler
>
implements ResultHandler
{
final H resultHandler;
final AsynchronousConnection connection;
AbstractRequestCompletionHandler(
final AsynchronousConnection 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
implements ResultHandler
{
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(AsynchronousConnection connection);
}
private final class RequestCompletionHandler extends
AbstractRequestCompletionHandler>
{
RequestCompletionHandler(final AsynchronousConnection connection,
final ResultHandler super R> resultHandler)
{
super(connection, resultHandler);
}
}
private final class SearchRequestCompletionHandler extends
AbstractRequestCompletionHandler
implements SearchResultHandler
{
SearchRequestCompletionHandler(final AsynchronousConnection connection,
final SearchResultHandler resultHandler)
{
super(connection, resultHandler);
}
/**
* {@inheritDoc}
*/
@Override
public final boolean handleEntry(final SearchResultEntry entry)
{
return resultHandler.handleEntry(entry);
}
/**
* {@inheritDoc}
*/
@Override
public final boolean handleReference(
final SearchResultReference reference)
{
return resultHandler.handleReference(reference);
}
}
private volatile ProxiedAuthV2RequestControl proxiedAuthControl = null;
private ServerConnectionImpl(final LDAPClientContext clientContext)
{
// Nothing to do.
}
/**
* {@inheritDoc}
*/
@Override
public void handleAbandon(final Integer requestContext,
final AbandonRequest request) throws UnsupportedOperationException
{
// Not implemented.
}
/**
* {@inheritDoc}
*/
@Override
public void handleAdd(final Integer requestContext,
final AddRequest request,
final ResultHandler super Result> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
addProxiedAuthControl(request);
final ConnectionCompletionHandler outerHandler =
new ConnectionCompletionHandler(resultHandler)
{
@Override
public void handleResult(final AsynchronousConnection connection)
{
final RequestCompletionHandler innerHandler =
new RequestCompletionHandler(connection, resultHandler);
connection.add(request, innerHandler, intermediateResponseHandler);
}
};
factory.getAsynchronousConnection(outerHandler);
}
/**
* {@inheritDoc}
*/
@Override
public void handleBind(final Integer requestContext, final int version,
final BindRequest request,
final ResultHandler super BindResult> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
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 outerHandler =
new ConnectionCompletionHandler(resultHandler)
{
@Override
public void handleResult(final AsynchronousConnection connection)
{
final ResultHandler innerHandler = new ResultHandler()
{
@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.bind(request, innerHandler,
intermediateResponseHandler);
}
};
proxiedAuthControl = null;
bindFactory.getAsynchronousConnection(outerHandler);
}
}
/**
* {@inheritDoc}
*/
@Override
public void handleCompare(final Integer requestContext,
final CompareRequest request,
final ResultHandler super CompareResult> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
addProxiedAuthControl(request);
final ConnectionCompletionHandler outerHandler =
new ConnectionCompletionHandler(resultHandler)
{
@Override
public void handleResult(final AsynchronousConnection connection)
{
final RequestCompletionHandler innerHandler =
new RequestCompletionHandler(connection, resultHandler);
connection.compare(request, innerHandler,
intermediateResponseHandler);
}
};
factory.getAsynchronousConnection(outerHandler);
}
/**
* {@inheritDoc}
*/
@Override
public void handleConnectionClosed(final Integer requestContext,
final UnbindRequest request)
{
// Client connection closed.
}
/**
* {@inheritDoc}
*/
@Override
public void handleConnectionDisconnected(final ResultCode resultCode,
final String message)
{
// Client disconnected by server.
}
/**
* {@inheritDoc}
*/
@Override
public void handleConnectionError(final Throwable error)
{
// Client connection failed.
}
/**
* {@inheritDoc}
*/
@Override
public void handleDelete(final Integer requestContext,
final DeleteRequest request,
final ResultHandler super Result> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
addProxiedAuthControl(request);
final ConnectionCompletionHandler outerHandler =
new ConnectionCompletionHandler(resultHandler)
{
@Override
public void handleResult(final AsynchronousConnection connection)
{
final RequestCompletionHandler innerHandler =
new RequestCompletionHandler(connection, resultHandler);
connection.delete(request, innerHandler,
intermediateResponseHandler);
}
};
factory.getAsynchronousConnection(outerHandler);
}
/**
* {@inheritDoc}
*/
@Override
public void handleExtendedRequest(
final Integer requestContext, final ExtendedRequest request,
final ResultHandler super R> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
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 outerHandler =
new ConnectionCompletionHandler(resultHandler)
{
@Override
public void handleResult(final AsynchronousConnection connection)
{
final RequestCompletionHandler innerHandler =
new RequestCompletionHandler(connection, resultHandler);
connection.extendedRequest(request, innerHandler,
intermediateResponseHandler);
}
};
factory.getAsynchronousConnection(outerHandler);
}
}
/**
* {@inheritDoc}
*/
@Override
public void handleModify(final Integer requestContext,
final ModifyRequest request,
final ResultHandler super Result> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
addProxiedAuthControl(request);
final ConnectionCompletionHandler outerHandler =
new ConnectionCompletionHandler(resultHandler)
{
@Override
public void handleResult(final AsynchronousConnection connection)
{
final RequestCompletionHandler innerHandler =
new RequestCompletionHandler(connection, resultHandler);
connection.modify(request, innerHandler,
intermediateResponseHandler);
}
};
factory.getAsynchronousConnection(outerHandler);
}
/**
* {@inheritDoc}
*/
@Override
public void handleModifyDN(final Integer requestContext,
final ModifyDNRequest request,
final ResultHandler super Result> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
addProxiedAuthControl(request);
final ConnectionCompletionHandler outerHandler =
new ConnectionCompletionHandler(resultHandler)
{
@Override
public void handleResult(final AsynchronousConnection connection)
{
final RequestCompletionHandler innerHandler =
new RequestCompletionHandler(connection, resultHandler);
connection.modifyDN(request, innerHandler,
intermediateResponseHandler);
}
};
factory.getAsynchronousConnection(outerHandler);
}
/**
* {@inheritDoc}
*/
@Override
public void handleSearch(final Integer requestContext,
final SearchRequest request, final SearchResultHandler resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
addProxiedAuthControl(request);
final ConnectionCompletionHandler outerHandler =
new ConnectionCompletionHandler(resultHandler)
{
@Override
public void handleResult(final AsynchronousConnection connection)
{
final SearchRequestCompletionHandler innerHandler =
new SearchRequestCompletionHandler(connection, resultHandler);
connection.search(request, innerHandler,
intermediateResponseHandler);
}
};
factory.getAsynchronousConnection(outerHandler);
}
private void addProxiedAuthControl(final Request request)
{
final ProxiedAuthV2RequestControl control = proxiedAuthControl;
if (control != null)
{
request.addControl(control);
}
}
}
private final ConnectionFactory factory;
private final ConnectionFactory bindFactory;
private Proxy(final ConnectionFactory factory,
final ConnectionFactory bindFactory)
{
this.factory = factory;
this.bindFactory = bindFactory;
}
/**
* {@inheritDoc}
*/
@Override
public ServerConnection handleAccept(
final LDAPClientContext clientContext) throws ErrorResultException
{
return new ServerConnectionImpl(clientContext);
}
}
/**
* Main method.
*
* @param args
* The command line arguments: listen address, listen port, remote
* address1, remote port1, remote address2, remote port2, ...
*/
public static void main(final String[] args)
{
if (args.length < 4 || args.length % 2 != 0)
{
System.err.println("Usage: listenAddress listenPort "
+ "remoteAddress1 remotePort1 remoteAddress2 remotePort2");
System.exit(1);
}
// Parse command line arguments.
final String localAddress = args[0];
final int localPort = Integer.parseInt(args[1]);
// Create load balancer.
final List factories = new LinkedList();
final List bindFactories = new LinkedList();
for (int i = 2; i < args.length; i += 2)
{
final String remoteAddress = args[i];
final int remotePort = Integer.parseInt(args[i + 1]);
factories.add(Connections.newConnectionPool(new LDAPConnectionFactory(
remoteAddress, remotePort), Integer.MAX_VALUE));
bindFactories.add(Connections.newConnectionPool(
new LDAPConnectionFactory(remoteAddress, remotePort),
Integer.MAX_VALUE));
}
final RoundRobinLoadBalancingAlgorithm algorithm = new RoundRobinLoadBalancingAlgorithm(
factories);
final RoundRobinLoadBalancingAlgorithm bindAlgorithm = new RoundRobinLoadBalancingAlgorithm(
bindFactories);
final ConnectionFactory factory = Connections.newLoadBalancer(algorithm);
final ConnectionFactory bindFactory = Connections
.newLoadBalancer(bindAlgorithm);
// Create listener.
final LDAPListenerOptions options = new LDAPListenerOptions()
.setBacklog(4096);
LDAPListener listener = null;
try
{
listener = new LDAPListener(localAddress, localPort, new Proxy(factory,
bindFactory), 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 Main()
{
// Not used.
}
}