package org.jgroups.stack;
import org.jgroups.Address;
import org.jgroups.PhysicalAddress;
import org.jgroups.annotations.GuardedBy;
import org.jgroups.blocks.cs.*;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.PingData;
import org.jgroups.util.ByteArrayDataInputStream;
import org.jgroups.util.ByteArrayDataOutputStream;
import org.jgroups.util.Util;
import java.io.DataInput;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.*;
/**
* Client stub that talks to a remote GossipRouter via blocking or non-blocking TCP
* @author Bela Ban
*/
public class RouterStub extends ReceiverAdapter implements Comparable<RouterStub>, ConnectionListener {
public interface StubReceiver {void receive(GossipData data);}
public interface MembersNotification {void members(List<PingData> mbrs);}
public interface CloseListener {void closed(RouterStub stub);}
protected BaseServer client;
protected final IpAddress local; // bind address
protected final IpAddress remote; // address of remote GossipRouter
protected final boolean use_nio;
protected StubReceiver receiver; // external consumer of data, e.g. TUNNEL
protected CloseListener close_listener;
protected static final Log log=LogFactory.getLog(RouterStub.class);
// max number of ms to wait for socket establishment to GossipRouter
protected int sock_conn_timeout=3000;
protected boolean tcp_nodelay=true;
// map to correlate GET_MBRS requests and responses
protected final Map<String,List<MembersNotification>> get_members_map=new HashMap<>();
/**
* Creates a stub to a remote GossipRouter
* @param bind_addr The local address to bind to. If null, one will be picked
* @param bind_port The local port. If 0, a random port will be used
* @param router_host The address of the remote {@link GossipRouter}
* @param router_port The port on which the remote GossipRouter is listening
* @param use_nio Whether to use blocking or non-blocking IO
* @param l The {@link org.jgroups.stack.RouterStub.CloseListener}
*/
public RouterStub(InetAddress bind_addr, int bind_port, InetAddress router_host, int router_port,
boolean use_nio, CloseListener l) {
local=new IpAddress(bind_addr, bind_port);
this.remote=new IpAddress(router_host, router_port);
this.use_nio=use_nio;
this.close_listener=l;
client=use_nio? new NioClient(bind_addr, bind_port, router_host, router_port)
: new TcpClient(bind_addr, bind_port, router_host, router_port);
client.addConnectionListener(this);
client.receiver(this);
client.socketConnectionTimeout(sock_conn_timeout).tcpNodelay(tcp_nodelay);
}
public RouterStub(IpAddress local, IpAddress remote, boolean use_nio, CloseListener l) {
this.local=local;
this.remote=remote;
this.use_nio=use_nio;
this.close_listener=l;
client=use_nio? new NioClient(local, remote) : new TcpClient(local, remote);
client.receiver(this);
client.addConnectionListener(this);
client.socketConnectionTimeout(sock_conn_timeout).tcpNodelay(tcp_nodelay);
}
public IpAddress local() {return local;}
public IpAddress remote() {return remote;}
public RouterStub receiver(StubReceiver r) {receiver=r; return this;}
public StubReceiver receiver() {return receiver;}
public boolean tcpNoDelay() {return tcp_nodelay;}
public RouterStub tcpNoDelay(boolean tcp_nodelay) {this.tcp_nodelay=tcp_nodelay; return this;}
public CloseListener connectionListener() {return close_listener;}
public RouterStub connectionListener(CloseListener l) {this.close_listener=l; return this;}
public int socketConnectionTimeout() {return sock_conn_timeout;}
public RouterStub socketConnectionTimeout(int timeout) {this.sock_conn_timeout=timeout; return this;}
public boolean useNio() {return use_nio;}
public IpAddress gossipRouterAddress() {return remote;}
public boolean isConnected() {return client != null && ((Client)client).isConnected();}
public RouterStub set(String attr, Object val) {
switch(attr) {
case "tcp_nodelay":
tcpNoDelay((Boolean)val);
break;
default:
throw new IllegalArgumentException("Attribute " + attr + " unknown");
}
return this;
}
/**
* Registers mbr with the GossipRouter under the given group, with the given logical name and physical address.
* Establishes a connection to the GossipRouter and sends a CONNECT message.
* @param group The group cluster) name under which to register the member
* @param addr The address of the member
* @param logical_name The logical name of the member
* @param phys_addr The physical address of the member
* @throws Exception Thrown when the registration failed
*/
public void connect(String group, Address addr, String logical_name, PhysicalAddress phys_addr) throws Exception {
synchronized(this) {
_doConnect();
}
try {
writeRequest(new GossipData(GossipType.REGISTER, group, addr, logical_name, phys_addr));
}
catch(Exception ex) {
throw new Exception(String.format("connection to %s failed: %s", group, ex));
}
}
public synchronized void connect() throws Exception {
_doConnect();
}
@GuardedBy("lock")
protected void _doConnect() throws Exception {
client.start();
}
public void disconnect(String group, Address addr) throws Exception {
writeRequest(new GossipData(GossipType.UNREGISTER, group, addr));
}
public void destroy() {
Util.close(client);
}
/**
* Fetches a list of {@link PingData} from the GossipRouter, one for each member in the given group. This call
* returns immediately and when the results are available, the
* {@link org.jgroups.stack.RouterStub.MembersNotification#members(List)} callback will be invoked.
* @param group The group for which we need members information
* @param callback The callback to be invoked.
*/
public void getMembers(final String group, MembersNotification callback) throws Exception {
if(callback == null)
return;
// if(!isConnected()) throw new Exception ("not connected");
synchronized(get_members_map) {
List<MembersNotification> set=get_members_map.get(group);
if(set == null)
get_members_map.put(group, set=new ArrayList<>());
set.add(callback);
}
try {
writeRequest(new GossipData(GossipType.GET_MBRS, group, null));
}
catch(Exception ex) {
removeResponse(group, callback);
throw new Exception(String.format("connection to %s broken. Could not send %s request: %s",
gossipRouterAddress(), GossipType.GET_MBRS, ex));
}
}
public void sendToAllMembers(String group, Address sender, byte[] data, int offset, int length) throws Exception {
sendToMember(group, null, sender, data, offset, length); // null destination represents mcast
}
public void sendToMember(String group, Address dest, Address sender, byte[] data, int offset, int length) throws Exception {
try {
writeRequest(new GossipData(GossipType.MESSAGE, group, dest, data, offset, length).setSender(sender));
}
catch(Exception ex) {
throw new Exception(String.format("connection to %s broken. Could not send message to %s: %s",
gossipRouterAddress(), dest, ex));
}
}
@Override
public void receive(Address sender, byte[] buf, int offset, int length) {
ByteArrayDataInputStream in=new ByteArrayDataInputStream(buf, offset, length);
GossipData data=new GossipData();
try {
data.readFrom(in);
switch(data.getType()) {
case MESSAGE:
case SUSPECT:
if(receiver != null)
receiver.receive(data);
break;
case GET_MBRS_RSP:
notifyResponse(data.getGroup(), data.getPingData());
break;
}
}
catch(Exception ex) {
log.error(Util.getMessage("FailedReadingData"), ex);
}
}
@Override
public void receive(Address sender, ByteBuffer buf) {
Util.bufferToArray(sender, buf, this);
}
public void receive(Address sender, DataInput in) throws Exception {
GossipData data=new GossipData();
data.readFrom(in);
switch(data.getType()) {
case MESSAGE:
case SUSPECT:
if(receiver != null)
receiver.receive(data);
break;
case GET_MBRS_RSP:
notifyResponse(data.getGroup(), data.getPingData());
break;
}
}
@Override
public void connectionClosed(Connection conn, String reason) {
if(close_listener != null)
close_listener.closed(this);
}
@Override
public void connectionEstablished(Connection conn) {
}
@Override public int compareTo(RouterStub o) {
return remote.compareTo(o.remote);
}
public int hashCode() {return remote.hashCode();}
public boolean equals(Object obj) {
return compareTo((RouterStub)obj) == 0;
}
public String toString() {
return String.format("RouterStub[localsocket=%s, router_host=%s]", client.localAddress(), remote);
}
protected synchronized void writeRequest(GossipData req) throws Exception {
int size=req.serializedSize();
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(size+5);
req.writeTo(out);
client.send(remote, out.buffer(), 0, out.position());
}
protected void removeResponse(String group, MembersNotification notif) {
synchronized(get_members_map) {
List<MembersNotification> set=get_members_map.get(group);
if(set == null || set.isEmpty()) {
get_members_map.remove(group);
return;
}
if(set.remove(notif) && set.isEmpty())
get_members_map.remove(group);
}
}
protected void notifyResponse(String group, List<PingData> list) {
if(group == null)
return;
if(list == null)
list=Collections.emptyList();
synchronized(get_members_map) {
List<MembersNotification> set=get_members_map.get(group);
while(set != null && !set.isEmpty()) {
try {
MembersNotification rsp=set.remove(0);
rsp.members(list);
}
catch(Throwable t) {
log.error("failed notifying %s: %s", group, t);
}
}
get_members_map.remove(group);
}
}
}