/*******************************************************************************
$Source: /cvs/repositories/openii3/project/java/source/org/openeai/afa/ScheduledCommandImpl.java,v $
$Revision: 1.19 $
*******************************************************************************/
/**********************************************************************
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.afa;
import org.openeai.*;
import org.openeai.xml.*;
import org.openeai.moa.objects.resources.*;
import org.openeai.moa.*;
import org.openeai.config.*;
import org.openeai.jms.producer.PubSubProducer;
import org.openeai.jms.producer.MessageProducer;
import javax.jms.JMSException;
import javax.jms.TextMessage;
import java.util.*;
import java.io.*;
import org.jdom.output.XMLOutputter;
import org.jdom.Document;
import org.jdom.Element;
import org.apache.log4j.*;
import org.openeai.transport.ProducerId;
//import org.openeai.transport.SyncService;
/**
* This is the parent class of all ScheduledCommands. These commands are Java
* components that are used to execute specific business logic associated to a
* given Schedule. It provides convenience methods for its decendants.
* <P>
* @author Tod Jackson (tod@openeai.org)
* @author Steve Wheat (steve@openeai.org)
* @version 3.0 - 28 January 2003
* @see Schedule Schedule
* @see ScheduledCommand
*/
public class ScheduledCommandImpl extends OpenEaiObject {
public static Category logger;
private HashMap m_msgComponents = new HashMap();
private Vector m_appConfigs = new Vector();
private AppConfig m_appConfig = null;
private PubSubProducer m_syncErrorPublisher = null;
private boolean m_inboundXmlValidation = false;
private boolean m_outboundXmlValidation = false;
private String m_syncErrorSyncPrimedDocumentUri = "";
/**
* Constructor
*/
public ScheduledCommandImpl(CommandConfig cConfig) throws InstantiationException {
setAppName(cConfig.getAppName());
setAppConfig(cConfig.getAppConfig());
setInboundXmlValidation(cConfig.getInboundXmlValidation());
setOutboundXmlValidation(cConfig.getOutboundXmlValidation());
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;
}
try {
PropertyConfig pConfig = null;
String uri = null;
try {
pConfig = (PropertyConfig)getAppConfig().getObject("SyncErrorSyncProperties");
}
catch (Exception e) {
// ignoring this error and letting it fall through...
}
if (pConfig == null) {
logger.warn("Missing 'SyncErrorSyncProperties' PropertyConfig element in configuration document. Processing will continue.");
}
else {
uri = pConfig.getProperties().getProperty("SyncErrorSyncPrimedDocumentUri",null);
}
if (uri == null) {
logger.warn("Missing 'SyncErrorSyncPrimedDocumentUri' property in 'SyncErrorSyncProperties' PropertyConfig element. Processing will continue.");
}
setSyncErrorSyncPrimedDocumentUri(uri);
}
catch (Exception e) {
logger.fatal("[ScheduledCommandImpl] Exception occurred obtaining 'SyncErrorSyncPrimedDocumentUri' property. Can't continue.");
logger.fatal(e.getMessage(), e);
throw new InstantiationException(e.getMessage());
}
// sync error publisher (if one exists)...
try {
PubSubProducer syncErrorPublisher = (PubSubProducer)getAppConfig().getObject("SyncErrorPublisher");
if (syncErrorPublisher == null) {
logger.warn("[ScheduledCommandImpl] no 'SyncErrorPublisher' in Config document. Processing will continue.");
return;
}
setSyncErrorPublisher(syncErrorPublisher);
if (getSyncErrorPublisher().isStarted() == false) {
try {
if (getSyncErrorPublisher().startPublisher() == false) {
String errorMessage = "[ScheduledCommandImpl] Could not start SyncErrorPublisher!";
logger.fatal(errorMessage);
throw new InstantiationException(errorMessage);
}
}
catch (JMSException e) {
String errorMessage = "[ScheduledCommandImpl] Exception starting SyncErrorPublisher! Exception: " + e.getMessage();
logger.fatal(errorMessage, e);
throw new InstantiationException(errorMessage);
}
}
}
catch (Exception e) {
String errorMessage = "[ScheduledCommandImpl] Could not find SyncErrorPublisher in AppConfig! Exception: " + e.getMessage() + " Processing will continue.";
logger.warn(errorMessage);
}
}
/**
* Set inbound xml validation. This is used to determine whether or not to
* validate xml. Since Scheduled commands do not consume messages like
* ConsumerCommands, this attribute can be used for general decision making
* when considering whether or not to validate XML Documents that may exist as
* part of the Scheduled Command's execution.
* @param validate boolean
*/
protected void setInboundXmlValidation(boolean validate) {
m_inboundXmlValidation = validate;
}
/**
* Get inbound xml validation.
* @return boolean
*/
protected boolean getInboundXmlValidation() {
return m_inboundXmlValidation;
}
/**
* Set outbound xml validation. This is used to determine whether or not to
* validate xml. Since Scheduled commands do not return messages like
* ConsumerCommands, this attribute can be used for general decision making
* when considering whether or not to validate XML Documents that may exist as
* part of the Scheduled Command's execution.
* @param validate
*/
protected void setOutboundXmlValidation(boolean validate) {
m_outboundXmlValidation = validate;
}
/**
* Get outbound xml validation.
* @return boolean
*/
protected boolean getOutboundXmlValidation() {
return m_outboundXmlValidation;
}
/**
* Set SyncErrorPublisher associated to this command. This may be used by the
* Scheduled Command to notify enterprise services of errors that occur during
* the execution of an unattended scheduled command. It is not required.
* @param pubSub
*/
private void setSyncErrorPublisher(PubSubProducer pubSub) {
m_syncErrorPublisher = pubSub;
}
/**
* Get SyncErrorPublisher associated to this command. It is called by commands
* when they need to publish a sync error via the publishSyncError method also
* defined in ScheduledCommandImpl.
* @return PubSubProducer
*/
public final PubSubProducer getSyncErrorPublisher() {
return m_syncErrorPublisher;
}
/**
* Set AppConfig associated to this command. It is called in the
* constructor of ScheculedCommandImpl.
* @param aConfig
*/
protected void setAppConfig(AppConfig aConfig) {
m_appConfig = aConfig;
}
/**
* Get AppConfig associated to this command. It is called by commands.
* @return AppConfig
*/
public final AppConfig getAppConfig() {
return m_appConfig;
}
/**
* Returns the SyncErrorSync primed document that will be used if this ScheduledCommand
* needs to publish a Sync-Error-Sync messages if errors occur during the execution of
* the command.
* @return String the document URI to the primed Sync-Error-Sync document
*/
protected String getSyncErrorSyncPrimedDocumentUri() {
return m_syncErrorSyncPrimedDocumentUri;
}
/**
* Sets the SyncErrorSync primed document that will be used if this ScheduledCommand
* needs to publish a Sync-Error-Sync messages if errors occur during the execution of
* the command.
* @param uri String the document URI to the primed Sync-Error-Sync document
*/
protected void setSyncErrorSyncPrimedDocumentUri(String uri) {
m_syncErrorSyncPrimedDocumentUri = uri;
}
/**
* This method is used to publish a Sync-Error-Sync message when/if the ScheduledCommand has
* any errors during processing (during the 'execute' method).
*<P>
* @param action String the action being performed. If a 'null' action is passed in, 'Unknown' is used. This
* will cause the Sync-Error-Sync document being published to be invalid because the list of actions
* is constrained in SctSegments.
*<P>
* @param errors java.util.List an ArrayList of org.openeai.moa.objects.Error objects that have
* been built with the appropriate error number, error type and error descriptions.
*<P>
* @param e Throwable an exception that has occurred in the ScheduledCommand. This exception will be added
* to the list of errors passed in.
*/
protected void publishSyncError(String action, java.util.List errors, Throwable e) {
org.openeai.moa.objects.resources.Error anError =
new org.openeai.moa.objects.resources.Error();
ByteArrayOutputStream bw = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(bw, true);
e.printStackTrace(pw);
String errMessage = "Exception: " + e.getMessage() + "\n" + bw.toString();
anError.setType("system");
anError.setErrorNumber("SCHEDULEDCOMMAND-1001");
anError.setErrorDescription(errMessage);
errors.add(anError);
publishSyncError(action, errors);
}
/**
* This method is used to publish a Sync-Error-Sync message when/if the ScheduledCommand has
* any errors during processing (during the 'execute' method).
*<P>
* @param action String the action being performed. If a 'null' action is passed in, 'Unknown' is used. This
* will cause the Sync-Error-Sync document being published to be invalid because the list of actions
* is constrained in SctSegments.
*<P>
* @param java.util.List a List of org.openeai.moa.objects.Error objects that have
* been built with the appropriate error number, error type and error descriptions.
*/
protected void publishSyncError(String action, java.util.List errors) {
XmlDocumentReader xmlReader = new XmlDocumentReader();
Document syncErrorDoc = null;
try {
syncErrorDoc =
xmlReader.initializeDocument(getSyncErrorSyncPrimedDocumentUri(), getOutboundXmlValidation());
if (syncErrorDoc == null) {
String errMessage = "[PublishSyncError] Missing 'SyncErrorSyncPrimedDocumentUri' property in configuration document. Can't publish sync error.";
logger.fatal(errMessage);
return;
}
}
catch (XmlDocumentReaderException e) {
logger.fatal("[PublishSyncError] Error initializing Sync-Error-Sync primed document, could not publish the sync error sync message.");
logger.fatal(e.getMessage(), e);
return;
}
Element eControlArea = getControlArea(syncErrorDoc.getRootElement());
Result aResult = new Result();
ProcessedMessageId processedMsgId = new ProcessedMessageId();
// Build the dynamic portion of the sync document's control area
// Our sender information will go in the "Sender" element of the primed
// document and in the ProcessedMessageId of the Result that gets built
// Set the sender element
// Build the ProcessedMessageId Object out of the contents of the
// application that had the error.
String messageSequence = Integer.toString(getSyncErrorPublisher().incrementMessageSequence());
try {
// processedMsgId is going to be the same as MessageId (built below)
processedMsgId.setProducerId(getSyncErrorPublisher().getProducerId(null).getId());
processedMsgId.setSenderAppId(getAppName());
processedMsgId.setMessageSeq(messageSequence);
}
catch (Exception e) {
logger.fatal("Error building ProcessedMessageId Element in Sync-Error-Sync message!");
logger.fatal(e.getMessage(), e);
}
// This is the control area information relevant to this Gateway
eControlArea.removeChild("Result");
eControlArea.removeChild("Datetime");
eControlArea.removeChild("Sender");
Sender sender = new Sender();
MessageId msgId = new MessageId();
msgId.setProducerId(getSyncErrorPublisher().getProducerId(null).getId());
msgId.setSenderAppId(getAppName());
msgId.setMessageSeq(messageSequence);
sender.setMessageId(msgId);
Authentication auth = new Authentication();
auth.setAuthUserId(getAppName());
sender.setAuthentication(auth);
Element eSender = null;
try {
eSender = (Element)sender.buildOutputFromObject();
}
catch (Exception e) {
logger.fatal("[publishSyncError1] Exception occurred building an Element from the Sender object. Can't publish Sync error. Exception: " + e.getMessage());
logger.fatal(e.getMessage(), e);
return;
}
// Set the datetime element
Datetime dt = new Datetime();
Element eDatetime = null;
try {
eDatetime = (Element)dt.buildOutputFromObject();
}
catch (Exception e) {
logger.fatal("[publishSyncError1] Exception occurred building an Element from the Datetime object. Can't publish Sync error. Exception: " + e.getMessage());
logger.fatal(e.getMessage(), e);
return;
}
eControlArea.addContent(eSender);
eControlArea.addContent(eDatetime);
if (action == null) {
action = "Unknown";
}
aResult.setAction(action);
aResult.setStatus("failure");
aResult.setProcessedMessageId(processedMsgId);
for (int i=0; i<errors.size(); i++) {
org.openeai.moa.objects.resources.Error anError =
(org.openeai.moa.objects.resources.Error)errors.get(i);
aResult.addError(anError);
}
try {
Element eResult = (Element)aResult.buildOutputFromObject();
eControlArea.addContent(eResult);
syncErrorDoc.getRootElement().removeContent(eControlArea);
syncErrorDoc.getRootElement().addContent(eControlArea);
TextMessage syncErrorMessage = getSyncErrorPublisher().createTextMessage();
XMLOutputter xmlOut = new XMLOutputter();
syncErrorMessage.setText(xmlOut.outputString(syncErrorDoc));
if (getSyncErrorPublisher().getDefaultCommandName().length() == 0 ||
getSyncErrorPublisher().getDefaultCommandName() == null) {
getSyncErrorPublisher().setDefaultCommandName("EnterpriseSyncErrorSync");
}
syncErrorMessage.setStringProperty(MessageProducer.MESSAGE_ID,msgId.toString());
syncErrorMessage.setStringProperty(MessageProducer.COMMAND_NAME,getSyncErrorPublisher().getDefaultCommandName());
//TODO: change this...
// getSyncErrorPublisher().publishMessage(syncErrorMessage);
}
catch (Exception e) {
logger.fatal("Error publishing Sync-Error-Sync message!");
logger.fatal(e.getMessage(), e);
}
}
/**
* 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;
}
/**
* 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.
* Clients should never need to call this method directly.
*
* @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;
}
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;
}
/**
*
**/
public void shutdown() throws ScheduledCommandException {
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 ScheduledCommandException(e.getMessage(), e);
}
}
}