Package org.objectweb.joram.client.jms.connection

Source Code of org.objectweb.joram.client.jms.connection.RequestMultiplexer$Closer

/*
* JORAM: Java(TM) Open Reliable Asynchronous Messaging
* Copyright (C) 2001 - 2011 ScalAgent Distributed Technologies
* Copyright (C) 1996 - 2000 Dyade
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
* USA.
*
* Initial developer(s): ScalAgent Distributed Technologies
*/
package org.objectweb.joram.client.jms.connection;

import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import javax.jms.IllegalStateException;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;
import javax.jms.JMSSecurityException;

import org.objectweb.joram.client.jms.Connection;
import org.objectweb.joram.shared.client.AbstractJmsReply;
import org.objectweb.joram.shared.client.AbstractJmsRequest;
import org.objectweb.joram.shared.client.ConsumerMessages;
import org.objectweb.joram.shared.client.MomExceptionReply;
import org.objectweb.joram.shared.client.PingRequest;
import org.objectweb.joram.shared.client.SessDenyRequest;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.common.Debug;

public class RequestMultiplexer {

  private static class Status {
    public static final int OPEN = 0;
    public static final int CLOSE = 1;
   
    private static final String[] names = {"OPEN", "CLOSE"};

    public static String toString(int status) {
      return names[status];
    }
  }
 
  private static Logger logger = Debug.getLogger(RequestMultiplexer.class.getName());

  /**
   * Converts a {@link MomExceptionReply} to the corresponding
   * {@link JMSException}.
   *
   * @param excReply the MOM reply to convert
   * @return the corresponding Exception
   */
  public static JMSException buildJmsException(MomExceptionReply excReply) {
    JMSException jmsExc = null;
    int excType = excReply.getType();
    if (excType == MomExceptionReply.AccessException) {
      jmsExc = new JMSSecurityException(excReply.getMessage());
    } else if (excType == MomExceptionReply.DestinationException) {
      jmsExc = new InvalidDestinationException(excReply.getMessage());
    } else {
      jmsExc = new JMSException(excReply.getMessage());
    }
    return jmsExc;
  }

  private Connection cnx;

  private volatile int status;

  private RequestChannel channel;

  private Map requestsTable;

  private int requestCounter;

  private DemultiplexerDaemon demtpx;

  private Timer timer;
 
  /**
   * The task responsible for keeping
   * the connection alive.
   */
  private HeartBeatTask heartBeatTask;

  private javax.jms.ExceptionListener exceptionListener;

  /**
   * The date of the last request
   */
  private volatile long lastRequestDate;
 
  public RequestMultiplexer(Connection cnx,
                            RequestChannel channel,
                            long heartBeat) throws JMSException {
    this.channel = channel;
    this.cnx = cnx;
    requestsTable = new Hashtable();
    requestCounter = 0;
    timer = new Timer();
    channel.setTimer(timer);
    try {
      channel.connect();
    } catch (JMSException exc) {
      throw exc;
    } catch (Exception exc) {
      // Wraps the incoming exception
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "", exc);
      if (timer != null) timer.cancel();
      throw new JMSException(exc.toString());
    }
   
    demtpx = new DemultiplexerDaemon();
    demtpx.start();
    setStatus(Status.OPEN);
   
    if (heartBeat > 0) {
      heartBeatTask = new HeartBeatTask(heartBeat);
      lastRequestDate = System.currentTimeMillis();
      try {
        heartBeatTask.start();
      } catch (Exception exc) {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "", exc);
        throw new JMSException(exc.toString());
      }
    }
  }

  private void setStatus(int status) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "RequestMultiplexer.setStatus(" + Status.toString(status) + ')');
    this.status = status;
  }
 
  public boolean isClosed() {
    return status == Status.CLOSE;
  }
 
  public void closing() {
    channel.closing();
  }

  public void setExceptionListener(javax.jms.ExceptionListener exceptionListener) {
    this.exceptionListener = exceptionListener;
  }

  public javax.jms.ExceptionListener getExceptionListener() {
    return exceptionListener;
  }

  public void sendRequest(AbstractJmsRequest request) throws JMSException {
    sendRequest(request, null);
  }
 
  public void sendRequest(AbstractJmsRequest request, ReplyListener listener) throws JMSException {

    synchronized (this) {
      if (status == Status.CLOSE)
        throw new IllegalStateException("Connection closed");

      if (requestCounter == Integer.MAX_VALUE) {
        requestCounter = 0;
      }

      request.setRequestId(requestCounter++);

      if (listener != null) {
        requestsTable.put(new Integer(request.getRequestId()), listener);
      }

      if (heartBeatTask != null) {
        lastRequestDate = System.currentTimeMillis();
      }
    }

    try {
      channel.send(request);
    } catch (Exception exc) {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "", exc);
      JMSException jmsExc = new JMSException(exc.toString());
      jmsExc.setLinkedException(exc);
      throw jmsExc;
    }
  }
 
  public void setMultiThreadSync(int delay, int threshold) {
    channel = new MultiThreadSyncChannel(channel, delay, threshold);
  }

  /**
   * Not synchronized because it would possibly
   * deadlock with some reply listeners
   * (actually requestors).
   */
  public void close() {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "RequestMultiplexer.close()");
   
    synchronized (this) {
      if (status == Status.CLOSE)
        return;
      // Immediately set the status as no error
      // can be thrown. This enables to release
      // the lock and avoid any dead lock
      // with the demultiplexer thread that
      // calls close() when interrupted.
      setStatus(Status.CLOSE);
    }

    if (heartBeatTask != null) heartBeatTask.cancel();
    if (timer != null) timer.cancel();
    channel.close();
    demtpx.stop();

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, " -> requestsTable=" + requestsTable);
   
    // The requests table can't be accessed
    // either by an external thread (status CLOSE)
    // or by the internal demultiplexer thread (stopped).
   
    cleanup();
  }

  /**
   * Used by:
   * 1- close()
   * 2- the connector layer (OutboundConnection.cleanup())
   */
  public void cleanup() {
    // Create first a copy of the current keys
    // registered into the requests table.
    Integer[] requestIds;
    synchronized (requestsTable) {
      Set keySet = requestsTable.keySet();
      requestIds = new Integer[keySet.size()];
      keySet.toArray(requestIds);
    }
    for (int i = 0; i < requestIds.length; i++) {
      ReplyListener rl = (ReplyListener) requestsTable.get(requestIds[i]);
      // The listener may be null because the table
      // may have been modified meanwhile.
      if (rl != null) {
        rl.replyAborted(requestIds[i].intValue());
      }
    }
    requestsTable.clear();
  }

  /**
   * Not synchronized because it would possibly
   * deadlock with some reply listeners
   * (actually requestors).
   */
  public void abortRequest(int requestId) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "RequestMultiplexer.abortRequest(" + requestId + ')');
   
    ReplyListener rl = doAbortRequest(requestId);   
    if (rl != null) {
      rl.replyAborted(requestId);
    }
  }
 
  private synchronized ReplyListener doAbortRequest(int requestId) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "RequestMultiplexer.doAbortRequest(" + requestId + ')');
   
    if (status == Status.CLOSE) return null;
    return (ReplyListener)requestsTable.remove(
      new Integer(requestId));
  }

  /**
   * Not synchronized because it may be called by the
   * demultiplexer during a concurrent close. It would deadlock
   * as the close waits for the demultiplexer to stop.
   */
  private void route(AbstractJmsReply reply) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "RequestMultiplexer.route(" + reply + ')');
   
    int requestId = reply.getCorrelationId();
    Integer requestKey = new Integer(requestId);
    ReplyListener rl = (ReplyListener)requestsTable.get(requestKey);
    if (reply instanceof MomExceptionReply) {
      MomExceptionReply excReply = (MomExceptionReply) reply;
      if (rl instanceof ErrorListener) {
        ((ErrorListener) rl).errorReceived(requestId, excReply);
      } else {
        // The listener is null or doesn't implement ErrorListener
        if (exceptionListener != null) {
          exceptionListener.onException(buildJmsException(excReply));
        }
      }
    } else {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, " -> rl = " + rl + ')');
      if (rl != null) {
        try {
          if (rl.replyReceived(reply)) {
            requestsTable.remove(requestKey);
          }
        } catch (AbortedRequestException exc) {
          logger.log(BasicLevel.WARN, " -> Request aborted: " + requestId);
          abortReply(reply);
        }
      } else {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, " -> Listener not found for the reply: " + requestId);
        abortReply(reply);
      }
    }
  }

  private void abortReply(AbstractJmsReply reply) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "RequestMultiplexer.abortReply(" + reply + ')');
   
    if (reply instanceof ConsumerMessages) {
      deny((ConsumerMessages)reply);
    }
    // Else nothing to do.
  }

  public void deny(ConsumerMessages messages) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "RequestMultiplexer.deny(" + messages + ')');

    Vector msgList = messages.getMessages();
    Vector ids = new Vector();
    for (int i = 0; i < msgList.size(); i++) {
      ids.addElement(((org.objectweb.joram.shared.messages.Message) msgList.elementAt(i)).id);
    }
    SessDenyRequest deny = new SessDenyRequest(messages.comesFrom(),
                                               ids,
                                               messages.getQueueMode());
    try {
      sendRequest(deny);
    } catch (JMSException exc) {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "", exc);
      // Connection failure
      // Nothing to do
    }
  }

  class onExceptionRunner implements Runnable {
    Exception exc;

    onExceptionRunner(Exception exc) {
      this.exc = exc;
    }

    public void run() {
      onException(exc);
    }
  }

  private void onException(Exception exc) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "RequestMultiplexer.onException(" + exc + ')');
    JMSException jmsExc;
    if (exc instanceof JMSException) {
      jmsExc = (JMSException) exc;
    } else {
      jmsExc = new IllegalStateException(exc.getMessage());
    }
    if (exceptionListener != null)
      exceptionListener.onException(jmsExc);
  }

  public void schedule(TimerTask task, long period) {
    if (timer != null) {
      try {
        timer.schedule(task, period);
      } catch (Exception exc) {
        if (logger.isLoggable(BasicLevel.ERROR))
          logger.log(BasicLevel.ERROR, "", exc);
      }
    }
  }

  public void setDemultiplexerDaemonName(String name) {
    demtpx.setName(name);
  }
 
  public String getDemultiplexerDaemonName() {
    return demtpx.getName();
  }

  private class DemultiplexerDaemon extends fr.dyade.aaa.common.Daemon {
    DemultiplexerDaemon() {
      // The real name is set later when
      // the proxy id and connection id are known
      // see setDemultiplexerDaemonName()
      super("Connection#?");
    }

    public void run() {
      try {
        loop:
        while (running) {
          canStop = true;
          AbstractJmsReply reply;
          try {
            reply = channel.receive();
          } catch (Exception exc) {
            if (logger.isLoggable(BasicLevel.DEBUG))
              logger.log(BasicLevel.DEBUG, "Exception during receive", exc);
            // Check if the connection is not already
            // closed (the exception may occur as a consequence
            // of a closure or at the same time as an independant
            // close call).
            if (! isClosed()) {
              RequestMultiplexer.this.close();
              // The connection close() must be
              // called by another thread. Calling it with
              // this thread (demultiplexer daemon) could
              // lead to a deadlock if another thread called
              // close() just before.
              Closer closer = new Closer(exc);
              new Thread(closer).start();
            } else {
              // Else it means that the connection is already closed
              // Runs the onException in a separate thread in order to avoid
              // deadlock in connector onException (synchronized).
              onExceptionRunner oer = new onExceptionRunner(exc);
              new Thread(oer).start();
            }
           
            break loop;
          }
          canStop = false;
          route(reply);
          if (!running && isClosed()) {
            if (logger.isLoggable(BasicLevel.DEBUG))
              logger.log(BasicLevel.DEBUG, "DemultiplexerDaemon ended and Socket closed.");
            onExceptionRunner oer = new onExceptionRunner(new Exception("DemultiplexerDaemon ended and Socket closed."));
            new Thread(oer).start();
          }
        }
      } finally {
        finish();
      }
    }
   
    /**
     * Enables the daemon to stop itself.
     */
    public void stop() {
      if (isCurrentThread()) {
        finish();
      } else {
        super.stop();
      }
    }

    protected void shutdown() {}

    protected void close() {}
  }
 
  private class Closer implements Runnable {
    private Exception exc;
   
    Closer(Exception e) {
      exc = e;
    }
   
    public void run() {
      try {
        RequestMultiplexer.this.cnx.close();
      } catch (JMSException exc2) {
        if (logger.isLoggable(BasicLevel.WARN))
          logger.log(BasicLevel.WARN, "Error during close", exc2);
      }
     
      onException(exc);
    }
  }

  /**
   * Timer task responsible for sending a ping message
   * to the server if no request has been sent during
   * the specified timeout ('cnxPendingTimer' from the
   * factory parameters).
   */
  private class HeartBeatTask extends TimerTask {   

    private long heartBeat;

    HeartBeatTask(long heartBeat) {
      this.heartBeat = heartBeat;
    }

    public void run() {
      try {
        long date = System.currentTimeMillis();       
        if ((date - lastRequestDate) > heartBeat) {
          sendRequest(new PingRequest());
        }
      } catch (Exception exc) {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "", exc);
      }
    }

    public void start() throws Exception {
      if (timer != null)
        timer.schedule(this, heartBeat, heartBeat);
    }
  }

}
TOP

Related Classes of org.objectweb.joram.client.jms.connection.RequestMultiplexer$Closer

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.