package net.varkhan.serv.p2p.network; import net.varkhan.serv.p2p.Listenable; import net.varkhan.serv.p2p.connect.*; import net.varkhan.serv.p2p.connect.config.MapProperties; import net.varkhan.serv.p2p.connect.transport.MTXAddress; import net.varkhan.serv.p2p.message.MesgPayload; import net.varkhan.serv.p2p.message.dispatch.MesgReceiver; import net.varkhan.serv.p2p.message.transport.MTXTransport; 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 7:22 AM */ public class P2PGroup extends P2PNode implements PeerNode, PeerProperties, PeerGroup, MTXAddress { private long lastUpdate; private long minHeartBeat; private long maxHeartBeat; private P2PHub hub; private P2PLocalHost here; private final ConcurrentMap<MTXTransport,Set<P2PHost>> channels=new ConcurrentHashMap<MTXTransport,Set<P2PHost>>(); private final Set<GroupListener> groupUL =new CopyOnWriteArraySet<GroupListener>(); public P2PGroup(String name, PeerProperties props, P2PHub hub) { super(name, props); this.here=hub.local(); this.hub=hub; lastUpdate=System.currentTimeMillis(); } private MTXTransport getPreferredChannel() { MTXTransport channel=null; long num=0; for(Map.Entry<MTXTransport,Set<P2PHost>> e : channels.entrySet()) { long n=e.getValue().size(); if(n>num) { num=n; channel=e.getKey(); } } return channel; } @Override @SuppressWarnings("unchecked") public <T> T as(Class<T> c) { if(c.isAssignableFrom(MTXAddress.class)) return (T) this; return null; } public InetAddress addrMTXgroup() { MTXTransport c=getPreferredChannel(); if(c==null) return null; return c.getGroupAddr(); } public int portMTXgroup() { MTXTransport c=getPreferredChannel(); if(c==null) return 0; return c.getGroupPort(); } public State state() { if(!channels.isEmpty()) return State.RUNNING; return State.STOPPED; } public void start() { //To change body of implemented methods use File | Settings | File Templates. } public void stop() { for(Iterator<MTXTransport> iterator=channels.keySet().iterator();iterator.hasNext();) { MTXTransport c=iterator.next(); try { c.stop(); } catch(IOException e) { /* ignore*/ } iterator.remove(); } } public boolean call(String method, MesgPayload message, MesgReceiver handler) throws IOException { for(Map.Entry<MTXTransport,Set<P2PHost>> e: channels.entrySet()) { MTXTransport c = e.getKey(); try { if(c.call(here, this, method, message, handler)) continue; } catch(IOException x) { /* Go on next alternative */ } // We couldn't send as multicast. This can happen for a number of reasons: // improper configuration, unreachable nodes, or message too large. // We will try to resend to each node individually. Set<P2PHost> m=e.getValue(); for(P2PHost p: m) { 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(UpdateListener<T> list) { if(list instanceof GroupListener) groupUL.add((GroupListener) list); } protected void addHost(PeerHost p) { for(Iterator<GroupListener> iterator=groupUL.iterator();iterator.hasNext();) { GroupListener ul=iterator.next(); try { ul.addHost(this, p); } catch(Throwable t) { /* ignore */ } try { if(ul.finished()) iterator.remove(); } catch(Throwable t) { /* ignore */ } } } protected void delHost(PeerHost p) { for(Iterator<GroupListener> iterator=groupUL.iterator();iterator.hasNext();) { GroupListener ul=iterator.next(); try { ul.delHost(this, p); } catch(Throwable t) { /* ignore */ } try { if(ul.finished()) iterator.remove(); } catch(Throwable t) { /* ignore */ } } } protected void updGroup() { for(Iterator<GroupListener> iterator=groupUL.iterator();iterator.hasNext();) { GroupListener ul=iterator.next(); try { ul.update(this); } catch(Throwable t) { /* ignore */ } try { if(ul.finished()) iterator.remove(); } catch(Throwable t) { /* ignore */ } } } public Collection<PeerHost> hosts() { Set<PeerHost> m = new HashSet<PeerHost>(); for(Set<P2PHost> s: channels.values()) m.addAll(s); return m; } public void update(P2PHost host, PeerProperties props) { lastUpdate = System.currentTimeMillis(); setProperties(props); checkChannels(host, props); } private void checkChannels(P2PHost host, PeerProperties props) { try { String mtxName=props.getProperty(Realm.LOCAL, name+'.'+PROPERTY_MTX_ADDR); InetAddress mtxAddr=InetAddress.getByName(mtxName); int mtxPort=props.getProperty(Realm.LOCAL, name+'.'+PROPERTY_MTX_PORT, -1); if(mtxAddr==null || mtxPort<0) return; for(Map.Entry<MTXTransport,Set<P2PHost>> e: channels.entrySet()) { MTXTransport c=e.getKey(); if(c.getGroupAddr().equals(mtxAddr) && c.getGroupPort()==mtxPort) return; } MTXTransport ch=hub.getMtxChannel(name, mtxPort, mtxAddr); channels.putIfAbsent(ch,new CopyOnWriteArraySet<P2PHost>(Arrays.asList(host))); } catch(IOException e) { // ignore this update } } public boolean isStale() { long age=System.currentTimeMillis()-lastUpdate; return age>=3*minHeartBeat || age>=2*maxHeartBeat; } public Collection<MTXTransport> getMTXChannels() { return channels.keySet(); } public static MapProperties getProperties(MapProperties props, String name, InetAddress mtxAddr, int mtxPort) { props.setProperty(Realm.LOCAL, name+'.'+PROPERTY_MTX_ADDR, mtxAddr.getHostAddress()); props.setProperty(Realm.LOCAL, name+'.'+PROPERTY_MTX_PORT, Long.toString(mtxPort)); return props; } }