Package jade.imtp.leap.nio

Source Code of jade.imtp.leap.nio.BackEndDispatcher

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;
  }
 
}

TOP

Related Classes of jade.imtp.leap.nio.BackEndDispatcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.