/*
* Copyright: Almende B.V. (2014), Rotterdam, The Netherlands
* License: The Apache Software License, Version 2.0
*/
package com.almende.eve.protocol;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import com.almende.eve.capabilities.handler.Handler;
import com.almende.eve.protocol.jsonrpc.formats.JSONMessage;
import com.almende.eve.protocol.jsonrpc.formats.JSONRequest;
import com.almende.util.callback.AsyncCallback;
import com.almende.util.callback.SyncCallback;
import com.almende.util.threads.ThreadPool;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* The Class InboxProtocol, provides an easy way to get a single threaded agent,
* only one inbound message in a single thread at a time.
*/
public class InboxProtocol implements Protocol {
/** The inbox. */
private BlockingQueue<Meta> inbox = new LinkedBlockingQueue<Meta>();
private InboxProtocolConfig params = null;
protected final Boolean[] stop = new Boolean[] { false };
private Set<String> callbackIds = new HashSet<String>(5);
final Boolean[] sequencer = new Boolean[] { false, false };
protected Runnable loop = null;
/**
* Instantiates a new inbox protocol.
*
* @param params
* the params
* @param handle
* the handle
*/
public InboxProtocol(final ObjectNode params, final Handler<Object> handle) {
this.params = InboxProtocolConfig.decorate(params);
initDefLoop();
}
/**
* Instantiates a new inbox protocol.
*
* @param params
* the params
* @param handle
* the handle
* @param noInit
* the no init
*/
public InboxProtocol(final ObjectNode params, final Handler<Object> handle, boolean noInit) {
this.params = InboxProtocolConfig.decorate(params);
}
/**
* Inits the def loop.
*/
protected void initDefLoop() {
chgLooper(new Runnable() {
// Agent thread
@Override
public void run() {
stop[0] = false;
while (!stop[0]) {
try {
final Meta next = getNext(inbox);
next(next);
synchronized (sequencer) {
while (!sequencer[0]) {
sequencer.wait();
}
}
} catch (InterruptedException e) {
// Nothing todo.
}
}
}
});
}
/**
* Gets the next.
*
* @param inbox
* the inbox
* @return the next
* @throws InterruptedException
* the interrupted exception
*/
protected Meta getNext(final BlockingQueue<Meta> inbox) throws InterruptedException {
// sequencer[0] is the actual wait latch
sequencer[0] = false;
// sequencer[1] is the flag to skip triggering on the
// latch if this is a reply to synchronous call.
sequencer[1] = false;
final Meta next = inbox.take();
if (!callbackIds.isEmpty()) {
final JSONMessage message = JSONMessage.jsonConvert(next.getMsg());
if (message != null) {
// No need to parse it again later.
next.setMsg(message);
if (message.isResponse() && callbackIds.remove(message.getId())) {
sequencer[1] = true;
}
}
}
return next;
}
/**
* Next.
*
* @param next
* the next
*/
protected void next(final Meta next) {
ThreadPool.getPool().execute(new Runnable() {
@Override
public void run() {
if (next != null) {
next.nextIn();
}
synchronized (sequencer) {
if (!sequencer[1]) {
sequencer[0] = true;
sequencer.notifyAll();
}
}
}
});
}
/**
* Gets the inbox.
*
* @return the inbox
*/
public Queue<Meta> getInbox() {
return inbox;
}
/**
* Sets the inbox.
*
* @param inbox
* the new inbox
* @param chgLooper
* the chg looper
*/
public void setInbox(BlockingQueue<Meta> inbox, boolean chgLooper) {
this.inbox = inbox;
if (chgLooper) {
chgLooper(loop);
}
}
/**
* Replaces the inbox send loop;.
*
* @param loop
* the new looper
*/
public void chgLooper(Runnable loop) {
this.loop = loop;
this.stop[0] = true;
synchronized (sequencer) {
sequencer[0] = true;
sequencer.notifyAll();
}
ThreadPool.getPool().execute(loop);
}
/**
* Gets the sequencer.
*
* @return the sequencer
*/
public Boolean[] getSequencer() {
return sequencer;
}
/*
* (non-Javadoc)
* @see com.almende.eve.capabilities.Capability#getParams()
*/
@Override
public InboxProtocolConfig getParams() {
return this.params;
}
/*
* (non-Javadoc)
* @see com.almende.eve.capabilities.Capability#delete()
*/
@Override
public void delete() {
// empty inbox
stop[0] = true;
inbox.clear();
callbackIds.clear();
}
/*
* (non-Javadoc)
* @see
* com.almende.eve.protocol.Protocol#inbound(com.almende.eve.protocol.Meta)
*/
@Override
public boolean inbound(Meta msg) {
try {
inbox.put(msg);
} catch (InterruptedException e) {}
// explicitely not calling next on protocol stack from this point.
return false;
}
/*
* (non-Javadoc)
* @see
* com.almende.eve.protocol.Protocol#outbound(com.almende.eve.protocol.Meta)
*/
@Override
public boolean outbound(Meta msg) {
if (params.isSupportSynccalls()) {
final JSONMessage message = JSONMessage.jsonConvert(msg.getMsg());
if (message != null && message.isRequest()) {
final JSONRequest request = (JSONRequest) message;
AsyncCallback<?> callback = request.getCallback();
if (callback != null && callback instanceof SyncCallback<?>) {
callbackIds.add(request.getId().asText());
synchronized (sequencer) {
sequencer[0] = true;
sequencer.notifyAll();
}
}
}
}
// just forwarding...
return msg.nextOut();
}
}