/*
*
*/
package com.caringo.client.locate;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;
import javax.jmdns.impl.JmDNSImpl;
import org.apache.log4j.Logger;
/**
* ZeroconfLocator implements Locator using Zeroconf (http://www.zeroconf.org/). <br>
* <br>
* Copyright (c) 2008 by Caringo, Inc. -- All rights reserved<br>
* This is free software, distributed under the terms of the New BSD license.<br>
* See the LICENSE.txt file included in this archive.<br>
* <br>
*
* @author jmike
* @created Apr 15, 2007
* @id $Id: ZeroconfLocator.java 927 2008-02-25 16:47:51Z jmike $
*/
public class ZeroconfLocator implements Locator {
private static final Logger logger = Logger.getLogger(ZeroconfLocator.class);
private Set<String> poolSet = new HashSet<String>();
private List<InetSocketAddress> poolList = new ArrayList<InetSocketAddress>();
private static final String TYPE_SCSP = "_scsp._tcp.local.";
private static final String SUBTYPE_SCSP = "._sub." + TYPE_SCSP;
private String clusterName;
private JmDNS jmdns;
private SubServiceListener subServiceListener;
private NamedServiceListener namedServiceListener;
public ZeroconfLocator(String clusterName) throws IOException {
this.clusterName = clusterName;
}
// /
// / Locator
// /
/**
* @see com.caringo.client.locate.Locator#foundDead(java.net.InetSocketAddress)
*/
public synchronized void foundDead(InetSocketAddress addr) {
String addrIp = toIpString(addr);
if (poolSet.contains(addrIp)) {
logger.warn(addr.getAddress() + " was missing, removing from pool.");
poolSet.remove(addrIp);
poolList.remove(addr);
}
invariant();
}
/**
* @see com.caringo.client.locate.Locator#foundAlive(java.net.InetSocketAddress)
*/
public synchronized void foundAlive(InetSocketAddress addr) {
String addrIp = toIpString(addr);
if (!poolSet.contains(addrIp)) {
poolList.add(addr);
poolSet.add(addrIp);
}
invariant();
}
/**
* @see com.caringo.client.locate.Locator#locate()
*/
public InetSocketAddress locate() {
InetSocketAddress result = null;
int retries = 5;
while (poolList.size() == 0 && retries > 0) {
--retries;
logger.warn("Pool was empty, re-querying. " + retries + " retries left.");
jmdns.list(SUBTYPE_SCSP);
}
int memberCount = poolList.size();
if (memberCount > 0) {
synchronized (this) {
if (memberCount > 1) {
// rotate to spread the load
result = poolList.remove(0);
poolList.add(result);
} else {
result = poolList.get(0);
}
}
}
return result;
}
/**
* @see com.caringo.client.locate.Locator#locateAll()
*/
public InetSocketAddress[] locateAll() {
return poolList.toArray(new InetSocketAddress[poolList.size()]);
}
/**
* @throws IOException
* @see com.caringo.client.locate.Locator#start
*/
public void start() throws IOException {
logger.info("ZeroconfLocator starting...");
jmdns = new JmDNSImpl();
String queryDomain = getQueryDomain();
subServiceListener = new SubServiceListener();
jmdns.addServiceListener(queryDomain, subServiceListener);
namedServiceListener = new NamedServiceListener();
jmdns.addServiceListener(TYPE_SCSP, namedServiceListener);
jmdns.list(queryDomain);
try {
Thread.sleep(6000);
} catch (InterruptedException ex) {
// no-op
}
logger.info("ZeroconfLocator started");
}
/**
* @see com.caringo.client.locate.Locator#stop()
*/
public void stop() {
jmdns.close();
logger.info("ZeroconfLocator stopped");
}
// /
// / ServiceListener
// /
class SubServiceListener implements ServiceListener {
/**
* @see javax.jmdns.ServiceListener#serviceAdded(javax.jmdns.ServiceEvent)
*/
public void serviceAdded(ServiceEvent event) {
// logger.debug("SUBADDED " + event.getType() + " : " +
// event.getName());
Pair<String, String> nametype = splitName(event.getName());
// logger.debug("SUBADDED ?? " + nametype.left + ": " +
// nametype.right);
namedServiceListener.resolve(nametype.right, nametype.left);
}
/**
* @see javax.jmdns.ServiceListener#serviceRemoved(javax.jmdns.ServiceEvent)
*/
public void serviceRemoved(ServiceEvent event) {
// logger.debug("SUBREMOVED " + event.getType() + " : " +
// event.getName());
}
public void serviceResolved(ServiceEvent event) {
// logger.debug("SUBRESOLVED " + event.getType() + " : " +
// event.getName());
}
}
class NamedServiceListener implements ServiceListener {
private Set<String> pendingResolution = new HashSet<String>();
public void resolve(String type, String name) {
pendingResolution.add(name);
jmdns.requestServiceInfo(type, name, 0);
}
public void serviceAdded(ServiceEvent event) {
// logger.debug("NAMEDADDED " + event.getType() + " : " +
// event.getName());
jmdns.requestServiceInfo(event.getType(), event.getName(), 0);
}
public void serviceRemoved(ServiceEvent event) {
String svcName = event.getName();
// logger.debug("NAMEDREMOVED " + event.getType() + " : " +
// svcName);
ServiceInfo svcInfo = event.getInfo();
if (svcInfo != null) {
String svcHost = svcInfo.getHostAddress();
if (poolSet.contains(svcHost)) {
logger.debug("Removed" + svcName);
foundDead(new InetSocketAddress(svcHost, svcInfo.getPort()));
}
if (pendingResolution.contains(svcName)) {
pendingResolution.remove(svcName);
}
}
}
public void serviceResolved(ServiceEvent event) {
// logger.debug("NAMEDRESOLVED " + event.getType() + " : " +
// event.getName());
String name = event.getName();
ServiceInfo info = event.getInfo();
if (info != null) {
if (pendingResolution.contains(name)) {
String svcHost = info.getHostAddress();
if (!poolSet.contains(svcHost)) {
logger.debug("Resolved " + name + " to " + svcHost);
foundAlive(new InetSocketAddress(svcHost, info.getPort()));
pendingResolution.remove(name);
}
}
} else {
jmdns.requestServiceInfo(event.getType(), name, 0);
}
}
}
// /
// / Support
// /
private Pair<String, String> splitName(String domain) {
int dotIdx = domain.indexOf(".");
if (dotIdx == -1) {
return new Pair<String, String>(domain, null);
} else {
String name = domain.substring(0, dotIdx);
String type = domain.substring(dotIdx + 1);
return new Pair<String, String>(name, type);
}
}
private void invariant() {
if (poolSet.size() != poolList.size()) {
logger.warn("Pool set and list size mismatch, rebuilding set");
poolSet.clear();
for (InetSocketAddress addr : poolList) {
poolSet.add(toIpString(addr));
}
}
}
private String getQueryDomain() {
return clusterName + SUBTYPE_SCSP;
}
/**
* Extracts the IP address as a string from an InetSocketAddress.
*
* @param addr
* @return
*/
private String toIpString(InetSocketAddress addr) {
return addr.getAddress().getHostAddress();
}
@Override
public String toString() {
return "ZeroconfLocator (cluster='" + clusterName + "')";
}
}