Package org.xmlBlaster.util.dispatch

Source Code of org.xmlBlaster.util.dispatch.DispatchConnection

/*------------------------------------------------------------------------------
Name:      DispatchConnection.java
Project:   xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
------------------------------------------------------------------------------*/
package org.xmlBlaster.util.dispatch;

import java.util.logging.Logger;
import java.util.logging.Level;

import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.util.def.Constants;
import org.xmlBlaster.util.Timestamp;
import org.xmlBlaster.util.I_Timeout;
import org.xmlBlaster.util.qos.address.AddressBase;
import org.xmlBlaster.util.qos.StatusQosData;
import org.xmlBlaster.util.queuemsg.MsgQueueEntry;
import org.xmlBlaster.util.xbformat.I_ProgressListener;

/**
* Holding all necessary infos to establish a remote connection
* and check this connection.
* <pre>
*    State chart of a single connection:
*
*          +<--toAlive()----+
*          |                |
*    #########            ##########         ##########
*   #         #          #          #       #          #
*   #  ALIVE  #          # POLLING  #       #   DEAD   #
*   #         #          #          #       #          #
*    #########            ##########         ##########
*      |   |                |    |             |    |
*      |   +--toPolling()-->+    +--toDead()-->+    |
*      |                                            |
*      +-------------toDead()---------------------->+
*
* </pre>
* <p>
* Note that DispatchConnection can't recover from DEAD state
* you need to create a new instance if desired
* </p>
* @author xmlBlaster@marcelruff.info
* @author michele@laghi.eu
*/
abstract public class DispatchConnection implements I_Timeout
{
   public final String ME;
   protected final Global glob;
   private static Logger log = Logger.getLogger(DispatchConnection.class.getName());

   public static final String PING_THREAD_NAME = "_xmlBlaster.timerThread";
  
   protected DispatchConnectionsHandler connectionsHandler = null;

   /** For logging only */
   protected final String myId;
   protected AddressBase address;

   private Timestamp timerKey;
   /** Protects timerKey refresh */
   private final Object PING_TIMER_MONITOR = new Object();

   protected ConnectionStateEnum state = ConnectionStateEnum.UNDEF;

   protected int retryCounter = 0;
   private final long logEveryMillis; // Every hour (60000: every minute a log)
   private int logInterval = 10;

   /**
    * Flag if the remote server is reachable but is not willing to process our requests (standby mode).
    * This flag is only evaluated in POLLING state
    */
   protected boolean serverAcceptsRequests = false;
   protected boolean physicalConnectionOk = false;
   protected long previousBytesWritten;
   protected long previousBytesRead;
   private boolean bypassPingOnActivity = true;

   /**
    * Our loadPlugin() and initialize() needs to be called next.
    * @param connectionsHandler The DevliveryConnectionsHandler witch i belong to
    * @param address The address i shall connect to
    */
   public DispatchConnection(Global glob, DispatchConnectionsHandler connectionsHandler, AddressBase address) {
      if (address == null)
         throw new IllegalArgumentException("DispatchConnection expects an address!=null");
      this.ME = "DispatchConnection-" + connectionsHandler.getDispatchManager().getQueue().getStorageId() + " ";
      this.glob = glob;

      this.logEveryMillis = glob.getProperty().get("dispatch/logRetryEveryMillis", 3600000L); // every hour a log
      if (log.isLoggable(Level.FINE))
         log.fine("dispatch/logRetryEveryMillis=" + this.logEveryMillis);
      this.connectionsHandler = connectionsHandler;
      this.myId = connectionsHandler.getDispatchManager().getQueue().getStorageId().getId() + " ";
      this.address = address;
   }

   public void setAddress(AddressBase addressthrows XmlBlasterException {
      if (log.isLoggable(Level.FINE))
         log.fine(ME +  "setAddress: configuration has changed (with same url) set to new address object");
      this.address = address;
   }

   /**
    * @return A nice name for logging
    */
   abstract public String getName();

   /**
    * Connects on protocol level to the server and tries a ping.
    * Needs to be called after construction
    * <p>
    * Calls connectLowLevel() which needs to be implemented by derived classes
    * loadPlugin() needs to be called before.
    * </p>
    * Called by ClientDispatchConnectionsHandler or CbDispatchConnectionsHandler
    */
   public final void initialize() throws XmlBlasterException {
      this.retryCounter = 0;

      if (this.logEveryMillis <= 0) {
         logInterval = -1; // no logging
      }
      else if (address.getDelay() < 1 || address.getDelay() > this.logEveryMillis// millisec
         logInterval = 1;
      else
         logInterval = (int)(this.logEveryMillis / address.getDelay());

      try {
         connectLowlevel();
         handleTransition(true, null);
      }
      catch (XmlBlasterException e) {
         if (ErrorCode.COMMUNICATION_FORCEASYNC.equals(e.getErrorCode())) {
            if (log.isLoggable(Level.FINE)) log.fine(ME + "initialize:" + e.getMessage());
         }
         else {
            if (address.isFromPersistenceRecovery()) {
               if (log.isLoggable(Level.FINE))
                  log.fine(ME + "initialize:" + e.getMessage());
            }
            else {
               log.warning(ME + "initialize:" + e.getMessage());
            }
         }
         if (retry(e)) {    // all types of ErrorCode.COMMUNICATION*
            handleTransition(true, e); // never returns (only if DEAD) - throws exception
         }
         else {
            connectionsHandler.toDead(this, e);
            throw e;
         }
      }
      catch (Throwable throwable) {
         throwable.printStackTrace();
         XmlBlasterException e = (throwable instanceof XmlBlasterException) ? (XmlBlasterException)throwable :
                                  new XmlBlasterException(glob, ErrorCode.INTERNAL_UNKNOWN, ME, "", throwable);
         if (log.isLoggable(Level.FINE)) log.fine(ME + e.toString());
         connectionsHandler.toDead(this, e);
         throw e;
      }

      if (log.isLoggable(Level.FINE)) log.fine(ME + "Created driver for protocol '" + this.address.getType() + "'");
   }

   private boolean retry(XmlBlasterException e) {
      if (e.isCommunication())  // all types of ErrorCode.COMMUNICATION*
         return true;
      //if (e.isErrorCode(ErrorCode.USER_SECURITY_AUTHENTICATION_ACCESSDENIED))
      //   return true; // If the client was killed in the server and tries to reconnect with old sessionId
      return false;
   }

   public void finalize()
   {
      if (this.timerKey != null) {
         this.glob.getPingTimer().removeTimeoutListener(this.timerKey);
         this.timerKey = null;
      }
      try {
         super.finalize();
         //if (log.isLoggable(Level.FINE)) log.fine(ME + "finalize - garbage collected");
      }
      catch (Throwable e) {
         e.printStackTrace();
      }
   }

   public final AddressBase getAddress() {
      return this.address;
   }

   /** Called on COMMUNICATION errors, reset protocol driver for reconnect polling */
   abstract public void resetConnection();

   /**
    * The derived class should create an instance of the protocol driver.
    */
   abstract public void loadPlugin() throws XmlBlasterException;

   /**
    * Connect on protocol layer and try an initial low level ping.
    * @exception XmlBlasterException with ErrorCode.COMMUNICATION* if server is not reachable
    *            or other exceptions on other errors
    */
   abstract public void connectLowlevel() throws XmlBlasterException;

   /** A human readable name of the protocol plugin */
   abstract public String getDriverName();

   /**
    * Send the messages.
    * @param msgArr Should be a copy of the original, since we export it which changes/encrypts the content.
    * msgArr[i].getReturnObj() transports the returned string array from the client which is decrypted
    * if necessary, for oneway updates it is null
    * @param isAsyncMode true if coming from queue
    */
   abstract public void doSend(MsgQueueEntry[] msgArr_, boolean isAsyncMode) throws XmlBlasterException;

   /**
    * Should be overwritten by extending classes.
    */
   abstract public I_ProgressListener registerProgressListener(I_ProgressListener listener);

   /**
    * Send the messages back to the client.
    * @param msgArr Should be a copy of the original, since we export it which changes/encrypts the content
    * @param isAsyncMode true if coming from queue
    * @return The returned string from the client which is decrypted if necessary, for oneway updates it is null
    */
   public void send(MsgQueueEntry[] msgArr, boolean isAsyncMode) throws XmlBlasterException
   {
      if (log.isLoggable(Level.FINER)) log.finer(ME + "send(msgArr.length=" + msgArr.length + ")");
      if (msgArr == null || msgArr.length == 0) return; // assert

      if (isDead()) { // assert
         log.severe(ME + "Connection to " + this.address.toString() + " is in state DEAD, msgArr.length=" + msgArr.length + " messages are lost");
         throw new XmlBlasterException(glob, ErrorCode.COMMUNICATION_NOCONNECTION_DEAD, ME, "Internal problem: Connection to " + this.address.toString() + " is in state DEAD, msgArr.length=" + msgArr.length + " messages are lost");
      }

      // Send the message ...
      try {
         long now = System.currentTimeMillis();
         doSend(msgArr, isAsyncMode);
         this.connectionsHandler.getDispatchStatistic().setRoundTripDelay(System.currentTimeMillis() - now);
         handleTransition(true, null);
         return;
      }
      catch (XmlBlasterException e) {
         if (isPolling() && log.isLoggable(Level.FINE)) log.fine(ME + "Exception from update(), retryCounter=" + retryCounter + ", state=" + this.state.toString());
         for (int i=0; i<msgArr.length; i++)
            msgArr[i].incrRedeliverCounter();
         if (isDead()) throw e;
         handleTransition(true, e);
         if (isDead()) throw e;
         if (retry(e)) {
            log.severe(ME + "Exception from update(), retryCounter=" + retryCounter + ", state=" + this.state.toString() + ": " + e.getMessage());
         }
         else {
            throw e; // forward server side exception to the client
         }
      }

      Thread.dumpStack();
      throw new XmlBlasterException(glob, ErrorCode.INTERNAL_UNKNOWN, ME, "This exception is never reached" + toXml(""));
   }

   /**
    * Does the real ping to the remote server instance.
    * @param data QoS, never null
    * @return ping return QoS, never null
    * @see org.xmlBlaster.protocol.I_XmlBlaster#ping(String)
    */
   abstract public String doPing(String data) throws XmlBlasterException;

   /**
    * Ping the remote server instance (callback of the client or xmlBlaster itself)
    */
   public final String ping(String data) throws XmlBlasterException {
      return ping(data, true);
   }

   /**
    * Ping the xmlBlaster server or callback server of the client.
    * Sets serverAcceptsRequests to false if the protocol reaches the remote server but this
    * is in standby mode.
    * @param byDispatchConnectionsHandler true if invoked by DispatchConnectionsHandler
    *        we can throw exceptions back.
    *        false: If invoked by our timer/ping thread, we need to notify the situation
    */
   protected final String ping(String data, boolean byDispatchConnectionsHandler) throws XmlBlasterException {
      if (log.isLoggable(Level.FINER)) log.finer(ME + "ping(" + data + ")");
      if (isDead()) { // assert
         log.severe(ME + "Protocol driver is in state DEAD, ping failed");
         throw new XmlBlasterException(glob, ErrorCode.COMMUNICATION_NOCONNECTION_DEAD, ME, "Protocol driver is in state DEAD, ping failed");
      }
     
      data = (data==null)?"":data;
      boolean stalled = false;
      DispatchStatistic stats = this.connectionsHandler.getDispatchStatistic();
      try {
         if (log.isLoggable(Level.FINE))
            log.fine(ME + stats.toXml(""));

         // check if an ongoing write operation on the same client
         if ((stats.ongoingWrite() || stats.ongoingRead()) && this.bypassPingOnActivity) {
            if (log.isLoggable(Level.FINE))
               log.fine(ME + "AN ONGOING WRITE- OR READ OPERATION WHILE PINGING");
            long currentBytesWritten = stats.getOverallBytesWritten() + stats.getCurrBytesWritten();
            long currentBytesRead = stats.getOverallBytesRead() + stats.getCurrBytesRead();
            if (this.previousBytesWritten != currentBytesWritten || this.previousBytesRead != currentBytesRead) {
               if (log.isLoggable(Level.FINE))
                  log.fine(ME + "there was an activity since last ping, previousWritten='" + this.previousBytesWritten + "' and currentWritten='" + currentBytesWritten + "' previousRead='" + this.previousBytesRead + "' currentRead='" + currentBytesRead + "'");
               this.previousBytesWritten = currentBytesWritten;
               this.previousBytesRead = currentBytesRead;
               handleTransition(false, null);
               stalled = false;
               return Constants.RET_OK;
            }
            else {
               if (forcePingFailure()) {
                  if (log.isLoggable(Level.FINE)) {
                     String debug = " currentBytesWritten='" + currentBytesWritten + "' currentBytesRead='" + currentBytesRead + "'";
                     log.fine(ME + "there was NO activity since last ping, throwing an exception" + debug);
                  }
                  throw new XmlBlasterException(glob, ErrorCode.COMMUNICATION_RESPONSETIMEOUT,
                        glob.getId(), ME, (String)null,
                        "Ping result: The dispatcher was busy sending data but there was no activity since last ping",
                        (String)null, (Timestamp)null,
                        (String)null, (String)null, (String)null,
                        true)/* We need to set serverSide==true ! */
               }
               else {
                  String debug = " currentBytesWritten='" + currentBytesWritten + "' currentBytesRead='" + currentBytesRead + "'";
                  log.fine(ME + "there was NO activity since last ping, will set the connection to stalled. " + debug);
                  stalled = true;
                  return Constants.RET_OK;
               }
            }
         }
         stalled = false;
         this.previousBytesWritten = stats.getOverallBytesWritten() + stats.getCurrBytesWritten();
         this.previousBytesRead = stats.getOverallBytesRead() + stats.getCurrBytesRead();

         long now = System.currentTimeMillis();
         String returnVal = doPing(data);
         this.connectionsHandler.getDispatchStatistic().setPingRoundTripDelay(System.currentTimeMillis() - now);
         // Ignore "" returns as this was specified to always return in older xmlBlaster versions
         if (returnVal.length() > 0 && returnVal.indexOf("OK") == -1) {
            // Fake a server standby exception: ping() is not specified to transport a remote XmlBlasterException but carries standby information in the state id.
            StatusQosData qos = glob.getStatusQosFactory().readObject(returnVal);
            if (!Constants.STATE_OK.equals(qos.getState())) {
               throw new XmlBlasterException(glob, ErrorCode.COMMUNICATION_NOCONNECTION_SERVERDENY,
                           glob.getId(), ME, (String)null,
                           "Ping result: The server is in run level " + qos.getState() + " and not ready for requests",
                           (String)null, (Timestamp)null,
                           (String)null, (String)null, (String)null,
                           true)/* We need to set serverSide==true ! */
            }
         }
         if (log.isLoggable(Level.FINE) && isAlive())
            log.fine(ME + "Success for ping('" + data + "'), return='" + returnVal + "'");
         handleTransition(byDispatchConnectionsHandler, null);
         return returnVal;
      }
      catch (Throwable e) { // the remote ping does not throw any XmlBlasterException, see xmlBlaster.idl
         if (isAlive() && log.isLoggable(Level.FINE))
            log.fine(ME + "Exception from remote ping(), retryCounter=" + retryCounter + ", state=" + this.state.toString() + ": " + e.toString());
         e = processResponseTimeoutException(stats, e);
         if (e == null)
            stalled = true;
         handleTransition(byDispatchConnectionsHandler, e);
         return ""; // Only reached if from timeout
      }
      finally {
         if (stats != null)
            stats.setStalled(stalled);
      }
   }

   /**
    * Returns true if it is an XmlBlasterException with error code COMMUNICATION_RESPONSETIMEOUT, false otherwise
    * @param e
    * @return
    */
   private final static boolean isResponseTimeout(Throwable e) {
      if (e == null)
         return false;
      if (e instanceof XmlBlasterException) {
         XmlBlasterException ex = (XmlBlasterException)e;
         if (ex.getErrorCode().equals(ErrorCode.COMMUNICATION_RESPONSETIMEOUT))
            return true;
         return isResponseTimeout(ex.getEmbeddedException());
      }
      return false;
   }

   /**
    *
    * @param e the original Communication Exception
    * @return the original communication exception or null if it wants to force a 'stalled' in case a ping timeout occured
    */
   private final Throwable processResponseTimeoutException(DispatchStatistic stats, Throwable e) {
      if (this.address == null || !this.address.isStallOnPingTimeout())
         return e;
      if (isResponseTimeout(e)) {
         return null;
      }
      return e;
   }

   /** On reconnect polling try to establish the connection */
   abstract protected void reconnect() throws XmlBlasterException;

   /**
    * We are notified to do the next ping or reconnect polling.
    * <p />
    * When connected, we ping<br />
    * When connection is lost, we do reconnect polling
    * @param userData You get bounced back your userData which you passed
    *                 with Timeout.addTimeoutListener()
    *                 here it is "poll" or null
    */
   public final void timeout(Object userData) {
     
      String origThreadName = Thread.currentThread().getName();
      Thread.currentThread().setName(PING_THREAD_NAME);
     
      try {
         //this.timerKey = null; Fix marcel 2008-08-18: seems to increase reconnect polling if server is in RL4
  
         if (isDead())
            return;
  
         boolean isPing = (userData == null);
  
         if (isPing) {
            if (log.isLoggable(Level.FINE))
               log.fine(ME + " timeout -> Going to ping remote server, physicalConnectionOk=" + this.physicalConnectionOk + ", serverAcceptsRequests=" + this.serverAcceptsRequests + " ...");
            try {
               /*String result = */ping("", false);
            }
            catch (XmlBlasterException e) {
               if (isDead()) {
                  if (log.isLoggable(Level.FINE)) log.fine(ME + "We are shutdown already: " + e.toString());
               }
               else {
                  e.printStackTrace();
                  log.severe(ME + "PANIC: " + e.toString());
               }
            } // is handled in ping() itself
         }
         else { // reconnect polling
            try {
               if (log.isLoggable(Level.FINE)) log.fine(ME + "timeout -> Going to check if remote server is available again, physicalConnectionOk=" + this.physicalConnectionOk + ", serverAcceptsRequests=" + this.serverAcceptsRequests + " ...");
               reconnect(); // The ClientDispatchConnection may choose to ping only
               try {
                  /*String result = */ping("", false);
               }
               catch (XmlBlasterException e) {
                  if (isDead()) {
                     if (log.isLoggable(Level.FINE)) log.fine(ME + "We are shutdown already: " + e.toString());
                  }
                  else {
                     e.printStackTrace();
                     log.severe(ME + "PANIC: " + e.toString()); // is handled in ping() itself
                  }
               }
            }
            catch (Throwable e) {
               if (log.isLoggable(Level.FINE)) log.fine("Polling for remote connection" + this.myId + " failed:" + e.getMessage());
               if (e instanceof NullPointerException)
                   e.printStackTrace();
               if (logInterval > 0 && (retryCounter % logInterval) == 0)
                  log.warning("No connection established, " + this.myId + address.getLogId() + " still seems to be down after " + (retryCounter+1) + " connection retries.");
               try { handleTransition(false, e); } catch(XmlBlasterException e2) { e.printStackTrace(); log.severe("PANIC: " + e.toString()); }
            }
         }
      }
      finally {
         if (origThreadName != null) {
            Thread.currentThread().setName(origThreadName);
         }
      }
   }

   protected void spanPingTimer(long timeout, boolean isPing) throws XmlBlasterException {
      String userData = (isPing) ? null : "poll";
      synchronized (this.PING_TIMER_MONITOR) {
         this.timerKey = this.glob.getPingTimer().addOrRefreshTimeoutListener(this,
               timeout, userData, this.timerKey);
      }
   }

   private boolean isAuthenticationException(XmlBlasterException ex) {
           ErrorCode code = ex.getErrorCode();
           if (code == null)
                   return false;
           return code.isOfType(ErrorCode.USER_SECURITY_AUTHENTICATION);
   }
  
   /**
    * @param toReconnected If true if the connection is OK (it is a transition to reconnected)
    * @param byDispatchConnectionsHandler true if invoked by DispatchConnectionsHandler,
    *        false if invoked by our timer/ping
    * @param The problem, is expected to be not null for toReconnected==false
    * @exception XmlBlasterException If delivery failed
    */
   protected final void handleTransition(boolean byDispatchConnectionsHandler,
                                       Throwable throwable) throws XmlBlasterException {

      boolean toReconnected = (throwable == null) ? true : false;

      XmlBlasterException ex = (throwable == null) ? null : ((throwable instanceof XmlBlasterException) ? (XmlBlasterException)throwable : null);
      ConnectionStateEnum oldState = this.state;
      this.serverAcceptsRequests = (ex == null) ? true : !ex.isErrorCode(ErrorCode.COMMUNICATION_NOCONNECTION_SERVERDENY);
      this.physicalConnectionOk = (ex == null ||
                                   glob.isServerSide() && !ex.isServerSide() ||
                                   !glob.isServerSide() && ex.isServerSide()) ? true : false;
      if (ex != null && ex.getErrorCode().isOfType(ErrorCode.COMMUNICATION_NOCONNECTION))
         this.physicalConnectionOk = false;

      if (ex != null) {
          if (isAuthenticationException(ex)) {
              connectionsHandler.toDead(this, ex);
              if (byDispatchConnectionsHandler) {
                 throw ex;
              }
              else {
                 // ping timer thread, no sense to throw an exception:
              }
          }
         if (retry(ex)) {
            toReconnected = false;
         }
         else {
            toReconnected = true;
         }
      }

      if (ex != null) {
         if (ErrorCode.COMMUNICATION_USER_HOLDBACK.equals(ex.getErrorCode())) {
            this.connectionsHandler.getDispatchManager().setDispatcherActive(false);
            log.warning("We have set dispatchActive=false, please activate dispatcher manually when the client side problem is resolved: " + ex.getMessage());
         }
      }

      if (log.isLoggable(Level.FINE)) log.fine(ME + "Connection transition " + oldState.toString() + " -> toReconnected=" + toReconnected + " byDispatchConnectionsHandler=" + byDispatchConnectionsHandler + ": " + ((ex == null) ? "" : ex.toXml()));

      synchronized (this.connectionsHandler.dispatchManager) {
         if (isDead()) {   // ignore, not possible
            if (log.isLoggable(Level.FINE)) log.fine(ME + "Connection transition " + oldState.toString() + " -> " + this.state.toString() +
                      " for " + ME + ": We ignore it: " + ((throwable == null) ? "No throwable" : throwable.toString()));
            if (connectionsHandler.getDispatchManager().isShutdown()) {
               return; // Can happen if DispatchWorker is currently delivering and the client has disconnected in the same time (thus DEAD)
            }
            if (throwable == null) {
               throw new XmlBlasterException(glob, ErrorCode.INTERNAL_ILLEGALSTATE, ME, "Connection transition " + oldState.toString() + " -> " + this.state.toString());
            }
            //Thread.currentThread().dumpStack();
            throw XmlBlasterException.convert(glob, ME, "Connection transition " + oldState.toString() + " -> " + this.state.toString(), throwable);
         }

         /*
         if (oldState == ConnectionStateEnum.UNDEF) { //startup
            if (toReconnected) {
               this.state = ConnectionStateEnum.ALIVE;
               connectionsHandler.toAlive(this);
            }
            else {
               this.state = ConnectionStateEnum.POLLING;
               connectionsHandler.toPolling(this);
            }
         }
         */

         if (toReconnected && isAlive()) { //everything is ok
            if (this.address.getPingInterval() > 0L) { // respan ping timer (even for native plugins we do a dummy ping)
               // timerKey==null (byDispatchConnectionsHandler==false) -> call from a ping timeout: respan
               //
               // timerKey!=null (byDispatchConnectionsHandler==true)  -> call between pings from successful update() invocation:
               // We don't need to ping directly after a successful invocation
               // so we respan the timer.
               // Probably this slows down on many updates and seldom pings,
               // should we remove the following two lines?
               spanPingTimer(this.address.getPingInterval(), true);
            }
            return;
         }

         if (timerKey != null) {
            this.glob.getPingTimer().removeTimeoutListener(timerKey);
            timerKey = null;
         }

         if (toReconnected /*&& this.serverAcceptsRequests*/ && (isPolling() || isUndef()) && !this.address.isFromPersistenceRecovery()) {
            this.state = ConnectionStateEnum.ALIVE;
            retryCounter = 0; // success
            log.info("Connection '" + getAddress().getType() + "' transition " + oldState.toString() + " -> " + this.state.toString() + ": Success, " + ME + " connected.");
            if (this.address.getPingInterval() > 0L) // respan ping timer
               timerKey = this.glob.getPingTimer().addTimeoutListener(this, this.address.getPingInterval(), null);
            connectionsHandler.toAlive(this);
            return;
         }
        
         this.address.setFromPersistenceRecovery(false); // reset
           
         if (this.address.getRetries() == -1 || retryCounter < this.address.getRetries()) {
            // poll for connection ...
            this.state = ConnectionStateEnum.POLLING;
            retryCounter++;
            if (this.address.getDelay() > 0L) { // respan reconnect poller
               if (log.isLoggable(Level.FINE)) log.fine("Polling for server with delay=" + this.address.getDelay() + " oldState=" + oldState + " retryCounter=" + retryCounter);
               if (!this.physicalConnectionOk) resetConnection();
               // next line would have spanned timeout twice and outside the synchronize (2007-00-05 Michele)
               long realSpanTime = address.getDelay();
               //  timerKey = this.glob.getPingTimer().addTimeoutListener(this, this.address.getDelay(), "poll");
               if (oldState == ConnectionStateEnum.ALIVE || oldState == ConnectionStateEnum.UNDEF) {
                  String str = (throwable != null) ? ": " + throwable.toString() : "";
                  //if (throwable != null) throwable.printStackTrace();
                  log.warning(ME + "Connection transition " + oldState.toString() + " -> " + this.state.toString() + ": " +
                               this.address.getLogId() +
                               " is unaccessible, we poll for it every " + this.address.getDelay() + " msec" + str);
                  if (log.isLoggable(Level.FINE)) log.fine(ME + "Connection transition " + oldState.toString() + " -> " + this.state.toString() + " for " + ME +
                               ": retryCounter=" + retryCounter + ", delay=" + this.address.getDelay() + ", maxRetries=" + this.address.getRetries() + str);
                  connectionsHandler.toPolling(this);
                  // spanPingTimer(400, false); // do one instant try
                  realSpanTime = 400;
               }
               if (log.isLoggable(Level.FINE))
                  log.fine(ME + " Respanning timeout for polling to '" + realSpanTime + "' ms");
               spanPingTimer(realSpanTime, false);
               if (byDispatchConnectionsHandler)
                  throw new XmlBlasterException(glob, ErrorCode.COMMUNICATION_NOCONNECTION_POLLING, ME, "We are in polling mode, can't handle request. oldState=" + oldState, throwable);
               return;
            }
            else {
               if (oldState == ConnectionStateEnum.ALIVE) {
                  resetConnection();
                  String str = (throwable != null) ? ": " + throwable.toString() : "";
                  log.warning("Connection transition " + oldState.toString() + " -> " + this.state.toString() + ": " + this.address.toString() + " is unaccessible" + str);
                  if (log.isLoggable(Level.FINE)) log.fine(ME + "Connection transition " + oldState.toString() + " -> " + this.state.toString() + " for " + ME +
                                 ": retryCounter=" + retryCounter + ", delay=" + this.address.getDelay() + ", maxRetries=" + this.address.getRetries() + str);
               }
            }
         }

         // error giving up ...
         this.state = ConnectionStateEnum.DEAD;
      } // synchronized because of timerKey and status transition

      // error giving up ...
      if (throwable == null) {
         ex = new XmlBlasterException(glob, ErrorCode.COMMUNICATION_NOCONNECTION_DEAD, ME,
              "Connection transition " + oldState.toString() + " -> " + this.state.toString() + ": " +
              this.address.toString() + " is unaccessible.", throwable);
      }
      else if (throwable instanceof XmlBlasterException) {
         ex = (XmlBlasterException)throwable;
      }
      else {
         ex = new XmlBlasterException(glob, ErrorCode.INTERNAL_UNKNOWN, ME, "Sending of messge failed", throwable);
      }
      log.warning(ME + "Connection transition " + oldState.toString() + " -> " + this.state.toString() + ": retryCounter=" + retryCounter +
                   ", maxRetries=" + this.address.getRetries());

      connectionsHandler.toDead(this, ex);

      if (byDispatchConnectionsHandler) {
         throw ex;
      }
      else {
         // ping timer thread, no sense to throw an exception:
      }
   }

   /**
    * Stop all remote connections.
    */
   public void shutdown() throws XmlBlasterException {
      this.state = ConnectionStateEnum.DEAD;
      if (log.isLoggable(Level.FINER)) log.finer(ME + "Entering shutdown ...");
      if (this.timerKey != null) {
         this.glob.getPingTimer().removeTimeoutListener(this.timerKey);
         this.timerKey = null;
      }
      retryCounter = 0;
   }

   public final ConnectionStateEnum getState() {
      return this.state;
   }

   public final boolean isUndef() {
      return this.state == ConnectionStateEnum.UNDEF;
   }

   public final boolean isAlive() {
      return this.state == ConnectionStateEnum.ALIVE;
   }

   public final boolean isPolling() {
      return this.state == ConnectionStateEnum.POLLING;
   }

   public final boolean isDead() {
      return this.state == ConnectionStateEnum.DEAD;
   }

   /**
    * Dump state of this object into a XML ASCII string.
    * <br>
    * @param extraOffset indenting of tags for nice output
    * @return internal state as an XML ASCII string
    */
   abstract public String toXml(String extraOffset);

   /**
    *
    * @return true if the implementation has to throw an exception in case the ping is invoked when
    * there is an ongoing write or read operation on the same connection.
    * Normally it shall give false on the callback and true on the client side. The client side shall
    * give true because otherwise if such a situation arizes it will never go in polling and no
    * reconnect will be triggered.
    */
   abstract protected boolean forcePingFailure();
}
TOP

Related Classes of org.xmlBlaster.util.dispatch.DispatchConnection

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.