Package org.openeai.jms.consumer

Source Code of org.openeai.jms.consumer.PointToPointConsumer$MyQueueListener

/*******************************************************************************
$Source: /cvs/repositories/openii3/project/java/source/org/openeai/jms/consumer/PointToPointConsumer.java,v $
$Revision: 1.26 $
*******************************************************************************/

/**********************************************************************
This file is part of the OpenEAI Application Foundation or
OpenEAI Message Object API created by Tod Jackson
(tod@openeai.org) and Steve Wheat (steve@openeai.org) at
the University of Illinois Urbana-Champaign.

Copyright (C) 2002 The OpenEAI Software Foundation

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 (at your option) 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

For specific licensing details and examples of how this software
can be used to build commercial integration software or to implement
integrations for your enterprise, visit http://www.OpenEai.org/licensing.
*/

package org.openeai.jms.consumer;

// JNDI Stuff
import javax.naming.*;
import javax.naming.directory.*;

// Java Messaging Service
import javax.jms.*;
import javax.jms.Queue;

// General
import java.util.*;

import java.io.*;

import java.lang.reflect.*;

import org.openeai.layouts.EnterpriseLayoutException;
import org.openeai.config.*;
import org.openeai.moa.objects.resources.*;
import org.openeai.jms.consumer.commands.*;
import org.openeai.threadpool.*;
import org.openeai.xml.*;
import org.openeai.jms.producer.MessageProducer;

// Parsing and processing the XML data contained in the message
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;

/**
* This consumer consumes messages from a JMS Queue.  Then based on the content of the message
* it executes commands associated to the consumer and the message consumed as specified in
* it's configuration document (deployment descriptor).
*<P>
* If the JMSReplyTo property of the consumed message contains data
* (i.e. - the Queue to which replies should be sent), it will execute a 'RequestCommand' and
* return it's response to the calling application.  Otherwise, it will execute a 'SyncCommand'.
*<P>
* The actual business logic related to the message consumed is performed by the Request/Sync command
* implementations.
* <P>
  * @author      Tod Jackson (tod@openeai.org)
  * @author      Steve Wheat (steve@openeai.org)
  * @version     3.0  - 4 February 2003
*/
public class PointToPointConsumer extends MessageConsumer {

  private QueueConnectionFactory m_qcf = null;
  private Queue m_queue = null;
  private QueueConnection m_queueConnection = null;
  private QueueSession m_queueSession = null;
  private QueueReceiver m_queueReceiver = null;
  private HashMap m_requests = new HashMap(5, 0.75f);
  private MyQueueListener m_listener = null;
  private boolean m_monitorRunning = false;
  private static String INITIALIZING_REQUESTS = "Initializing Requests";

  public PointToPointConsumer() {
    setAppName("P2P Consumer v1.0");
  }

  public PointToPointConsumer(String cFactory, String qName) {
    setAppName("P2P Consumer v1.0");
    setConnectionFactoryName(cFactory);
    setDestinationName(qName);
  }

  /**
  * As AppConfig reads through a gateway's deployment document, it will build a
  * ConsumerConfig Java object and pass that object to this constructor.  Then
  * this consumer will have all the information it needs to initialize itself which
  * includes:
  * <ul>
  * <li>Initializing the consumer itself
  * <li>Initializing all Commands that the consumer may execute
  * <ul>
  * <P>
  * @param cConfig org.openeai.config.ConsumerConfig
  * @see org.openeai.config.ConsumerConfig
  **/
  public PointToPointConsumer(ConsumerConfig cConfig) throws JMSException,
                                                             NamingException,
                                                             IOException {
    setAppName(cConfig.getAppName());
    setConfig(cConfig);
    setCommandConfigs(cConfig.getCommandConfigs());
    init(cConfig.getProperties());
    if (cConfig.getThreadPoolConfig() == null) {
      // error
      String errMessage =
        "Could not locate a ThreadPoolConfig object in the " +
        "configuration document for the PointToPoint Consumer named " +
        getConsumerName() + ".  This is required for all consumers.";
      logger.fatal(errMessage);
      throw new JMSException(errMessage);
    }
    setThreadPool(new ThreadPoolImpl(cConfig.getThreadPoolConfig()));
    if (getStartOnInitialization()) {
      startConsumer();
    }
  }

  // StartGetter/Setters

  /**
  * This method stops the Consumer's "Monitor Thread" so it won't attempt
  * to restart the consumer.
  * <P>
  * When the consumer is started it starts a Thread that monitors the Consumer's
  * connection to the broker.  If that connection is broken for some reason, that
  * "Monitor Thread" will attempt to restart the consumer.  This continues indefinitely
  * until the consumer is able to re-connect to the broker.
  * <P>
  * This method allows an application to in effect stop that monitor thread so they can
  * shut the consumer down without it restarting itself.
  **/
  public void stopMonitor() {
    m_monitorRunning = false;
  }

  /**
  * This method starts the Consumer's "Monitor Thread".  This is a thread that runs
  * for the life of the consumer and checks the status of the consumer's connection
  * to the broker every thirty seconds.  If that connection is broken
  * for some reason, the Monitor Thread will attempt to restart the consumer,
  * re-connecting it to the broker.  It will continue to do this until either the
  * consumer is able to re-connect or the consumer is shutdown.
  **/
  public void startMonitor() {
    if (m_monitorRunning == false) {
      MonitorConsumer monitorConsumer = new MonitorConsumer(30000);
      new Thread(monitorConsumer).start();
      m_monitorRunning = true;
    }
  }

  /**
  * Returns the Consumer's QueueConnectionFactory object.
  * <P>
  * See the JMS Specification to learn more about JMS objects.
  * <P>
  * @return javax.jms.QueueConnectionFactory
  **/
  public QueueConnectionFactory getQueueConnectionFactory() {
    return m_qcf;
  }

  /**
  * Sets the Consumer's QueueConnectionFactory object.
  * <P>
  * See the JMS Specification to learn more about JMS objects.
  * <P>
  * @param qcf javax.jms.QueueConnectionFactory
  **/
  public void setQueueConnectionFactory(QueueConnectionFactory qcf) {
    m_qcf = qcf;
  }

  /**
  * Returns the Consumer's Queue object.
  * <P>
  * See the JMS Specification to learn more about JMS objects.
  * <P>
  * @return javax.jms.Queue
  **/
  public Queue getQueue() {
    return m_queue;
  }

  /**
  * Sets the Consumer's Queue object.
  * <P>
  * See the JMS Specification to learn more about JMS objects.
  * <P>
  * @param queue javax.jms.Queue
  **/
  public void setQueue(Queue queue) {
    m_queue = queue;
  }

  /**
  * Returns the Consumer's QueueConnection object.
  * <P>
  * See the JMS Specification to learn more about JMS objects.
  * <P>
  * @return javax.jms.QueueConnection
  **/
  public QueueConnection getQueueConnection() {
    return m_queueConnection;
  }

  /**
  * Sets the Consumer's QueueConnection object.
  * <P>
  * See the JMS Specification to learn more about JMS objects.
  * <P>
  * @param queueConnection javax.jms.QueueConnection
  **/
  public void setQueueConnection(QueueConnection queueConnection) {
    m_queueConnection = queueConnection;
  }

  /**
  * Returns the Consumer's QueueSession object.
  * <P>
  * See the JMS Specification to learn more about JMS objects.
  * <P>
  * @return javax.jms.QueueSession
  **/
  public QueueSession getQueueSession() {
    return m_queueSession;
  }

  /**
  * Sets the Consumer's QueueSession object.
  * <P>
  * See the JMS Specification to learn more about JMS objects.
  * <P>
  * @param session javax.jms.QueueSession
  **/
  public void setQueueSession(QueueSession session) {
    m_queueSession = session;
  }

  /**
  * Returns the Consumer's QueueReceiver object.
  * <P>
  * See the JMS Specification to learn more about JMS objects.
  * <P>
  * @return javax.jms.QueueReceiver
  **/
  public QueueReceiver getQueueReceiver() {
    return m_queueReceiver;
  }

  /**
  * Sets the Consumer's QueueReceiver object.
  * <P>
  * See the JMS Specification to learn more about JMS objects.
  * <P>
  * @param queueReceiver javax.jms.QueueReceiver
  **/
  public void setQueueReceiver(QueueReceiver queueReceiver) {
    m_queueReceiver = queueReceiver;
  }
  // End Getter/Setters

  /**
  * Calls the MessageConsumer initializeConsumer method to initialize
  * any SyncCommands that this consumer may need to execute, then it
  * initializes all RequestCommands associated to this consumer.  Since
  * this is a PointToPointConsumer, messages may be sent to this consumer
  * that expect a response.
  * <P>
  * @throws JMSException if any errors occur initializing any of the Sync or
  * Request commands.
  **/
  public void initializeConsumer() throws JMSException {
    try {
      super.initializeConsumer();
      setInitializationStatus(INITIALIZING_REQUESTS);
      initRequestCommands();
      setInitializationStatus(INITIALIZED);
    }
    catch (Exception e) {
      throw new JMSException(e.getMessage());
    }
  }

  /**
  * Adds an initialized RequestCommand object to the list of RequestCommands that might be executed by
  * this Consumer.  Uses the className and CommandConfig object associated to the commandName paramater
  * passed in to instantiate the command.  Then, it adds that initialized command to this Consumer's
  * HashMap of RequestCommands that might be executed by this PointToPointConsumer.  Therefore, the
  * command itself is only intantiated and initialized once, then, its exeucte method
  * is called whenever this consumer determines that a message it consumed is to be
  * processed by that command.
  * <P>
  * It is here that the determination is made wether or not the command is the "default"
  * or "absolute" command based on information found in the CommandConfig object
  * associated to the Command.
  * <P>
  * @param requestName String name of the command
  * @param className String class name of the command that should be instantiated and
  * initialized as specified in the CommandConfig Element/Java object
  * @see org.openeai.config.CommandConfig
  * @see org.openeai.config.ConsumerConfig
  * @see org.openeai.jms.consumer.commands.ConsumerCommand
  * @see org.openeai.jms.consumer.commands.RequestCommand
  * @see org.openeai.jms.consumer.commands.RequestCommandImpl
  * @see org.openeai.jms.consumer.commands.SyncCommand
  * @see org.openeai.jms.consumer.commands.SyncCommandImpl
  **/
  public void addRequestCommand(String requestName,
                                String className) throws JMSException {
    try {
      logger.info("Initializing request: " + requestName + "->" + className +
                  " for gateway: " + getAppName());
      CommandConfig theCommandConfig = getCommandConfig(requestName);
      if (theCommandConfig.isDefault()) {
        logger.info(requestName + " is the 'default' RequestCommand for the " +
                    getConsumerName() + " consumer.");
        setDefaultCommandName(requestName);
      }
      if (theCommandConfig.isAbsolute()) {
        logger.info(requestName +
                    " is the 'absolute' RequestCommand for the " +
                    getConsumerName() + " consumer.");
        setAbsoluteCommandName(requestName);
      }
      Class[] parms = { theCommandConfig.getClass() };
      java.lang.Class obj = java.lang.Class.forName(className);
      try {
        Constructor c = obj.getConstructor(parms);
        Object[] o = { theCommandConfig };
        try {
          m_requests.put(requestName.toLowerCase(), c.newInstance(o));
        }
        catch (Exception e) {
          throw new JMSException(e.getMessage());
        }
      }
      catch (Exception ne) {
        throw new JMSException(ne.getMessage());
      }
    }
    catch (Exception e) {
      throw new JMSException(e.getMessage());
    }
  }

  /**
  * Iterates through all RequestCommands that were listed in the CommandConfig objects
  * associated to this Consumer in its deployment document and instantiates those
  * RequestCommands by calling the addRequestCommand method.
  * <P>
  * @throws JMSException if errors occur initializing the SyncCommands
  * @see #addRequestCommand(String, String)
  **/
  protected void initRequestCommands() throws JMSException {
    /*
      Instantiate and add each IncomingRequest implementation
      specified in the properties file to the requests hash map
    */
    // IncomingRequest implementations.
    logger.debug("Initializing requests...");
    Enumeration requests = getProperties().propertyNames();
    while (requests.hasMoreElements()) {
      String keyName = (String)requests.nextElement();
      if (keyName.toLowerCase().indexOf("requestcommand") != -1) {
        String className = getProperties().getProperty(keyName);
        String reqName = keyName.substring(keyName.indexOf(".") + 1);
        try {
          addRequestCommand(reqName, className);
        }
        catch (Exception e) {
          throw new JMSException(e.getMessage());
        }
      }
    }
  }

  /**
  * Returns a RequestCommand from the HashMap of RequestCommands supported by this Consumer.
  * This method looks for the RequestCommand with a name matching the name passed
  * in and returns it.  This will be an initialized command that is ready to be executed.
  * The consumer will call this method when it consumes a message and determines
  * that there is an expected reply.
  * <P>
  * It will use the "COMMAND_NAME" JMS Property from the message to
  * retrieve that RequestCommand from its list.
  * <P>
  * @param commandName String the name of the RequestCommand to find (COMMAND_NAME property on the JMS Message)
  * @return RequestCommand the RequestCommand that matches the name passed in and should
  * be executed.
  * @throws IOException if a RequestCommand with the specified name cannot be found.
  **/
  protected RequestCommand getRequestCommand(String commandName) throws IOException {
    if (commandName == null) {
      commandName = "";
    }
    if (m_requests.containsKey(commandName.toLowerCase())) {
      return (RequestCommand)m_requests.get(commandName.toLowerCase());
    }
    else {
      throw new IOException("Invalid request identifier " + commandName);
    }
  }

  /**
  * RequestCommand execution routine.  This method is called by the
  * RequestTransaction's run method and is used to
  * actually execute the RequestCommand associated to the message consumed.
  * <P>
  * The method will look for a JMS String property called "COMMAND_NAME" on the Message
  * passed in.  If the property is found, it will execute the RequestCommand associated
  * to that name and return the output from that execution.
  * <P>
  * This property is automatically set by the OpenEAI Message Object API (MOA) foundation
  * when a message is sent using an organization's MOA implementation (a business object).
  * <P>
  * If an "Absolute" command has been associated to the consumer, it will execute
  * that command implementation NO MATTER what.
  * <P>
  * If no COMMAND_NAME is found on the Message passed in and there has been a "Default"
  * command associated to the consumer, it will execute that command implementation.
  * <P>
  * If no COMMAND_NAME property exists and there has been no "Default" or "Absolute"
  * command associated to this consumer, an error will occur.
  * <P>
  * If a COMMAND_NAME property DOES exist but it doesn't map to a known command implementation
  * and no "Absolute" command has been specified, an error will occur.
  * <P>
  * @param messageNumber int a message number managed by the consumer (the number of messages consumed by the consumer).
  * @param aMessage Message the JMS Message consumed by the consumer.
  * @return Message the output from the call to #org.openeai.jms.consumer.commands.RequestCommand.execute(int, Message)
  * @throws JMSException if errors occur executing the Command.  Note, this should be a very
  * rare occurrence because Commands are generally responsible for handling their own errors.
  * The most common place where an exception might be thrown by a command is if it
  * had problems retrieving the data from the Message passed to it.  Otherwise, the Command
  * should either publish a Sync-Error or return an Error to the requesting application depending
  * on the type of command being executed (RequestCommand vs. SyncCommand)
  **/
  protected Message handleRequest(int messageNumber,
                                  Message aMessage) throws JMSException {
    // Get request name from Properties
    // - this is what will be mapped to an actual IncomingRequest object
    //   in the "getRequest" method
    String requestName =
      aMessage.getStringProperty(MessageProducer.COMMAND_NAME);

    // backward compatibility - need to check for MESSAGE_NAME
    if (requestName == null || requestName.length() == 0) {
      requestName = aMessage.getStringProperty("REQUEST_NAME");
    }

    logger.debug("Incomming Request Name is " + requestName);

    // If the message coming in doesn't have a COMMAND_NAME or a COMMAND_NAME,
    // then we'll use the default command associated with this consumer.
    // If the consumer doesn't have a default command associated with
    // it, we'll return the CoreMessaging-Generic-Response-Reply with an error.
    if (requestName == null || requestName.length() == 0) {
      requestName = getDefaultCommandName();
      logger.info("No COMMAND_NAME passed in, using 'Default' RequestCommand if one has been defined.  Default is: " +
                  requestName);
    }

    // if the consumer has an "absolute" command name specified, it will be executed
    // no matter what's passed in.
    if (getAbsoluteCommandName() != null &&
        getAbsoluteCommandName().length() > 0) {
      requestName = getAbsoluteCommandName();
      logger.info("This consumer only handles one command.  The 'absolute' command is: " +
                  requestName);
    }

    // Attempt to locate the Java command associated to the COMMAND_NAME JMS Property
    RequestCommand request = null;
    try {
      request = getRequestCommand(requestName);
    }
    catch (Exception e) {
      logger.fatal("Invalid request " + requestName);

      TextMessage tMsg = (TextMessage)aMessage;
      String errText =
        buildGenericErrorResponse(aMessage, "system", "CONSUMER-0001",
                                  "Invalid request: " + requestName);
      tMsg.clearBody();
      tMsg.setText(errText);
      return tMsg;
    }

    // Execute the Java command associated to the request comming in (COMMAND_NAME)
    Message retMessage = null;
    try {
      retMessage = request.execute(messageNumber, aMessage);
      return retMessage;
    }
    catch (Exception e) {
      logger.fatal(e.getMessage(), e);
      TextMessage tMsg = (TextMessage)aMessage;
      String errText =
        buildGenericErrorResponse(aMessage, "system", "CONSUMER-0002",
                                  "Exception occurred executing request: " +
                                  requestName + "  Exception: " +
                                  e.getMessage());
      tMsg.clearBody();
      tMsg.setText(errText);
      return tMsg;
    }
  }

  private String buildGenericErrorResponse(Message aMessage, String errType,
                                           String errNumber,
                                           String errDesc) throws JMSException {
    // Actually, we need to return a CoreMessaging-Generic-Response-Reply
    // with the error in it
    Document errorDoc = getGenericErrorDoc();

    TextMessage textMsg = null;
    try {
      textMsg = (TextMessage)aMessage;
    }
    catch (ClassCastException e1) {
      logger.fatal(e1.getMessage(), e1);
      throw new JMSException(e1.getMessage());
    }

    // Build an XML Document out of the contents of the message passed in...
    Document inDoc = null;
    try {
      XmlDocumentReader xmlReader = new XmlDocumentReader();
      String msgBody = textMsg.getText();
      if (msgBody != null) {
        inDoc =
            xmlReader.initializeDocument(new ByteArrayInputStream(msgBody.getBytes()),
                                         false);
      }
      else {
        // error, have to use the 'primed' Generic-Response-Reply because there's no data in the message
        // passed in.
        if (errorDoc != null) {
          Element controlArea = getControlArea(errorDoc.getRootElement());

          Result aResult = new Result();
          aResult.setStatus("failure");
          aResult.setAction("UnknownMessageAction");

          org.openeai.moa.objects.resources.Error anError =
            new org.openeai.moa.objects.resources.Error();
          anError.setType(errType);
          anError.setErrorNumber(errNumber);
          anError.setErrorDescription(errDesc);

          ProcessedMessageId processedMsgId = new ProcessedMessageId();
          processedMsgId.setProducerId("UnknownProducer");
          processedMsgId.setSenderAppId("UnknownSender");
          processedMsgId.setMessageSeq("UnknownMessageSequence");

          aResult.setProcessedMessageId(processedMsgId);
          aResult.addError(anError);

          controlArea.removeChild("Result");
          Element eResult = null;
          try {
            eResult = (Element)aResult.buildOutputFromObject();
          }
          catch (EnterpriseLayoutException ele1) {
            logger.fatal(ele1.getMessage(), ele1);
            throw new JMSException(ele1.getMessage());
          }
          controlArea.addContent(eResult);

          XMLOutputter xOut = new XMLOutputter();
          return xOut.outputString(errorDoc);
        }
        else {
          return null;
        }
      }
    }
    catch (XmlDocumentReaderException e2) {
      logger.fatal(getConsumerName() +
                   " - Error creating document from message passed in");
      logger.fatal(e2.getMessage(), e2);
      throw new JMSException(e2.getMessage());
    }

    Element inControlArea = getControlArea(inDoc.getRootElement());
    String msgAction = null;
    if (inControlArea != null) {
      msgAction = inControlArea.getAttribute("messageAction").getValue();
    }
    else {
      String errMessage =
        getConsumerName() + " - Could not retrieve ControlArea from message passed in.";
      logger.fatal(errMessage);
      throw new JMSException(errMessage);
    }

    if (errorDoc != null) {
      Element controlArea = getControlArea(errorDoc.getRootElement());

      Result aResult = new Result();
      aResult.setStatus("failure");
      aResult.setAction(msgAction);

      org.openeai.moa.objects.resources.Error anError =
        new org.openeai.moa.objects.resources.Error();
      anError.setType(errType);
      anError.setErrorNumber(errNumber);
      anError.setErrorDescription(errDesc);

      ProcessedMessageId processedMsgId = new ProcessedMessageId();
      Element eRequestSender = inControlArea.getChild("Sender");
      Sender reqSender = new Sender();
      try {
        reqSender.buildObjectFromInput(eRequestSender);
      }
      catch (EnterpriseLayoutException ele) {
        logger.fatal(ele.getMessage(), ele);
        throw new JMSException(ele.getMessage());
      }

      processedMsgId.setProducerId(reqSender.getMessageId().getProducerId());
      processedMsgId.setSenderAppId(reqSender.getMessageId().getSenderAppId());
      processedMsgId.setMessageSeq(reqSender.getMessageId().getMessageSeq());

      aResult.setProcessedMessageId(processedMsgId);
      aResult.addError(anError);

      controlArea.removeChild("Result");
      Element eResult = null;
      try {
        eResult = (Element)aResult.buildOutputFromObject();
      }
      catch (EnterpriseLayoutException ele1) {
        logger.fatal(ele1.getMessage(), ele1);
        throw new JMSException(ele1.getMessage());
      }
      controlArea.addContent(eResult);

      XMLOutputter xOut = new XMLOutputter();
      return xOut.outputString(errorDoc);
    }
    return null;
  }

  /**
   * This method looks at the document and returns the appropriate ControlArea.
   * Since there can be three different control areas based on the message
   * (ControlAreaRequest, ControlAreaReply and ControlAreaSync) we need to have
   * some intelligence built in when retrieving the element from the document.
   *
   * @param root  org.jdom.Element the root element of the document
   *
   * @return          Element the ControlArea element (may be ControlAreaRequest,
   *                  ControlAreaReply or ControlAreaSync depending on the doc)
   */
  private Element getControlArea(Element root) {
    java.util.List cList = root.getChildren();
    Element retElem = null;
    for (int i = 0; i < cList.size(); i++) {
      Element current = (Element)cList.get(i);
      if (current.getName().indexOf("ControlArea") != -1) {
        retElem = current;
      }
    }
    return retElem;
  }

  /**
  * Invokes MessageConsumer.init(Properties) and adds the ConsumerShutDownHook
  * for this consumer.
  * <P>
  * @param props Properties
  * @throws IOException
  * @see MessageConsumer#init(Properties)
  **/
  protected void init(Properties props) throws IOException {
    super.init(props);

    Runtime.getRuntime().addShutdownHook(new ConsumerShutdownHook());
  }

  /**
  * Starts the consumer making it ready to consume messages from the Queue that
  * it connects to.  This follows the typical JMS pattern of starting a message consumer.
  * This includes:
  * <ul>
  * <li>Retrieving the JMS Administered objects (QueueConnectionFactory and Queue)
  * from a directory server or other JNDI source
  * <li>Creating a QueueConnection with the QueueConnectionFactory
  * <li>Creating a QueueSession with the QueueConnection
  * <li>Creating a QueueReceiver with the QueueSession and Queue
  * <li>Establishing the MessageListener that will be used when messages are delivered
  * to the Queue.
  * </ul>
  * <P>
  * Additionally, this method starts the Consumer's Monitor that will monitor and
  * attempt to resolve any broker connection issues encountered for the life of the
  * Consumer.
  * <P>
  * @throws JMSException
  * @throws NamingException
  * @see PubSubConsumer#startConsumer
  * @see MonitorConsumer
  **/
  public void startConsumer() throws JMSException, NamingException {
    try {
      if (getInitializationStatus().equals(NOT_INITIALIZED)) {
        initializeConsumer();
      }
    }
    catch (Exception e) {
      logger.fatal(e.getMessage(), e);
      throw new JMSException(e.getMessage());
    }
    logger.info("I'm the " + getConsumerName() + " PointToPointConsumer");

    // With JNDI
    // Create InitialContext object
    logger.debug("Creating InitialContext");
    DirContext ic = null;
    // Assume the m_providerUrl and m_initCtxFactory variables have already
    // been set.
    try {
      ic = getInitialContext();
      if (ic == null) {
        throw new NamingException("Error creating initial context");
      }
      logger.debug("Created initial context");
    }
    catch (NamingException ne) {
      logger.fatal(ne.getMessage(), ne);
      throw new NamingException(ne.getMessage());
    }

    try {
      // Lookup QueueConnectionFactory and Queue names
      logger.debug("Looking up queue connection factory name " +
                   getConnectionFactoryName());
      m_qcf = (QueueConnectionFactory)ic.lookup(getConnectionFactoryName());
      logger.debug("Looking up queue name " + getDestinationName());
      m_queue = (Queue)ic.lookup(getDestinationName());

      // Close InitialContext resources
      ic.close();
    }
    catch (NamingException ne) {
      logger.fatal(ne.getMessage(), ne);
      throw new NamingException(ne.getMessage());
    }
    // End JNDI

    logger.debug("Creating queue connection");
    try {
      if (getUserName() == null || getUserName().length() < 1) {
        m_queueConnection = m_qcf.createQueueConnection();
      }
      else {
        m_queueConnection =
            m_qcf.createQueueConnection(getUserName(), getPassword());
      }

      // Create TopicSession on the connection just created
      logger.debug("Creating queue session");
      m_queueSession =
          m_queueConnection.createQueueSession(getTransacted(), QueueSession.AUTO_ACKNOWLEDGE);

      // Create QueueReceiver
      logger.debug("Creating Queue receiver");
      m_queueReceiver = m_queueSession.createReceiver(m_queue);

      // Listen for messages
      m_listener = new MyQueueListener(m_queueReceiver, m_queueConnection);

      setConsumerStatus(STARTED);
    }
    catch (JMSException je) {
      // Don't throw exception here?  Let MonitorConsumer keep trying???
      String errMessage =
        "Error starting consumer.  Exception: " + je.getMessage() +
        "  Will let MonitorConsumer Thread attempt to restart.";
      logger.fatal(errMessage);
    }
    // Start the monitor if it isn't already running.
    startMonitor();
    return;
  }

  public void stop() {
    stopConsumer();
    shutdownCommands();
  }

  /**
  * Attempts to cleanly shutdown the Consumer.  This includes closing all JMS
  * resources (QueueReceiver, QueueSession and QueueConnection).  If errors
  * occur, it will log those errors as warnings.  However, regardless of the
  * outcome of the "clean" shutdown attempt, the consumer will be stopped.  This
  * method is called anytime the consumer detects connection problems to the broker
  * or when the consumer receives a shutdown hook from the operating system.
  * <P>
  * @see MonitorConsumer
  * @see ConsumerShutdownHook
  **/
  public void stopConsumer() {
    boolean exceptionOccurred = false;

    setConsumerStatus(STOPPED);
    stopMonitor();
    try {
      if (m_queueReceiver != null) {
        m_queueReceiver.close();
      }
    }
    catch (Exception e) {
      exceptionOccurred = true;
      logger.warn("Error closing QueueReceiver: " + e.getMessage());
    }

    try {
      if (m_queueSession != null) {
        m_queueSession.close();
      }
    }
    catch (Exception e) {
      exceptionOccurred = true;
      logger.warn("Error closing QueueSession: " + e.getMessage());
    }

    try {
      if (m_queueConnection != null) {
        m_queueConnection.stop();
      }
    }
    catch (Exception e) {
      exceptionOccurred = true;
      logger.warn("Error stopping QueueConnection: " + e.getMessage());
    }

    try {
      if (m_queueConnection != null) {
        m_queueConnection.close();
      }
    }
    catch (Exception e) {
      exceptionOccurred = true;
      logger.warn("Error closing QueueConnection: " + e.getMessage());
    }

    m_qcf = null;
    m_queue = null;

    if (exceptionOccurred) {
      logger.info("Everything was stopped but there were exceptions.");
    }
    else {
      logger.info("Everything was stopped successfully!");
    }
  }

  /**
  * Shuts down all commands executed by this consumer.
  **/
  public void shutdownCommands() {
    // (tj) 4/17/2003 - now, shutdown the commands this consumer executes.
    // first the RequestCommands
    Iterator it = m_requests.keySet().iterator();
    while (it.hasNext()) {
      String key = (String)it.next();
      RequestCommand r = (RequestCommand)m_requests.get(key);
      try {
        logger.info("Shutting down RequestCommand '" + key + "'");
        r.shutdown();
      }
      catch (Exception e) {
        logger.warn("Error shutting down RequestCommand '" + key +
                    "'  Processing will continue.");
      }
    }
    logger.info("All RequestCommands have been shutdown.");

    // now, since PointToPointProducers can also handle SyncCommands,
    // we need to potentially shut them down also
    // (tj) 4/17/2003 - now, shutdown the commands this consumer executes.
    Iterator it2 = m_messages.keySet().iterator();
    while (it2.hasNext()) {
      String key = (String)it2.next();
      SyncCommand s = (SyncCommand)m_messages.get(key);
      try {
        logger.info("Shutting down SyncCommand '" + key + "'");
        s.shutdown();
      }
      catch (Exception e) {
        logger.warn("Error shutting down SyncCommand '" + key +
                    "'  Processing will continue.");
      }
    }
    logger.info("All SyncCommands have been shutdown.");
  }

  /**
  * This is the JMS MessageListener implementation for OpenEAI PointToPointConsumers.  It
  * uses the QueueReceiver and QueueConnection established when the consumer was
  * started to listen for messages delivered to the Queue specified in the Consumer's
  * ConsumerConfig object.
  * <P>
  * When a message is delivered to the queue the this objects onMessage method is invoked.
  * <P>
  * @author      Tod Jackson (tod@openeai.org)
  * @version     3.0  - 28 January 2003
  * @see org.openeai.config.ConsumerConfig
  * @see org.openeai.config.ThreadPoolConfig
  * @see org.openeai.threadpool.ThreadPool
  * @see PubSubConsumer.MyTopicListener#onMessage(Message)
  * @see #onMessage(Message)
  **/
  protected class MyQueueListener implements MessageListener {
    private int messageCount = 1;

    // if we figure out how to create a replier without a queue,
    // we could just create it here and then pass it to the RequestTransaction
    // transaction instead of creating and closing one each time
    // in the RequestTransaction
    private QueueSender m_replier = null;

    public MyQueueListener(QueueReceiver queueRecvr,
                           QueueConnection queueConn) throws JMSException {
      queueRecvr.setMessageListener(this);
      queueConn.start();
      m_replier = m_queueSession.createSender(null);
      logger.info(getConsumerName() + " - Ready to receive requests...");
    }

    /**
    *
    **/
    public void onMessage(Message m) {
      try {
        if (consumptionStopped()) {
          logger.info(getConsumerName() +
                      " - Cannot consume any more messages, because the consumer is being shutdown.  Going to sleep.");
          m_inProcessMessages = new Vector();
          m_inProcessMessages.add(m);
          try {
            Thread.sleep(300000);
          }
          catch (Exception te) {
          }
        }
        logger.info(getConsumerName() + " - Handling Request number: " +
                    messageCount);
        Queue replyQueue = (Queue)m.getJMSReplyTo();
        if (replyQueue != null) {
          // RequestTransaction will process the incoming message and
          // return a response.
          if (getThreadPool() != null) {
            boolean keepTrying = true;
            while (keepTrying) {
              try {
                getThreadPool().addJob(new RequestTransaction(messageCount, m,
                                                              m_replier,
                                                              replyQueue));
                keepTrying = false;
              }
              catch (ThreadPoolException e) {
                logger.warn("ThreadPool is busy, sleeping and trying it again.");
                try {
                  Thread.sleep(1000);
                  logger.info("Woke up, trying to add message to ThreadPool again...");
                }
                catch (Exception te) {
                }
              }
            }
          }
          else {
            new RequestTransaction(messageCount, m, m_replier,
                                   replyQueue).run();
          }
        }
        else {
          // MessageTransaction will process the incoming message.
          if (getThreadPool() != null) {
            boolean keepTrying = true;
            while (keepTrying) {
              try {
                getThreadPool().addJob(new MessageTransaction(messageCount,
                                                              m));
                keepTrying = false;
              }
              catch (ThreadPoolException e) {
                logger.warn("ThreadPool is busy, sleeping and trying it again.");
                try {
                  Thread.sleep(1000);
                  logger.info("Woke up, trying to add message to ThreadPool again...");
                }
                catch (Exception te) {
                }
              }
            }
          }
          else {
            new MessageTransaction(messageCount, m).run();
          }
        }
        if (m_queueSession.getTransacted()) {
          m_queueSession.commit();
        }
        messageCount++;
        logger.info(getConsumerName() + " - Ready to receive requests...");
      }
      catch (JMSException je) {
        logger.fatal(je.getMessage(), je);
        try {
          if (m_queueSession.getTransacted()) {
            m_queueSession.rollback();
          }
        }
        catch (JMSException e) {
          logger.fatal("Error rolling transaction back or determining the transaction mode.");
          logger.fatal(e.getMessage(), e);
        }
      }
      catch (Exception e) {
        logger.fatal(e.getMessage(), e);
        try {
          if (m_queueSession.getTransacted()) {
            m_queueSession.rollback();
          }
        }
        catch (JMSException je) {
          logger.fatal("Error rolling transaction back or determining the transaction mode.");
          logger.fatal(je.getMessage(), je);
        }
      }
    }
  }

  /**
  * This is the class that is used to execute the RequestCommand associated to a
  * message consumed by the Consumer.  When a message is delivered to the Destination
  * onwhich the consumer is connected and there <b>is</b> an expected reply,
  * it will instantiate this class passing a message number and the actual message
  * it consumed and add this classes run method to the ThreadPool (if the ThreadPool
  * is in use).  If the ThreadPool is not in use, the consumer will simply call
  * this classes run method and will block until that command's execution is complete.
  * <P>
  * By adding this "job" to the ThreadPool, the consumer does not wait for the
  * command to complete execution before consuming another message.  Therefore,
  * you can affect how many "jobs" may be in progress by configuring the ThreadPool
  * associated to this Consumer.
  * <P>
  * @author      Tod Jackson (tod@openeai.org)
  * @version     3.0  - 28 January 2003
  * @see org.openeai.config.ThreadPoolConfig
  * @see org.openeai.threadpool.ThreadPool
  * @see PubSubConsumer.MyTopicListener#onMessage(Message)
  * @see PointToPointConsumer.MyQueueListener#onMessage(Message)
  * @see MessageConsumer.MessageTransaction
  **/
  protected class RequestTransaction implements java.lang.Runnable {
    private Message theMessage = null;
    private Queue replyQueue = null;
    private int m_msgNumber = 0;
    private QueueSender m_replier = null;

    public RequestTransaction(int msgNumber, Message inMsg,
                              QueueSender replier, Queue outQueue) {
      theMessage = inMsg;
      replyQueue = outQueue;
      m_msgNumber = msgNumber;
      m_replier = replier;
    }

    public void run() {
      try {
        logger.info(getConsumerName() + " - Handling request " + m_msgNumber +
                    " in RequestTransaction thread.");
        int deliveryMode = theMessage.getJMSDeliveryMode();
        String messageId =
          theMessage.getStringProperty(MessageProducer.MESSAGE_ID);
        logger.debug("PointToPointConsumer:RequestTransaction - message id coming in: " +
                     messageId);
        Message msg = handleRequest(m_msgNumber, theMessage);
        logger.info(getConsumerName() +
                    " - Done handling request in RequestTransaction.");
        logger.debug("Created replier, sending reply.");
        msg.setJMSDeliveryMode(deliveryMode);
        if (messageId != null) {
          msg.clearProperties();
          msg.setStringProperty(MessageProducer.MESSAGE_ID, messageId);
        }

  /* newMessage added to allow routing between different broker implementations - TJ,TC */
  TextMessage newMessage = getQueueSession().createTextMessage();
        newMessage.setStringProperty(MessageProducer.MESSAGE_ID, msg.getStringProperty(MessageProducer.MESSAGE_ID));
        newMessage.setText(((TextMessage)msg).getText());

        m_replier.send(replyQueue, newMessage);
        logger.info(getConsumerName() + " - Sent Message " + m_msgNumber +
                    " back to client on Queue: " + replyQueue.getQueueName() +
                    " from RequestTransaction.");
      }
      catch (Exception e) {
        logger.fatal(e.getMessage(), e);
      }
    }
  }

  /**
   * This Thread will be started when the consumer receives a shutdown signal from the os.
   * It is established via the Runtime.getRuntime().addShutdownHook(new ConsumerShutdownHook());
   * in the init() method.  The purpose of this is to allow a "clean" shutdown of the consumers
   * without losing any messages that might be in progress when shutdown occurrs.
   * <P>
   * @author Tod Jackson
   */
  protected class ConsumerShutdownHook extends Thread {
    public void run() {
      logger.info(getConsumerName() +
                  " - Consumer shutdown hook, stopping consumer");
      logger.info(getConsumerName() + " - Waiting for threads to complete...");

      // Stop all consumption to give our thread pool a chance to finish up.
      stopConsumption();

      // we'll probably only want to wait for a maximum period of time
      // until we go ahead and stop the consumer regardless of threads
      // in progress??
      if (getThreadPool() != null) {
        int elapsedWaitTime = 0;
        while (getThreadPool().getJobsInProgress() > 0 &&
          elapsedWaitTime < getMaximumThreadPoolShutdownWaitTime()) {
         
          try {
            elapsedWaitTime += 500;
            Thread.sleep(500);           
          }
          catch (Exception e) {
          }
        }
      }
      if (getThreadPool() != null && getThreadPool().getJobsInProgress() > 0) {
        logger.warn("There are still " + getThreadPool().getJobsInProgress() +
          " threads in process.  However, the maximum time to wait for the " +
          "thread pool to empty (" + getMaximumThreadPoolShutdownWaitTime() +
          " milliseconds) has expired.  Consumer shutdown is continuing.");
      }
      else {
        logger.info(getConsumerName() + " - All threads are complete.");
      }
     

      // Process any messages that were in process when shutdown started.
      if (m_inProcessMessages != null) {
        logger.info(getConsumerName() + " - Processing " +
                    m_inProcessMessages.size() +
                    " messages that were in progress when shutdown was started.");
        for (int i = 0; i < m_inProcessMessages.size(); i++) {
          Message msg = (Message)m_inProcessMessages.get(i);
          try {
            Queue replyQueue = (Queue)msg.getJMSReplyTo();
            if (replyQueue != null) {
              QueueSender m_replier = m_queueSession.createSender(null);
              new RequestTransaction(0, msg, m_replier, replyQueue).run();
            }
            else {
              new MessageTransaction(0, msg).run();
            }
          }
          catch (Exception e) {
            logger.fatal("Exception handling message in PointToPointConsumer:ConsumerShutdownHook: " +
                         e.getMessage());
          }
        }
      }

      stopConsumer();
      logger.info(getConsumerName() +
                  " - Consumer shutdown hook, consumer stopped, now exiting.");
    }
  }

  /**
   * This Thread will sleep for a specified period of time and then wake up
   * and check the status of the consumer by attempting to create/delete a TemporaryQueue.
   * If the creation of the TemporaryQueue fails, it assumes there is something wrong
   * with the consumer's connection to the broker and it is not consuming any messages.
   * When that happens, it attempts to do a "clean" shutdown on the consumer and
   * then restarts the consumer which will re-establish its connection to the broker
   * and it will start consuming messages again.  This means, if brokers must be taken down
   * for any reason (on purpose or not), gateways will NOT need to be restarted
   * when the broker comes back up, rather, they will do that themselves.  This process
   * continues until it is stopped via the stopMonitor method or until the gateway
   * is stopped.  If a broker is down for an extended period of time, this Monitor
   * will continue to try and reconnect until the broker is back online.
   * <P>
   * The thread is started when the consumer is started the first time.
   * <P>
   * @author Tod Jackson
   * @see #stopMonitor
   */
  protected class MonitorConsumer implements java.lang.Runnable {
    private int m_sleepInterval = 30000; // thirty seconds

    public MonitorConsumer(int sleepInterval) {
      m_sleepInterval = sleepInterval;
    }

    private boolean restartConsumer() {
      if (consumptionStopped() == false) {
        stopConsumer();
        try {
          startConsumer();
        }
        catch (Exception e1) {
          logger.fatal("Error restarting consumer.  Exception: " +
                       e1.getMessage());
        }
        return true;
      }
      else {
        logger.info("Consumer is being shutdown, won't attempt to restart in MonitorConsumer.");
        return false;
      }
    }

    public void run() {
      // sleep for m_sleepInterval
      // wake up, try to do something with the session
      // if an exception occurs, restart the consumer
      boolean stayAlive = true;
      while (stayAlive) {
        try {
          Thread.sleep(m_sleepInterval);
          if (m_monitorRunning == false) {
            logger.info("Monitor has been stopped.  Returning from Monitor Thread.");
            return;
          }
        }
        catch (Exception e) {
          logger.fatal("Error sleeping...");
        }
        // wake up and try to access the session
        try {
          if (m_queueSession != null) {
            TemporaryQueue tq = m_queueSession.createTemporaryQueue();
            tq.delete();
            logger.debug("Session is okay.");
          }
          else {
            logger.fatal("Session is null, need to restart the consumer.");
            stayAlive = restartConsumer();
          }
        }
        catch (JMSException e) {
          logger.fatal("Session is not usable, need to restart the consumer.  Exception: " +
                       e.getMessage());
          stayAlive = restartConsumer();
        }
      }
    }
  }
}
TOP

Related Classes of org.openeai.jms.consumer.PointToPointConsumer$MyQueueListener

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.