![JAR search and dependency download from the Maven repository](/logo.png)
io.nextop.client.node.MultiNode Maven / Gradle / Ivy
package io.nextop.client.node;
import com.google.common.collect.ImmutableSet;
import io.nextop.Authority;
import io.nextop.client.MessageControl;
import io.nextop.client.MessageControlChannel;
import io.nextop.client.MessageControlNode;
import io.nextop.client.MessageControlState;
import io.nextop.log.NL;
import rx.Scheduler;
import javax.annotation.Nullable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.util.*;
// multi down
// this node maintains multiple (n) downstream nodes ranked by preference
// routes control to the highest pref downsteam that is active
// when the active state of a downstream changes, this node ensures that the active state of the highest downstream is exclusively set
public class MultiNode extends AbstractMessageControlNode {
private final DownstreamState[] downstreamStates;
private Queue pendingMessageControls = new LinkedList();
private boolean active = false;
private Collection localSubnets = Collections.emptyList();
/** @param downstreams the order is the preference (0 top) */
public MultiNode(Downstream ... downstreams) {
int n = downstreams.length;
downstreamStates = new DownstreamState[n];
for (int i = 0; i < n; ++i) {
downstreamStates[i] = new DownstreamState(downstreams[i], false);
}
}
@Nullable
private MessageControlNode getActiveDownstream() {
for (DownstreamState state : downstreamStates) {
if (state.downActive) {
assert state.upActive;
return state.downstream.node;
}
}
return null;
}
private void setActiveDownstream() {
// scan the down states for the first with upActive and compatible
// if there is another set downActive, unset that, then set the new
if (!active) {
clearActiveDownstream();
} else {
int firstUpActiveIndex = -1;
for (int i = 0, n = downstreamStates.length; i < n; ++i) {
DownstreamState state = downstreamStates[i];
if (state.upActive && state.compatible) {
firstUpActiveIndex = i;
break;
}
}
if (firstUpActiveIndex < 0) {
clearActiveDownstream();
} else if (!downstreamStates[firstUpActiveIndex].downActive) {
// unset
clearActiveDownstream();
// set
DownstreamState activeDownstreamState = downstreamStates[firstUpActiveIndex];
activeDownstreamState.downActive = true;
activeDownstreamState.downstream.node.onActive(true);
for (@Nullable MessageControl mc; null != (mc = pendingMessageControls.poll()); ) {
activeDownstreamState.downstream.node.onMessageControl(mc);
}
}
}
}
private void clearActiveDownstream() {
for (int i = 0, n = downstreamStates.length; i < n; ++i) {
DownstreamState state = downstreamStates[i];
if (state.downActive) {
state.downstream.node.onActive(false);
break;
}
}
assert null == getActiveDownstream();
}
@Override
protected void initSelf(@Nullable Bundle savedState) {
upstream.onActive(true);
try {
localSubnets = findLocalSubnets();
} catch (IOException e) {
NL.nl.handled("node.multi.init", e);
// go forward with no subnets
}
}
@Override
protected void initDownstream(final @Nullable Bundle savedState) {
final int n = downstreamStates.length;
for (int i = 0; i < n; ++i) {
final DownstreamState state = downstreamStates[i];
state.downstream.node.init(new MessageControlChannel() {
@Override
public void onActive(boolean active) {
state.upActive = active;
setActiveDownstream();
}
@Override
public void onMessageControl(MessageControl mc) {
switch (mc.dir) {
case SEND:
// route to the active
MultiNode.this.onMessageControl(mc);
break;
case RECEIVE:
// up
upstream.onMessageControl(mc);
break;
default:
throw new IllegalArgumentException();
}
}
@Override
public MessageControlState getMessageControlState() {
return MultiNode.this.getMessageControlState();
}
@Override
public void post(Runnable r) {
MultiNode.this.post(r);
}
@Override
public void postDelayed(Runnable r, int delayMs) {
MultiNode.this.postDelayed(r, delayMs);
}
@Override
public Scheduler getScheduler() {
return MultiNode.this.getScheduler();
}
}, savedState);
}
}
@Override
public void onActive(boolean active) {
this.active = active;
setActiveDownstream();
// TODO on inactive with pending, should pending be sent back upstream?
}
@Override
public void onMessageControl(MessageControl mc) {
// filter incompatible downstreams
// adjust the compatibility state to the AND of all seen messages
// see #3 https://github.com/nextopio/nextop-client/issues/3
// TODO reset the compatibility state at some point
if (contains(localSubnets, mc.message.route.via.authority)) {
// filter incompatible downstreams
boolean modified = false;
for (DownstreamState state : downstreamStates) {
if (state.compatible && !state.downstream.support.contains(Downstream.Support.LOCAL)) {
state.compatible = false;
modified = true;
}
}
if (modified) {
setActiveDownstream();
}
}
@Nullable MessageControlNode activeDownstream = getActiveDownstream();
if (null != activeDownstream) {
activeDownstream.onMessageControl(mc);
} else {
// append to the queue for when the active state flips, see #setActiveDownstream
pendingMessageControls.add(mc);
}
}
private static final class DownstreamState {
final Downstream downstream;
// set by the downstream up into the multi node
boolean upActive;
// set by the multi node into the downstream
boolean downActive = false;
/** this is set false depending on the downstream support for messages.
* @see Downstream#support */
// TODO currently if set, it is never reset; do that at some point
boolean compatible = true;
DownstreamState(Downstream downstream, boolean upActive) {
this.downstream = downstream;
this.upActive = upActive;
}
}
public static final class Downstream {
public static enum Support {
LOCAL
}
public static Downstream create(MessageControlNode node, Support ... support) {
return new Downstream(node, ImmutableSet.copyOf(support));
}
public final MessageControlNode node;
public final ImmutableSet support;
Downstream(MessageControlNode node, ImmutableSet support) {
this.node = node;
this.support = support;
}
}
/////// SUBNET ///////
/* this is used to address #3
* @see https://github.com/nextopio/nextop-client/issues/3 */
private static Collection findLocalSubnets() throws IOException {
Queue is = new LinkedList();
Enumeration e = NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
is.add(e.nextElement());
}
List subnets = new ArrayList(4);
for (NetworkInterface i; null != (i = is.poll()); ) {
for (InterfaceAddress ia : i.getInterfaceAddresses()) {
subnets.add(Subnet.valueOf(ia));
}
@Nullable NetworkInterface p = i.getParent();
if (null != p) {
is.offer(p);
}
}
return subnets;
}
private static final class Subnet {
static Subnet valueOf(InterfaceAddress ia) {
InetAddress a = ia.getAddress();
return new Subnet(a.getHostName(), bits(a.getAddress()), ia.getNetworkPrefixLength());
}
final String host;
final BitSet address;
final int prefixLength;
Subnet(String host, BitSet address, int prefixLength) {
this.host = host;
this.address = address;
this.prefixLength = prefixLength;
}
}
private static BitSet bits(byte[] bytes) {
// TODO java7: BitSet.valueOf(bytes)
BitSet bits = new BitSet(8 * bytes.length);
for (int i = 0, n = bytes.length; i < n; ++i) {
int b = 0xFF & bytes[i];
int j = 8 * i;
for (int k = 0; k < 8; ++k) {
bits.set(j + k, 0 != (b >>> (7 - k)));
}
}
return bits;
}
static boolean contains(Collection subnets, Authority authority) {
switch (authority.type) {
case LOCAL:
return false;
case NAMED:
for (Subnet subnet : subnets) {
if (subnet.host.equals(authority.getHost())) {
return true;
}
}
return false;
case IP:
BitSet address = bits(authority.getIp().getAddress());
top:
for (Subnet subnet : subnets) {
for (int i = 0, n = subnet.prefixLength; i < n; ++i) {
if (address.get(i) != subnet.address.get(i)) {
continue top;
}
}
return true;
}
return false;
default:
throw new IllegalArgumentException();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy