package net.varkhan.serv.p2p.network;
import net.varkhan.base.management.report.JMXAverageMonitorReport;
import net.varkhan.serv.p2p.Listenable;
import net.varkhan.serv.p2p.PeerHub;
import net.varkhan.serv.p2p.connect.PeerNode;
import net.varkhan.serv.p2p.connect.PeerAddress;
import net.varkhan.serv.p2p.connect.PeerProperties;
import net.varkhan.serv.p2p.connect.PeerGroup;
import net.varkhan.serv.p2p.connect.PeerHost;
import net.varkhan.serv.p2p.connect.config.MapProperties;
import net.varkhan.serv.p2p.connect.transport.MTXAddress;
import net.varkhan.serv.p2p.connect.PeerWorld;
import net.varkhan.serv.p2p.message.dispatch.MesgDispatcher;
import net.varkhan.serv.p2p.message.MesgPayload;
import net.varkhan.serv.p2p.message.dispatch.MesgReceiver;
import net.varkhan.serv.p2p.message.PeerResolver;
import net.varkhan.serv.p2p.message.transport.MTXTransport;
import net.varkhan.serv.p2p.message.protocol.BinaryPayload;
import net.varkhan.serv.p2p.connect.protocol.BinaryPropertiesSerializer;
import net.varkhan.serv.p2p.connect.transport.UDPAddress;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* <b></b>.
* <p/>
*
* @author varkhan
* @date 5/29/11
* @time 11:49 PM
*/
public class P2PHub extends P2PNode implements PeerNode, PeerProperties, PeerWorld, MTXAddress, /*UDPAddress,*/ PeerHub, PeerResolver {
private final P2PLocalHost local;
private final MTXTransport glmtx;
// private final UDPTransport gludp;
private final JMXAverageMonitorReport stats;
private final ConcurrentMap<String,P2PRemoteHost> hosts =new ConcurrentHashMap<String,P2PRemoteHost>();
private final ConcurrentMap<String,P2PGroup> groups=new ConcurrentHashMap<String,P2PGroup>();
private final Set<PeerWorld.AllListener> worldUL=new CopyOnWriteArraySet<PeerWorld.AllListener>();
private final Set<PeerGroup.GroupListener> groupUL=new CopyOnWriteArraySet<PeerGroup.GroupListener>();
private final Set<PeerHost.HostListener> hostUL =new CopyOnWriteArraySet<PeerHost.HostListener>();
private final InetAddress localAddr;
private final InetAddress bcastAddr = InetAddress.getByName("255.255.255.255");
private final MesgDispatcher dispatcher;
private long lastUpdate;
private Thread thread;
private volatile State state=State.STOPPED;
public P2PHub(String name, MesgDispatcher dispatcher, InetAddress localAddr, int localPort, InetAddress globalAddr, int globalPort, long heartBeat) throws IOException {
super("*");
stats=new JMXAverageMonitorReport(10, name);
this.localAddr=localAddr;
this.dispatcher=dispatcher;
local=new P2PLocalHost(name, this, dispatcher, localAddr, localPort, stats, heartBeat);
glmtx=new MTXTransport(this, dispatcher, local, localAddr, globalAddr, globalPort, stats);
// gludp=new UDPTransport(this, dispatcher, local, localAddr, globalPort+1, stats);
setProperty(Realm.LOCAL, PROPERTY_MTX_ADDR, globalAddr.getHostAddress());
setProperty(Realm.LOCAL, PROPERTY_MTX_PORT, Long.toString(globalPort));
}
@Override
@SuppressWarnings("unchecked")
public <T> T as(Class<T> c) {
if(MTXAddress.class==c) return (T) this;
if(UDPAddress.class==c) return (T) this;
return null;
}
@Override
public void join(String group, PeerProperties props) {
P2PGroup g=groups.get(group);
if(g==null) try {
g=new P2PGroup(group, props, this);
}
catch(Exception e) {
throw new RuntimeException("Invalid group properties "+props, e);
}
if(groups.putIfAbsent(group, g)!=null) g=groups.get(group);
else {
g.update(local, props);
}
if(g!=null) local.addGroup(g);
addHost(g, local);
updGroup(g);
}
public MTXTransport getMtxChannel(String group, int mtxPort, InetAddress mtxAddr) throws IOException {
return new MTXTransport(this, dispatcher, local, localAddr, mtxAddr, mtxPort, stats);
}
@Override
public void leave(String group) {
P2PGroup g=groups.get(group);
if(g==null) return;
if(g.hosts().isEmpty()) groups.remove(group);
local.delGroup(g);
delHost(g, local);
updGroup(g);
}
@Override
public String set(String var, String val) {
return val;
}
@Override
public String get(String var) {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
public PeerWorld world() {
return this;
}
public InetAddress addrMTXgroup() {
return glmtx.getGroupAddr();
}
public int portMTXgroup() {
return glmtx.getGroupPort();
}
// @Override
// public InetAddress addrUDP() {
// return bcastAddr;
// }
//
// @Override
// public int portUDP() {
// return gludp.getPort();
// }
private static int threadNumber;
private static synchronized int nextThreadNumber() {
return threadNumber++;
}
public synchronized void start() {
state=State.STARTING;
if(thread!=null) return;
local.start();
try {
glmtx.start();
// gludp.start();
}
catch(IOException e) {
// This we can't recover from: there is no way to have a hub working properly without this
try { local.stop(); }
catch(Exception x) { /* ignore*/ }
return;
}
thread=new WpHeartBeat();
thread.setDaemon(true);
thread.setName(P2PHub.class.getSimpleName()+"-"+nextThreadNumber());
thread.start();
state=State.RUNNING;
}
public boolean isStarted() { return thread!=null; }
public void stop() { stop(local.getHeartBeat()); }
public synchronized void stop(long timeout) {
state=State.STOPPING;
Thread t=thread;
if(t==null) return;
thread=null;
t.interrupt();
try { t.join(timeout); }
catch(InterruptedException e) { /* ignore */ }
// Shut down all group channels
for(P2PGroup g : groups.values()) {
g.stop();
}
try {
// gludp.stop();
glmtx.stop();
}
catch(IOException e) { /* ignore*/ }
local.stop();
state=State.STOPPED;
}
public Collection<PeerHost> hosts() {
return new HashSet<PeerHost>(hosts.values());
}
public Collection<? extends PeerGroup> groups() {
return new HashSet<PeerGroup>(groups.values());
}
public boolean call(String method, MesgPayload message, MesgReceiver handler) throws IOException {
if(glmtx.call(local, this, method, message, handler)) return true;
// if(gludp.call(local, this, method, message, handler)) return true;
for(Map.Entry<String,P2PRemoteHost> e : hosts.entrySet()) {
P2PNode p=e.getValue();
try { p.call(method, message, handler); }
catch(IOException x) { /* Ignore errors on individual nodes */ }
}
// Group calls always succeed
return true;
}
public boolean repl(String method, MesgPayload message, long sequence) throws IOException {
// Replies are never sent to a group
return false;
}
@Override
public <T extends Listenable> void addListener(Listenable.UpdateListener<T> list) {
if(list instanceof PeerHost.HostListener) hostUL.add((PeerHost.HostListener) list);
if(list instanceof PeerGroup.GroupListener) groupUL.add((PeerGroup.GroupListener) list);
if(list instanceof PeerHost.HostListener) hostUL.add((PeerHost.HostListener) list);
}
public State state() {
return state;
}
private void updHost(PeerHost p) {
for(Iterator<PeerHost.HostListener> iterator=hostUL.iterator();iterator.hasNext();) {
PeerHost.HostListener ul=iterator.next();
try { ul.update(p); }
catch(Throwable t) { /* ignore */ }
try { if(ul.finished()) iterator.remove(); }
catch(Throwable t) { /* ignore */ }
}
}
private void addHost(PeerHost p) {
hosts.putIfAbsent(p.name(), (P2PRemoteHost) p);
for(Iterator<PeerWorld.AllListener> iterator=worldUL.iterator();iterator.hasNext();) {
PeerWorld.AllListener ul=iterator.next();
try { ul.addHost(this, p); }
catch(Throwable t) { /* ignore */ }
try { if(ul.finished()) iterator.remove(); }
catch(Throwable t) { /* ignore */ }
}
for(PeerGroup g : p.groups()) {
addHost(g, p);
}
}
private void addHost(PeerGroup g, PeerHost p) {
for(Iterator<GroupListener> iterator=groupUL.iterator();iterator.hasNext();) {
GroupListener ul=iterator.next();
try { ul.addHost(g, p); }
catch(Throwable t) { /* ignore */ }
try { if(ul.finished()) iterator.remove(); }
catch(Throwable t) { /* ignore */ }
}
if(g instanceof P2PGroup) ((P2PGroup) g).addHost(p);
}
private void delHost(PeerHost p) {
hosts.remove(p.name());
for(PeerGroup g : p.groups()) {
delHost(g, p);
}
for(Iterator<PeerWorld.AllListener> iterator=worldUL.iterator();iterator.hasNext();) {
PeerWorld.AllListener ul=iterator.next();
try { ul.delHost(this, p); }
catch(Throwable t) { /* ignore */ }
try { if(ul.finished()) iterator.remove(); }
catch(Throwable t) { /* ignore */ }
}
}
private void delHost(PeerGroup g, PeerHost p) {
for(Iterator<GroupListener> iterator=groupUL.iterator();iterator.hasNext();) {
GroupListener ul=iterator.next();
try { ul.delHost(g, p); }
catch(Throwable t) { /* ignore */ }
try { if(ul.finished()) iterator.remove(); }
catch(Throwable t) { /* ignore */ }
}
if(g instanceof P2PGroup) ((P2PGroup) g).delHost(p);
}
private void updGroup(PeerGroup g) {
for(Iterator<GroupListener> iterator=groupUL.iterator();iterator.hasNext();) {
GroupListener ul=iterator.next();
try { ul.update(g); }
catch(Throwable t) { /* ignore */ }
try { if(ul.finished()) iterator.remove(); }
catch(Throwable t) { /* ignore */ }
}
if(g instanceof P2PGroup) ((P2PGroup) g).updGroup();
}
private void addGroup(PeerGroup p) {
groups.putIfAbsent(p.name(), (P2PGroup) p);
for(Iterator<PeerWorld.AllListener> iterator=worldUL.iterator();iterator.hasNext();) {
PeerWorld.AllListener ul=iterator.next();
try { ul.addGroup(this, p); }
catch(Throwable t) { /* ignore */ }
try { if(ul.finished()) iterator.remove(); }
catch(Throwable t) { /* ignore */ }
}
}
private void delGroup(PeerGroup p) {
groups.remove(p.name());
for(Iterator<PeerWorld.AllListener> iterator=worldUL.iterator();iterator.hasNext();) {
PeerWorld.AllListener ul=iterator.next();
try { ul.delGroup(this, p); }
catch(Throwable t) { /* ignore */ }
try { if(ul.finished()) iterator.remove(); }
catch(Throwable t) { /* ignore */ }
}
}
public P2PLocalHost local() { return local; }
@Override
public PeerAddress resolve(String name) {
if(name==null || name.length()==0 || local.name().equals(name)) return local;
if(this.name().equals(name)) return this;
PeerAddress p = hosts.get(name);
if(p!=null) return p;
PeerAddress g = groups.get(name);
if(g!=null) return g;
return null;
}
private final class WpHeartBeat extends Thread {
public void run() {
long time = System.currentTimeMillis();
// Send first ping
ping(P2PHub.this);
// Start cleanup / update cycle
while(thread!=null) {
long period = local.getHeartBeat();
// Sleep until next heartbeat, or awoken
long elapsed = System.currentTimeMillis()-time;
if(elapsed<period) try { Thread.sleep(period-elapsed); }
catch(InterruptedException e) { if(thread==null) return; }
// By now, we should have gotten answers to our last ping
time = System.currentTimeMillis();
// Discard stale nodes
for(Iterator<P2PRemoteHost> iterator=hosts.values().iterator();iterator.hasNext();) {
P2PRemoteHost p=iterator.next();
try {
if(p.isStale()) {
iterator.remove();
delHost(p);
}
}
catch(Throwable t) {
// Failing nodes are forcibly removed
iterator.remove();
delHost(p);
}
}
// Discard stale groups
for(Iterator<P2PGroup> iterator=groups.values().iterator();iterator.hasNext();) {
P2PGroup p=iterator.next();
try {
if(p.isStale()) delGroup(p);
}
catch(Throwable t) {
// Failing groups are forcibly removed
iterator.remove();
delGroup(p);
}
}
// Send another ping
ping(P2PHub.this);
}
}
}
public void ping(PeerAddress dst) {
try {
glmtx.ping(local, dst, "", info(), 1);
// gludp.ping(local, dst, "", info(), 1);
}
catch(IOException e) {
stats.inc(P2PHub.class.getSimpleName()+".Ping.error["+e.getClass().getSimpleName()+"]");
}
}
@Override
public PeerAddress update(String pname, MesgPayload data) {
if(local.name().equals(pname)) return local;
lastUpdate = System.currentTimeMillis();
P2PRemoteHost host = this.hosts.get(pname);
MapProperties props = toProperties(data);
if(host!=null) {
host.update(props);
updHost(host);
}
else {
host = new P2PRemoteHost(pname,props, local, local.getUDPChannel(), local.getTCPChannel());
if(this.hosts.putIfAbsent(pname,host)!=null) {
host = this.hosts.get(pname);
if(host!=null) {
host.update(props);
updHost(host);
}
}
else {
addHost(host);
}
}
// Case where location was concurrently removed
if(host==null) return null;
Collection<P2PGroup> groups = host.groups();
if(groups!=null && !groups.isEmpty()) for(P2PGroup group: groups) {
String gname = group.name();
P2PGroup g = this.groups.get(gname);
if(g!=null) {
g.update(host,props);
updGroup(g);
}
else {
g = new P2PGroup(gname, props, this);
if(this.groups.putIfAbsent(gname,g)!=null) {
g = this.groups.get(gname);
// Do a group update to get channels set up
g.update(host,props);
updGroup(g);
}
else {
// Do a group update to get channels set up
g.update(host,props);
addGroup(g);
}
}
}
return host;
}
@Override
public MesgPayload info() {
return toMessage(local);
}
private static final BinaryPropertiesSerializer<Object> ptser=new BinaryPropertiesSerializer<Object>();
public MesgPayload toMessage(MapProperties point) {
BinaryPayload msg=new BinaryPayload();
ByteArrayOutputStream os=new ByteArrayOutputStream();
ptser.encode(point, os, null);
msg.setData(os.toByteArray());
return msg;
}
public MapProperties toProperties(MesgPayload mesg) {
try {
return ptser.decode(mesg.getDataAsStream(), null);
}
catch(IOException e) {
return null;
}
}
}