package com.hwlcn.ldap.ldap.sdk;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.SocketFactory;
import com.hwlcn.core.annotation.NotMutable;
import com.hwlcn.core.annotation.ThreadSafety;
import com.hwlcn.ldap.util.ThreadSafetyLevel;
import static com.hwlcn.ldap.util.Debug.*;
import static com.hwlcn.ldap.util.Validator.*;
/**
* This class provides a server set implementation that will attempt to
* establish connections to servers in the order they are provided. If the
* first server is unavailable, then it will attempt to connect to the second,
* then to the third, etc. Note that this implementation also makes it possible
* to use failover between distinct server sets, which means that it will first
* attempt to obtain a connection from the first server set and if all attempts
* fail, it will proceed to the second set, and so on. This can provide a
* significant degree of flexibility in complex environments (e.g., first use a
* round robin server set containing servers in the local data center, but if
* none of those are available then fail over to a server set with servers in a
* remote data center).
* <BR><BR>
* <H2>Example</H2>
* The following example demonstrates the process for creating a failover server
* set with information about individual servers. It will first try to connect
* to ds1.example.com:389, but if that fails then it will try connecting to
* ds2.example.com:389:
* <PRE>
* String[] addresses =
* {
* "ds1.example.com",
* "ds2.example.com"
* };
* int[] ports =
* {
* 389,
* 389
* };
* FailoverServerSet failoverSet = new FailoverServerSet(addresses, ports);
* </PRE>
* This second example demonstrates the process for creating a failover server
* set which actually fails over between two different data centers (east and
* west), with each data center containing two servers that will be accessed in
* a round-robin manner. It will first try to connect to one of the servers in
* the east data center, and if that attempt fails then it will try to connect
* to the other server in the east data center. If both of them fail, then it
* will try to connect to one of the servers in the west data center, and
* finally as a last resort the other server in the west data center:
* <PRE>
* String[] eastAddresses =
* {
* "ds-east-1.example.com",
* "ds-east-2.example.com",
* };
* int[] eastPorts =
* {
* 389,
* 389
* }
* RoundRobinServerSet eastSet =
* new RoundRobinServerSet(eastAddresses, eastPorts);
*
* String[] westAddresses =
* {
* "ds-west-1.example.com",
* "ds-west-2.example.com",
* };
* int[] westPorts =
* {
* 389,
* 389
* }
* RoundRobinServerSet westSet =
* new RoundRobinServerSet(westAddresses, westPorts);
*
* FailoverServerSet failoverSet = new FailoverServerSet(eastSet, westSet);
* </PRE>
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class FailoverServerSet
extends ServerSet
{
private final AtomicBoolean reOrderOnFailover;
private final ServerSet[] serverSets;
public FailoverServerSet(final String[] addresses, final int[] ports)
{
this(addresses, ports, null, null);
}
public FailoverServerSet(final String[] addresses, final int[] ports,
final LDAPConnectionOptions connectionOptions)
{
this(addresses, ports, null, connectionOptions);
}
public FailoverServerSet(final String[] addresses, final int[] ports,
final SocketFactory socketFactory)
{
this(addresses, ports, socketFactory, null);
}
public FailoverServerSet(final String[] addresses, final int[] ports,
final SocketFactory socketFactory,
final LDAPConnectionOptions connectionOptions)
{
ensureNotNull(addresses, ports);
ensureTrue(addresses.length > 0,
"FailoverServerSet.addresses must not be empty.");
ensureTrue(addresses.length == ports.length,
"FailoverServerSet addresses and ports arrays must be the same size.");
reOrderOnFailover = new AtomicBoolean(false);
final SocketFactory sf;
if (socketFactory == null)
{
sf = SocketFactory.getDefault();
}
else
{
sf = socketFactory;
}
final LDAPConnectionOptions co;
if (connectionOptions == null)
{
co = new LDAPConnectionOptions();
}
else
{
co = connectionOptions;
}
serverSets = new ServerSet[addresses.length];
for (int i=0; i < serverSets.length; i++)
{
serverSets[i] = new SingleServerSet(addresses[i], ports[i], sf, co);
}
}
public FailoverServerSet(final ServerSet... serverSets)
{
ensureNotNull(serverSets);
ensureFalse(serverSets.length == 0,
"FailoverServerSet.serverSets must not be empty.");
this.serverSets = serverSets;
reOrderOnFailover = new AtomicBoolean(false);
}
public FailoverServerSet(final List<ServerSet> serverSets)
{
ensureNotNull(serverSets);
ensureFalse(serverSets.isEmpty(),
"FailoverServerSet.serverSets must not be empty.");
this.serverSets = new ServerSet[serverSets.size()];
serverSets.toArray(this.serverSets);
reOrderOnFailover = new AtomicBoolean(false);
}
public ServerSet[] getServerSets()
{
return serverSets;
}
public boolean reOrderOnFailover()
{
return reOrderOnFailover.get();
}
public void setReOrderOnFailover(final boolean reOrderOnFailover)
{
this.reOrderOnFailover.set(reOrderOnFailover);
}
@Override()
public LDAPConnection getConnection()
throws LDAPException
{
return getConnection(null);
}
@Override()
public LDAPConnection getConnection(
final LDAPConnectionPoolHealthCheck healthCheck)
throws LDAPException
{
if (reOrderOnFailover.get() && (serverSets.length > 1))
{
synchronized (this)
{
try
{
return serverSets[0].getConnection(healthCheck);
}
catch (final LDAPException le)
{
debugException(le);
}
int successfulPos = -1;
LDAPConnection conn = null;
LDAPException lastException = null;
for (int i=1; i < serverSets.length; i++)
{
try
{
conn = serverSets[i].getConnection(healthCheck);
successfulPos = i;
break;
}
catch (final LDAPException le)
{
debugException(le);
lastException = le;
}
}
if (successfulPos > 0)
{
int pos = 0;
final ServerSet[] setCopy = new ServerSet[serverSets.length];
for (int i=successfulPos; i < serverSets.length; i++)
{
setCopy[pos++] = serverSets[i];
}
for (int i=0; i < successfulPos; i++)
{
setCopy[pos++] = serverSets[i];
}
System.arraycopy(setCopy, 0, serverSets, 0, setCopy.length);
return conn;
}
else
{
throw lastException;
}
}
}
else
{
LDAPException lastException = null;
for (final ServerSet s : serverSets)
{
try
{
return s.getConnection(healthCheck);
}
catch (LDAPException le)
{
debugException(le);
lastException = le;
}
}
throw lastException;
}
}
@Override()
public void toString(final StringBuilder buffer)
{
buffer.append("FailoverServerSet(serverSets={");
for (int i=0; i < serverSets.length; i++)
{
if (i > 0)
{
buffer.append(", ");
}
serverSets[i].toString(buffer);
}
buffer.append("})");
}
}