/*******************************************************************************
$Source: /cvs/repositories/openii3/project/java/source/org/openeai/jms/consumer/commands/ConsumerCommand.java,v $
$Revision: 1.12 $
*******************************************************************************/
/**********************************************************************
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.commands;
import org.openeai.*;
import org.openeai.xml.*;
import org.openeai.config.*;
import org.openeai.moa.*;
import org.openeai.moa.jmsobjects.*;
import org.openeai.moa.objects.resources.*;
import org.openeai.moa.objects.testsuite.*;
import java.util.*;
import java.io.*;
import javax.jms.*;
import org.apache.log4j.*;
import org.jdom.output.XMLOutputter;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Attribute;
/**
* The "default" anscestor of all Commands that are executed by either PubSubConsumer or PointToPointConsumer.
*<P>
* Organizations can add an additional layer between this class and the command implementations they develop
* or they can even eliminate the use of this class altogether. This class provides many useful convenience
* methods that are commonly needed by all command implementations. Additional common routines will be added
* to this layer as time goes by...
* <P>
* @author Tod Jackson (tod@openeai.org)
* @author Steve Wheat (steve@openeai.org)
* @version 3.0 - 28 January 2003
*<P>
* @see RequestCommandImpl
* @see RequestCommand
* @see SyncCommandImpl
* @see SyncCommand
*/
public class ConsumerCommand extends OpenEaiObject {
public static Category logger;
private HashMap m_msgComponents = new HashMap();
private Vector m_appConfigs = new Vector();
private AppConfig m_appConfig = new AppConfig();
private boolean m_inboundXmlValidation = false;
private boolean m_outboundXmlValidation = false;
private boolean m_writeToFile = false;
private String m_messageDumpDir = "";
protected final static String MESSAGE_ACTION = "messageAction";
protected final static String MESSAGE_CATEGORY = "messageCategory";
protected final static String MESSAGE_OBJECT = "messageObject";
protected final static String MESSAGE_RELEASE = "messageRelease";
protected final static String MESSAGE_TYPE = "messageType";
protected final static String CREATE_ACTION = "create";
protected final static String DELETE_ACTION = "delete";
protected final static String QUERY_ACTION = "query";
protected final static String UPDATE_ACTION = "update";
protected final static String DATA_AREA = "DataArea";
protected final static String NEW_DATA = "NewData";
protected final static String DELETE_DATA = "DeleteData";
protected final static String BASELINE_DATA = "BaselineData";
protected final static String TEST_ID = "TestId";
protected final static String SENDER = "Sender";
public ConsumerCommand() {
}
/**
* Default constructor behavior that will apply to all command implementations.
* Sets the AppConfig object
* Sets Xml Validation (inbound and outbound)
* Determines if this command should write incomming messages to a file and
* establishes the location to which those files should be written.
* @param cConfig CommandConfig the CommandConfig Java object that wraps the CommandConfig
* Element from the deployment document.
* @throws InstantiationException if errors occur during initialization.
*/
public ConsumerCommand(CommandConfig cConfig) throws InstantiationException {
setAppName(cConfig.getAppName());
setAppConfig(cConfig.getAppConfig());
setInboundXmlValidation(cConfig.getInboundXmlValidation());
setOutboundXmlValidation(cConfig.getOutboundXmlValidation());
setWriteToFile(cConfig.writeToFile());
setMessageDumpDirectory(cConfig.getMessageDumpDirectory());
try {
LoggerConfig lConfig = new LoggerConfig();
lConfig = (LoggerConfig)getAppConfig().getObjectByType(lConfig.getClass().getName());
logger = Category.getInstance(getClass().getName().substring(0, getClass().getName().lastIndexOf('.')));
PropertyConfigurator.configure(lConfig.getProperties());
}
catch (Exception e) {
logger = org.openeai.OpenEaiObject.logger;
}
}
/* ControlArea Convenience methods */
private String getControlAreaAttributeValue( Document inDoc, String attrName ) {
Element eControlArea = getControlArea(inDoc.getRootElement());
if (eControlArea != null) {
Attribute a = eControlArea.getAttribute( attrName );
if (a != null) {
return a.getValue();
}
}
return null;
}
/**
* Returns the value of the 'messageCategory' Attribute from the ControlArea
* in the Document passed in. Returns null if the attribute does not exist.
*<P>
* @return String the messageCategory value from the ControlArea or null if none exists
* @param inDoc Document the Document object that was created from the JMS Message
* passed to the exeucte method of the command being executed.
**/
protected String getMessageCategory( Document inDoc ) {
return getControlAreaAttributeValue( inDoc, MESSAGE_CATEGORY );
}
/**
* Returns the value of the 'messageObject' Attribute from the ControlArea
* in the Document passed in. Returns null if the attribute does not exist.
*<P>
* @return String the messageObject value from the ControlArea or null if none exists
* @param inDoc Document the Document object that was created from the JMS Message
* passed to the exeucte method of the command being executed.
**/
protected String getMessageObject( Document inDoc ) {
return getControlAreaAttributeValue( inDoc, MESSAGE_OBJECT );
}
/**
* Returns the value of the 'messageAction' Attribute from the ControlArea
* in the Document passed in. Returns null if the attribute does not exist.
*<P>
* @return String the messageAction value from the ControlArea or null if none exists
* @param inDoc Document the Document object that was created from the JMS Message
* passed to the exeucte method of the command being executed.
**/
protected String getMessageAction( Document inDoc ) {
return getControlAreaAttributeValue( inDoc, MESSAGE_ACTION );
}
/**
* Returns the value of the 'messageType' Attribute from the ControlArea
* in the Document passed in. Returns null if the attribute does not exist.
*<P>
* @return String the messageType value from the ControlArea or null if none exists
* @param inDoc Document the Document object that was created from the JMS Message
* passed to the exeucte method of the command being executed.
**/
protected String getMessageType( Document inDoc ) {
return getControlAreaAttributeValue( inDoc, MESSAGE_TYPE );
}
/**
* Returns the value of the 'messageRelease' Attribute from the ControlArea
* in the Document passed in. Returns null if the attribute does not exist.
*<P>
* @return String the messageRelease value from the ControlArea or null if none exists
* @param inDoc Document the Document object that was created from the JMS Message
* passed to the exeucte method of the command being executed.
**/
protected String getMessageRelease( Document inDoc ) {
return getControlAreaAttributeValue( inDoc, MESSAGE_RELEASE );
}
/* End ControlArea Convenience methods */
/**
* Gets the message body from the JMS message passed in and creates an XML document
* from that data. Also, this method writes the message to a file (as an xml document)
* if writeToFile is true, this is established in the Command's constructor.
* @param messageNumber int
* @param aMessage JMS Message
*/
protected Document initializeInput(int messageNumber, Message aMessage) throws JMSException {
// Handle Text Message
TextMessage textMsg = null;
try {
textMsg = (TextMessage)aMessage;
}
catch (ClassCastException e) {
logger.fatal(e.getMessage(), e);
throw new JMSException(e.getMessage());
}
// Build an XML Document out of the contents of the message passed in...
Document inDoc = null;
try {
XmlDocumentReader xmlReader = new XmlDocumentReader();
inDoc = xmlReader.initializeDocument(new ByteArrayInputStream(textMsg.getText().getBytes()), getInboundXmlValidation());
}
catch (XmlDocumentReaderException e) {
logger.fatal("Error creating document from message passed in");
logger.fatal("Message sent in is: \n" + getMessageBody(inDoc));
logger.fatal(e.getMessage(), e);
throw new JMSException(e.getMessage());
}
logger.debug("Processing message " + getMessageBody(inDoc));
if (writeToFile()) {
// Write the XML Document to a file, using the MessageId as the name
try {
writeMessageToFile(inDoc);
}
catch (IOException e) {
logger.fatal("Exception occurred writing message to file. Exception: " + e.getMessage());
}
}
return inDoc;
}
/**
* Sets the messageDumpDirectory variable that will be the target of the writeMessageToFile call if
* the command needs to write the incoming message to a file.
* It is called in the from the Command constructor.
* @param dumpDir String
*/
protected void setMessageDumpDirectory(String dumpDir) {
m_messageDumpDir = dumpDir;
}
/**
* Returns the messageDumpDirectory variable that will be the target of the writeMessageToFile call if
* the command needs to write the incoming message to a file.
* It is called by the Command if it needs to write the message to file.
* @return - String
*/
protected String getMessageDumpDirectory() {
return m_messageDumpDir;
}
/**
* Sets the writeToFile variable that will be used by the commands to determine if they should write the message
* to a file.
* It is called in the from the Command constructor.
* @param writeToFile boolean
*/
protected void setWriteToFile(boolean writeToFile) {
m_writeToFile = writeToFile;
}
/**
* Returns the writeToFile variable that will be used by the commands to determine if they should write the message
* to a file.
* It is called by commands to determine if they should write the incoming message to a file.
* @return - boolean
*/
protected boolean writeToFile() {
return m_writeToFile;
}
/**
* Returns the messageBody that was built during the initializeInput method call.
* this will be a 'String' object built from the message body of the JMS message passed in.
* @return String
*/
public final String getMessageBody(Document inDoc) {
XMLOutputter xmlOut = new XMLOutputter();
return xmlOut.outputString(inDoc);
}
/**
* Set AppConfig associated to this command. It is called in the
* constructor of Command.
* @param aConfig AppConfig
*/
protected void setAppConfig(AppConfig aConfig) {
m_appConfig = aConfig;
}
/**
* Get AppConfig associated to this command. It is called by commands to get access
* to command specific OpenEAI objects like message objects, db connection pools, producers
* threadpools, properties etc..
* @return AppConfig
*/
public final AppConfig getAppConfig() {
return m_appConfig;
}
/**
* Set inbound xml validation. This is used to determine whether or not to
* validate the xml document as when it's first consumed. It is called in the
* constructor of Command.
* @param validate
*/
protected void setInboundXmlValidation(boolean validate) {
m_inboundXmlValidation = validate;
}
/**
* Get inbound xml validation. This is used to determine whether or not to
* validate the xml document as when it's first consumed.
* @return boolean
*/
protected boolean getInboundXmlValidation() {
return m_inboundXmlValidation;
}
/**
* Set outbound xml validation. This is used to determine whether or not to
* validate the xml document before it's returned or routed. It is called in the
* constructor of Command.
* @param validate
*/
protected void setOutboundXmlValidation(boolean validate) {
m_outboundXmlValidation = validate;
}
/**
* Get outbound xml validation. This is used to determine whether or not to
* validate the xml document before it's returned or routed. It is called by
* commands...
* @return boolean
*/
protected boolean getOutboundXmlValidation() {
return m_outboundXmlValidation;
}
protected void addAppConfig(AppConfig aConfig) {
m_appConfigs.add(aConfig);
}
protected Vector getAppConfigs() {
return m_appConfigs;
}
/**
* Sets the HashMap that is a list of Messaging Components (gateways) that this command
* needs to know about. This is specifically used by gateways like Routers and Proxies
* which need to forward sync or request messages to an end point. This is a list
* of AppConfig objects that correspond to that end point and contains message object,
* producers etc. that are needed to route/forward a message to that end point.
*<P>
* @param components HashMap a HashMap containing AppConfigs for end points of intrest to this command.
*<P>
* @return void
*/
public final void setMsgComponents(HashMap components) {
m_msgComponents = components;
}
/**
* Returns the HashMap that is a list of Messaging Components (gateways) that this command
* needs to know about. This is specifically used by gateways like Routers and Proxies
* which need to forward sync or request messages to an end point. This is a list
* of AppConfig objects that correspond to that end point and contains message object,
* producers etc. that are needed to route/forward a message to that end point.
*<P>
* @return HashMap a HashMap containing AppConfigs for end points of intrest to this command.
*/
public final HashMap getMsgComponents() {
return m_msgComponents;
}
private void writeMessageToFile(Document inDoc) throws IOException {
Element eControlArea = getControlArea(inDoc.getRootElement());
String msgObject = eControlArea.getAttribute("messageObject").getValue();
writeMessageToFile(msgObject, inDoc, getMessageDumpDirectory());
}
/**
* Used by decendant commands to serialize a message passed to the command to the file system.
* Generally, this method will only be called when the initializeInput method is called and
* the JMS Message is initially turned into an XML Document (org.jdom.Document). However, some
* commands may also wish to serialize the document themselves, for example, a Router may wish
* to serialize the document after it's modified the content of the message and before it routes it.
*<P>
* @param msgObject String the name of the message object contained in the message
* @param doc Document the XML document being serialize
* @param msgDumpDir String the path to which the file should be written.
*/
protected void writeMessageToFile(String msgObject, Document doc, String msgDumpDir) throws IOException {
// Write the XML Document to a file, using the MessageId as the name
// Create the dump directory if it doesn't exist..
File messageStore = new File(msgDumpDir);
if (messageStore.exists() == false) {
messageStore.mkdirs();
}
XMLOutputter xmlOut = new XMLOutputter();
Element eControlArea = getControlArea(doc.getRootElement());
Element eMessageId = eControlArea.getChild("Sender").getChild("MessageId");
String senderAppId = eMessageId.getChild("SenderAppId").getText();
String producerId = eMessageId.getChild("ProducerId").getText();
String messageSeq = eMessageId.getChild("MessageSeq").getText();
String fileName = senderAppId + "-" + producerId + "-" + messageSeq + ".xml";
if (msgDumpDir.lastIndexOf("/") != msgDumpDir.length() - 1) {
msgDumpDir = msgDumpDir + "/";
}
logger.info("Writing message to file: " + msgDumpDir + fileName);
try {
xmlOut.output(doc, new FileOutputStream(msgDumpDir + fileName));
}
catch (Exception e) {
String msg = "Error dumping message to a file. Exception: " + e.getMessage();
logger.fatal(msg);
throw new IOException(msg);
}
}
/**
* Builds a single Error object that can be added to the ArrayList of errors (or for any other reason)
* to pass to the publishSyncError method.
*
* @param errType String error Type ('application' or 'system')
* @param errNumber String error Number
* @param errDescription String error Description
*
* @return org.openeai.moa.objects.Error the error object that gets built.
*/
protected org.openeai.moa.objects.resources.Error buildError(String errType, String errNumber, String errDescription) {
org.openeai.moa.objects.resources.Error anError =
new org.openeai.moa.objects.resources.Error();
anError.setType(errType);
anError.setErrorNumber(errNumber);
anError.setErrorDescription(errDescription);
return anError;
}
protected String convertToString(Document doc) throws IOException {
XMLOutputter xmlOut = new XMLOutputter();
ByteArrayOutputStream bArray = new ByteArrayOutputStream();
xmlOut.output(doc, bArray);
return new String(bArray.toByteArray());
}
/**
* Takes the release number passed in which is generally pulled from a message
* passed to a command and converts it into a release number that can be used
* for naming conventions in configuration documents. Since gateways have to be
* able to support multiple versions of a message object, it is sometimes helpful
* to adopt a naming convention to name these objects. Then, a ConsumerCommand implementation
* can take the release from the message and convert it into the release used to
* name an object in order to retrieve that object from an AppConfig.
* <P>
* For example, the messageRelease attribute in the message consumed is "1.0", this
* method will convert that to "v1_0" which is a common naming convention that's been
* used by many of the OpenEAI reference implementation applications/gateways. The
* gateway has a message object with this release appended to its name in its
* configuration document. For example, "BasicPerson.v1_0". Then, the command
* can retrieve message objects from its AppConfig without having to hard code any
* names etc. instead, it can rely on the contents of the message and use that information
* to derive the necessary name.
* <P>
* @param release String the messageRelease pulled from the message (e.g. - 1.0, 2.0 etc.)
* @return String the converted release (e.g. - v1_0, v2_0 etc.) that can be used
* to pull the object from the commands AppConfig.
**/
public final String generateRelease(String release) {
if (release != null) {
return "v" + release.replace('.', '_');
}
else {
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.
* Therefore, command implementations can just call this method and get a ControlArea
* element based on the type of message being processed without having to make
* assumptions regarding the type of message it's processing and without performing
* this logic themselves.
*
* @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)
*/
protected 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;
}
/**
* Returns the test ID extracted from the supplied document. This TestId will
* be made part of any sync messages published as a result of a Request action
* being performed.
* <P>
* This data is then used by the OpenEAI TestSuite Application to correlate
* the Sync message published to the Request message that precipitated the
* Sync message and verify that the appropriate Sync message is published.
* <P>
* @param testIdObjectName the name of the TestId object as specified in this
* Command's AppConfig (Configuration Element).
* @param eControlArea the Control Area element from which to extract a test ID.
**/
protected TestId extractTestId( String testIdObjectName, Element eControlArea ) {
Element eTestId = eControlArea.getChild( SENDER ).getChild( TEST_ID );
TestId testId = null;
try {
testId = (TestId) getAppConfig().getObject( testIdObjectName );
}
catch (Exception e) {
// ignore this exception
logger.debug(e.getMessage(), e);
}
if (eTestId != null && testId != null) {
try {
testId.buildObjectFromInput( eTestId );
logger.debug( "extractTestId found test ID " + testId );
}
catch (Exception e) {
logger.warn( "Could not build the TestId object from the TestId Element in the ControlArea. Ignoring this." );
}
}
else {
logger.debug( "Could not find the TestId Element in the ControlArea. Ignoring this." );
}
return testId;
}
/**
*
**/
public void shutdown() throws CommandException {
try {
logger.info(getAppConfig().getName() + " shutting myself down.");
getAppConfig().shutdown();
for (int i=0; i<getAppConfigs().size(); i++) {
AppConfig a = (AppConfig)getAppConfigs().get(i);
logger.info(a.getName() + " shutting myself down.");
a.shutdown();
}
}
catch (Exception e) {
throw new CommandException(e.getMessage(), e);
}
}
}