/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.bus;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
import divconq.hub.Hub;
import divconq.lang.op.OperationResult;
// a possible enhancement is to always prefer a local hub if present
public class ServiceRouter {
protected String name;
protected final ConcurrentHashMap<String, HubRouter> hubs = new ConcurrentHashMap<>();
// used for round robin load balancing
protected HubRouter localhub = null;
protected final List<HubRouter> directhublist = new CopyOnWriteArrayList<>();
protected final List<HubRouter> tunnelhublist = new CopyOnWriteArrayList<>();
protected int next = 0;
protected ReentrantLock lock = new ReentrantLock();
public ServiceRouter(String name) {
this.name = name;
}
public String getName() {
return name;
}
public OperationResult sendMessage(Message msg) {
String to = msg.getFieldAsString("ToHub");
if ("*".equals(to))
to = null;
// repeat until one of the hubs accepts, it could be that one hub is disconnecting and returns
// "false" on the send!!
OperationResult or = null;
for (int i = 0; i < 3; i++) {
HubRouter hub = (to != null) ? this.hubs.get(to) : this.nextHub();
if (hub != null) {
or = hub.deliverMessage(msg);
// if sent then jump out of loop
if (!or.hasErrors() || Hub.instance.isStopping())
break;
}
// wait for stuff to clear up (if connects/disconnects are in progress)
try {
Thread.sleep(1000);
}
catch (InterruptedException x) {
}
}
if (or == null) {
or = new OperationResult();
or.error(1, "No hub available"); // TODO code
}
return or;
}
public boolean isAvailable() {
for (int i = 0; i < 3; i++) {
HubRouter hub = this.nextHub();
if ((hub != null) && hub.isActive())
return true;
// wait for stuff to clear up (if connects/disconnects are in progress)
try {
Thread.sleep(1000);
}
catch (InterruptedException x) {
}
}
return false;
}
// there is room for improvement here - this approach assumes that direct/local connections a preferred
// and that tunnel connections will never be more than 2 layers as in talking between 101 and 201
// where 111 and 211 are tunnels for the two targets - anything more complex than this would require
// a better algorithm
// |---------|---------|------------|---------|---------|
// | LAN 101 | DMZ 111 | [internet] | DMZ 211 | DMZ 201 |
// |---------|---------|------------|---------|---------|
public HubRouter nextHub() {
this.lock.lock();
try {
// try local connections first
if (this.localhub != null)
return this.localhub;
// try direct connections second
int subcount = this.directhublist.size();
// prefer a direct hub over a proxy hub
if (subcount > 0) {
if (this.next >= subcount)
this.next = 0;
HubRouter np = this.directhublist.get(this.next);
//System.out.println("Choosing between: " + subcount + " hubs picked: " + np.getName());
this.next++;
return np;
}
// try tunnel connections third
subcount = this.tunnelhublist.size();
// prefer a direct hub over a proxy hub
if (subcount > 0) {
if (this.next >= subcount)
this.next = 0;
HubRouter np = this.tunnelhublist.get(this.next);
//System.out.println("Choosing between: " + subcount + " hubs picked: " + np.getName());
this.next++;
return np;
}
return null;
}
finally {
this.lock.unlock();
}
}
public void index(HubRouter hub) {
this.lock.lock();
try {
boolean relevant = hub.isActive() && hub.getServices().contains(this.name);
boolean current = this.directhublist.contains(hub);
if (relevant == current)
return;
if (relevant) {
this.hubs.put(hub.getHubId(), hub);
if (hub.isLocal())
this.localhub = hub;
else if (hub.isDirect())
this.directhublist.add(hub);
else if (hub.isTunneled())
this.tunnelhublist.add(hub);
}
else {
this.hubs.remove(hub.getHubId());
if (hub.isLocal()) {
this.localhub = null;
}
else {
this.directhublist.remove(hub);
this.tunnelhublist.remove(hub);
}
}
}
finally {
this.lock.unlock();
}
}
public void indexLocal() {
this.lock.lock();
try {
HubRouter hub = Hub.instance.getBus().getLocalHub();
this.hubs.put(hub.getHubId(), hub);
this.localhub = hub;
}
finally {
this.lock.unlock();
}
}
public void remove(HubRouter hub) {
this.lock.lock();
try {
this.hubs.remove(hub.getHubId());
if (hub.isLocal()) {
this.localhub = null;
}
else {
this.directhublist.remove(hub);
this.tunnelhublist.remove(hub);
}
}
finally {
this.lock.unlock();
}
}
public Collection<HubRouter> hubList() {
return this.hubs.values();
}
}