/*******************************************************************************
$Source: /cvs/repositories/openii3/project/java/source/org/openeai/implementations/gateways/transroutergateway/TransRouterCommand.java,v $
$Revision: 1.13 $
*******************************************************************************/
/**********************************************************************
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.openeai.implementations.gateways.transroutergateway;
import javax.jms.*;
import java.util.*;
import java.io.*;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.output.XMLOutputter;
import org.apache.log4j.*;
import org.openeai.jms.producer.*;
import org.openeai.jms.consumer.*;
import org.openeai.jms.consumer.commands.*;
import org.openeai.layouts.*;
import org.openeai.config.*;
import org.openeai.moa.*;
//import org.openeai.moa.jmsobjects.*;
import org.openeai.moa.objects.resources.*;
import org.openeai.xml.*;
/**
* OpenEAI Reference Implementation Enterprise Router.
* <P>
* The gateway consumes all synchronization messages published by authoritative
* systems and routes those messages to all interested end-points by executing
* this command.
* <P>
* <B>These are the configuration parameters associated to this command. These are
* specified the Command's Configuration element in the deployment document associated
* to the gateway.</B>
* <P>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>Property Name</TH>
* <TH>Required</TH>
* <TH>Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ProducerConfigs</TD>
* <TD>yes</TD>
* <TD>This is the Sync-Error-Sync publisher that the Router will use when/if
* there are any errors while processing a message it consumes. It should be
* configured accordingly. All SyncCommand implementations require a
* PubSubProducer <B>that must be named</B> 'SyncErrorPublisher' that they use to publish Sync-Error-Sync
* message if errors occur when they're processing the message. This is useful
* for SyncCommand implementations in particular because they're generally ran
* unattended.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>PropertyConfigs</TD>
* <TD>yes</TD>
* <TD>These are the properties that are used by the Sync-Error-Sync publisher
* they are required by all SyncCommand implementations. The name of the PropertyConfig
* <B>must be</B> 'SyncErrorSyncProperties' and they must include the 'SyncErrorSyncPrimedDocumentUri'
* property that points to a Sync-Error-Sync primed document.</TD>
* </TR>
* </TABLE>
* <P>
* <B>The following configuration paramaters associated to the Enterprise Router command
* establish resources that will be used to route the messages consumed
* by the gateway to the appropriate end-points.</B>
* <P>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>Property Name</TH>
* <TH>Required</TH>
* <TH>Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>MessagingComponents</TD>
* <TD>yes</TD>
* <TD>This is a container Element that provides a mechanism for specifying which
* MessageGateways will be routed to. It simply contains one or more MessageGateway Elements
* which are used by this command to establish AppConfig objects associated to each end-point.
* The AppConfig objects that get established will contain all the resources needed to
* route the messages to the end-points appropriately.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>MessageGateway</TD>
* <TD>yes</TD>
* <TD>This Element is used to specify the name of the target gateway that will be routed
* to. It is also a container for the Configuration Element which is implemented by the AppConfig
* Java object and contains all initialized resources required to route the messages to the
* end points. The "id" attribute is used to specify the name of the end point. The
* Description element is used to provide a description of the end point. There should be a
* MessageGateway Element specified for each end-point that should be routed to.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>Configuration</TD>
* <TD>yes</TD>
* <TD>This Element is implmemented by the AppConfig java object. This is the same configuration
* Element used by all other messaging components that use an AppConfig. This will contain the
* resources required to route to end points. Those resources include:
* <ul>
* <li>ProducerConfigs - PubSubProducers that are connected to the end-point's destination (Topic).
* This producer will be used to route the message. Additional producers could be specified here
* if needed. Additional producers could be needed by the RoutingCriteria class that gets executed
* to perform "content based" routing. If needed, they would be specified and configured here.
* <li>MessageObjectConfigs - This would be a list of all Message Objects that the end point is interested in.
* The router will retrieve the messageObject (e.g. - BasicPerson, BasicEmployee etc.)
* and messageRelease (e.g. - 1.0, 1.1 etc.) from the message consumed by the gateway
* and use that information to build a message object name that looks like this "BasicPerson.v1_0".
* If the end point's configuration contains a message object by that name,
* the router may route the message to that end point depending on whether or not the end-point
* has an Routing Criteria (content based routing rules) associated to it.
* <li>PropertyConfigs - These are a set of properties associated to the end point. There
* <B>must be</B> one PropertyConfig object named 'RoutingProperties' that this command
* uses to make decisions about how/if it should route to the end point. The Properties that
* may exist in this PropertyConfig element are:
* <P>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>Property Name</TH>
* <TH>Required</TH>
* <TH>Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>routeToTarget (true|false)</TD>
* <TD>no (default=true)</TD>
* <TD>This property is used to determine if the command should route to the end-point
* at all.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>translateToTargetValues (true|false)</TD>
* <TD>no (default=false)</TD>
* <TD>If true, values in the message will be 'reverse' translated to application
* specific values for the target being routed to. If false, the values in the
* message will be used as they were passed in (i.e. - no reverse translation).</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>provideTargetAppName (true|false)</TD>
* <TD>no (default=true)</TD>
* <TD>If true, the TargetInfo Element of the message routed by this command
* will include the name of the application that published the message.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>provideSourceControlArea (true|false)</TD>
* <TD>no (default=false)</TD>
* <TD>If true, the message that the command routes to the end-point will include
* the ControlAreaSync Element from the original message published by the authoritative
* source. This can be useful if the end-point wants to know the originating information
* such as the time stamp when the message was originally published.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>dumpOutput (true|false)</TD>
* <TD>no (default=false)</TD>
* <TD>If true, the router will dump the contents of the message it's routing prior to
* routing it. All commands have the ability dump the message consumed by the gateway.
* However, this is a little different in that it's actually going to dump the message
* that results from the routing process prior to routing it. This is generally something
* that should only be turned on during development/testing or when additional information
* is needed regarding the activities of the router.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>routingCriteriaClass.1-n</TD>
* <TD>no</TD>
* <TD>These properties are used to specify RoutingCriteriaCommand implementations that should
* be used to determine, based on content and other factors, if the message should be routed to the end point.
* Multiple classes may be specified, using "dot" notation, or some other distinguishing technique,
* in the name of the class. They will be executed in the order they're specified.
* Note, they are NOT currently executed based on the number to the right of the "dot".
* That is simply a technique used to specify multiple RoutingCriteria classes.
* <P>
* If a routing criteria class(es) exist, this command will call the 'shouldRoute' method on the
* RoutingCriteriaCommand implementations passing the AppConfig object associated to the end-point
* and the XML Document built from the JMS Message consumed. Then, that RoutingCriteriaCommand can
* do whatever it likes to determine if the end point is interested in this message. This may include
* things a simple as verifying certain data items in the message or it may be something as complex
* as querying other authoritative sources for additional information that may not be supplied in the
* message consumed. These RoutingCriteriaCommands are intended to be very specific to the end point
* in question.</TD>
* </TR>
* </TABLE>
* <P>
* </TD>
* </TR>
* </TABLE>
*
* <P>
* Sync Errors that might be published by this gateway:
* <P>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>Error Number</TH>
* <TH>Error Type</TH>
* <TH>Error Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>OpenEAI_ROUTER-1001</TD>
* <TD>application</TD>
* <TD>This error will be published if the command has problems converting the
* body of the JMS message into an XML Document. The reason for the error should
* be included in the ErrorDescription element of the Sync Error. If this error occurs
* the message <b>will not</b> be routed to any targets but a record of the failure will
* be published and the original message will have typically been logged by the original
* publishing applications LoggingProducer.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>OpenEAI_ROUTER-2001</TD>
* <TD>application</TD>
* <TD>This error will be published if a DataArea child element cannot be found in
* the document created from the body of the JMS message consumed. The name of the object
* the command was looking for will be specified in the ErrorDescription. The expected
* location of this element will be determined by the ControlAreaSync@messageAction and the
* ControlAreaSync@messageObject attributes. For example, if the ControlArea@messageAction is 'Create'
* and the ControlAreaSync@messageObject is 'BasicPerson' the command will look for the
* DataArea/Create/BasicPerson Element that should exist in the Document. If this element is not found,
* a Sync Error will be published and the message <b>will not</b> be routed to any targets
* but a record of the failure will be published and the original message will have typically
* been logged by the original publishing applications LoggingProducer.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>OpenEAI_ROUTER-2002</TD>
* <TD>application</TD>
* <TD>This error will be published if there are 'RoutingCriteria' classes associated to the component
* the Router is attempting route to and that RoutingCriteria class cannot be successfully <b>instantiated</b>.
* If this occurs, the Router will publish the error an continue to the next application in the list of
* applications it's suppose to route to. Therefore, there will be a record of this failure but it <b>will
* not</b> prohibit the message from being routed to the other destinations that might be interested in this
* message.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>OpenEAI_ROUTER-2003</TD>
* <TD>application</TD>
* <TD>This error will be published if there are 'RoutingCriteria' classes associated to the component
* the Router is attempting route to and that RoutingCriteria class cannot be successfully <b>executed</b>.
* If this occurs, the Router will publish the error an continue to the next application in the list of
* applications it's suppose to route to. Therefore, there will be a record of this failure but it <b>will
* not</b> prohibit the message from being routed to the other destinations that might be interested in this
* message.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>OpenEAI_ROUTER-2004</TD>
* <TD>application</TD>
* <TD>This error will be published if there are errors building the appopriate ControlAreaSync element that
* needs to be associated to the message being routed to the current target.
* If this occurs, the Router will publish the error an continue to the next application in the list of
* applications it's suppose to route to. Therefore, there will be a record of this failure but it <b>will
* not</b> prohibit the message from being routed to the other destinations that might be interested in this
* message.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>OpenEAI_ROUTER-2005</TD>
* <TD>application</TD>
* <TD>This error will be published if the ControlAreaSync@messageAction is not
* 'Create', 'Update' or 'Delete'. These are the only actions currently supported by this version
* of the router. If this error occurs the message <b>will not</b> be routed
* to any targets but a record of the failure will
* be published and the original message will have typically been logged by the original
* publishing applications LoggingProducer.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>OpenEAI_ROUTER-2006</TD>
* <TD>application</TD>
* <TD>This error will be published if the router encounter issues trying to build
* the XML document it needs to router from the contents of the new ControlAreaSync and the
* message object it retrieved from the original message. This is the message that it will be
* forwarding to the current target. This message is of the same type and action of the original message
* but it includes the new ControlAreaSync with the router information in it. It might also contain different data in the
* object included in the DataArea of the message if the router found application specific translations associated to the object
* when it serializes the object to an XML element.
* If this occurs, the Router will publish the error an continue to the next application in the list of
* applications it's suppose to route to. Therefore, there will be a record of this failure but it <b>will
* not</b> prohibit the message from being routed to the other destinations that might be interested in this
* message.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>OpenEAI_ROUTER-2007</TD>
* <TD>application</TD>
* <TD>This error will be published if the 'outboundXmlValidation' property is
* set to 'true' for the router command and the message that would be routed is
* not a valid XML document. After the router has built the new message that it needs to route, it
* will check to see if outbound XML validation is "turned on". If it is, it
* will validate the new XML Document. If the document is not valid, it will publish this error.
* If this occurs, the Router will publish the error an continue to the next application in the list of
* applications it's suppose to route to. Therefore, there will be a record of this failure but it <b>will
* not</b> prohibit the message from being routed to the other destinations that might be interested in this
* message.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>OpenEAI_ROUTER-2008</TD>
* <TD>application</TD>
* <TD>This error will be published if the router encounters issues actually
* routing the message to the current application. This means, if it has problems
* actually publishing the new message to the target using the PubSubProducer associated to
* that target, it will publish this error.
* If this occurs, the Router will publish the error an continue to the next application in the list of
* applications it's suppose to route to. Therefore, there will be a record of this failure but it <b>will
* not</b> prohibit the message from being routed to the other destinations that might be interested in this
* message.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>OpenEAI_ROUTER-2009</TD>
* <TD>application</TD>
* <TD>This error will be published if the router encounters issues either building
* a Java object from the DataArea XML Element in the original Message or generating an XML Element from that
* object that will be included in the new message being routed.
* If this error occurs the message <b>will not</b> be routed
* to any additional targets but a record of the failure will
* be published and the original message will have typically been logged by the original
* publishing applications LoggingProducer.</TD>
* </TR>
* </TABLE>
* <P>
* @author Tod Jackson (tod@openeai.org)
* @version 1.0 - 26 March 2003
* @see org.openeai.config.AppConfig
* @see org.openeai.config.LoggerConfig
* @see org.openeai.config.ConsumerConfig
* @see org.openeai.config.CommandConfig
* @see org.openeai.config.ProducerConfig
* @see org.openeai.config.MessageObjectConfig
* @see RoutingCriteriaCommand
* @see RoutingCriteriaCommand#shouldRoute(AppConfig, Document)
*/
public class TransRouterCommand
extends SyncCommandImpl
implements SyncCommand {
private int m_messageSequence = 0;
/**
* Constructor
*/
public TransRouterCommand(CommandConfig cConfig) throws InstantiationException {
super(cConfig);
// This is so the inherited Logger configuration (specified at the gateway level)
// can be overriden at the command level.
try {
LoggerConfig lConfig = new LoggerConfig();
lConfig = (LoggerConfig)getAppConfig().getObjectByType(lConfig.getClass().getName());
logger = Category.getInstance(getClass().getName());
PropertyConfigurator.configure(lConfig.getProperties());
logger.warn("got a LoggerConfig from the command's app config..." + lConfig.getProperties());
}
catch (Exception e) {
logger = org.openeai.OpenEaiObject.logger;
logger.debug("no LoggerConfig in the command's app config...");
logger.debug(e.getMessage(), e);
}
PropertyConfig pConfig = new PropertyConfig();
try {
pConfig = (PropertyConfig)getAppConfig().getObjectByType(pConfig.getClass().getName());
setProperties(pConfig.getProperties());
}
catch (EnterpriseConfigurationObjectException e) {
logger.fatal(e.getMessage(), e);
throw new InstantiationException(e.getMessage());
}
// Add all applications listed in config file to m_apps.
// These will be used each time execute is called to build
// an AppConfig object for each application that must be
// Translated/Routed to.
// This will be a list of MessageComponent Elements.
setMsgComponents(cConfig.getMsgComponents());
// Go through all message component elements and create and store an app config
// named the same as what's stored with the message component element.
HashMap mComponents = getMsgComponents();
Collection cValues = mComponents.values();
Iterator it = cValues.iterator();
while (it.hasNext()) {
Element eComponent = null;
try {
eComponent = (Element)it.next();
Element eConfig = eComponent.getChild("Configuration");
if (eConfig != null) {
AppConfig aTempConfig = new AppConfig();
aTempConfig.setName(eComponent.getAttribute("id").getValue());
aTempConfig.init(eConfig);
addAppConfig(aTempConfig);
}
else {
// Error
String msg = "Couldn't find a 'Configuration' element for MessageComponent: " +
eComponent.getAttribute("id").getValue();
logger.fatal(msg);
throw new InstantiationException(msg);
}
}
catch (Exception e) {
logger.fatal("Error initializing AppConfig for MessageComponent: " +
eComponent.getAttribute("id").getValue());
logger.fatal("Exception: " + e.getMessage());
throw new InstantiationException(e.getMessage());
}
}
logger.info("TransRouter initialized successfully.");
}
public void execute(int messageNumber, Message aMessage) throws CommandException {
logger.debug("TransRouterCommand is executing...");
Document inDoc = null;
try {
inDoc = initializeInput(messageNumber, aMessage);
}
catch (Exception e) {
String errMessage = "Exception occurred processing input message in ConsumerCommand. Exception: " + e.getMessage();
ArrayList errors = new ArrayList();
errors.add(buildError("application", "OpenEAI_ROUTER-1001", errMessage));
publishSyncError(new Element("EmptyControlArea"), errors, e);
throw new CommandException(e.getMessage());
}
Element eControlArea = getControlArea(inDoc.getRootElement());
Element eDataArea = inDoc.getRootElement().getChild(DATA_AREA);
String msgAction = eControlArea.getAttribute(MESSAGE_ACTION).getValue();
String msgObject = eControlArea.getAttribute(MESSAGE_OBJECT).getValue();
String msgRelease = eControlArea.getAttribute(MESSAGE_RELEASE).getValue();
// need to get the messageRelease and create the appropriate package name from that...
String msgObjectName = msgObject + "." + generateRelease(msgRelease);
String dataAreaChild = NEW_DATA; // Default, this will work for Create and Update
if (msgAction.equalsIgnoreCase(DELETE_ACTION)) {
dataAreaChild = DELETE_DATA;
}
// Get the DataArea element out of the document
// This should correspond to one of our message objects.
Element eInput = eDataArea.getChild(dataAreaChild).getChild(msgObject);
if (eInput == null) {
// Error!
String errMessage = "Could not find an element at DataArea/" + dataAreaChild + "/" + msgObject +
"in the " + msgObject + "-" + msgAction + "-Sync Document passed in";
logger.fatal(errMessage);
logger.fatal("Message sent in is: \n" + getMessageBody(inDoc));
ArrayList errors = new ArrayList();
errors.add(buildError("application", "OpenEAI_ROUTER-2001", errMessage));
publishSyncError(eControlArea, errors);
return;
}
try {
// For each application we're forwarding to (a list of AppConfigs)
// "transform" the message into a version that may contain application
// specific values for that application and forward it on to that application
// using the producer associated to that application.
for (int i=0; i<getAppConfigs().size(); i++) {
Document outDoc = (Document)inDoc.clone();
AppConfig aConfig = (AppConfig)getAppConfigs().get(i);
logger.info("Checking routing info for application: " + aConfig.getName());
// Get the 'RoutingProperties' associated to this target. This will tell us
// things about what we're suppose to do with this message before we route it.
// Like, dump it to a file, etc.
PropertyConfig pConfig = null;
try {
pConfig = (PropertyConfig)aConfig.getObject("RoutingProperties");
}
catch (EnterpriseConfigurationObjectException e) {
logger.warn("No 'RoutingProperties' for Application " + aConfig.getName() + " will use defaults.");
}
Properties aProps = null;
if (pConfig != null) {
aProps = pConfig.getProperties();
}
else {
aProps = new Properties();
}
boolean dumpOutput = new Boolean(aProps.getProperty("dumpOutput","false")).booleanValue();
boolean provideTargetAppName = new Boolean(aProps.getProperty("provideTargetAppName","true")).booleanValue();
boolean provideSourceControlArea = new Boolean(aProps.getProperty("provideSourceControlArea","false")).booleanValue();
boolean routeToTarget = new Boolean(aProps.getProperty("routeToTarget","true")).booleanValue();
boolean translateToTargetValues = new Boolean(aProps.getProperty("translateToTargetValues","false")).booleanValue();
// Get the producer associated to the application we're forwarding to.
PubSubProducer pubSub = new PubSubProducer();
try {
pubSub = (PubSubProducer)aConfig.getObjectByType(pubSub.getClass().getName());
}
catch (EnterpriseConfigurationObjectException e) {
logger.info("Application " + aConfig.getName() + " doesn't have a PubSubProducer. Continuing to next Application.");
logger.info(e.getMessage(), e);
continue;
}
ActionableEnterpriseObject anXeo = null;
// Try to get the message object out of the AppConfig associated to tha app
// we're forwarding to. If the application doesn't care about the current
// message object, just continue on to the next application in the list.
try {
anXeo = (ActionableEnterpriseObject)aConfig.getObject(msgObjectName);
}
catch (EnterpriseConfigurationObjectException e) {
logger.info("Application " + aConfig.getName() + " doesn't care about " +
"objects of type " + msgObject);
continue;
}
// build a list of instantiated 'RoutingCriteriaCommand' objects that will be used
// to perform 'content based' routing for the application we're forwarding to
// (if applicable).
Enumeration keys = aProps.keys();
ArrayList routingCriteriaObjects = new ArrayList();
boolean routingCriteriaErrors = false;
while (keys.hasMoreElements()) {
String keyName = (String)keys.nextElement();
if (keyName.toLowerCase().indexOf("routingcriteriaclass") != -1) {
String className = aProps.getProperty(keyName,null);
if (className != null && className.length() > 0) {
try {
RoutingCriteriaCommand rcCommand = (RoutingCriteriaCommand)Class.forName(className).newInstance();
routingCriteriaObjects.add(rcCommand);
}
catch (Exception e) {
routingCriteriaErrors = true;
String errMsg = "Exception instantiating 'RoutingCriteriaClass' " + className +
" for Application: " + aConfig.getName() + " Exception: " + e.getMessage();
logger.fatal(errMsg);
logger.fatal(e.getMessage(), e);
ArrayList errors = new ArrayList();
errors.add(buildError("application", "OpenEAI_ROUTER-2002", errMsg));
publishSyncError(eControlArea, errors, e);
}
}
}
}
if (routingCriteriaErrors) {
logger.fatal("There were errors instantiating the 'RoutingCriteriaClass' (or classes) for the Application " +
aConfig.getName() + " can't route to target without the required Routing Criteria rules. Will continue to the next application.");
continue;
}
else {
// determine if we should route to this application based on message content (if applicable).
boolean shouldRouteErrors = false;
boolean shouldRoute = true;
routeTest: for (int xx=0; xx<routingCriteriaObjects.size(); xx++) {
RoutingCriteriaCommand rcc = (RoutingCriteriaCommand)routingCriteriaObjects.get(xx);
try {
if (rcc.shouldRoute(aConfig, inDoc) == false) {
shouldRoute = false;
break routeTest;
}
}
catch (Exception e) {
shouldRouteErrors = true;
String errMsg = "Exception executing 'RoutingCriteriaClass' " + rcc.getClass().getName() +
" for Application: " + aConfig.getName() + " Exception: " + e.getMessage();
logger.fatal(errMsg);
logger.fatal(e.getMessage(), e);
ArrayList errors = new ArrayList();
errors.add(buildError("application", "OpenEAI_ROUTER-2003", errMsg));
publishSyncError(eControlArea, errors, e);
}
}
if (shouldRouteErrors) {
logger.fatal("There were errors executing the 'RoutingCriteriaClass' (or classes) for the Application " +
aConfig.getName() + " can't route to target without the required Routing Criteria rules. Will continue to the next application.");
continue;
}
if (shouldRoute == false) {
logger.info("Application " + aConfig.getName() +
" doesn't want to be routed to based on the contents of the " +
msgObject + "-" + msgAction + " message. Will continue to the next application.");
continue;
}
}
// process and route the message to the target application...
// if we get here, we know the application is interested in the message we've consumed
// and there was nothing in the message content that prohibited it from being
// routed.
if (anXeo != null) {
// Now, build the object from the XML passed in.
// Populate the object with enterprise values
logger.info("Message Number: " + messageNumber + " - Building a " + msgObject +
" message object for Gateway " + aConfig.getName());
anXeo.getXmlEnterpriseObject().buildObjectFromInput(eInput);
logger.debug("Built object out of the input.");
// Now build an element containing application specific values
// for the current application (if applicable).
// 4/28/2004 TJ - only do this if the translateToTargetValues property is
// true. If this property is false, we won't do the 'reverse' translation
// we'll just route the element from the message we consumed in its
// current state.
Element eOut = null;
if (translateToTargetValues) {
logger.debug("Creating an XML Element out of the " + anXeo.getClass().getName() +
" for application " + aConfig.getName());
eOut = (Element)anXeo.getXmlEnterpriseObject().buildOutputFromObject(aConfig.getName());
}
else {
logger.info("NOT translating to target specific values.");
eOut = (Element)eInput.clone();
}
// 4/28/2004 TJ - commenting out this code because it's really not needed
// and it will slow things down just a bit...
// logger.debug("Built Element from object.");
// XMLOutputter xmlOutTemp = new XMLOutputter();
// logger.debug("XML is: \n" + xmlOutTemp.outputString(eOut));
// Now, we need to re-populate the document with the application
// specific information.
// Rebuild the control area
Element newControlArea = (Element)eControlArea.clone();
try {
newControlArea = buildControlArea(newControlArea, pubSub, provideTargetAppName, provideSourceControlArea, aConfig.getName());
outDoc.getRootElement().removeChild(eControlArea.getName());
outDoc.getRootElement().addContent(newControlArea);
}
catch (EnterpriseFieldException e) {
String errMsg = "Error building control area for message I'm " +
"forwarding to Application: " + aConfig.getName();
logger.fatal(errMsg);
logger.fatal(e.getMessage(), e);
ArrayList errors = new ArrayList();
errors.add(buildError("application", "OpenEAI_ROUTER-2004", errMsg));
publishSyncError(eControlArea, errors, e);
continue;
}
try {
if (msgAction.equalsIgnoreCase(CREATE_ACTION)) {
// Rebuild the data area
outDoc.getRootElement().removeChild(DATA_AREA);
outDoc.getRootElement().addContent(new Element(DATA_AREA));
outDoc.getRootElement().getChild(DATA_AREA).addContent(new Element(dataAreaChild));
outDoc.getRootElement().
getChild(DATA_AREA).
getChild(dataAreaChild).
addContent(eOut);
}
else if (msgAction.equalsIgnoreCase(DELETE_ACTION)) {
// Rebuild the data area
outDoc.getRootElement().removeChild(DATA_AREA);
outDoc.getRootElement().addContent(new Element(DATA_AREA));
outDoc.getRootElement().getChild(DATA_AREA).addContent(new Element(dataAreaChild));
Element eDeleteAction = (Element)inDoc.getRootElement().
getChild(DATA_AREA).
getChild(dataAreaChild).
getChild("DeleteAction").clone();
outDoc.getRootElement().
getChild(DATA_AREA).
getChild(dataAreaChild).
addContent(eDeleteAction);
outDoc.getRootElement().
getChild(DATA_AREA).
getChild(dataAreaChild).
addContent(eOut);
}
else if (msgAction.equalsIgnoreCase(UPDATE_ACTION)) {
Element eBaseline = outDoc.getRootElement().
getChild(DATA_AREA).
getChild("BaselineData").
getChild(msgObject);
ActionableEnterpriseObject anXeoBaseline =
(ActionableEnterpriseObject)aConfig.getObject(msgObjectName);
// Rebuild the data area
outDoc.getRootElement().removeChild(DATA_AREA);
outDoc.getRootElement().addContent(new Element(DATA_AREA));
outDoc.getRootElement().getChild(DATA_AREA).addContent(new Element(dataAreaChild));
outDoc.getRootElement().getChild(DATA_AREA).addContent(new Element("BaselineData"));
anXeoBaseline.getXmlEnterpriseObject().buildObjectFromInput(eBaseline);
Element eBaselineOut = null;
if (translateToTargetValues) {
logger.debug("Creating a Baseline XML Element out of the " + anXeo.getClass().getName() +
" for application " + aConfig.getName());
eBaselineOut = (Element)anXeoBaseline.getXmlEnterpriseObject().buildOutputFromObject(aConfig.getName());
}
else {
logger.info("NOT translating BaselineData to target specific values.");
eBaselineOut = (Element)eBaseline.clone();
}
// New data
outDoc.getRootElement().
getChild(DATA_AREA).
getChild(dataAreaChild).
addContent(eOut);
// Baseline data
outDoc.getRootElement().
getChild(DATA_AREA).
getChild("BaselineData").
addContent(eBaselineOut);
}
else {
// Error
String errMsg = "Invalid message action: " + msgAction + " can't continue processing this messagin.";
logger.fatal(errMsg);
ArrayList errors = new ArrayList();
errors.add(buildError("system", "OpenEAI_ROUTER-2005", errMsg));
publishSyncError(eControlArea, errors);
return;
}
}
catch (Exception e) {
String errMsg = "Error building document for message I'm " +
"forwarding to Application: " + aConfig.getName();
logger.fatal(errMsg);
logger.fatal(e.getMessage(), e);
ArrayList errors = new ArrayList();
errors.add(buildError("application", "OpenEAI_ROUTER-2006", errMsg));
publishSyncError(eControlArea, errors, e);
continue;
}
if (getOutboundXmlValidation()) {
XmlValidator xmlValidator = new XmlValidator();
boolean isValid = true;
synchronized (outDoc) {
isValid = xmlValidator.isValid(outDoc);
}
if (isValid == false) {
String errorMsg = outDoc.getRootElement().getName() +
" document is not valid after TransRoute modifications. Can't forward message.";
logger.fatal(errorMsg);
XMLOutputter xmlOut = new XMLOutputter();
logger.fatal("Document content is: \n" + xmlOut.outputString(outDoc));
ArrayList errors = new ArrayList();
errors.add(buildError("system", "OpenEAI_ROUTER-2007", errorMsg));
publishSyncError(eControlArea, errors);
continue;
}
}
if (dumpOutput) {
String dumpDir = getMessageDumpDirectory();
if (dumpDir.lastIndexOf("/") != dumpDir.length() - 1) {
dumpDir = dumpDir + "/";
}
dumpDir = dumpDir + "output/" + aConfig.getName() + "/";
try {
writeMessageToFile(msgObject, outDoc, dumpDir);
}
catch (IOException e) {
logger.fatal("Exception occurred writing output message to file. Processing will continue. Exception: " + e.getMessage());
}
}
// if routing is disabled for this application, we're just going to continue here.
// we're waiting until here to make this decision so we can let it "dump" the output document
// that would be routed without actually routing it...
if (routeToTarget == false) {
logger.info("Routing is 'disabled' to target " + aConfig.getName() + " continuing to next target.");
continue;
}
// Now, take the "transformed" message and forward it on to the application
// using the pubsub producer associated to that application.
TextMessage outMessage = pubSub.createTextMessage();
// XMLOutputter xmlOut = new XMLOutputter();
try {
// outMessage.setText(xmlOut.outputString(outDoc));
// outMessage.setStringProperty(MessageProducer.COMMAND_NAME, anXeo.getCommandName());
// outMessage.setStringProperty(MessageProducer.MESSAGE_ID, getMessageId(newControlArea));
// pubSub.publishMessage(outMessage);
MessageId msgId = getMessageId(newControlArea);
anXeo.setMessageId(msgId);
pubSub.publishMessage(anXeo, outDoc);
logger.info("Message Number: " + messageNumber + " - Routed " + msgObject +
" message to Gateway: " + aConfig.getName());
}
catch (Exception e) {
logger.fatal(e.getMessage(), e);
ArrayList errors = new ArrayList();
errors.add(buildError("system", "OpenEAI_ROUTER-2008", "Exception occurred routing " +
msgObject + "/" + msgAction + " message to gateway " + aConfig.getName() ));
publishSyncError(eControlArea, errors, e);
}
}
else {
// Should never get here!
logger.debug("Could not find a " + msgObjectName +
" in the AppConfig for " + aConfig.getName());
}
}
}
catch (Exception e) {
String errMessage = "Exception occurred building the object or building the output. Exception: " + e.getMessage();
logger.fatal(e.getMessage(), e);
ArrayList errors = new ArrayList();
errors.add(buildError("system", "OpenEAI_ROUTER-2009", errMessage));
publishSyncError(eControlArea, errors, e);
return;
}
}
/*
private String getMessageId(Element controlArea) {
Element eMsgId = controlArea.getChild("Sender").getChild("MessageId");
if (eMsgId != null) {
MessageId msgId = new MessageId();
try {
msgId.buildObjectFromInput(eMsgId);
}
catch (Exception e) {
logger.fatal("Error building message id from XML. Exception: " + e.getMessage());
return null;
}
return msgId.toString();
}
else {
return null;
}
}
*/
private MessageId getMessageId(Element controlArea) {
Element eMsgId = controlArea.getChild("Sender").getChild("MessageId");
if (eMsgId != null) {
MessageId msgId = new MessageId();
try {
msgId.buildObjectFromInput(eMsgId);
}
catch (Exception e) {
logger.fatal("Error building message id from XML. Exception: " + e.getMessage());
return null;
}
return msgId;
}
else {
return null;
}
}
private synchronized Element buildControlArea(Element controlArea,
PubSubProducer pubSub, boolean provideTargetAppName,
boolean provideSourceControlArea, String targetAppName) throws EnterpriseFieldException {
// We will:
// - the the message id and authentication elements out of the sender's control area
// - use the authentication information in the message we're forwarding on
// - create new message id information from the producer passed in and our app id (TransRoute)
// Add the current ControlAreaSync element passed in to the SourceInfo
// element of the control area we're building. That new sourceinfo element
// will be added later (at the bottom of this method) when all the other
// new elements are added.
Element eSourceInfo = controlArea.getChild("SourceInfo");
boolean replaceSourceInfo = false;
if (eSourceInfo == null) {
eSourceInfo = new Element("SourceInfo");
}
else {
replaceSourceInfo = true;
}
if (provideSourceControlArea) {
Element sourceInfoControlArea = (Element)controlArea.clone();
eSourceInfo.addContent(sourceInfoControlArea);
replaceSourceInfo = true;
}
controlArea.removeChild("SourceInfo");
// Set the sender element
Sender oldSender = new Sender();
Element eInputSender = controlArea.getChild("Sender");
try {
oldSender.buildObjectFromInput(eInputSender);
}
catch (EnterpriseLayoutException e) {
throw new EnterpriseFieldException(e.getMessage());
}
controlArea.removeChild("Sender");
Authentication oldAuth = oldSender.getAuthentication();
MessageId newMsgId = new MessageId();
newMsgId.setSenderAppId("EnterpriseTransRouter");
if (pubSub.getProducerId(null) == null) {
newMsgId.setProducerId("EmptyProducerId");
}
else {
newMsgId.setProducerId(pubSub.getProducerId(null).getId());
}
// Since a producer's message sequence gets reset everytime
// it's retrieved from AppConfig, we have to maintain our
// own message sequence number in this command
// so the messages we forward on to other gateways
// will have a unique message id in their control area.
m_messageSequence++;
newMsgId.setMessageSeq(Integer.toString(m_messageSequence));
Sender newSender = new Sender();
if (oldSender.getTestId() != null) {
newSender.setTestId(oldSender.getTestId());
}
newSender.setAuthentication(oldAuth);
newSender.setMessageId(newMsgId);
Element eNewSender = null;
try {
eNewSender = (Element)newSender.buildOutputFromObject();
}
catch (EnterpriseLayoutException e) {
throw new EnterpriseFieldException(e.getMessage());
}
// Set the datetime element
controlArea.removeChild("Datetime");
Datetime dt = new Datetime();
Element eDatetime = null;
try {
eDatetime = (Element)dt.buildOutputFromObject();
}
catch (EnterpriseLayoutException e) {
throw new EnterpriseFieldException(e.getMessage());
}
if (replaceSourceInfo) {
controlArea.addContent(eSourceInfo);
}
// If the target we're routing to wants it, we're going to provide the name of that
// target app so we can know more about these messages if they have problems and get
// logged via EnterpriseSyncErrorLogger
Element eTargetInfoTemp = null;
Element eTargetInfo = null;
eTargetInfoTemp = controlArea.getChild("TargetInfo");
controlArea.removeChild("TargetInfo");
if (provideTargetAppName) {
if (eTargetInfoTemp == null) {
eTargetInfo = new Element("TargetInfo");
}
else {
eTargetInfo = (Element)eTargetInfoTemp.clone();
}
eTargetInfo.removeChild("TargetAppName");
Element eTargetAppName = new Element("TargetAppName");
eTargetAppName.setText(targetAppName);
eTargetInfo.addContent(eTargetAppName);
controlArea.addContent(eTargetInfo);
}
else if (eTargetInfoTemp != null) {
// have to add the original one back in...
eTargetInfo = (Element)eTargetInfoTemp.clone();
controlArea.addContent(eTargetInfo);
}
controlArea.addContent(eNewSender);
controlArea.addContent(eDatetime);
return controlArea;
}
}