/*******************************************************************************
$Source: /cvs/repositories/openii3/project/java/examples/org/any_openeai_enterprise/gateways/brg/BannerRequestCommand.java,v $
$Revision: 1.3 $
*******************************************************************************/
/**********************************************************************
This file is part of the OpenEAI sample, reference implementation,
and deployment management suite 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 program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; 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 implement integrations for your enterprise, visit
http://www.OpenEai.org/licensing.
*/
package org.any_openeai_enterprise.gateways.brg;
import javax.jms.*;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.output.XMLOutputter;
import java.util.*;
import org.openeai.config.*;
import org.openeai.jms.consumer.*;
import org.openeai.jms.consumer.commands.*;
import org.openeai.dbpool.*;
import org.openeai.moa.*;
import org.openeai.moa.objects.testsuite.*;
import org.any_openeai_enterprise.gateways.brg.RequestLogicExecutor;
/**
* This command is the base command for all Request message consumption that
* the might be sending to Banner. This gateway can be used
* to process requests that either aren't supported by SCT's message support or
* to fulfill a need that isn't provided YET. It can also be used as
* an example or pattern for exposing any systems' business API via messaging. It is a
* "general purpose" gateway that can be extended as that message support is
* identified.
* <P>
* The gateway is extended by implementing additional 'RequestLogicExecutor' classes
* that support the requests being sent to the gateway.
* <P>
* Currently, it consumes requests of the following type:
* <ul>
* <li>com.sct.Person/BasicPerson/1.0
* </ul>
* <P>
* <B>Gateway Configuration</B>
* <P>
* These are the configuration parameters required by this gateway.
* They are specified in the AnyOpenEAIEnterprise.xml Deployment Descriptor included
* with the sample enterprise.
* This gateway is identified in the Deployment
* Descriptor as <b>org.any_openeai_enterprise.BannerGateway</b>.
* <P>
* Configuration for a gateway can be broken into two pieces:
* <ul>
* <li>Configuring the consumer to connect to a topic or queue and telling it what commands it executes.
* <li>Configuring the command(s) executed by that consumer.
* </ul>
* <P>
* <b>Configuring the Consumer</b>
* <P>
* The items listed below can be found in the 'Configuration' Element associated to
* the edu.uillinois.aits.BannerRequestGateway gateway in the deployment descriptor.
* This Configuration element becomes the AppConfig
* object associated to this gateway <b>(remember, all OpenEAI gateways are started through
* the org.openeai.jms.consumer.MessageConsumerClient class)</b>.
* <P>
* The consumer's Configuration is fairly simple. It contains a LoggerConfig object that specifies
* how the log4j logger will behave and it contains a ConsumerConfigs element. Below is a description
* of the Consumer configuration. Note, not all configurable items are mentioned but that
* information can be reviewed by looking at the core OpenEAI API Javadoc.
* <P>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>Configuration Object Name</TH>
* <TH>Required</TH>
* <TH>Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>BannerP2PConsumer1</TD>
* <TD>yes</TD>
* <TD>An OpenEAI PointToPointConsumer object configured to consume JMS messages off
* the 'cn=BannerGatewayQueue' queue. When the EnterpriseRequestProxy
* forwards requests to that queue, this consumer will consume the message. Then it will
* exeute this command which will determine what business logic needs to be executed (if any)
* and execute that logic and return the resulting message to the requesting application.
* <P>
* The Consumer's configuration specifies that it will connect to the broker
* and be ready to consume messages when it is initialized ('startOnInitialization=true') use the
* InitialContextFactory 'org.exolab.jms.jndi.rmi.RmiJndiInitialContextFactory' to retrieve the JMS
* administered objects from the directory server normally location specified by the ProviderURL element.
* Then it will connect to the JMS Provider using the 'cn=BannerConsumerQCF' JMS QueueConnectionFactory
* administered object(s).
* <P>
* It will execute the Commands specified (this command) in a ThreadPool and it will
* detect when the ThreadPool is busy and only add another Command execution to
* the ThreadPool when the ThreadPool has
* available threads to process the transaction (checkBeforeProcessing=true).</TD>
* </TR>
* </TABLE>
* <P>
* <b>Configuring the Command(s) executed by the Consumer</b>
* <P>
* The BannerRequestCommand command (this class) will NOT validate
* the JDOM Document created from the JMS Message that's passed to it (inboundXmlValidation=false).
* If you'd like it to validate incomming XML, you can change this attribute to true.
* Additionally, it will not write the XML Document to a file (writeToFile=false) when it receives it. As stated
* before, this command is the 'absolute' command associated to this consumer. So, no matter what "COMMAND_NAME" is
* associated to the JMS Message as it is consumed by the consumer, this command will be executed. The
* BannerRequestCommand (CommandName) is implemented by the
* org.any_openeai_enterprise.messaging.gateways.BannerRequestCommand class
* (CommandClass, which is this class) and
* it is a 'requestMessage' Command (type=requestMessage). This means, this command
* will return a reply to the requesting application. Specifically, it will return
* the reply generated by the RequestLogicExecutor associated to the messageObject
* in the request it consumes (e.g. - BasicPerson).
* <P>
* The items listed below can be found in the 'Configuration' Element associated to the
* BannerRequestCommand in the deployment descriptor. This Configuration element becomes the AppConfig
* object associated to this Command that is used by the command when it is executed.
* <P>
* They are broken apart by Configuration object type. This command
* will return CoreMessaging/Generic-Response-Reply messages if it ever has any problems performing the business logic,
* it will use Message Objects to operate on the data contained in the message consumed, it will
* use a DbConnectionPool to retrieve and/or persist data and it will use several general Properties object.
* Therefore, there are four categories of configuration objects used:
* MessageObjectConfigs, DbConnectionPoolConfigs and PropertyConfigs.
* <P>
* <P>
* <b>MessageObjectConfigs</b>
* <P>
* These configuration objects are used to configure all Message objects required by this command.
* <P>
* The objects are used to operate on the data supplied in the sync messages as well
* as to retrieve and/or persist the relevant information to the database.
* <P>
* As new request consumption requirements are identified, there will be additional
* message objects that must be listed here in order to effectively operate on the
* messages consumed.
* <P>
* Currently, this gateway only provides support for BasicPerson-Query,
* BasicPerson-Create, BasicPerson-Update and BasicPerson-Delete request messages
* so it is the only object needed. The TestId object is
* simply listed so we can use the OpenEAI TestSuite application to send request
* messages to this gateway and it can log information about the TestId included
* in that message (to correlate that message back to the test suite application). This
* is how we'll verify that this gateway is performing it's function correctly.
* <P>
* <P>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>Configuration Object Type</TH>
* <TH>Configuration Object Name</TH>
* <TH>Required</TH>
* <TH>Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>DbConnectionPoolConfigs</TD>
* <TD>DbPool</TD>
* <TD>yes</TD>
* <TD>This is an OpenEAI org.openeai.dbpool.EnterpriseConnectionPool object. The command will
* retrieve a pre-established database connection from the pool and execute SQL statements.
* The connections in the pool are connected to Banner.</TD>
* </TR>
* </TABLE>
* <P>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>Configuration Object Type</TH>
* <TH>Configuration Object Name</TH>
* <TH>Required</TH>
* <TH>Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>PropertyConfig</TD>
* <TD>GeneralProperties</TD>
* <TD>yes</TD>
* <TD>These are 'general' properties that will be associated to this Command. These
* can be properties used by this command or they can be properties used by any of
* the RequestLogicExecutor classes that this command may execute.</TD>
* </TR>
* </TABLE>
* <P>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>Configuration Object Type</TH>
* <TH>Configuration Object Name</TH>
* <TH>Required</TH>
* <TH>Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>PropertyConfig</TD>
* <TD>MessageMappings</TD>
* <TD>yes</TD>
* <TD>These are mappings between message objects and RequestLogicExecutor implementations.
* The BasicPersonRequestImpl class is currently the only one listed because that's the only
* support we have to provide at this time. There are three ways a message object can be
* associated to a specific RequestLogicExecutor implementation. These are all based on
* naming standards that can be derived from data included in the message consumed
* by the gateway. They are:
* <ul>
* <li>messageObject + messageRelease + messageAction (e.g. - BasicPerson.v1_0.Query)
* <li>messageObject + messageRelease (e.g. - BasicPerson.v1_0)
* <li>messageObject (e.g. - BasicPerson)
* </ul>
* These names can be used as PropertyNames and the PropertyValue associated to them
* should be the name of the RequestLogicExecutor implementation that should be used
* when a message of the appropriate type is consumed. For example:
* <P>
* If a BasicPerson-Query-Request message is consumed with a messageRelease=1.0 is
* consumed, the messageObject attribute in the message will be 'BasicPerson', the
* messageRelease attribute will be '1.0' and the messageAction will be 'Query'.
* So, if in the MessageMappings properties there exists a PropertyName of
* 'BasicPerson.v1_0.Query' with a PropertyValue of
* 'org.any_openeai_enterprise.messaging.gateways.executors.v1_0.BasicPersonQueryImpl' that
* class will be instantiated, configured and executed to perform the Query functionality required.
* The reason there are different ways this mapping can be made is to give developers more
* flexibility as to the granularity of the request logic implementations they wish to
* provide. For example, you may want to simply put all support for all BasicPerson requests
* into one class alone. Or, you may want to break it apart by action or by release etc.
* </TD>
* </TR>
* </TABLE>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>Configuration Object Type</TH>
* <TH>Configuration Object Name</TH>
* <TH>Required</TH>
* <TH>Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>PropertyConfig</TD>
* <TD>BasicPersonProperties</TD>
* <TD>yes</TD>
* <TD>Properties associated specifically to the BasicPersonRequestImpl class. That class
* will also get a copy of the AppConfig associated to the Command but this allows developers
* to separate properties specifically associated to a particular implementation which
* will hopefully help make documentation and configuration easier to understand.</TD>
* </TR>
* </TABLE>
* <P>
* @author Tod Jackson (tod@openeai.org)
**/
public class BannerRequestCommand
extends RequestCommandImpl
implements RequestCommand {
private HashMap m_executorMappings = new HashMap();
protected final static String REQUEST_MESSAGE_TYPE = "Request";
public BannerRequestCommand(CommandConfig cConfig) throws InstantiationException {
super(cConfig);
try {
// default properties associated to this command (there could be others named differently).
PropertyConfig pConfig = (PropertyConfig)getAppConfig().getObject("GeneralProperties");
setProperties(pConfig.getProperties());
}
catch (Exception e) {
logger.fatal("Error initializing 'BannerRequestCommand' command.");
logger.fatal(e.getMessage(), e);
throw new InstantiationException(e.getMessage());
}
try {
// default properties associated to this command (there could be others named differently).
PropertyConfig pConfig = (PropertyConfig)getAppConfig().getObject("MessageMappings");
Properties props = pConfig.getProperties();
Enumeration keys = props.keys();
while (keys.hasMoreElements()) {
String key = (String)keys.nextElement();
String executorClassName = props.getProperty(key);
String executorName = key.toLowerCase();
logger.info("Adding executor: " + executorName + "-" + executorClassName);
m_executorMappings.put(executorName, executorClassName);
}
}
catch (Exception e) {
logger.fatal("Error initializing 'BannerRequestCommand' command.");
logger.fatal(e.getMessage(), e);
throw new InstantiationException(e.getMessage());
}
}
public final Message execute(int messageNumber, Message aMessage) throws CommandException {
String errMessage = "";
// parse the text from the JMS message into a JDOM document.
Document inDoc = null;
try {
inDoc = initializeInput(messageNumber, aMessage);
}
catch (Exception e) {
String errMsg = "Exception occurred processing input message in BannerRequestCommand. Exception: " + e.getMessage();
throw new CommandException(errMsg, e);
}
// retrieve text portion of message passed in
TextMessage msg = (TextMessage)aMessage;
try {
msg.clearBody(); // So we don't have to do it later...
}
catch (JMSException e) {
throw new CommandException(e.getMessage(), e);
}
// Get Control area from XML document
Element eControlArea = getControlArea(inDoc.getRootElement());
// Get Control Area (protocol) properties from message consumed.
String msgAction = eControlArea.getAttribute(MESSAGE_ACTION).getValue();
String msgCategory = eControlArea.getAttribute(MESSAGE_CATEGORY).getValue();
String msgType = eControlArea.getAttribute(MESSAGE_TYPE).getValue();
String msgRelease = eControlArea.getAttribute(MESSAGE_RELEASE).getValue();
String msgObject = eControlArea.getAttribute(MESSAGE_OBJECT).getValue();
String msgObjectName = msgObject + "." + generateRelease(msgRelease);
TestId testId = extractTestId(TEST_ID, eControlArea);
// get the appropriate primed reply dodument.
Document replyDoc = (Document)getReplyDoc(msgAction, msgObject, msgRelease).clone();
if (msgType.equalsIgnoreCase(REQUEST_MESSAGE_TYPE) == false) {
errMessage = "The BannerRequestGateway doesn't support " +
msgType + " messages. Since this gateway only supports request " +
"messages that expect a reply. Please check that the sending " +
"application is sending the appropriate type of messages and " +
"'Primed' XML documens associated to the messages objects being " +
"used by the application are correct.";
logger.fatal(errMessage);
ArrayList errors = logErrors("BRG-1001", errMessage, inDoc);
String replyContents = buildReplyDocumentWithErrors(eControlArea, replyDoc, errors);
return getMessage(msg, replyContents);
}
// if the message object isn't one we're interested in return an error
// look for a mapping name in the following formats:
// - msgObject + release + action (e.g - BasicPerson.v1_0.Query)
// - msgObject + release (e.g. - BasicPerson.v1_0)
// - msgObject (e.g. - BasicPerson)
// If none of these exist, it's an error
String mappingName = null;
if (m_executorMappings.containsKey(msgObjectName.toLowerCase() + "." + msgAction.toLowerCase()) == false) {
if (m_executorMappings.containsKey(msgObjectName.toLowerCase()) == false) {
if (m_executorMappings.containsKey(msgObject.toLowerCase()) == false) {
errMessage = "The BannerRequestGateway doesn't support " +
msgCategory + "/" +
msgObject + "-" +
msgAction + "-" +
msgType + " version " + msgRelease + " messages.";
logger.fatal(errMessage);
ArrayList errors = logErrors("BRG-1002", errMessage, inDoc);
String replyContents = buildReplyDocumentWithErrors(eControlArea, replyDoc, errors);
return getMessage(msg, replyContents);
}
else {
mappingName = msgObject.toLowerCase();
}
}
else {
mappingName = msgObjectName.toLowerCase();
}
}
else {
mappingName = msgObjectName.toLowerCase() + "." + msgAction.toLowerCase();
}
// Get the message object (e.g. BasicPerson, LightweightPerson etc.) element out of the document.
// This will exist in the DataArea portion of the document. We know this because
// The OpenEAI Message Protocol says so.
String dataAreaChild = "";
Element eMessageObject = null;
if (msgAction.equalsIgnoreCase(QUERY_ACTION)) {
// since there is only ever one query object (so far), this should be okay.
java.util.List lChildren = inDoc.getRootElement().getChild(DATA_AREA).getChildren();
for (int i=0; i<lChildren.size(); i++) {
eMessageObject = (Element)lChildren.get(i);
}
}
else {
// determine where the message object is in the Document base on messageAction.
dataAreaChild = NEW_DATA; // Default, this will work for Create and Update
if (msgAction.equalsIgnoreCase(DELETE_ACTION)) {
dataAreaChild = DELETE_DATA;
}
eMessageObject = inDoc.getRootElement().
getChild(DATA_AREA).
getChild(dataAreaChild).
getChild(msgObject);
}
if (eMessageObject == null) {
// Error!
errMessage = "Could not find a " + msgObject + " element at " +
inDoc.getRootElement().getName() + "/" + DATA_AREA + "/" + dataAreaChild + "/" + msgObject +
" in the " + msgObject + "-" + msgAction + "-" + msgAction + " Document passed in";
ArrayList errors = logErrors("BRG-1005", errMessage, inDoc);
String replyContents = buildReplyDocumentWithErrors(eControlArea, replyDoc, errors);
return getMessage(msg, replyContents);
}
// if the msgAction is Update, we'll need to get the baseline object out of the document as well.
Element eBaselineMessageObject = null;
if (msgAction.equalsIgnoreCase(UPDATE_ACTION)) {
// we need to create a baseline XEO also so we have to get the baseline element...
eBaselineMessageObject = inDoc.getRootElement().
getChild(DATA_AREA).
getChild(BASELINE_DATA).
getChild(msgObject);
if (eBaselineMessageObject == null) {
// Error!
errMessage = "Could not find a " + msgObject + " + element at " +
DATA_AREA + "/" + BASELINE_DATA + "/" + msgObject +
" in the "+msgObject+"-"+msgAction+"-" + msgAction + " Document passed in";
ArrayList errors = logErrors("BRG-1010", errMessage, inDoc);
String replyContents = buildReplyDocumentWithErrors(eControlArea, replyDoc, errors);
return getMessage(msg, replyContents);
}
}
// now, based on the message object name (and version?) execute the specific
// business logic associated to that object...
logger.info("Retrieving request logic executor for mapping name: '" + mappingName + "'.");
String executorClassName = (String)m_executorMappings.get(mappingName);
String executorName = msgObject;
try {
// instantiate that class
logger.info("Instantiating executor: " + executorClassName);
RequestLogicExecutor executor = (RequestLogicExecutor)Class.forName(executorClassName).newInstance();
try {
PropertyConfig pConfig = (PropertyConfig)getAppConfig().getObject(msgObject + "Properties");
executor.setProperties(pConfig.getProperties());
}
catch (Exception e) {
logger.warn("No executor specific properties found for the " + executorName +
" executor. Using Command's 'GeneralProperties' properties.");
executor.setProperties(getProperties());
}
executor.setExecutorAppConfig(getAppConfig());
executor.setMessageObject(eMessageObject);
executor.setBaseline(eBaselineMessageObject);
executor.setMessageAction(msgAction);
executor.setMessageObjectName(msgObject);
executor.setMessageRelease(msgRelease);
executor.setTestId(testId);
executor.execute(replyDoc);
String replyContents = buildReplyDocument(eControlArea, replyDoc);
return getMessage(msg, replyContents);
}
catch (Exception e) {
errMessage =
"Exception occurred executing the business logic associated " +
"to the " + msgObject + "-" + msgAction + "-" + msgType + " message consumed. This error " +
"occurred in the " + executorClassName + " class. Exception was " + e.getMessage();
logger.fatal(errMessage, e);
ArrayList errors = logErrors("BRG-1015", errMessage, e, inDoc);
String replyContents = buildReplyDocumentWithErrors(eControlArea, replyDoc, errors);
return getMessage(msg, replyContents);
}
}
/**
* Based on the 'messageAction' associated to the request we determine which 'PrimedXmlDocument' associated
* to the message object being operated on should be returned. This is specified in the Command's
* deployment descriptor on each message object.
* <P>
* For example, our command supports com.any-erp-vendor.Person/BasicPerson/1.0/Query-Request
* and com.any-erp-vendor.Person/BasicPerson/1.0/Create-Request. We know
* based on the OpenEAI Message Protocol that the response to a com.any-erp-vendor.Person/BasicPerson/1.0/Query-Request is
* com.any-erp-vendor.Person/BasicPerson/1.0/Provide-Reply. We also know that the
* response to a com.any-erp-vendor.Person/BasicPerson/1.0/Create-Request is a
* org.openeai.CoreMessaging/1.0/Response-Reply with the appropriate status,
* action and error information filled in.
* <P>
* Because of all this knowledge, we configure the message objects used by this Command to have the appropriate
* 'PrimedXmlDocument' entries that allows us to simply get the appropriate reply document from the object
* based on the action. If the Action is 'Query' we'll be returning the 'primed' Provide document associated to the
* object. If the action is anything else, we'll be returning the 'primed' generic Response document associated to
* the object.
**/
final protected Document getReplyDoc(String msgAction, String msgObject, String msgRelease) throws CommandException {
try {
XmlEnterpriseObject xeo = (XmlEnterpriseObject)getAppConfig().getObject(msgObject + "." + generateRelease(msgRelease));
Document rDoc = null;
if (msgAction.equalsIgnoreCase(QUERY_ACTION)) {
rDoc = xeo.getProvideDoc();
}
else {
rDoc = xeo.getResponseDoc();
}
if (rDoc == null) {
logger.fatal("Could not find a reply document for the '" + msgObject + "-" + msgAction + "' request.");
throw new CommandException("Could not find an appropriate reply document for the '" + msgObject + "-" + msgAction + " request.");
}
else {
logger.info("Found a reply document for the '" + msgObject + "-" + msgAction + "' request.");
}
return rDoc;
}
catch (Exception e) {
logger.fatal(e.getMessage(), e);
throw new CommandException(e.getMessage(), e);
}
}
/*
final private ArrayList logErrors(String errNumber, String errMessage, Throwable e, Document inDoc) {
logger.fatal(errMessage, e);
logger.fatal("Message sent in is: \n" + getMessageBody(inDoc));
ArrayList errors = new ArrayList();
errors.add(buildError("application", errNumber, errMessage));
return errors;
}
final private ArrayList logErrors(String errNumber, String errMessage, Document inDoc) {
logger.fatal(errMessage);
logger.fatal("Message sent in is: \n" + getMessageBody(inDoc));
ArrayList errors = new ArrayList();
errors.add(buildError("application", errNumber, errMessage));
return errors;
}
*/
}