package com.hwlcn.ldap.ldap.sdk;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.SocketFactory;
import com.hwlcn.ldap.util.Debug;
import com.hwlcn.core.annotation.NotMutable;
import com.hwlcn.ldap.util.StaticUtils;
import com.hwlcn.core.annotation.ThreadSafety;
import com.hwlcn.ldap.util.ThreadSafetyLevel;
import com.hwlcn.ldap.util.Validator;
import static com.hwlcn.ldap.ldap.sdk.LDAPMessages.*;
/**
* This class provides a server set implementation that will attempt to
* establish connections to all associated servers in parallel, keeping the one
* that was first to be successfully established and closing all others.
* <BR><BR>
* Note that this server set implementation may only be used in conjunction with
* connection options that allow the associated socket factory to create
* multiple connections in parallel. If the
* {@link com.hwlcn.ldap.ldap.sdk.LDAPConnectionOptions#allowConcurrentSocketFactoryUse} method returns
* false for the associated connection options, then the {@code getConnection}
* methods will throw an exception.
* <BR><BR>
* <H2>Example</H2>
* The following example demonstrates the process for creating a fastest connect
* server set that may be used to establish connections to either of two
* servers. When using the server set to attempt to create a connection, it
* will try both in parallel and will return the first connection that it is
* able to establish:
* <PRE>
* String[] addresses =
* {
* "ds1.example.com",
* "ds2.example.com",
* };
* int[] ports =
* {
* 389,
* 389
* }
* FastestConnectServerSet fastestConnectSet =
* new FastestConnectServerSet(addresses, ports);
* </PRE>
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class FastestConnectServerSet
extends ServerSet
{
private final int[] ports;
private final LDAPConnectionOptions connectionOptions;
private final SocketFactory socketFactory;
private final String[] addresses;
public FastestConnectServerSet(final String[] addresses, final int[] ports)
{
this(addresses, ports, null, null);
}
public FastestConnectServerSet(final String[] addresses, final int[] ports,
final LDAPConnectionOptions connectionOptions)
{
this(addresses, ports, null, connectionOptions);
}
public FastestConnectServerSet(final String[] addresses, final int[] ports,
final SocketFactory socketFactory)
{
this(addresses, ports, socketFactory, null);
}
public FastestConnectServerSet(final String[] addresses, final int[] ports,
final SocketFactory socketFactory,
final LDAPConnectionOptions connectionOptions)
{
Validator.ensureNotNull(addresses, ports);
Validator.ensureTrue(addresses.length > 0,
"RoundRobinServerSet.addresses must not be empty.");
Validator.ensureTrue(addresses.length == ports.length,
"RoundRobinServerSet addresses and ports arrays must be the same " +
"size.");
this.addresses = addresses;
this.ports = ports;
if (socketFactory == null)
{
this.socketFactory = SocketFactory.getDefault();
}
else
{
this.socketFactory = socketFactory;
}
if (connectionOptions == null)
{
this.connectionOptions = new LDAPConnectionOptions();
}
else
{
this.connectionOptions = connectionOptions;
}
}
public String[] getAddresses()
{
return addresses;
}
public int[] getPorts()
{
return ports;
}
public SocketFactory getSocketFactory()
{
return socketFactory;
}
public LDAPConnectionOptions getConnectionOptions()
{
return connectionOptions;
}
@Override()
public LDAPConnection getConnection()
throws LDAPException
{
return getConnection(null);
}
@Override()
public LDAPConnection getConnection(
final LDAPConnectionPoolHealthCheck healthCheck)
throws LDAPException
{
if (! connectionOptions.allowConcurrentSocketFactoryUse())
{
throw new LDAPException(ResultCode.CONNECT_ERROR,
ERR_FASTEST_CONNECT_SET_OPTIONS_NOT_PARALLEL.get());
}
final ArrayBlockingQueue<Object> resultQueue =
new ArrayBlockingQueue<Object>(addresses.length, false);
final AtomicBoolean connectionSelected = new AtomicBoolean(false);
final FastestConnectThread[] connectThreads =
new FastestConnectThread[addresses.length];
for (int i=0; i < connectThreads.length; i++)
{
connectThreads[i] = new FastestConnectThread(addresses[i], ports[i],
socketFactory, connectionOptions, healthCheck, resultQueue,
connectionSelected);
}
for (final FastestConnectThread t : connectThreads)
{
t.start();
}
try
{
final long effectiveConnectTimeout;
final long connectTimeout =
connectionOptions.getConnectTimeoutMillis();
if ((connectTimeout > 0L) && (connectTimeout < Integer.MAX_VALUE))
{
effectiveConnectTimeout = connectTimeout;
}
else
{
effectiveConnectTimeout = Integer.MAX_VALUE;
}
int connectFailures = 0;
final long stopWaitingTime =
System.currentTimeMillis() + effectiveConnectTimeout;
while (true)
{
final Object o;
final long waitTime = stopWaitingTime - System.currentTimeMillis();
if (waitTime > 0L)
{
o = resultQueue.poll(waitTime, TimeUnit.MILLISECONDS);
}
else
{
o = resultQueue.poll();
}
if (o == null)
{
throw new LDAPException(ResultCode.CONNECT_ERROR,
ERR_FASTEST_CONNECT_SET_CONNECT_TIMEOUT.get(
effectiveConnectTimeout));
}
else if (o instanceof LDAPConnection)
{
return (LDAPConnection) o;
}
else
{
connectFailures++;
if (connectFailures >= addresses.length)
{
throw new LDAPException(ResultCode.CONNECT_ERROR,
ERR_FASTEST_CONNECT_SET_ALL_FAILED.get());
}
}
}
}
catch (final LDAPException le)
{
Debug.debugException(le);
throw le;
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.CONNECT_ERROR,
ERR_FASTEST_CONNECT_SET_CONNECT_EXCEPTION.get(
StaticUtils.getExceptionMessage(e)),
e);
}
}
@Override()
public void toString(final StringBuilder buffer)
{
buffer.append("FastestConnectServerSet(servers={");
for (int i=0; i < addresses.length; i++)
{
if (i > 0)
{
buffer.append(", ");
}
buffer.append(addresses[i]);
buffer.append(':');
buffer.append(ports[i]);
}
buffer.append("})");
}
}