Package jade.imtp.leap.nio

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

package jade.imtp.leap.nio;

//#J2ME_EXCLUDE_FILE

import jade.core.BackEnd;
import jade.core.BackEndContainer;
import jade.core.BEConnectionManager;
import jade.core.FrontEnd;
import jade.core.IMTPException;
import jade.core.Profile;
import jade.core.ProfileException;
import jade.core.Runtime;
import jade.core.Timer;
import jade.core.TimerListener;
import jade.imtp.leap.BackEndSkel;
import jade.imtp.leap.FrontEndStub;
import jade.imtp.leap.ICPException;
import jade.imtp.leap.MicroSkeleton;
import jade.imtp.leap.Dispatcher;
import jade.imtp.leap.JICP.Connection;
import jade.imtp.leap.JICP.JICPMediatorManager;
import jade.imtp.leap.JICP.JICPProtocol;
import jade.imtp.leap.JICP.JICPPacket;
import jade.util.Logger;
import jade.util.leap.Properties;

import java.io.IOException;
import java.net.InetAddress;

/**
*
* @author Eduard Drenth: Logica, 11-jul-2009
*
*/
public class NIOHTTPBEDispatcher implements NIOMediator, Dispatcher, BEConnectionManager {
  // Local statuses
  private static final int ACTIVE = 0;
  private static final int NOT_ACTIVE = 1;
 
  // Front-end statuses
  private static final int CONNECTED = 0;
  private static final int CONNECTING = 1;
  private static final int DISCONNECTED = 2;
  private static final int TERMINATED = 3;
 
  private static final long OUTGOING_COMMANDS_CONNECTION_TIMEOUT = 30000; // 30 sec
 
  private static final long RESPONSE_TIMEOUT = 30000; // 30 sec
  private static final long RESPONSE_TIMEOUT_INCREMENT = 100; // 100 msec
 
  private static final int MAX_SID = 0x0f;

  private JICPMediatorManager myMediatorManager;
  private String myID;
  private MicroSkeleton mySkel = null;
  private FrontEndStub myStub = null;
  private BackEndContainer myContainer = null;
 
  private int status = ACTIVE;
  private int frontEndStatus = CONNECTING;
  private long maxDisconnectionTime;
  private Timer maxDisconnectionTimer = null;
  private long keepAliveTime;
  private Timer keepAliveTimer = null;

  private JICPPacket lastResponse = null;
  private byte lastIncomingCommandSid;

  private boolean waitingForFlush = false
  private Connection outgoingCommandsConnection = null;
  private Object outgoingCommandsConnectionLock = new Object();
  private int nextOutgoingCommandSid;
  private JICPPacket responseToLastOutgoingCommand = null;
  private Object responseToLastOutgoingCommandLock = new Object();

  private Logger myLogger = Logger.getMyLogger(getClass().getName());

  //////////////////////////////////////////
  // NIOMediator interface implementation
  //////////////////////////////////////////
  public String getID() {
    return myID;
  }

  /**
   * Initialize parameters and activate the BackEndContainer
   */
  public void init(JICPMediatorManager mgr, String id, Properties props) throws ICPException {
    myMediatorManager = mgr;
    myID = id;

    // Read parameters
    // 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
    }

    // Max disconnection time
    keepAliveTime = JICPProtocol.DEFAULT_KEEP_ALIVE_TIME;
    try {
      keepAliveTime = Long.parseLong(props.getProperty(JICPProtocol.KEEP_ALIVE_TIME_KEY));
    } catch (Exception e) {
      // Keep default
    }

    // Counter to assign the SID to the next command to be delivered to the FE (only present if this is a back-end re-creation)
    nextOutgoingCommandSid = 0;
    try {
      // The FE indicates the SID of the last command it received --> Increment it by 1
      nextOutgoingCommandSid = increment(Integer.parseInt(props.getProperty("lastsid")));
    }
    catch (Exception e) {
      // Keep default
    }
   
    // SID of last command received from the FE (only present if this is a back-end re-creation)
    lastIncomingCommandSid = 0x10;
    try {
      // The FE indicates the SID of the next command it will send us --> Decrement it by 1
      lastIncomingCommandSid = (byte) decrement(Integer.parseInt(props.getProperty("outcnt")));
    }
    catch (Exception e) {
      // Keep default
    }
   
    myLogger.log(Logger.INFO, "Created NIOHTTPBEDispatcher V1.0. ID = " + myID + "\n- Max-disconnection-time = " + maxDisconnectionTime+ "\n- Keep-alive-time = " + keepAliveTime);
    myLogger.log(Logger.CONFIG, myID+" - Next outgoing command SID = " + nextOutgoingCommandSid + ", Last incoming command SID = " + lastIncomingCommandSid);

    myStub = new FrontEndStub(this);
    mySkel = startBackEndContainer(props);
  }

  private 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();
      myLogger.log(Logger.CONFIG, myID+" - BackEndContainer " + myID + " successfully joined the platform.");
      return new BackEndSkel(myContainer);
    } catch (ProfileException pe) {
      // should never happen
      pe.printStackTrace();
      throw new ICPException("Error creating profile");
    }
  }

  /**
   * Termination self initiated or forced by the MediatorManager we are attached to.
   */
  public void kill() {
    status = NOT_ACTIVE;
    // Force the BackEndContainer to terminate.
    // This will also cause this NIOHTTPBEDispatcher to terminate and deregister from the MediatorManager
    myContainer.shutDown();
  }

  public void tick(long time) {
    // Not used: just do nothing
  }
 
  /**
   * Handle an incoming connection. This is called by the MediatorManager
   * when a CREATE or CONNECT_MEDIATOR request is received.
   * In both cases the Front-end is connecting -->
   * Set the front-end status to CONNECTING, but don't use this connection: to allow us 
   * sending commands to the front-end an initial dummy response will be received soon.
   */
  public synchronized boolean handleIncomingConnection(Connection c, JICPPacket pkt, InetAddress addr, int port) {
    myLogger.log(Logger.INFO, myID+" - Front-end connecting ["+addr+":"+port+"]");
    setFrontEndConnecting();
    // Returning false will make the MediatorManager close the connection
    return false;
  }

  /**
   * Handle an incoming JICP packet received by the MediatorManager
   */
  public JICPPacket handleJICPPacket(Connection c, JICPPacket pkt, InetAddress addr, int port) throws ICPException {
    JICPPacket response = null;
    if ((status == ACTIVE) && (frontEndStatus != TERMINATED)) {
      if (myLogger.isLoggable(Logger.FINE)) {
        myLogger.log(Logger.FINE, myID+" - Incoming packet. Type = "+pkt.getType()+", SID = "+pkt.getSessionID()+", terminated-info = "+((pkt.getInfo() & JICPProtocol.TERMINATED_INFO) != 0));
      }
      String from = " [" + addr + ":" + port + "]";
      if (pkt.getType() == JICPProtocol.COMMAND_TYPE) {
        // COMMAND
        if ((pkt.getInfo() & JICPProtocol.TERMINATED_INFO) != 0) {
          // PEER TERMINATION NOTIFICATION
          // The remote FrontEnd terminated spontaneously --> Terminate and notify up.
          myLogger.log(Logger.INFO, myID+" - Peer termination notification received");
          handlePeerSelfTermination();
          // Since we return null the MediatorManager would keep the connection open --> close it explicitly
          return createTerminationNotificationAck();
        } else {
          // NORMAL COMMAND
          // Serve the incoming command and send back the response
          byte sid = pkt.getSessionID();
          if (sid == lastIncomingCommandSid) {
            myLogger.log(Logger.WARNING, myID+" - Duplicated command received. SID = " + sid);
            response = lastResponse;
          } else {
            if (myLogger.isLoggable(Logger.FINE)) {
              myLogger.log(Logger.FINE, myID+" - Incoming command received. SID = " + sid);
            }
 
            byte[] rspData = mySkel.handleCommand(pkt.getData());
            if (myLogger.isLoggable(Logger.FINE)) {
              myLogger.log(Logger.FINE, myID+" - Incoming command served. SID = " + sid);
            }
            response = new JICPPacket(JICPProtocol.RESPONSE_TYPE, JICPProtocol.DEFAULT_INFO, rspData);
            response.setSessionID(sid);
            lastIncomingCommandSid = sid;
            lastResponse = response;
          }
        }
      } else {
        // RESPONSE.
        handleResponse(c, pkt, from);
        if ((pkt.getInfo() & JICPProtocol.TERMINATED_INFO) != 0) {
          // PEER TERMINATION NOTIFICATION.
          // The remote FrontEnd terminated as a consequence of an EXIT command
          setFrontEndTerminated();
          shutdown();
          // Since we return null the MediatorManager would keep the connection open --> close it explicitly
          return createTerminationNotificationAck();
        }
      }
    }
    else {
      myLogger.log(Logger.FINE, "Unexpected packet received after termination. Type = "+pkt.getType()+", SID = "+pkt.getSessionID()+", terminated-info = "+((pkt.getInfo() & JICPProtocol.TERMINATED_INFO) != 0));
    }

    return response;
  }

  public JICPPacket handleJICPPacket(JICPPacket pkt, InetAddress addr, int port) throws ICPException {
    throw new ICPException("Unexpected call");
  }

  public void handleConnectionError(Connection c, Exception e) {
    // The MediatorManager got an exception reading from connection c
    // FIXME: What should we do? For the moment just print a warning
    if ((status == ACTIVE) && (frontEndStatus != TERMINATED)) {
      myLogger.log(Logger.WARNING, myID+" - Exception reading from the connection", e);
    }
  }

  public Properties getProperties() {
    return new Properties();
  }

  private void handlePeerSelfTermination() {
    // The FrontEnd exited --> Set its new status and then suicide!
    setFrontEndTerminated();
    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 myStub;
  }

  /**
   * Termination initiated by the BackEndContainer (i.e. by the platform).
   * When the BackEndContainer exits 
   */
  public void shutdown() {
    myLogger.log(Logger.INFO, myID+" - Initiate NIOHTTPBEDispatcher shutdown");
    status = NOT_ACTIVE;

    // Deregister from the MediatorManager
    if (myID != null) {
      myMediatorManager.deregisterMediator(myID);
      myID = null;
    }
   
    // Clean everything
    clean();
  }

 
  //////////////////////////////////////////
  // Dispatcher interface implementation
  //////////////////////////////////////////
  /**
   * This is called by the Stub using this Dispatcher to dispatch a serialized command to the FrontEnd.
   * Mutual exclusion with itself to ensure one command at a time is dispatched.
   */
  public synchronized byte[] dispatch(byte[] payload, boolean flush) throws ICPException {
    if (status == ACTIVE) {
      if (frontEndStatus == CONNECTED) {
        // The following check preserves dispatching order when the
        // front-end has just reconnected but flushing of postponed commands has not started yet
        if (waitingForFlush && !flush) {
          throw new ICPException("Upsetting dispatching order");
        }
        waitingForFlush = false;
 
        // Wait for the connection to deliver outgoing commands to be ready
        Connection c = getOutgoingCommandsConnection();
 
        // Send the command to the front-end
        JICPPacket cmd = new JICPPacket(JICPProtocol.COMMAND_TYPE, JICPProtocol.DEFAULT_INFO, payload);
        int sid = nextOutgoingCommandSid;
        nextOutgoingCommandSid = increment(nextOutgoingCommandSid);
        if (myLogger.isLoggable(Logger.FINE)) {
          myLogger.log(Logger.FINE, myID+" - Delivering outgoing command to front-end. SID = " + sid);
        }
        cmd.setSessionID((byte) sid);
        boolean deliverOK = false;
        try {
          c.writePacket(cmd);
          close(c);
          // Wait for the response
          JICPPacket response = getResponse(RESPONSE_TIMEOUT + RESPONSE_TIMEOUT_INCREMENT * (cmd.getLength() / 1024));
          deliverOK = true;
          if (myLogger.isLoggable(Logger.FINE)) {
            myLogger.log(Logger.FINE, myID+" - Response got. SID = " + sid);
          }
          if (response.getType() == JICPProtocol.ERROR_TYPE) {
            // Communication OK, but there was a JICP error on the peer
            throw new ICPException(new String(response.getData()));
          }
          return response.getData();
        }
        catch (IOException ioe) {
          setFrontEndDisconnected();
          throw new ICPException("Error delivering outgoing command to front-end. ", ioe);
        }
        catch (ICPException icpe) {
          // Note that in this case setFrontEndDisconnected() is already called within getResponse() or getOutgoingCommandsConnection()
          throw icpe;
        }
        finally {
          if (!deliverOK) {
            nextOutgoingCommandSid = decrement(nextOutgoingCommandSid);
          }
        }
      }
      else {
        throw new ICPException("Front-end not connected");
      }
    }
    else {
      throw new ICPException("Not-active");
    }
  }
 
  private synchronized void dispatchKeepAlive() {
    if (status == ACTIVE) {
      if (frontEndStatus == CONNECTED) {
        try {
          // Wait for the connection to deliver outgoing commands to be ready
          Connection c = getOutgoingCommandsConnection();
   
          // Send the command to the front-end
          if (myLogger.isLoggable(Logger.FINER)) {
            myLogger.log(Logger.FINER, myID+" - Delivering keep-alive to front-end");
          }
          JICPPacket cmd = new JICPPacket(JICPProtocol.KEEP_ALIVE_TYPE, JICPProtocol.DEFAULT_INFO, null);
          c.writePacket(cmd);
          close(c);
          // Wait for the response
          JICPPacket response = getResponse(RESPONSE_TIMEOUT + RESPONSE_TIMEOUT_INCREMENT * (cmd.getLength() / 1024));
          if (isKeepAliveResponse(response)) {
            if (myLogger.isLoggable(Logger.FINER)) {
              myLogger.log(Logger.FINER, myID+" - Keep-alive response got");
            }
          }
          else {
            // Should never happen
            myLogger.log(Logger.WARNING, "Unexpected response received while waiting for Keep-alive response");
          }
        }
        catch (IOException ioe) {
          myLogger.log(Logger.WARNING, myID+" - Error delivering keep-alive packet to the front-end", ioe);
          setFrontEndDisconnected();
        }
        catch (ICPException icpe) {
          // Note that in this case setFrontEndDisconnected() is already called within getResponse() or getOutgoingCommandsConnection()
          if (frontEndStatus != TERMINATED) {
            myLogger.log(Logger.WARNING, myID+" - Keep-alive error. "+icpe.getMessage());
          }
        }
      }
    }
  }

  /**
   * Wait until a connection to deliver commands to the FrontEnd is ready
   * @see handleResponse()
   */
  private Connection getOutgoingCommandsConnection() throws ICPException {
    try {
      synchronized (outgoingCommandsConnectionLock) {
        while (outgoingCommandsConnection == null) {
          outgoingCommandsConnectionLock.wait(OUTGOING_COMMANDS_CONNECTION_TIMEOUT);
          if (outgoingCommandsConnection == null) {
            if (frontEndStatus == TERMINATED) {
              // We terminated in the meanwhile
              throw new ICPException("Terminated");
            }
            else {
              // Timeout expired
              setFrontEndDisconnected();
              throw new ICPException("Response timeout");
            }
          }
        }
        Connection c = outgoingCommandsConnection;
        outgoingCommandsConnection = null;
        return c;
      }
    }
    catch (InterruptedException ie) {
      throw new ICPException("Interrupted while waiting for outgoing-commands-connection");
    }
  }

  /**
   * Wait until the response to the last outgoing command is received
   * @see handleResponse()
   */
  private JICPPacket getResponse(long timeout) throws ICPException {
    try {
      synchronized (responseToLastOutgoingCommandLock) {
        while (responseToLastOutgoingCommand == null) {
          responseToLastOutgoingCommandLock.wait(timeout);
          if (responseToLastOutgoingCommand == null) {
            if (frontEndStatus == TERMINATED) {
              // We terminated in the meanwhile
              throw new ICPException("Terminated");
            }
            else {
              // Timeout expired
              setFrontEndDisconnected();
              throw new ICPException("Response timeout");
            }
          }
        }
        JICPPacket response = responseToLastOutgoingCommand;
        responseToLastOutgoingCommand = null;
        return response;
      }
    }
    catch (InterruptedException ie) {
      throw new ICPException("Interrupted while waiting for response to outgoing command");
    }
  }

  private void handleResponse(Connection c, JICPPacket response, String from) {
    if (frontEndStatus != CONNECTED) {
      if (frontEndStatus == CONNECTING) {
        // Initial dummy response
        myLogger.log(Logger.INFO, myID+" - Initial dummy response received.");
      }
      else {
        // Unexpected response
        myLogger.log(Logger.WARNING, myID+" - Unexpected (likely out of time) response received.", new Exception("DUMMY!!!!"));
      }
      // In any case now we are connected
      setFrontEndConnected();
    }
    else {
      // Normal response: pass it to the thread that dispatched the command
      if (isKeepAliveResponse(response)) {
        if (myLogger.isLoggable(Logger.FINER)) {
          myLogger.log(Logger.FINER, myID+" - Keep-alive response received");
        }
      }
      else {
        if (myLogger.isLoggable(Logger.FINE)) {
          myLogger.log(Logger.FINE, myID+" - Response received. SID = " + response.getSessionID());
        }
      }
      synchronized (responseToLastOutgoingCommandLock) {
        responseToLastOutgoingCommand = response;
        responseToLastOutgoingCommandLock.notifyAll();
      }
    }

    // Store the connection: it will be used to deliver the next outgoing command
    synchronized (outgoingCommandsConnectionLock) {
      outgoingCommandsConnection = c;
      outgoingCommandsConnectionLock.notifyAll();
    }
   
    updateKeepAliveTimer();
  }


  private void close(Connection c) {
    try {
      c.close();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  private int increment(int val) {
    return (val + 1) & MAX_SID;
  }
 
  private int decrement(int val) {
    val--;
    if (val < 0) {
      val = MAX_SID;
    }
    return val;
  }
 
  private boolean isKeepAliveResponse(JICPPacket response) {
    // The OK_INFO bit is set only on KEEP-ALIVE responses
    return (response.getInfo() & JICPProtocol.OK_INFO) != 0;
  }
 
  private void setFrontEndConnecting() {
    frontEndStatus = CONNECTING;
    resetMaxDisconnectionTimer()
    outgoingCommandsConnection = null;
    responseToLastOutgoingCommand = null;
  }
 
  private void setFrontEndConnected() {
    frontEndStatus = CONNECTED;
    resetMaxDisconnectionTimer();
    waitingForFlush = myStub.flush();
  }
 
  private void setFrontEndDisconnected() {
    frontEndStatus = DISCONNECTED;
    activateMaxDisconnectionTimer();
  }
 
  private void setFrontEndTerminated() {
    frontEndStatus = TERMINATED;
  }
 
  private synchronized void updateKeepAliveTimer() {
    if (keepAliveTime > 0) {
      // Update the timer that triggers the delivery of a KEEP-ALIVE packet
      if (keepAliveTimer != null) {
        Runtime.instance().getTimerDispatcher().remove(keepAliveTimer);
      }
      long now = System.currentTimeMillis();
      keepAliveTimer = new Timer(now + keepAliveTime, new TimerListener() {
        public void doTimeOut(Timer t) {
          dispatchKeepAlive();
        }
      });
      keepAliveTimer = Runtime.instance().getTimerDispatcher().add(keepAliveTimer);
      if (myLogger.isLoggable(Logger.FINEST)) {
        myLogger.log(Logger.FINEST, myID+" - Keep-alive timer activated.");
      }
    }
  }
 
  // No need for synchronization as this is always executed within a synchronized block
  private void activateMaxDisconnectionTimer() {
    // Set the disconnection timer
    long now = System.currentTimeMillis();
    maxDisconnectionTimer = new Timer(now + maxDisconnectionTime, new TimerListener() {
      public void doTimeOut(Timer t) {
        synchronized (NIOHTTPBEDispatcher.this) {
          if (frontEndStatus != CONNECTED) {
            myLogger.log(Logger.WARNING, myID+" - Max disconnection timeout expired.");
            // The remote FrontEnd is probably down --> notify up.
            handlePeerSelfTermination();
          }
        }
      }
    });
    maxDisconnectionTimer = Runtime.instance().getTimerDispatcher().add(maxDisconnectionTimer);
    myLogger.log(Logger.INFO, myID+" - Max-disconnection-timer activated.");
  }

  private void resetMaxDisconnectionTimer() {
    if (maxDisconnectionTimer != null) {
      Runtime.instance().getTimerDispatcher().remove(maxDisconnectionTimer);
      maxDisconnectionTimer = null;
    }
  }
 
  private void clean() {
    // Be sure not to leave any pending timer
    resetMaxDisconnectionTimer();
    // If there is some thread waiting for something, make it exit
    synchronized (responseToLastOutgoingCommandLock) {
      responseToLastOutgoingCommand = null;
      responseToLastOutgoingCommandLock.notifyAll();
    }
    synchronized (outgoingCommandsConnectionLock) {
      outgoingCommandsConnection = null;
      outgoingCommandsConnectionLock.notifyAll();
    }
  }
 
  private JICPPacket createTerminationNotificationAck() {
    return new JICPPacket(JICPProtocol.COMMAND_TYPE, JICPProtocol.TERMINATED_INFO, null);
  }
}
TOP

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

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.