jadex.platform.service.message.streams.OutputConnectionHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jadex-platform Show documentation
Show all versions of jadex-platform Show documentation
The Jadex platform package contains implementations of platform services as well as the platform component itself.
package jadex.platform.service.message.streams;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;
import jadex.bridge.IComponentStep;
import jadex.bridge.IInternalAccess;
import jadex.bridge.service.annotation.Timeout;
import jadex.commons.SUtil;
import jadex.commons.Tuple2;
import jadex.commons.future.CounterResultListener;
import jadex.commons.future.DelegationResultListener;
import jadex.commons.future.Future;
import jadex.commons.future.IFuture;
import jadex.commons.future.IResultListener;
import jadex.platform.service.message.MessageService;
//import javax.swing.JPanel;
//import javax.swing.JTextField;
//import javax.swing.Timer;
/**
* The output connection handler.
*/
public class OutputConnectionHandler extends AbstractConnectionHandler implements IOutputConnectionHandler
{
//-------- attributes --------
/** The data sent (not acknowledged). */
protected Map sent;
/** The data to send. */
protected List>> tosend;
/** The current sequence number. */
protected int seqnumber;
/** The max number of packets that can be sent without an ack is received. */
protected int maxsend;
/** The max number of messages that can be sending concurrently (i.e. passed to message service but sending not yet completed). */
protected int maxqueued;
/** The number of received elements after which an ack is sent. */
protected int ackcnt;
/** The number of sending messages (i.e. passed to message service but sending not yet completed). */
protected int queuecnt;
/** The acknowledgement timer. */
protected TimerTask acktimer;
/** Flag if multipackets should be used. */
protected boolean multipackets;
/** The packet size to collect (in bytes). */
protected int mpmaxsize;
/** The collected data for a packet. */
protected List multipacket;
/** The current multipacket size. */
protected int mpsize;
/** The current multipacket future (shared by all write requests that put data in the same multi packet). */
protected Future mpfut;
/** The max delay before a multipacket is sent (even if not full). */
protected long mpsendtimeout;
/** The multipacket send timer. */
protected TimerTask mpsendtimer;
/** Close request flag (when a closereq message was received). */
protected boolean closereqflag;
/** Stop flag (is sent in ack from input side) to signal that the rceiver is flooded with data). */
protected Tuple2 stopflag;
/** Flag if close was already sent. */
protected boolean closesent;
/** Future used in waitForReady(). */
protected Future readyfuture;
//-------- constructors --------
/**
* Create a new handler.
*/
public OutputConnectionHandler(MessageService ms, Map nonfunc)
{
super(ms, nonfunc);//, maxresends, acktimeout, leasetime);
this.tosend = new ArrayList>>();
this.sent = new LinkedHashMap();
this.seqnumber = 0;
this.maxsend = 200;
this.maxqueued = 4;
this.ackcnt = 10;
this.multipackets = true;
this.mpmaxsize = 4500;
this.multipacket = new ArrayList();
this.mpsize = 0;
this.mpsendtimeout = 3000;
this.stopflag = new Tuple2(Boolean.FALSE, -1);
// createDataTimer();
}
//-------- methods called from message service --------
/**
* Received a request to close the connection.
*/
public void closeRequestReceived()
{
scheduleStep(new IComponentStep()
{
public IFuture execute(IInternalAccess ia)
{
closereqflag = true;
checkClose();
sendTask(createTask(StreamSendTask.ACKCLOSEREQ, null, null, nonfunc));
return IFuture.DONE;
}
});
}
/**
* Called from message service.
*
* Uses: sent, lastack
*/
public void ackDataReceived(final AckInfo ackinfo)
{
scheduleStep(new IComponentStep()
{
public IFuture execute(IInternalAccess ia)
{
// Update stop if newer sequence number
if(stopflag.getSecondEntity().intValue()(ackinfo.isStop()? Boolean.TRUE: Boolean.FALSE, Integer.valueOf(ackinfo.getEndSequenceNumber()));
// remove all acked packets
for(int i=ackinfo.getStartSequenceNumber(); i<=ackinfo.getEndSequenceNumber(); i++)
{
DataSendInfo tup = sent.remove(Integer.valueOf(i));
if(tup!=null)
{
tup.getFuture().setResult(null);
}
}
// System.out.println("ack "+System.currentTimeMillis()+": seq="+seqnumber+" stop="+ackinfo.isStop()+" startack="+ackinfo.getStartSequenceNumber()+" endack="+ackinfo.getEndSequenceNumber()+" sent="+sent.size());
// System.out.println(sent);
// Trigger resend of unacknowledged messages, if necessary.
checkResend();
// Try to send stored messages after some others have been acknowledged
sendStored();
// Check ready state.
checkWaitForReady();
// Try to close if close is requested.
checkClose();
return IFuture.DONE;
}
});
}
//-------- methods called from connection ---------
/**
*
*/
public void notifyInited()
{
scheduleStep(new IComponentStep()
{
public IFuture execute(IInternalAccess ia)
{
checkWaitForReady();
sendStored();
checkClose();
return IFuture.DONE;
}
});
}
/**
* Called from connection.
* Initiates closing procedure (is different for initiator and participant).
*/
public IFuture doClose()
{
final Future ret = new Future();
scheduleStep(new IComponentStep()
{
public IFuture execute(IInternalAccess ia)
{
// System.out.println("do close output side");
closereqflag = true;
checkClose();
return IFuture.DONE;
}
}).addResultListener(new IResultListener()
{
public void resultAvailable(Void result)
{
}
public void exceptionOccurred(Exception exception)
{
con.setClosed();
ret.setException(exception);
}
});
return ret;
}
/**
* Called from connection.
*
* Uses: sent, tosend
*/
public IFuture send(final byte[] dat)
{
// Todo: need to copy dat in case user uses array otherwise...
final Future ret = new Future();
// ret.addResultListener(new IResultListener()
// {
// public void resultAvailable(Void result)
// {
// System.out.println("send end");
// }
// public void exceptionOccurred(Exception exception)
// {
// System.out.println("send end ex");
// }
// });
scheduleStep(new IComponentStep()
{
public IFuture execute(IInternalAccess ia)
{
// System.out.println("called send: "+sent.size());
sendStored();
if(multipackets)
{
addMultipacket(dat).addResultListener(new DelegationResultListener(ret));
}
else
{
StreamSendTask task = (StreamSendTask)createTask(StreamSendTask.DATA, dat, getNextSequenceNumber(), nonfunc);
doSendData(task).addResultListener(new DelegationResultListener(ret));
}
// Check ready state.
checkWaitForReady();
return IFuture.DONE;
}
});
return ret;
}
/**
* Flush the data.
*/
public void flush()
{
scheduleStep(new IComponentStep()
{
public IFuture execute(IInternalAccess ia)
{
if(multipackets)
{
sendAcknowledgedMultiPacket();
}
sendStored();
checkWaitForReady();
return IFuture.DONE;
}
});
}
/**
* Wait until the connection is ready for the next write.
* @return Calls future when next data can be written. Provides a value of how much data should be given to the connection for best performance.
*/
public IFuture waitForReady()
{
final Future ret = new Future();
scheduleStep(new IComponentStep()
{
public IFuture execute(IInternalAccess ia)
{
if(readyfuture!=null)
{
ret.setException(new RuntimeException("Must not be called twice without waiting for result."));
}
else
{
// System.out.println("readyfuture inited");
readyfuture = ret;
checkWaitForReady();
}
return IFuture.DONE;
}
});
return ret;
}
//-------- internal methods (single threaded) --------
/**
*
*/
protected void checkWaitForReady()
{
if(readyfuture!=null)
{
// System.out.println("waitforready: "+con.isInited()+" "+(maxsend-sent.size())+" "+isStop()+" "+isClosed());
if(isSendAllowed() && !isClosed())
{
// System.out.println("readyfuture fired");
Future ret = readyfuture;
readyfuture = null;
// ret.setResult(Integer.valueOf(mpmaxsize)); // todo: packet size*allowed messages?
int pa = sent.size()-maxsend;
ret.setResult(Integer.valueOf(pa>0? pa*mpmaxsize: mpmaxsize));
}
else if(isClosed())
{
Future ret = readyfuture;
readyfuture = null;
ret.setException(new RuntimeException("Connection closed."));
}
}
}
/**
*
*/
protected IFuture doSendData(StreamSendTask task)
{
IFuture ret;
if(isSendAllowed())
{
// System.out.println("send "+System.currentTimeMillis()+": "+task.getSequenceNumber());
ret = sendData(task);
}
else
{
// System.out.println("store "+System.currentTimeMillis()+": "+task.getSequenceNumber());
ret = new Future();
tosend.add(new Tuple2>(task, (Future)ret));
}
return ret;
}
/**
* Called internally.
*
* Uses: sent, tosend
*/
protected void sendStored()
{
// System.out.println("sendStored: sent="+sent.size()+", allowed="+allowed+", tosend="+tosend.size());
// Cannot use just isSendAllowed() as at least one message
// should be sent in case of stop to provoke acks with continue
boolean test = con.isInited() && sent.size()> tup = tosend.remove(0);
// System.out.println("send Stored: "+tup.getFirstEntity().getSequenceNumber());
sendData(tup.getFirstEntity()).addResultListener(new DelegationResultListener(tup.getSecondEntity()));
// Send only one test message if in stop mode.
test = false;
}
}
/**
* Called internally.
*
* Add data to a multi packet.
* @parm data The data.
*/
protected IFuture addMultipacket(byte[] data)
{
IFuture ret = new Future();
int start = 0;
int len = Math.min(mpmaxsize-mpsize, data.length);
Set> futs = new HashSet>();
while(len>0)
{
byte[] part = new byte[len];
System.arraycopy(data, start, part, 0, len);
futs.add(addMultiPacketChunk(part));
start += len;
len = Math.min(mpmaxsize-mpsize, data.length-start);
}
if(futs.size()>0)
{
CounterResultListener lis = new CounterResultListener(futs.size(),
new DelegationResultListener((Future)ret));
for(IFuture fut: futs)
{
fut.addResultListener(lis);
}
}
else
{
ret = IFuture.DONE;
}
return ret;
}
/**
* Called internally.
*
* Add data chunk.
* @param data The data.
*/
protected IFuture addMultiPacketChunk(byte[] data)
{
if(mpfut==null)
mpfut = new Future();
IFuture ret = mpfut;
// Install send timer on first packet
if(mpsize==0)
createMultipacketSendTimer(getSequenceNumber());
multipacket.add(data);
mpsize += data.length;
if(mpsize==mpmaxsize)
{
sendAcknowledgedMultiPacket().addResultListener(new DelegationResultListener(mpfut));
mpfut = null;
}
return ret;
}
/**
* Called internally.
*
* Send a multi packet.
*/
protected IFuture sendAcknowledgedMultiPacket()
{
IFuture ret = IFuture.DONE;
if(multipacket.size()>0)
{
byte[] target;
if(multipacket.size()==1)
{
target = multipacket.get(0);
}
else
{
target = new byte[mpsize];
int start = 0;
for(int i=0; i sendData(StreamSendTask task)
{
DataSendInfo tup = sent.get(task.getSequenceNumber());
if(tup==null)
{
// First try.
tup = new DataSendInfo(task);
// add task to unacknowledged sent list
sent.put(task.getSequenceNumber(), tup);
}
else
{
// Retry -> clone task for resend
task = tup.retry();
}
// System.out.println("send "+System.currentTimeMillis()+": "+task.getSequenceNumber());
sendTask(task);
queuecnt++;
// System.out.println("queue: "+queuecnt);
// final int seqno = task.getSequenceNumber();
task.getFuture().addResultListener(new IResultListener()
{
public void resultAvailable(Void result)
{
// System.out.println("Sent "+System.currentTimeMillis()+": seq="+seqno);
sendDone();
}
public void exceptionOccurred(Exception exception)
{
// System.out.println("Not sent "+System.currentTimeMillis()+": seq="+seqno+", "+exception);
// exception.printStackTrace();
sendDone();
}
protected void sendDone()
{
scheduleStep(new IComponentStep()
{
public IFuture execute(IInternalAccess ia)
{
queuecnt--;
sendStored();
checkWaitForReady();
return IFuture.DONE;
}
});
}
});
return tup.getFuture();
}
/**
* Triggers resends of packets if no ack has been received in acktimeout.
* @param id The message id.
* @return The timer.
*/
protected TimerTask createBulkAckTimer(final Object id)
{
TimerTask ret;
if(acktimeout!=Timeout.NONE)
{
// Test if packets have been sent till last timer was inited
ret = ms.waitForRealDelay(acktimeout, new IComponentStep()
{
public IFuture execute(IInternalAccess ia)
{
DataSendInfo tup = sent.get(id);
if(tup!=null)
{
tup.doResend();
}
return IFuture.DONE;
}
});
}
else
{
ret = null;
}
return ret;
}
/**
*
*/
protected boolean isSendAllowed()
{
return con.isInited() && sent.size()()
{
public IFuture execute(IInternalAccess ia)
{
// Send the packet if it is still the correct one
if(seqno==getSequenceNumber())
{
assert mpfut!=null;
sendAcknowledgedMultiPacket().addResultListener(new DelegationResultListener(mpfut));
mpfut = null;
}
return IFuture.DONE;
}
});
}
/**
*
*/
protected void checkClose()
{
// System.out.println("checkclose0: "+isCloseRequested()+", "+isDataSendFinished()+", "+con.isInited()+", "+!con.isClosed()+", "+con.isClosing()+", "+isDataAckFinished()+", "+closesent);
// Try to close if close is requested.
if(isCloseRequested() && isDataSendFinished() && con.isInited() && !con.isClosed())
{
// If close() was already called on connection directly perform close
if(con.isClosing())
{
// System.out.println("sending close output side");
// Send close message and wait until it was acked
sendAcknowledgedMessage(createTask(StreamSendTask.CLOSE, SUtil.intToBytes(seqnumber), null, nonfunc), StreamSendTask.CLOSE)
.addResultListener(new IResultListener