/*******************************************************************************
$Source: /cvs/repositories/openii3/project/java/source/org/openeai/implementations/services/els/commands/EnterpriseSyncErrorLogger.java,v $
$Revision: 1.6 $
*******************************************************************************/
/**********************************************************************
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.services.els.commands;
import java.io.*;
import java.sql.*;
import java.text.*;
import java.util.*;
import javax.jms.*;
import org.apache.log4j.*;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.*;
import org.jdom.output.XMLOutputter;
import org.openeai.config.*;
import org.openeai.dbpool.*;
import org.openeai.jms.producer.*;
import org.openeai.jms.consumer.*;
import org.openeai.jms.consumer.commands.*;
import org.openeai.loggingutils.MailService;
import org.openeai.xml.*;
/**
* The EnterpriseSyncErrorLogger is a part of the OpenEAI "Enterprise Logging Service" (ELS).
* This particular command consumes messages published to an organization's
* Enterprise sync error logging topic and stores those messages. Synchronization error messages
* are published by any sync consuming gateway when errors occur processing those messages.
* This way, a concise record can be kept of each error that occurs within that gateway. This is of
* course in addition to any information that gets logged by the gateway at the time of the error.
* <P>
* By deploying a EnterpriseSyncErrorLogger, an organization can keep track of all errors that
* occur in other Sync consuming gateways and keep a record of them. Then, interfaces can be developed to query those errors
* in order to determine what errors have occurred etc. and link them back to the actual message that
* was consumed by the gateway that had the problem. Protocol information (SenderAppId, MessageId etc.)
* is used to link these messages together.
* <P>
* @author Alan Schuele and Greg Hunt
* @version #
*/
public class EnterpriseSyncErrorLogger extends SyncCommandImpl implements SyncCommand {
private String m_xmlFileSpec = null;
private EnterpriseConnectionPool m_connPool = null;
/**
* Constructor
* @param Command Config
*/
public EnterpriseSyncErrorLogger(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);
}
try {
PropertyConfig pConfig = new PropertyConfig();
pConfig = (PropertyConfig)getAppConfig().getObjectByType(pConfig.getClass().getName());
setProperties(pConfig.getProperties());
// get PropertyConfig Properties
PropertyConfig propConfig = (PropertyConfig)getAppConfig().getObject("SyncLoggerProperties");
if (propConfig == null) {
String msg = "Unable to find PropertyConfig 'SyncLoggerProperties'";
logger.fatal(msg);
throw new InstantiationException(msg);
}
Properties props = propConfig.getProperties();
// Initialize email settings
this.setToAddr(props.getProperty("toAddr"));
this.setFromAddr(props.getProperty("fromAddr"));
this.setMailHost(props.getProperty("mailHost"));
m_xmlFileSpec = props.getProperty("xmlFileSpec");
// Initialize db connection pool (actually, it's already initialized and ready for use in AppConfig.)
m_connPool = (EnterpriseConnectionPool)getAppConfig().getObject("EnterpriseSyncErrorLoggerDbPool");
logger.info("EnterpriseSyncErrorLogger, instantiated successfully.");
}
catch (Exception e) {
logger.fatal(e.getMessage(), e);
throw new InstantiationException(e.getMessage());
}
}
/**
* Takes the message passed in, breaks it into the appropriate parts and logs it
* to the repository.
* <P>
* @param int the message number associated to the message consumed by the consumer executing this command.
* @param javax.jms.Message the message consumed by the consumer. In this case, the message should
* always be a CoreMessaging/Sync/Error-Sync message.
* @throws CommandException if errors occur initializing the command with the message
* passed in. This particular command may throw this exception when it has errors
* turning the text associated to the Message into a JDOM Document or if it has problems
* persisting the contents. This is because it would be redundant for this consumer to
* publish CoreMessaging/Sync/Error-Sync messages to itself. Instead of publishing the Sync/Error-Sync
* message, this command may either throw an exception or write the message to the file system if
* it has problems persisting the message.
**/
/**
* Inserts the current contents of this object into the database (T_SYNC_ERR_MSG).
* The object was populated by the calling command (EnterpriseSyncLoggerCommand) and
* the java.sql.Connection object was retrieved from the Database Connection Pool object
* associated to this command.
* <P>
* This method not only logs the appropriate ControlArea information into the T_LOGGED_MESSAGE
* table but it breaks the actual message into segments if the message body is
* larger than 3900 bytes. This implementation was originally developed for an Oracle
* database and VARCHAR2 fields can be a maximum of 4000 characters. This is why the
* message body is broken into segments. Depending on the database implementation being
* used, this number may be different. The class could also be written to either allow this
* setting to be configurable or to break the message body into a universal segment that
* would work for any database. These individual segments are stored in the T_MESSAGE_SEGMENT
* table.
* <P>
* The ControlArea information (metadata) is inserted into the T_SYNC_ERR_MSG table. The
* information is pulled from the ControlArea of the message and inserted into the
* T_SYNC_ERR_MSG table:
* <P>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>ControlArea Element</TH>
* <TH>Column</TH>
* <TH>Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ControlAreaSync/Sender/MessageId/SenderAppId</TD>
* <TD>CONSUMING_APPL_NAME</TD>
* <TD>SenderAppId of the gateway that published the error.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ControlAreaSync/Sender/MessageId/ProducerId</TD>
* <TD>CONSUMING_PRODUCER_ID</TD>
* <TD>ProducerId associated to the gateway that published the error.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ControlAreaSync/Sender/MessageId/MessageSequence</TD>
* <TD>CONSUMING_MSG_SEQ</TD>
* <TD>MessageSequence associated to the gateay that published the error.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ControlAreaSync/Result/ProcessedMessageId/SenderAppId</TD>
* <TD>PRODUCING_APPL_NAME</TD>
* <TD>SenderAppId of the gateway that published the message consumed by the gateway that had the error.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ControlAreaSync/Result/ProcessedMessageId/ProducerId</TD>
* <TD>PRODUCING_PRODUCER_ID</TD>
* <TD>ProducerId of the gateway that published the message consumed by the gateway that had the error.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ControlAreaSync/Result/ProcessedMessageId/MessageSequence</TD>
* <TD>PRODUCING_MSG_SEQ</TD>
* <TD>MessageSequence of the gateway that published the message consumed by the gateway that had the error.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ControlAreaSync/Result@action</TD>
* <TD>PRODUCING_MSG_ACTION</TD>
* <TD>Message action associated to the message that consumed by the gateway that had the error.</TD>
* </TR>
* </TABLE>
* <P>
* The error information associated to the Sync-Error message consumed by this gateway is logged to
* the T_SYNC_ERROR table. This includes all Errors listed in the Result element of the Sync-Error
* message. A row will be inserted for each Error listed in the Result element. This information is stored as follows:
* <P>
* <TABLE BORDER=2 CELLPADDING=5 CELLSPACING=2>
* <TR>
* <TH>ControlArea Element</TH>
* <TH>Column</TH>
* <TH>Description</TH>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ControlAreaSync/Result/Error@type</TD>
* <TD>ERROR_TYPE</TD>
* <TD>The type of error that occurred in the consuming gateway when it tried to process the message.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ControlAreaSync/Result/Error/ErrorNumber</TD>
* <TD>ERROR_NUMBER</TD>
* <TD>The error number generated by the gateway that had the error processing the message.</TD>
* </TR>
* <TR HALIGN="left" VALIGN="top">
* <TD>ControlAreaSync/Result/Error/ErrorDescription</TD>
* <TD>CONSUMING_APPL_NAME</TD>
* <TD>The error description generated by the gateway that had the error processing the message.</TD>
* </TR>
* </TABLE>
* <P>
* @param int the message number associated to the message consumed by the consumer executing this command.
* @param javax.jms.Message the message consumed by the consumer. In this case, the message should
* always be a CoreMessaging/Sync/Error-Sync message.
* @throws CommandException if errors occur initializing the command with the message
* passed in. This particular command may throw this exception when it has errors
* turning the text associated to the Message into a JDOM Document or if it has problems
* persisting the contents. This is because it would be redundant for this consumer to
* publish CoreMessaging/Sync/Error-Sync messages to itself. Instead of publishing the Sync/Error-Sync
* message, this command may either throw an exception or write the message to the file system if
* it has problems persisting the message.
**/
public void execute(int messageNumber, Message aMessage) throws CommandException {
Document inDoc = null;
try {
inDoc = initializeInput(messageNumber, aMessage);
}
catch (Exception e) {
e.printStackTrace();
logger.fatal(e.getMessage(), e);
throw new CommandException(e.getMessage());
}
try {
insertMessage(inDoc);
}
catch (java.sql.SQLException e) {
e.printStackTrace();
logger.fatal(e.getMessage(), e);
throw new CommandException(e.getMessage());
}
}
private void insertMessage(Document doc) throws SQLException {
// Get the appropriate information out of the message
// Insert the values into the database along with the actual
// message as a string.
//boolean successOnInsert = false;
java.sql.Connection conn = null;
PreparedStatement insertStmt = null;
String consAppName, consProdId, consMsgSeq, prodAppName, prodProdId;
String prodMsgSeq, prodMsgAction, message;
//String logConsumerId = "SyncErrorLogConsumer";
String insertString =
"INSERT INTO T_SYNC_ERROR_MSG " +
" (CONSUMING_APPL_NAME, CONSUMING_PRODUCER_ID, CONSUMING_MSG_SEQ, PRODUCING_APPL_NAME, " +
" PRODUCING_PRODUCER_ID, PRODUCING_MSG_SEQ, PRODUCING_MSG_ACTION, MESSAGE, CREATE_DATE, CREATE_USER, MOD_DATE, MOD_USER) " +
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)";
String insertString2 =
"INSERT INTO T_SYNC_ERROR " +
" (SYNC_ERROR_MSG_ID, ERROR_TYPE, ERROR_NUMBER, ERROR_DESC, CREATE_DATE, CREATE_USER, MOD_DATE, MOD_USER) " +
"VALUES (?,?,?,?,?,?,?,?)";
String syncErrorMsgId=null, errorType, errorNumber, errorDescr;
Element controlArea = getControlArea(doc.getRootElement());
// Getting data to insert into T_SYNC_ERROR_MSG
consAppName = controlArea.getChild("Sender").getChild("MessageId").getChild("SenderAppId").getText();
consProdId = controlArea.getChild("Sender").getChild("MessageId").getChild("ProducerId").getText();
consMsgSeq = controlArea.getChild("Sender").getChild("MessageId").getChild("MessageSeq").getText();
prodAppName = controlArea.getChild("Result").getChild("ProcessedMessageId").getChild("SenderAppId").getText();
prodProdId = controlArea.getChild("Result").getChild("ProcessedMessageId").getChild("ProducerId").getText();
prodMsgSeq = controlArea.getChild("Result").getChild("ProcessedMessageId").getChild("MessageSeq").getText();
prodMsgAction = controlArea.getChild("Result").getAttribute("action").getValue();
// Getting data to insert into T_SYNC_ERROR
XMLOutputter xmlOut = new XMLOutputter();
ByteArrayOutputStream bArray = new ByteArrayOutputStream();
try {
xmlOut.output(doc, bArray);
}
catch (IOException e) {
logger.fatal(e.getMessage(), e);
errorHandler(e, doc); // Call routine to write doc out to a file
return;
}
byte[] b = bArray.toByteArray();
message = new String(b);
try {
conn = m_connPool.getConnection();
// conn.setAutoCommit(false);
// Setting to true to work around jdbc shortcomings - 1/24/02 Alan, Jun and Greg
conn.setAutoCommit(true);
insertStmt = conn.prepareStatement(insertString);
insertStmt.clearParameters();
insertStmt.setString(1, consAppName);
insertStmt.setString(2, consProdId);
insertStmt.setString(3, consMsgSeq);
insertStmt.setString(4, prodAppName);
insertStmt.setString(5, prodProdId);
insertStmt.setString(6, prodMsgSeq);
insertStmt.setString(7, prodMsgAction);
insertStmt.setString(8, message);
insertStmt.setTimestamp(9, new java.sql.Timestamp(System.currentTimeMillis()));
insertStmt.setString(10, conn.getMetaData().getUserName());
insertStmt.setTimestamp(11, new java.sql.Timestamp(System.currentTimeMillis()));
insertStmt.setString(12, conn.getMetaData().getUserName());
logger.debug("InsertStmt = " + insertStmt);
int insertRc = insertStmt.executeUpdate();
insertStmt.close();
if (insertRc != 1) {
// the insert wasn't successful
SQLException exc = new SQLException("Error inserting into T_SYNC_ERROR_MSG. Return Code: " + insertRc);
errorHandler(exc, doc);
throw exc;
}
else {
logger.info("Successfully inserted Message into T_SYNC_ERROR_MSG.");
// get id of row just entered
Statement stmt = conn.createStatement();
String query =
"SELECT SYNC_ERROR_MSG_ID FROM T_SYNC_ERROR_MSG WHERE " +
" CONSUMING_APPL_NAME = '" + consAppName + "' AND CONSUMING_PRODUCER_ID = '" +
consProdId + "' AND CONSUMING_MSG_SEQ = '"+ consMsgSeq +"'";
logger.debug("Query = " + query);
ResultSet rs = stmt.executeQuery(query);
rs.next();
syncErrorMsgId = rs.getString(1);
logger.debug("syncErrorMsgId from Select = " + syncErrorMsgId);
rs.close();
stmt.close();
logger.debug("about to insert error portion");
// Getting data to insert into T_SYNC_ERROR
insertStmt = conn.prepareStatement(insertString2);
Iterator itr = controlArea.getChild("Result").getChildren().iterator();
while (itr.hasNext()) {
Element errorElement = (Element)itr.next();
logger.debug(errorElement.getName());
if (errorElement.getName().equals("Error")) {
// Do something with these children
errorType = errorElement.getAttribute("type").getValue();
errorNumber = errorElement.getChild("ErrorNumber").getText();
errorDescr = errorElement.getChild("ErrorDescription").getText();
// check if greater than 3800 size
if (errorDescr.length() > 3800) {
errorDescr = errorDescr.substring(0, 3800);
}
logger.debug("about to insert error portion, errorNumber = " + errorNumber);
// Insert into T_SYNC_ERROR
insertStmt.clearParameters();
insertStmt.setString(1, syncErrorMsgId);
insertStmt.setString(2, errorType);
insertStmt.setString(3, errorNumber);
insertStmt.setString(4, errorDescr);
insertStmt.setTimestamp(5, new java.sql.Timestamp(System.currentTimeMillis()));
insertStmt.setString(6, conn.getMetaData().getUserName());
insertStmt.setTimestamp(7, new java.sql.Timestamp(System.currentTimeMillis()));
insertStmt.setString(8, conn.getMetaData().getUserName());
insertRc = insertStmt.executeUpdate();
if (insertRc != 1) {
// the insert wasn't successful
// conn.rollback();
SQLException exc = new SQLException("Error inserting. Return Code: " + insertRc);
// Call ErrorHandler
errorHandler(exc, doc);
throw exc;
}
else {
logger.info("Successfully inserted Message into T_SYNC_ERROR.");
//successOnInsert = true; insertStmt = conn.prepareStatement(insertString2);
}
}
}
insertStmt.close();
// conn.commit();
}
}
catch (SQLException se) { // Handle sql exeptions(s)
errorHandler(se, doc);
while (se != null) {
String errMsg = "";
errMsg ="SQLState: " + se.getSQLState() + " Message: " + se.getMessage()+
" Vendor: " + se.getErrorCode();
logger.fatal(errMsg + " " +se.getMessage(), se);
se = se.getNextException();
}
}
catch (Exception e) {
//if (successOnInsert == false) {
errorHandler(e, doc); // write message to a file
//}
logger.error(e.getMessage());
}
finally {
try {
insertStmt.close();
}
catch (SQLException see) {
logger.error(see.getMessage());
}
//m_connPool.releaseConnectierrorElement.getName()on(conn);
}
}
private void errorHandler(Exception exc, Document doc) {
exc.printStackTrace();
logger.error(exc.toString());
String datedDirectoryName = getDatedDirectoryName(m_xmlFileSpec);
boolean directoryChecker = this.isDirectoryFound(datedDirectoryName);
if (directoryChecker == false) {
directoryChecker = this.createDirectory(datedDirectoryName);
if (directoryChecker == false) {
logger.error("Error: Unable to write message to message-store. Now writing message to log.");
logger.error(doc.toString());
return;
}
}
// If you get to here, write out message to the directory
try {
// Write message out to directory specified in config xml doc
this.writeMessageToFile("", doc, datedDirectoryName);
}
catch (java.io.IOException ioe) {
logger.error("Error: Unable to write message to message-store directory: " + datedDirectoryName + " Now writing message to log.");
logger.error(doc.toString());
}
}
private String getDatedDirectoryName(String xmlFileSpec) {
int year, dayOfMonth, month;
Calendar today = new GregorianCalendar();
year = today.get(today.YEAR);
month = today.get(today.MONTH) + 1;
dayOfMonth = today.get(today.DAY_OF_MONTH);
String today_string = String.valueOf(year) + "-" +
String.valueOf(month) + "-" +
String.valueOf(dayOfMonth);
String datedDirectoryName = xmlFileSpec+"/"+today_string;
return datedDirectoryName;
}
private boolean isDirectoryFound(String xmlFileSpec) {
File messageStore = new File(xmlFileSpec);
return messageStore.exists();
}
private boolean createDirectory(String xmlFileSpec) {
File messageStore = new File(xmlFileSpec);
return messageStore.mkdirs();
}
public void sendEmail(String emailBody) {
// send email
try {
StringBuffer buf = new StringBuffer();
buf.append("Error in EnterpriseSyncErrorLogger. ");
buf.append("\n");
buf.append(emailBody);
buf.append("\n");
MailService mailService = new MailService(this.getFromAddr(), this.getToAddr() , "Error in EnterpriseSyncErrorLogger", buf.toString());
mailService.setMailHost(this.getMailHost());
mailService.sendMessage();
}
catch (Exception exc) {
logger.info("Error sending email message - " + exc);
}
}
}