/*******************************************************************************
* Copyright (c) 2007, 2015 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import org.eclipse.tcf.internal.core.ServiceManager;
import org.eclipse.tcf.internal.core.Token;
import org.eclipse.tcf.internal.core.TransportManager;
import org.eclipse.tcf.internal.services.local.LocatorService;
import org.eclipse.tcf.internal.services.remote.GenericProxy;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IErrorReport;
import org.eclipse.tcf.protocol.IPeer;
import org.eclipse.tcf.protocol.IService;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.protocol.JSON;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.ILocator;
/**
* Abstract implementation of IChannel interface.
*
* AbstractChannel implements communication link connecting two end points (peers).
* The channel asynchronously transmits messages: commands, results and events.
*
* Clients can subclass AbstractChannel to support particular transport (wire) protocol.
* Also, see StreamChannel for stream oriented transport protocols.
*/
public abstract class AbstractChannel implements IChannel {
public interface TraceListener {
public void onMessageReceived(char type, String token,
String service, String name, byte[] data);
public void onMessageSent(char type, String token,
String service, String name, byte[] data);
public void onChannelClosed(Throwable error);
}
public interface Proxy {
public void onCommand(IToken token, String service, String name, byte[] data);
public void onEvent(String service, String name, byte[] data);
public void onChannelClosed(Throwable error);
}
/**
* Represents a message sent through a channel between peers
*/
private static class Message {
/**
* Type of message.
* "C" for Commands.
* "R" for Command Results.
* "N" for Unkown Command Result.
* "P" for Progress Result.
* "E" for Events.
*/
final char type;
/**
* Token associated with the Command
*/
Token token;
/**
* Name of the service
*/
String service;
/**
* In case of a Command ("C" Message) or an Event ("E" Message), the name of it
*/
String name;
/**
* The array of bytes that accompanies the message
*/
byte[] data;
boolean is_sent;
boolean is_canceled;
Collection<TraceListener> trace;
/**
* Constructs a Message of the given type. Type could be 'C', 'R', 'N' 'P' or 'E'
* @param type type of message
*/
Message(char type) {
this.type = type;
}
@Override
public String toString() {
try {
StringBuffer bf = new StringBuffer();
bf.append('[');;
bf.append(type);
if (token != null) {
bf.append(' ');
bf.append(token.getID());
}
if (service != null) {
bf.append(' ');
bf.append(service);
}
if (name != null) {
bf.append(' ');
bf.append(name);
}
if (data != null) {
int i = 0;
while (i < data.length) {
int j = i;
while (j < data.length && data[j] != 0) j++;
bf.append(' ');
bf.append(new String(data, i, j - i, "UTF8"));
if (j < data.length && data[j] == 0) j++;
i = j;
}
}
bf.append(']');
return bf.toString();
}
catch (Exception x) {
return x.toString();
}
}
}
private final LinkedList<Map<String,String>> redirect_queue = new LinkedList<Map<String,String>>();
private final Map<Class<?>,IService> local_service_by_class = new HashMap<Class<?>,IService>();
private final Map<Class<?>,IService> remote_service_by_class = new HashMap<Class<?>,IService>();
private final Map<String,IService> local_service_by_name = new HashMap<String,IService>();
private final Map<String,IService> remote_service_by_name = new HashMap<String,IService>();
private final LinkedList<Message> out_queue = new LinkedList<Message>();
private final Collection<IChannelListener> channel_listeners = new ArrayList<IChannelListener>();
private final Map<String,IChannel.IEventListener[]> event_listeners = new HashMap<String,IChannel.IEventListener[]>();
private final Map<String,IChannel.ICommandServer> command_servers = new HashMap<String,IChannel.ICommandServer>();
/**
* Tokens used for output
*/
private final Map<String,Message> out_tokens = new LinkedHashMap<String,Message>();
private final Thread inp_thread;
private final Thread out_thread;
private boolean notifying_channel_opened;
private boolean registered_with_trasport;
private int state = STATE_OPENING;
private IToken redirect_command;
private final IPeer local_peer;
private IPeer remote_peer;
private Proxy proxy;
private boolean zero_copy;
private static final int pending_command_limit = 32;
private int local_congestion_level = -100;
private int remote_congestion_level = -100;
private long local_congestion_time;
private int local_congestion_cnt;
private Collection<TraceListener> trace_listeners;
/**
* @since 1.2
*/
protected static final boolean TRACE = Boolean.getBoolean("org.eclipse.tcf.core.tracing.channel");
public static final int
/**
* End of Stream
*/
EOS = -1,
/**
* End of Message
*/
EOM = -2;
protected AbstractChannel(IPeer remote_peer) {
this(LocatorService.getLocalPeer(), remote_peer);
}
protected AbstractChannel(IPeer local_peer, IPeer remote_peer) {
assert Protocol.isDispatchThread();
this.remote_peer = remote_peer;
this.local_peer = local_peer;
/**
* Thread used handles messages received through the channel
*/
inp_thread = new Thread() {
/**
* Empty byte array used when returning a zero-length byte array on {@code readBytes}
*/
final byte[] empty_byte_array = new byte[0];
/**
* Byte array used as temporary storage of bytes read on {@code readBytes}
*/
byte[] buf = new byte[1024];
/**
* Byte array used as temporary storage of bytes read on {@code readString}
*/
char[] cbf = new char[1024];
/**
* Byte array used to store the error
*/
byte[] eos_err_report;
/**
* Throws an IOException when the input thread reads a malformed Message from the channel
* @throws IOException with the message "Protocol syntax error"
*/
private void error() throws IOException {
throw new IOException("Protocol syntax error");
}
/**
* Reads bytes from a channel
* @param end the first byte character
* @return a byte array containing all the bytes read
* @throws IOException if it finds EOM or EOS reading from input stream
*/
private byte[] readBytes(int end) throws IOException {
int len = 0;
for (;;) {
int ch = read();
if (ch <= 0) {
if (ch == end) break;
if (ch == EOM) throw new IOException("Unexpected end of message");
if (ch < 0) throw new IOException("Communication channel is closed by remote peer");
}
if (len >= buf.length) {
byte[] tmp = new byte[buf.length * 2];
System.arraycopy(buf, 0, tmp, 0, len);
buf = tmp;
}
buf[len++] = (byte)ch;
}
if (len == 0) return empty_byte_array;
byte[] res = new byte[len];
System.arraycopy(buf, 0, res, 0, len);
return res;
}
/**
* Reads complete strings made of bytes and return the Java string that it forms
* @return string containing all the bytes read
* @throws IOException if it finds EOM or EOS reading from input stream
*/
private String readString() throws IOException {
int len = 0;
for (;;) {
int ch = read();
if (ch < 0) {
if (ch == EOM) throw new IOException("Unexpected end of message");
if (ch < 0) throw new IOException("Communication channel is closed by remote peer");
}
/*
* Check if ch is not part of the Basic Latin alphabet
*/
if ((ch & 0x80) != 0) {
int n = 0;
if ((ch & 0xe0) == 0xc0) {
ch &= 0x1f;
n = 1;
}
else if ((ch & 0xf0) == 0xe0) {
ch &= 0x0f;
n = 2;
}
else if ((ch & 0xf8) == 0xf0) {
ch &= 0x07;
n = 3;
}
else if ((ch & 0xfc) == 0xf8) {
ch &= 0x03;
n = 4;
}
else if ((ch & 0xfe) == 0xfc) {
ch &= 0x01;
n = 5;
}
while (n > 0) {
int b = read();
if (b < 0) {
if (b == EOM) throw new IOException("Unexpected end of message");
if (b < 0) throw new IOException("Communication channel is closed by remote peer");
}
ch = (ch << 6) | (b & 0x3f);
n--;
}
}
if (ch == 0) break;
/*
* Duplicate size of array used to hold the bytes, after the size is bigger than original array
*/
if (len >= cbf.length) {
char[] tmp = new char[cbf.length * 2];
System.arraycopy(cbf, 0, tmp, 0, len);
cbf = tmp;
}
cbf[len++] = (char)ch;
}
return new String(cbf, 0, len);
}
@Override
public void run() {
try {
while (true) {
int n = read();
if (n == EOM) continue;
if (n == EOS) {
try {
eos_err_report = readBytes(EOM);
if (eos_err_report.length == 0 || eos_err_report.length == 1 && eos_err_report[0] == 0) eos_err_report = null;
}
catch (Exception x) {
}
break;
}
final Message msg = new Message((char)n);
if (read() != 0) error();
switch (msg.type) {
case 'C':
msg.token = new Token(readBytes(0));
msg.service = readString();
msg.name = readString();
msg.data = readBytes(EOM);
break;
case 'P':
case 'R':
case 'N':
msg.token = new Token(readBytes(0));
msg.data = readBytes(EOM);
break;
case 'E':
msg.service = readString();
msg.name = readString();
msg.data = readBytes(EOM);
break;
case 'F':
msg.data = readBytes(EOM);
break;
default:
error();
}
/*
* Message handling is done in the dispatch thread
*/
Protocol.invokeLater(new Runnable() {
public void run() {
handleInput(msg);
}
});
int delay = local_congestion_level;
if (delay > 0) sleep(delay);
}
Protocol.invokeLater(new Runnable() {
public void run() {
if (out_tokens.isEmpty() && eos_err_report == null && state != STATE_OPENING) {
close();
}
else {
IOException x = new IOException("Communication channel is closed by remote peer");
if (eos_err_report != null) {
try {
Object[] args = JSON.parseSequence(eos_err_report);
if (args.length > 0 && args[0] != null) {
x.initCause(new Exception(Command.toErrorString(args[0])));
}
}
catch (IOException e) {
}
}
terminate(x);
}
}
});
}
catch (final Throwable x) {
try {
Protocol.invokeLater(new Runnable() {
public void run() {
terminate(x);
}
});
}
catch (IllegalStateException y) {
// TCF event dispatcher has shut down
}
}
}
};
/**
* Thread used to handle messages sent through the channel
*/
out_thread = new Thread() {
private final byte[] out_buf = new byte[0x4000];
private int out_buf_pos;
void writeBytes(byte[] buf) throws IOException {
if (buf.length > out_buf.length) {
write(out_buf, 0, out_buf_pos);
out_buf_pos = 0;
write(buf);
}
else {
int i = 0;
while (i < buf.length) {
if (out_buf_pos >= out_buf.length) {
write(out_buf);
out_buf_pos = 0;
}
int n = buf.length - i;
if (n > out_buf.length - out_buf_pos) n = out_buf.length - out_buf_pos;
System.arraycopy(buf, i, out_buf, out_buf_pos, n);
out_buf_pos += n;
i += n;
}
}
}
void writeString(String s) throws IOException {
int l = s.length();
for (int i = 0; i < l; i++) {
if (out_buf_pos + 4 > out_buf.length) {
write(out_buf, 0, out_buf_pos);
out_buf_pos = 0;
}
int ch = s.charAt(i);
if (ch < 0x80) {
out_buf[out_buf_pos++] = (byte)ch;
}
else if (ch < 0x800) {
out_buf[out_buf_pos++] = (byte)((ch >> 6) | 0xc0);
out_buf[out_buf_pos++] = (byte)(ch & 0x3f | 0x80);
}
else if (ch < 0x10000) {
out_buf[out_buf_pos++] = (byte)((ch >> 12) | 0xe0);
out_buf[out_buf_pos++] = (byte)((ch >> 6) & 0x3f | 0x80);
out_buf[out_buf_pos++] = (byte)(ch & 0x3f | 0x80);
}
else {
out_buf[out_buf_pos++] = (byte)((ch >> 18) | 0xf0);
out_buf[out_buf_pos++] = (byte)((ch >> 12) & 0x3f | 0x80);
out_buf[out_buf_pos++] = (byte)((ch >> 6) & 0x3f | 0x80);
out_buf[out_buf_pos++] = (byte)(ch & 0x3f | 0x80);
}
}
if (out_buf_pos >= out_buf.length) {
write(out_buf);
out_buf_pos = 0;
}
out_buf[out_buf_pos++] = 0;
}
@Override
public void run() {
try {
while (true) {
Message msg = null;
boolean last = false;
synchronized (out_queue) {
while (out_queue.size() == 0) out_queue.wait();
msg = out_queue.removeFirst();
if (msg == null) break;
last = out_queue.isEmpty();
if (msg.is_canceled) {
if (last) flush();
continue;
}
msg.is_sent = true;
}
if (msg.trace != null) {
final Message m = msg;
Protocol.invokeLater(new Runnable() {
public void run() {
for (TraceListener l : m.trace) {
try {
l.onMessageSent(m.type, m.token == null ? null : m.token.getID(),
m.service, m.name, m.data);
}
catch (Throwable x) {
Protocol.log("Exception in channel listener", x);
}
}
}
});
}
out_buf_pos = 0;
out_buf[out_buf_pos++] = (byte)msg.type;
out_buf[out_buf_pos++] = 0;
if (msg.token != null) writeString(msg.token.getID());
if (msg.service != null) writeString(msg.service);
if (msg.name != null) writeString(msg.name);
if (msg.data != null) writeBytes(msg.data);
write(out_buf, 0, out_buf_pos);
write(EOM);
int delay = 0;
int level = remote_congestion_level;
if (level > 0) delay = level * 10;
if (last || delay > 0) flush();
if (delay > 0) sleep(delay);
else yield();
}
write(EOS);
write(EOM);
flush();
}
catch (final Throwable x) {
try {
Protocol.invokeLater(new Runnable() {
public void run() {
terminate(x);
}
});
}
catch (IllegalStateException y) {
// TCF event dispatcher has shut down
}
}
}
};
inp_thread.setName("TCF Channel Receiver");
out_thread.setName("TCF Channel Transmitter");
}
protected void start() {
assert Protocol.isDispatchThread();
Protocol.invokeLater(new Runnable() {
public void run() {
try {
if (proxy != null) return;
if (state == STATE_CLOSED) return;
ServiceManager.onChannelCreated(AbstractChannel.this, local_service_by_name);
makeServiceByClassMap(local_service_by_name, local_service_by_class);
Object[] args = new Object[]{ local_service_by_name.keySet() };
sendEvent(Protocol.getLocator(), "Hello", JSON.toJSONSequence(args));
}
catch (IOException x) {
terminate(x);
}
}
});
inp_thread.start();
out_thread.start();
}
/**
* Redirect this channel to given peer using this channel remote peer locator service as a proxy.
* @param peer_id - peer that will become new remote communication endpoint of this channel
*/
public void redirect(String peer_id) {
Map<String,String> map = new HashMap<String,String>();
map.put(IPeer.ATTR_ID, peer_id);
redirect(map);
}
/**
* Redirect this channel to given peer using this channel remote peer locator service as a proxy.
* @param peer_attrs - peer that will become new remote communication endpoint of this channel
*/
public void redirect(final Map<String,String> peer_attrs) {
assert Protocol.isDispatchThread();
if (state == STATE_OPENING) {
redirect_queue.add(peer_attrs);
}
else {
assert state == STATE_OPEN;
assert redirect_command == null;
try {
final ILocator l = (ILocator)remote_service_by_class.get(ILocator.class);
if (l == null) throw new IOException("Cannot redirect channel: peer " +
remote_peer.getID() + " has no locator service");
final String peer_id = peer_attrs.get(IPeer.ATTR_ID);
if (peer_id != null && peer_attrs.size() == 1) {
final IPeer peer = l.getPeers().get(peer_id);
if (peer == null) {
// Peer not found, must wait for a while until peer is discovered or time out
final boolean[] found = new boolean[1];
Protocol.invokeLater(ILocator.DATA_RETENTION_PERIOD / 3, new Runnable() {
public void run() {
if (found[0]) return;
terminate(new Exception("Peer " + peer_id + " not found"));
}
});
l.addListener(new ILocator.LocatorListener() {
public void peerAdded(IPeer peer) {
if (peer.getID().equals(peer_id)) {
found[0] = true;
state = STATE_OPEN;
l.removeListener(this);
redirect(peer_id);
}
}
public void peerChanged(IPeer peer) {
}
public void peerHeartBeat(String id) {
}
public void peerRemoved(String id) {
}
});
}
else {
redirect_command = l.redirect(peer_id, new ILocator.DoneRedirect() {
public void doneRedirect(IToken token, Exception x) {
assert redirect_command == token;
redirect_command = null;
if (state != STATE_OPENING) return;
if (x != null) terminate(x);
remote_peer = peer;
remote_service_by_class.clear();
remote_service_by_name.clear();
event_listeners.clear();
}
});
}
}
else {
redirect_command = l.redirect(peer_attrs, new ILocator.DoneRedirect() {
public void doneRedirect(IToken token, Exception x) {
assert redirect_command == token;
redirect_command = null;
if (state != STATE_OPENING) return;
if (x != null) terminate(x);
final IPeer parent = remote_peer;
remote_peer = new TransientPeer(peer_attrs) {
public IChannel openChannel() {
IChannel c = parent.openChannel();
c.redirect(peer_attrs);
return c;
}
};
remote_service_by_class.clear();
remote_service_by_name.clear();
event_listeners.clear();
}
});
}
state = STATE_OPENING;
}
catch (Throwable x) {
terminate(x);
}
}
}
private void makeServiceByClassMap(Map<String,IService> by_name, Map<Class<?>,IService> by_class) {
for (IService service : by_name.values()) {
for (Class<?> fs : service.getClass().getInterfaces()) {
if (fs.equals(IService.class)) continue;
if (!IService.class.isAssignableFrom(fs)) {
continue;
}
by_class.put(fs, service);
}
}
}
public final int getState() {
return state;
}
public void addChannelListener(IChannelListener listener) {
assert Protocol.isDispatchThread();
assert listener != null;
channel_listeners.add(listener);
}
public void removeChannelListener(IChannelListener listener) {
assert Protocol.isDispatchThread();
channel_listeners.remove(listener);
}
public void addTraceListener(TraceListener listener) {
if (trace_listeners == null) {
trace_listeners = new ArrayList<TraceListener>();
}
else {
trace_listeners = new ArrayList<TraceListener>(trace_listeners);
}
trace_listeners.add(listener);
}
public void removeTraceListener(TraceListener listener) {
trace_listeners = new ArrayList<TraceListener>(trace_listeners);
trace_listeners.remove(listener);
if (trace_listeners.isEmpty()) trace_listeners = null;
}
public void addEventListener(IService service, IChannel.IEventListener listener) {
assert Protocol.isDispatchThread();
IChannel.IEventListener[] list = event_listeners.get(service.getName());
IChannel.IEventListener[] next = new IChannel.IEventListener[list == null ? 1 : list.length + 1];
if (list != null) System.arraycopy(list, 0, next, 0, list.length);
next[next.length - 1] = listener;
event_listeners.put(service.getName(), next);
}
public void removeEventListener(IService service, IChannel.IEventListener listener) {
assert Protocol.isDispatchThread();
IChannel.IEventListener[] list = event_listeners.get(service.getName());
for (int i = 0; i < list.length; i++) {
if (list[i] == listener) {
if (list.length == 1) {
event_listeners.remove(service.getName());
}
else {
IChannel.IEventListener[] next = new IChannel.IEventListener[list.length - 1];
System.arraycopy(list, 0, next, 0, i);
System.arraycopy(list, i + 1, next, i, next.length - i);
event_listeners.put(service.getName(), next);
}
return;
}
}
}
public void addCommandServer(IService service, IChannel.ICommandServer listener) {
assert Protocol.isDispatchThread();
if (command_servers.put(service.getName(), listener) != null) {
throw new Error("Only one command server per service is allowed");
}
}
public void removeCommandServer(IService service, IChannel.ICommandServer listener) {
assert Protocol.isDispatchThread();
if (command_servers.remove(service.getName()) != listener) {
throw new Error("Invalid command server");
}
}
public void close() {
assert Protocol.isDispatchThread();
if (state == STATE_CLOSED) {
return;
}
try {
sendEndOfStream(10000);
close(null);
}
catch (Exception x) {
close(x);
}
}
public void terminate(Throwable error) {
assert Protocol.isDispatchThread();
if (state == STATE_CLOSED) {
return;
}
try {
sendEndOfStream(500);
close(error);
}
catch (Exception x) {
if (error == null) error = x;
close(error);
}
}
private void sendEndOfStream(long timeout) throws Exception {
synchronized (out_queue) {
out_queue.clear();
out_queue.add(null);
out_queue.notifyAll();
}
out_thread.join(timeout);
}
private void close(final Throwable error) {
assert state != STATE_CLOSED;
state = STATE_CLOSED;
// Closing channel underlying streams can block for a long time,
// so it needs to be done by a background thread.
Thread thread = new Thread() {
@Override
public void run() {
try {
AbstractChannel.this.stop();
}
catch (Exception x) {
Protocol.log("Cannot close channel streams", x);
}
}
};
thread.setName("TCF Channel Cleanup");
thread.setDaemon(true);
thread.start();
if (error != null && remote_peer instanceof AbstractPeer) {
((AbstractPeer)remote_peer).onChannelTerminated();
}
if (registered_with_trasport) {
registered_with_trasport = false;
TransportManager.channelClosed(this, error);
}
if (proxy != null) {
try {
proxy.onChannelClosed(error);
}
catch (Throwable x) {
Protocol.log("Exception in channel listener", x);
}
}
Protocol.invokeLater(new Runnable() {
public void run() {
if (!out_tokens.isEmpty()) {
Exception x = null;
if (error instanceof Exception) x = (Exception)error;
else if (error != null) x = new Exception(error);
else x = new IOException("Channel is closed");
for (Message msg : out_tokens.values()) {
assert msg.token != null;
try {
String s = msg.toString();
if (s.length() > 72) s = s.substring(0, 72) + "...]";
IOException y = new IOException("Command " + s + " aborted");
y.initCause(x);
msg.token.getListener().terminated(msg.token, y);
}
catch (Throwable e) {
Protocol.log("Exception in command listener", e);
}
}
out_tokens.clear();
}
if (channel_listeners.size() > 0) {
for (IChannelListener l : channel_listeners.toArray(
new IChannelListener[channel_listeners.size()])) {
try {
l.onChannelClosed(error);
}
catch (Throwable x) {
Protocol.log("Exception in channel listener", x);
}
}
}
else if (error != null) {
Protocol.log("TCF channel terminated", error);
}
if (trace_listeners != null) {
for (TraceListener l : trace_listeners) {
try {
l.onChannelClosed(error);
}
catch (Throwable x) {
Protocol.log("Exception in channel listener", x);
}
}
}
}
});
}
public int getCongestion() {
assert Protocol.isDispatchThread();
int level = out_tokens.size() * 100 / pending_command_limit - 100;
if (remote_congestion_level > level) level = remote_congestion_level;
if (level > 100) level = 100;
return level;
}
public IPeer getLocalPeer() {
assert Protocol.isDispatchThread();
return local_peer;
}
public IPeer getRemotePeer() {
assert Protocol.isDispatchThread();
return remote_peer;
}
public Collection<String> getLocalServices() {
assert Protocol.isDispatchThread();
assert state != STATE_OPENING;
return local_service_by_name.keySet();
}
public Collection<String> getRemoteServices() {
assert Protocol.isDispatchThread();
assert state != STATE_OPENING;
return remote_service_by_name.keySet();
}
@SuppressWarnings("unchecked")
public <V extends IService> V getLocalService(Class<V> cls) {
assert Protocol.isDispatchThread();
assert state != STATE_OPENING;
return (V)local_service_by_class.get(cls);
}
@SuppressWarnings("unchecked")
public <V extends IService> V getRemoteService(Class<V> cls) {
assert Protocol.isDispatchThread();
assert state != STATE_OPENING;
return (V)remote_service_by_class.get(cls);
}
public <V extends IService> void setServiceProxy(Class<V> service_interface, IService service_proxy) {
String name = service_proxy.getName();
if (remote_service_by_name.get(name) == null) throw new Error("Service not available");
if (!notifying_channel_opened) throw new Error("setServiceProxe() can be called only from channel open call-back");
if (!(remote_service_by_name.get(name) instanceof GenericProxy)) throw new Error("Proxy already set");
if (remote_service_by_class.get(service_interface) != null) throw new Error("Proxy already set");
remote_service_by_class.put(service_interface, service_proxy);
remote_service_by_name.put(name, service_proxy);
}
public IService getLocalService(String service_name) {
assert Protocol.isDispatchThread();
assert state != STATE_OPENING;
return local_service_by_name.get(service_name);
}
public IService getRemoteService(String service_name) {
assert Protocol.isDispatchThread();
assert state != STATE_OPENING;
return remote_service_by_name.get(service_name);
}
public void setProxy(Proxy proxy, Collection<String> services) throws IOException {
this.proxy = proxy;
sendEvent(Protocol.getLocator(), "Hello", JSON.toJSONSequence(new Object[]{ services }));
local_service_by_class.clear();
local_service_by_name.clear();
}
private void addToOutQueue(Message msg) {
msg.trace = trace_listeners;
synchronized (out_queue) {
out_queue.add(msg);
out_queue.notifyAll();
}
}
public IToken sendCommand(IService service, String name, byte[] args, ICommandListener listener) {
assert Protocol.isDispatchThread();
if (state == STATE_OPENING) throw new Error("Channel is waiting for Hello message");
if (state == STATE_CLOSED) throw new Error("Channel is closed");
final Message msg = new Message('C');
msg.service = service.getName();
msg.name = name;
msg.data = args;
Token token = new Token(listener) {
@Override
public boolean cancel() {
assert msg.token == this;
assert Protocol.isDispatchThread();
if (state != STATE_OPEN) return false;
synchronized (out_queue) {
if (msg.is_sent) return false;
msg.is_canceled = true;
}
out_tokens.remove(getID());
return true;
}
};
msg.token = token;
out_tokens.put(token.getID(), msg);
addToOutQueue(msg);
return token;
}
/**
* Send a command's progress response. Used for commands that can deliver partial results.
* @param token token associated with this command/response
* @param results array of bytes containing the data of the message
*/
public void sendProgress(IToken token, byte[] results) {
assert Protocol.isDispatchThread();
if (state != STATE_OPEN) {
throw new Error("Channel is closed");
}
Message msg = new Message('P');
msg.data = results;
msg.token = (Token)token;
addToOutQueue(msg);
}
/**
* Send a command's result response. There's exactly one result per command
* @param token token associated with this command/response
* @param results array of bytes containing the data of the message
*/
public void sendResult(IToken token, byte[] results) {
assert Protocol.isDispatchThread();
if (state != STATE_OPEN) {
throw new Error("Channel is closed");
}
Message msg = new Message('R');
msg.data = results;
msg.token = (Token)token;
addToOutQueue(msg);
}
/**
* Sends an "unrecognized command" message for the command corresponding to the given token
* @param token token associated with this command/response
*/
public void rejectCommand(IToken token) {
assert Protocol.isDispatchThread();
if (state != STATE_OPEN) {
throw new Error("Channel is closed");
}
Message msg = new Message('N');
msg.token = (Token)token;
addToOutQueue(msg);
}
/**
* Sends an event message with the given name, for the given service with the given arguments
* @param service service this event belongs to
* @param name event's name
* @param args additional arguments of the event
*/
public void sendEvent(IService service, String name, byte[] args) {
assert Protocol.isDispatchThread();
if (!(state == STATE_OPEN || state == STATE_OPENING && service instanceof ILocator)) {
throw new Error("Channel is closed");
}
Message msg = new Message('E');
msg.service = service.getName();
msg.name = name;
msg.data = args;
addToOutQueue(msg);
}
public boolean isZeroCopySupported() {
return zero_copy;
}
/**
* Handles the message received from the channel
* @param msg
*/
@SuppressWarnings("unchecked")
private void handleInput(Message msg) {
assert Protocol.isDispatchThread();
if (state == STATE_CLOSED) {
return;
}
if (trace_listeners != null) {
for (TraceListener l : trace_listeners) {
try {
l.onMessageReceived(msg.type,
msg.token != null ? msg.token.getID() : null,
msg.service, msg.name, msg.data);
}
catch (Throwable x) {
Protocol.log("Exception in trace listener", x);
}
}
}
try {
Message cmd = null;
Token token = null;
switch (msg.type) {
case 'P':
case 'R':
case 'N':
String token_id = msg.token.getID();
cmd = msg.type == 'P' ? out_tokens.get(token_id) : out_tokens.remove(token_id);
if (cmd == null) {
throw new Exception("Invalid token received: " + token_id);
}
token = cmd.token;
break;
}
switch (msg.type) {
case 'C':
assert msg.service != null;
assert msg.name != null;
if (state == STATE_OPENING) {
throw new IOException("Received command " + msg.service + "." + msg.name + " before Hello message");
}
if (proxy != null) {
proxy.onCommand(msg.token, msg.service, msg.name, msg.data);
}
else {
token = msg.token;
IChannel.ICommandServer cmds = command_servers.get(msg.service);
if (cmds != null) {
cmds.command(token, msg.name, msg.data);
}
else {
rejectCommand(token);
}
}
break;
case 'P':
token.getListener().progress(token, msg.data);
sendCongestionLevel();
break;
case 'R':
token.getListener().result(token, msg.data);
sendCongestionLevel();
break;
case 'N':
{
String s = null;
if (remote_service_by_name.get(cmd.service) == null) {
s = "No such service: " + cmd.service;
}
else {
s = "Command is not recognized: " + cmd.service + "." + cmd.name;
}
token.getListener().terminated(token, new ErrorReport(s, IErrorReport.TCF_ERROR_INV_COMMAND));
}
break;
case 'E':
assert msg.service != null;
assert msg.name != null;
boolean hello = msg.service.equals(ILocator.NAME) && msg.name.equals("Hello");
if (hello) {
remote_service_by_name.clear();
remote_service_by_class.clear();
ServiceManager.onChannelOpened(this, (Collection<String>)JSON.parseSequence(msg.data)[0], remote_service_by_name);
makeServiceByClassMap(remote_service_by_name, remote_service_by_class);
zero_copy = remote_service_by_name.containsKey("ZeroCopy");
}
if (proxy != null && state == STATE_OPEN) {
proxy.onEvent(msg.service, msg.name, msg.data);
}
else if (hello) {
assert state == STATE_OPENING;
state = STATE_OPEN;
assert redirect_command == null;
if (redirect_queue.size() > 0) {
redirect(redirect_queue.removeFirst());
}
else {
notifying_channel_opened = true;
if (!registered_with_trasport) {
TransportManager.channelOpened(this);
registered_with_trasport = true;
}
if (channel_listeners.size() > 0) {
for (IChannelListener l : channel_listeners.toArray(
new IChannelListener[channel_listeners.size()])) {
try {
l.onChannelOpened();
}
catch (Throwable x) {
Protocol.log("Exception in channel listener", x);
}
}
}
else if (TRACE) {
Protocol.log("TCF channel opened but no one is listening.", null);
}
notifying_channel_opened = false;
}
}
else {
IChannel.IEventListener[] list = event_listeners.get(msg.service);
if (list != null) {
for (int i = 0; i < list.length; i++) {
list[i].event(msg.name, msg.data);
}
}
sendCongestionLevel();
}
break;
case 'F':
int len = msg.data.length;
if (len > 0 && msg.data[len - 1] == 0) len--;
remote_congestion_level = Integer.parseInt(new String(msg.data, 0, len, "ASCII"));
for (IChannelListener l : channel_listeners.toArray(
new IChannelListener[channel_listeners.size()])) {
try {
l.congestionLevel(getCongestion());
}
catch (Throwable x) {
Protocol.log("Exception in channel listener", x);
}
}
break;
default:
assert false;
break;
}
}
catch (Throwable x) {
terminate(x);
}
}
/**
*
* @throws IOException
*/
private void sendCongestionLevel() throws IOException {
if (++local_congestion_cnt < 8) {
return;
}
local_congestion_cnt = 0;
if (state != STATE_OPEN) return;
long time = System.currentTimeMillis();
if (time - local_congestion_time < 500) {
return;
}
assert Protocol.isDispatchThread();
int level = Protocol.getCongestionLevel();
if (level == local_congestion_level) {
return;
}
int i = (level - local_congestion_level) / 8;
if (i != 0) level = local_congestion_level + i;
local_congestion_time = time;
synchronized (out_queue) {
Message msg = out_queue.isEmpty() ? null : out_queue.get(0);
if (msg == null || msg.type != 'F') {
msg = new Message('F');
out_queue.add(0, msg);
out_queue.notify();
}
StringBuilder buffer = new StringBuilder();
buffer.append(local_congestion_level);
buffer.append((char)0); // 0 terminate
msg.data = buffer.toString().getBytes("ASCII");
msg.trace = trace_listeners;
local_congestion_level = level;
}
}
/**
* Read one byte from the channel input stream.
* @return next data byte or EOS (-1) if end of stream is reached,
* or EOM (-2) if end of message is reached.
* @throws IOException
*/
protected abstract int read() throws IOException;
/**
* Write one byte into the channel output stream.
* The method argument can be one of two special values:
* EOS (-1) end of stream marker;
* EOM (-2) end of message marker.
* The stream can put the byte into a buffer instead of transmitting it right away.
* @param n - the data byte.
* @throws IOException
*/
protected abstract void write(int n) throws IOException;
/**
* Flush the channel output stream.
* All buffered data should be transmitted immediately.
* @throws IOException
*/
protected abstract void flush() throws IOException;
/**
* Stop (close) channel underlying streams.
* If a thread is blocked by read() or write(), it should be
* resumed (or interrupted).
* @throws IOException
*/
protected abstract void stop() throws IOException;
/**
* Write array of bytes into the channel output stream.
* The stream can put bytes into a buffer instead of transmitting it right away.
* @param buf
* @throws IOException
*/
protected void write(byte[] buf) throws IOException {
assert Thread.currentThread() == out_thread;
for (int i = 0; i < buf.length; i++) {
write(buf[i] & 0xff);
}
}
/**
* Write array of bytes into the channel output stream.
* The stream can put bytes into a buffer instead of transmitting it right away.
* @param buf
* @param pos
* @param len
* @throws IOException
* @since 1.3
*/
protected void write(byte[] buf, int pos, int len) throws IOException {
assert Thread.currentThread() == out_thread;
for (int i = pos; i < pos + len; i++) {
write(buf[i] & 0xff);
}
}
}