package jade.imtp.leap.nio;
//#J2ME_EXCLUDE_FILE
import jade.core.FrontEnd;
import jade.core.BackEnd;
import jade.core.BackEndContainer;
import jade.core.BEConnectionManager;
import jade.core.Profile;
import jade.core.ProfileException;
import jade.core.IMTPException;
import jade.imtp.leap.BackEndSkel;
import jade.imtp.leap.FrontEndStub;
import jade.imtp.leap.Dispatcher;
import jade.imtp.leap.ICPException;
import jade.imtp.leap.JICP.JICPProtocol;
import jade.imtp.leap.JICP.JICPMediatorManager;
import jade.imtp.leap.JICP.JICPPacket;
import jade.imtp.leap.JICP.Connection;
import jade.util.leap.Properties;
import jade.util.Logger;
import java.io.IOException;
import java.net.InetAddress;
/**
This class implements the BIFEDispatcher related BackEnd dispatcher
managable by an asynchronous JICPMediatorManager
@author Giovanni Caire - Telecom Italia LAB S.p.A.
*/
public class BackEndDispatcher implements NIOMediator, BEConnectionManager, Dispatcher {
private static final long RESPONSE_TIMEOUT = 60000;
private long keepAliveTime;
private long maxDisconnectionTime;
private long expirationDeadline;
private long lastReceivedTime;
private boolean active = true;
private boolean peerActive = true;
private boolean connectionDropped = false;
private JICPMediatorManager myMediatorManager;
private String myID;
private Properties myProperties;
private BackEndContainer myContainer = null;
private Connection myConnection = null;
private Object writeLock = new Object();
protected InputManager inpManager;
protected OutputManager outManager;
private Logger myLogger = Logger.getMyLogger(getClass().getName());
/**
Retrieve the ID of this mediator. Returns null if this mediator
is not active
*/
public String getID() {
return (active ? myID : null);
}
/**
Retrieve the startup Properties for this NIOBEDispatcher.
*/
public Properties getProperties() {
return myProperties;
}
/**
Initialize this NIOMediator
*/
public void init(JICPMediatorManager mgr, String id, Properties props) throws ICPException {
System.out.println("BackEndDispatcher starting...");
myMediatorManager = mgr;
myID = id;
myProperties = props;
// Max disconnection time
maxDisconnectionTime = JICPProtocol.DEFAULT_MAX_DISCONNECTION_TIME;
try {
maxDisconnectionTime = Long.parseLong(props.getProperty(JICPProtocol.MAX_DISCONNECTION_TIME_KEY));
}
catch (Exception e) {
// Keep default
}
// Keep-alive time
keepAliveTime = JICPProtocol.DEFAULT_KEEP_ALIVE_TIME;
try {
keepAliveTime = Long.parseLong(props.getProperty(JICPProtocol.KEEP_ALIVE_TIME_KEY));
}
catch (Exception e) {
// Keep default
}
// inpCnt
int inpCnt = 0;
try {
inpCnt = (Integer.parseInt(props.getProperty("lastsid")) + 1) & 0x0f;
}
catch (Exception e) {
// Keep default
}
System.out.println("Next command for FE will have sessionID "+inpCnt);
// lastSid
int lastSid = 0x0f;
try {
lastSid = (byte) (Integer.parseInt(props.getProperty("outcnt")) -1);
if (lastSid < 0) {
lastSid = 0x0f;
}
}
catch (Exception e) {
// Keep default
}
FrontEndStub st = new FrontEndStub(this);
inpManager = new InputManager(inpCnt, st);
BackEndSkel sk = startBackEndContainer(props);
outManager = new OutputManager(lastSid, sk);
}
protected final BackEndSkel startBackEndContainer(Properties props) throws ICPException {
try {
String nodeName = myID.replace(':', '_');
props.setProperty(Profile.CONTAINER_NAME, nodeName);
myContainer = new BackEndContainer(props, this);
if (!myContainer.connect()) {
throw new ICPException("BackEnd container failed to join the platform");
}
// Possibly the node name was re-assigned by the main
myID = myContainer.here().getName();
if(myLogger.isLoggable(Logger.CONFIG)) {
myLogger.log(Logger.CONFIG,"BackEndContainer "+myID+" successfully joined the platform");
}
return new BackEndSkel(myContainer);
}
catch (ProfileException pe) {
// should never happen
pe.printStackTrace();
throw new ICPException("Error creating profile");
}
}
// Local variable only used in the kill() method
private Object shutdownLock = new Object();
/**
Kill the above container.
This may be called by the JICPMediatorManager or when
a peer termination notification is received.
*/
public void kill() {
// Avoid killing the above container two times
synchronized (shutdownLock) {
if (active) {
active = false;
myContainer.shutDown();
}
}
}
/**
* Passes to this JICPMediator the connection opened by the mediated
* entity.
* This is called by the JICPMediatorManager this Mediator is attached to
* as soon as the mediated entity (re)connects.
* @param c the connection to the mediated entity
* @param pkt the packet that was sent by the mediated entity when
* opening this connection
* @param addr the address of the mediated entity
* @param port the local port used by the mediated entity
* @return an indication to the JICPMediatorManager to keep the
* connection open.
*/
public synchronized boolean handleIncomingConnection(Connection c, JICPPacket pkt, InetAddress addr, int port) {
checkTerminatedInfo(pkt);
// Update keep-alive info
lastReceivedTime = System.currentTimeMillis();
if (peerActive) {
myConnection = c;
updateConnectedState();
inpManager.setConnection(myConnection);
connectionDropped = false;
return true;
}
else {
// The remote FrontEnd has terminated spontaneously -->
// Kill the above container (this will also kill this BackEndDispatcher).
kill();
return false;
}
}
/**
Notify this NIOMediator that an error occurred on one of the
Connections it is using. This information is important since,
unlike normal mediators, a NIOMediator typically does not read
packets from
connections on its own (the JICPMediatorManager does that in general).
*/
public synchronized void handleConnectionError(Connection c, Exception e) {
if (active && peerActive) {
if (c == myConnection) {
myConnection = null;
updateConnectedState();
inpManager.resetConnection();
myLogger.log(Logger.WARNING, myID+": Disconnection detected");
setExpirationDeadline();
}
}
}
/**
Passes to this mediator a JICPPacket received by the
JICPMediatorManager this mediator is attached to.
In a NIOMediator this should never be called.
*/
public JICPPacket handleJICPPacket(JICPPacket p, InetAddress addr, int port) throws ICPException {
throw new ICPException("Unexpected call");
}
/**
Overloaded version of the handleJICPPacket() method including
the <code>Connection</code> the incoming JICPPacket was received
from. This information is important since, unlike normal mediators,
a NIOMediator may not read packets from connections on its own (the
JICPMediatorManager does that in general).
*/
public JICPPacket handleJICPPacket(Connection c, JICPPacket pkt, InetAddress addr, int port) throws ICPException {
checkTerminatedInfo(pkt);
// Update keep-alive info
lastReceivedTime = System.currentTimeMillis();
JICPPacket reply = null;
byte type = pkt.getType();
switch (type) {
case JICPProtocol.DROP_DOWN_TYPE:
System.out.println("DROP_DOWN received: "+pkt.getSessionID());
// Note that the return packet is written inside the handleDropDown()
// method since the connection must be closed after the response has
// been sent back.
handleDropDown(c, pkt, addr, port);
break;
case JICPProtocol.COMMAND_TYPE:
System.out.println("COMMAND received: "+pkt.getSessionID());
if (peerActive) {
reply = outManager.handleCommand(pkt);
}
else {
// The remote FrontEnd has terminated spontaneously -->
// Kill the above container (this will also kill this NIOBEDispatcher).
kill();
}
break;
case JICPProtocol.KEEP_ALIVE_TYPE:
System.out.println("KEEP_ALIVE received: "+pkt.getSessionID());
reply = outManager.handleKeepAlive(pkt);
break;
case JICPProtocol.RESPONSE_TYPE:
case JICPProtocol.ERROR_TYPE:
System.out.println("RESPONSE/ERROR received: "+pkt.getSessionID());
inpManager.notifyIncomingResponseReceived(pkt);
break;
default:
throw new ICPException("Unexpected packet type "+type);
}
if (reply != null) {
try {
writePacket(myConnection, reply);
System.out.println("RESPONSE sent back: "+reply.getSessionID());
}
catch (IOException ioe) {
myLogger.log(Logger.WARNING, myID+": Communication error sending back response. "+ioe);
}
}
return null;
}
private void writePacket(Connection c, JICPPacket pkt) throws IOException {
// This is done to ensure that commands and responses are sent to FE separately
synchronized (writeLock) {
c.writePacket(pkt);
}
}
/**
This is periodically called by the JICPMediatorManager and is
used by this NIOMediator to evaluate the elapsed time without
the need of a dedicated thread or timer.
*/
public final void tick(long currentTime) {
if (active && !connectionDropped) {
// 1) Evaluate the keep alive
if (keepAliveTime > 0) {
if ((currentTime - lastReceivedTime) > (keepAliveTime + RESPONSE_TIMEOUT)) {
// Missing keep-alive.
// FIXME: to be implemented
}
}
// 2) Evaluate the max disconnection time
if (checkMaxDisconnectionTime(currentTime)) {
myLogger.log(Logger.SEVERE, myID+": Max disconnection time expired.");
// Consider as if the FrontEnd has terminated spontaneously -->
// Kill the above container (this will also kill this BackEndDispatcher).
kill();
}
}
}
////////////////////////////////////////////////
// BEConnectionManager interface implementation
////////////////////////////////////////////////
/**
Return a stub of the remote FrontEnd that is connected to the local BackEnd.
@param be The local BackEnd
@param props Additional (implementation dependent) connection configuration properties.
@return A stub of the remote FrontEnd.
*/
public FrontEnd getFrontEnd(BackEnd be, Properties props) throws IMTPException {
return inpManager.getStub();
}
/**
Make this BackEndDispatcher terminate.
*/
public void shutdown() {
active = false;
if(myLogger.isLoggable(Logger.INFO)) {
myLogger.log(Logger.INFO, myID+": shutting down");
}
// Deregister from the JICPServer
if (myID != null) {
myMediatorManager.deregisterMediator(myID);
}
inpManager.shutdown();
outManager.shutdown();
}
//////////////////////////////////////////
// Dispatcher interface implementation
//////////////////////////////////////////
public synchronized byte[] dispatch(byte[] payload, boolean flush) throws ICPException {
if (connectionDropped) {
// Move from DROPPED state to DISCONNECTED state and wait
// for the FE to reconnect
droppedToDisconnected();
throw new ICPException("Connection dropped");
}
else {
// Normal dispatch
JICPPacket pkt = new JICPPacket(JICPProtocol.COMMAND_TYPE, JICPProtocol.DEFAULT_INFO, payload);
pkt = inpManager.dispatch(pkt, flush);
return pkt.getData();
}
}
//////////////////////////////////////////////////////
// Methods related to connection drop-down management
//////////////////////////////////////////////////////
/**
Handle a connection DROP_DOWN request from the FE.
*/
protected void handleDropDown(Connection c, JICPPacket pkt, InetAddress addr, int port) {
if (myLogger.isLoggable(Logger.INFO)) {
myLogger.log(Logger.INFO, myID+": DROP_DOWN request received.");
}
try {
if (inpManager.isEmpty()) {
JICPPacket rsp = new JICPPacket(JICPProtocol.RESPONSE_TYPE, JICPProtocol.DEFAULT_INFO, null);
writePacket(c, rsp);
myConnection = null;
updateConnectedState();
inpManager.resetConnection();
connectionDropped = true;
}
else {
// If we have some postponed command to flush, refuse dropping the connection
myLogger.log(Logger.WARNING, myID+": DROP_DOWN request refused.");
JICPPacket rsp = new JICPPacket(JICPProtocol.ERROR_TYPE, JICPProtocol.DEFAULT_INFO, null);
writePacket(c, rsp);
}
}
catch (Exception e) {
myLogger.log(Logger.WARNING, myID+": Error writing DROP_DOWN response. "+e);
}
}
/**
Move from the connectionDropped state to the Disconnected state.
This may happen when
- a packet must be dispatched to the FE.
- an incoming connection is detected
*/
private void droppedToDisconnected() {
connectionDropped = false;
setExpirationDeadline();
requestRefresh();
}
/**
Request the FE to refresh the connection.
This default implementation does nothing. Subclasses may redefine this method to exploit some
application specific out-of-band channel
*/
protected void requestRefresh() {
}
public synchronized boolean isConnected() {
return myConnection != null;
}
private void updateConnectedState() {
myProperties.put(BEManagementHelper.CONNECTED, (isConnected() ? "true" : "false"));
}
/**
Inner class InputManager.
This class manages the delivery of commands to the FrontEnd
*/
protected class InputManager {
private Connection myConnection;
private boolean dispatching = false;
private boolean waitingForFlush;
private JICPPacket lastIncomingResponse;
private int inpCnt;
private FrontEndStub myStub;
InputManager(int c, FrontEndStub s) {
inpCnt = c;
myStub = s;
}
FrontEndStub getStub() {
return myStub;
}
void setConnection(Connection c) {
myConnection = c;
waitingForFlush = myStub.flush();
}
void resetConnection() {
synchronized (BackEndDispatcher.this) {
myConnection = null;
// If there was someone waiting for a response on the connection notify it.
BackEndDispatcher.this.notifyAll();
}
}
final boolean isEmpty() {
// We are empty if we are not dispatching a JICPPacket and our stub
// has no postponed commands waiting to be delivered.
return (!dispatching) && myStub.isEmpty();
}
void shutdown() {
resetConnection();
}
/**
Dispatch a JICP command to the FE and get back a reply.
*/
final JICPPacket dispatch(JICPPacket pkt, boolean flush) throws ICPException {
dispatching = true;
try {
if (active && myConnection != null) {
if (waitingForFlush && !flush) {
throw new ICPException("Upsetting dispatching order");
}
waitingForFlush = false;
if (myLogger.isLoggable(Logger.FINE)) {
myLogger.log(Logger.FINE, myID+": Sending command "+inpCnt+" to FE");
}
pkt.setSessionID((byte) inpCnt);
try {
lastIncomingResponse = null;
System.out.println("Sending command to FE "+pkt.getSessionID());
writePacket(myConnection, pkt);
System.out.println("Waiting for response from FE "+pkt.getSessionID());
pkt = waitForResponse(inpCnt, RESPONSE_TIMEOUT);
if (pkt != null) {
System.out.println("Response received from FE "+pkt.getSessionID());
if (myLogger.isLoggable(Logger.FINER)) {
myLogger.log(Logger.FINER, myID+": Response received from FE "+pkt.getSessionID());
}
if (pkt.getType() == JICPProtocol.ERROR_TYPE) {
// Communication OK, but there was a JICP error on the peer
throw new ICPException(new String(pkt.getData()));
}
checkTerminatedInfo(pkt);
if (!peerActive) {
// This is the response to an exit command --> Suicide, without
// killing the above container since it is already dying.
BackEndDispatcher.this.shutdown();
}
inpCnt = (inpCnt+1) & 0x0f;
return pkt;
}
else {
myLogger.log(Logger.WARNING, myID+": Response timeout expired");
handleConnectionError(myConnection, null);
throw new ICPException("Response timeout expired");
}
}
catch (IOException ioe) {
// There was an IO exception writing data to the connection
// --> reset the connection.
myLogger.log(Logger.WARNING, myID+": "+ioe);
handleConnectionError(myConnection, ioe);
throw new ICPException("Dispatching error.", ioe);
}
}
else {
throw new ICPException("Unreachable");
}
}
finally {
dispatching = false;
}
}
private JICPPacket waitForResponse(int sessionID, long timeout) {
try {
while (lastIncomingResponse == null ) {
BackEndDispatcher.this.wait(timeout);
if (lastIncomingResponse != null && lastIncomingResponse.getSessionID() != sessionID) {
myLogger.log(Logger.WARNING, myID+": Duplicated response from FE: type="+lastIncomingResponse.getType()+" info="+lastIncomingResponse.getInfo()+" SID="+lastIncomingResponse.getSessionID());
// Go back waiting
lastIncomingResponse = null;
continue;
}
break;
}
}
catch (Exception e) {}
return lastIncomingResponse;
}
private void notifyIncomingResponseReceived(JICPPacket rsp) {
synchronized (BackEndDispatcher.this) {
lastIncomingResponse = rsp;
BackEndDispatcher.this.notifyAll();
}
}
} // END of inner class InputManager
/**
Inner class OutputManager
This class manages the reception of commands and keep-alive
packets from the FrontEnd.
*/
protected class OutputManager {
private JICPPacket lastResponse;
private int lastSid;
private BackEndSkel mySkel;
OutputManager(int n, BackEndSkel s) {
lastSid = n;
mySkel = s;
}
void shutdown() {
}
final JICPPacket handleCommand(JICPPacket cmd) throws ICPException {
JICPPacket reply = null;
byte sid = cmd.getSessionID();
if (sid == lastSid) {
myLogger.log(Logger.WARNING,myID+": Duplicated packet from FE: pkt-type="+cmd.getType()+" info="+cmd.getInfo()+" SID="+sid);
reply = lastResponse;
}
else {
if(myLogger.isLoggable(Logger.FINE)) {
myLogger.log(Logger.FINE, myID+": Received command "+sid+" from FE");
}
byte[] rspData = mySkel.handleCommand(cmd.getData());
if(myLogger.isLoggable(Logger.FINER)) {
myLogger.log(Logger.FINER, myID+": Command "+sid+" from FE served ");
}
reply = new JICPPacket(JICPProtocol.RESPONSE_TYPE, JICPProtocol.DEFAULT_INFO, rspData);
reply.setSessionID(sid);
lastSid = sid;
lastResponse = reply;
}
return reply;
}
final JICPPacket handleKeepAlive(JICPPacket command) throws ICPException {
if(myLogger.isLoggable(Logger.FINEST)) {
myLogger.log(Logger.FINEST,myID+": Keep-alive received");
}
return new JICPPacket(JICPProtocol.RESPONSE_TYPE, JICPProtocol.DEFAULT_INFO, null);
}
} // END of inner class OutputManager
private synchronized final void setExpirationDeadline() {
expirationDeadline = System.currentTimeMillis() + maxDisconnectionTime;
}
private synchronized final boolean checkMaxDisconnectionTime(long currentTime) {
return (!isConnected()) && (currentTime > expirationDeadline);
}
private final boolean checkTerminatedInfo(JICPPacket pkt) {
if ((pkt.getInfo() & JICPProtocol.TERMINATED_INFO) != 0) {
peerActive = false;
if (myLogger.isLoggable(Logger.INFO)) {
myLogger.log(Logger.INFO, myID+": Peer termination notification received");
}
}
return peerActive;
}
}