package com.caringo.client.locate;
/**
* @author bguetzlaff
*
*/
import java.net.InetSocketAddress;
import java.io.ByteArrayOutputStream;
import java.lang.Integer;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Date;
import java.lang.IllegalArgumentException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HeaderElement;
import org.apache.commons.httpclient.NameValuePair;
import com.caringo.client.locate.Locator;
import com.caringo.client.locate.StaticLocator;
import com.caringo.client.locate.NoCastorNodesLocatedException;
import com.caringo.client.request.ScspRequestHandler;
import com.caringo.client.request.LocatorRedirectHandler;
import com.caringo.client.BasicScspResponseOutputStream;
import com.caringo.client.ScspHeaders;
public class ProxyLocator implements Locator {
protected class ProxyReader extends Object {
protected String clusterName;
protected String proxyAddr;
protected int proxyPort;
public ProxyReader(String clusterName, String proxyAddr, int proxyPort) {
this.clusterName = clusterName;
this.proxyAddr = proxyAddr;
this.proxyPort = proxyPort;
}
public InetSocketAddress[] discoverNodes() {
StaticLocator subLocator = new StaticLocator();
HttpClient httpCli = new HttpClient();
ScspHeaders metaData = new ScspHeaders();
subLocator.addAddress(proxyAddr, proxyPort);
if (0 < clusterName.length()) {
metaData.addValue("Scsp-Proxy-Cluster", clusterName);
}
ScspRequestHandler client = new ScspRequestHandler(httpCli, new LocatorRedirectHandler(subLocator), 3, 300, null);
BasicScspResponseOutputStream output = new BasicScspResponseOutputStream(null);
try {
client.read("", metaData, "", output);
}
catch (Exception ex) {
throw new NoCastorNodesLocatedException(String.format("Exception when communicating with SCSP Proxy (%s)", ex.toString()));
}
HashMap<String, String> proxyNodesInfo = new HashMap<String, String>();
String nodesInfo = output.getHeader("Scsp-Proxy-Nodes");
for (HeaderElement elem : HeaderElement.parseElements(nodesInfo)) {
proxyNodesInfo.put(elem.getName(), elem.getValue());
}
Integer nodeCount = Integer.parseInt(proxyNodesInfo.get("count"));
String outputBody = output.getBodyString();
if ((0 == nodeCount) || (0 == outputBody.length())) {
String reason = proxyNodesInfo.get("reason");
throw new NoCastorNodesLocatedException(reason);
}
ArrayList<InetSocketAddress> nodeIPs = new ArrayList<InetSocketAddress>();
for (String line : outputBody.split("\\r?\\n")) {
nodeIPs.add(new InetSocketAddress(line.trim(), 0));
}
return nodeIPs.toArray(new InetSocketAddress[nodeIPs.size()]);
}
}
private class DeadpoolEntry {
public long restoreTime;
public InetSocketAddress address;
public DeadpoolEntry(long restoreTime, InetSocketAddress address) {
this.restoreTime = restoreTime;
this.address = address;
}
}
private List<InetSocketAddress> poolList;
private List<DeadpoolEntry> deadPool = new ArrayList<DeadpoolEntry>();
private String clusterName;
private String proxyAddr;
private int proxyPort;
private int retryTime;
private ProxyReader proxyReader;
/**
* Constructor.
* @param clusterName -
* The name of the cluster to query.
* @param proxyAddr -
* The SCSP proxy address.
* @param retryTime -
* The numer of seconds that an address will live in the dead pool.
*/
public ProxyLocator(String clusterName, String proxyAddr, int retryTime) {
this.proxyAddr = proxyAddr;
this.clusterName = clusterName;
this.proxyPort = 80;
this.retryTime = retryTime;
setUpPools();
}
/**
* Constructor.
* @param clusterName -
* The name of the cluster to query.
* @param proxyAddr -
* The SCSP proxy address.
* @param retryTime -
* The numer of seconds that an address will live in the dead pool.
*/
public ProxyLocator(String clusterName, String proxyAddr, int proxyPort, int retryTime) throws IllegalArgumentException {
this.proxyAddr = proxyAddr;
this.clusterName = clusterName;
if ((0 >= proxyPort) || (65534 < proxyPort)) {
throw new IllegalArgumentException("Invalid port specified");
}
this.proxyPort = proxyPort;
this.retryTime = retryTime;
setUpPools();
}
private void setUpPools() {
proxyReader = new ProxyReader(clusterName, proxyAddr, proxyPort);
poolList = new ArrayList<InetSocketAddress>(Arrays.asList(proxyReader.discoverNodes()));
deadPool.clear();
}
/**
* @see com.caringo.client.locate.Locator#start
*/
public void start() {
// nothing to do here.
}
/**
* @see com.caringo.client.locate.Locator#stop()
*/
public void stop() {
// nothing to do here.
}
/**
* @see com.caringo.client.locate.Locator#locate()
*/
public synchronized InetSocketAddress locate()
{
InetSocketAddress result = null;
int memberCount = poolList.size();
if (memberCount > 0) {
result = poolList.get(0);
if (memberCount > 1) {
// rotate to spread the load
poolList.remove(0);
poolList.add(result);
checkDeadPool();
}
}
else
{
poolList = new ArrayList<InetSocketAddress>(Arrays.asList(proxyReader.discoverNodes()));
memberCount = poolList.size();
if (memberCount > 0) {
result = poolList.get(0);
if (memberCount > 1) {
// rotate to spread the load
poolList.remove(0);
poolList.add(result);
checkDeadPool();
}
}
}
return result;
}
/**
* @see com.caringo.client.locate.Locator#locateAll()
*/
public synchronized InetSocketAddress[] locateAll() {
poolList = new ArrayList<InetSocketAddress>(Arrays.asList(proxyReader.discoverNodes()));
if (0 < poolList.size()) {
checkDeadPool();
}
return poolList.toArray(new InetSocketAddress[poolList.size()]);
}
/**
* @see com.caringo.client.locate.Locator#foundDead(java.net.InetSocketAddress)
*/
public synchronized void foundDead(InetSocketAddress addr) {
if (poolList.size() > 1) {
if (poolList.contains(addr)) {
poolList.remove(addr);
if (retryTime > 0) {
deadPool.add(new DeadpoolEntry(new Date().getTime() * 1000 + retryTime, addr));
}
}
} else {
// don't remove the last entry
}
}
/**
* @see com.caringo.client.locate.Locator#foundAlive(java.net.InetSocketAddress)
*/
public synchronized void foundAlive(InetSocketAddress addr) {
if (!poolList.contains(addr)) {
poolList.add(addr);
List<DeadpoolEntry> removeList = new ArrayList<DeadpoolEntry>();
for (DeadpoolEntry e : deadPool) {
if (e.address == addr) {
removeList.add(e);
}
}
deadPool.removeAll(removeList);
}
}
private void checkDeadPool() {
ArrayList<DeadpoolEntry> removeList = new ArrayList<DeadpoolEntry>();
long now = new Date().getTime() * 1000;
for (DeadpoolEntry e : deadPool) {
if (e.restoreTime < now) {
removeList.add(e);
if (! poolList.contains(e.address)) {
poolList.add(e.address);
}
}
}
deadPool.removeAll(removeList);
}
}