/*******************************************************************************
$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();
}
}
}
}
}