package hk.hku.cecid.piazza.corvus.core.main.admin.hc.module;
import hk.hku.cecid.ebms.spa.EbmsProcessor;
import hk.hku.cecid.ebms.spa.dao.InboxDAO;
import hk.hku.cecid.ebms.spa.dao.InboxDVO;
import hk.hku.cecid.ebms.spa.dao.OutboxDAO;
import hk.hku.cecid.ebms.spa.dao.OutboxDVO;
import hk.hku.cecid.edi.as2.AS2Processor;
import hk.hku.cecid.edi.as2.dao.MessageDAO;
import hk.hku.cecid.edi.as2.dao.MessageDVO;
import hk.hku.cecid.edi.as2.dao.MessageDataSourceDAO;
import hk.hku.cecid.edi.as2.dao.RepositoryDAO;
import hk.hku.cecid.edi.as2.dao.RepositoryDVO;
import hk.hku.cecid.piazza.commons.dao.DAOException;
import hk.hku.cecid.piazza.commons.dao.Transaction;
import hk.hku.cecid.piazza.commons.mail.SmtpMail;
import hk.hku.cecid.piazza.commons.mail.SmtpMailException;
import hk.hku.cecid.piazza.commons.mail.SmtpMailProperties;
import hk.hku.cecid.piazza.commons.module.ActiveTask;
import hk.hku.cecid.piazza.commons.servlet.http.HttpDispatcherContext;
import hk.hku.cecid.piazza.commons.util.PropertyTree;
import hk.hku.cecid.piazza.corvus.core.main.admin.AdminMainProcessor;
import hk.hku.cecid.piazza.corvus.core.main.admin.hc.util.AdminProperties;
import hk.hku.cecid.piazza.corvus.core.main.admin.hc.util.AdminPropertiesException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import com.sun.mail.smtp.SMTPMessage;
public class SchedulerTask implements ActiveTask {
// private int retried;
private AdminProperties props;
/**
* Status messages
*/
private static final String IN_PROGESS = "processing";
private static final String COMPLETE = "success";
private static final String FAILED = "failed";
/**
* Default values
*/
private static final int DEFAULT_CUTOFF = 3;
private static final int DEFAULT_DAY = Calendar.SUNDAY;
private static final String DEFAULT_TIME = "00.00.00";
/**
* Formats
*/
public static final String DATE_FORMAT = "y.M.d.k.m.s";
public static final String TIME_FORMAT = "k.m.s";
public void execute() throws Exception {
/**
* Transactions
*/
Transaction as2 = null;
Transaction ebms = null;
try {
// load the properties
props = loadProps();
// check to see if housecleaning is turned on
if (!props.isOn()) {
return;
}
// check if the status is in progress
if (props.getStatus().equals(IN_PROGESS)) {
AdminLogging("Previous housecleaning was interrupted while in progress.");
AdminLogging("Rescheduling housecleaning.");
// set the status to failed
props.setStatus(FAILED);
props
.setReason("Previous housecleaning was interupted while in progress.");
// set the last run time
props.setLastRun(getDateFormat().format(new Date()));
// set the next run date
Date time = getTimeFormat().parse(props.getTime());
Calendar cal = GregorianCalendar.getInstance();
cal.setTime(time);
if (!setNextRunDateFromNow(props)) {
AdminError("Could not set the next run date.");
}
props.write();
}
if (checkStartTime(props.getNextRun())) {
// set the start status
setStartStatus();
if (checkNotExceeded(props.getNextRun())) {
// halt the context listeners
if (HttpDispatcherContext.getDefaultContext().halt()) {
// clean the as2 database
as2 = cleanAS2(props.getCutoff());
// clean the ebms database
ebms = cleanEBMS(props.getCutoff());
// commit the transactions
commitTx(new Transaction[] { as2, ebms });
AdminLogging("Transactions commited.");
// resume the context listeners
if (HttpDispatcherContext.getDefaultContext().resume()) {
// set the end status : success
setEndStatus(true, props);
props.setReason("");
} else {
// set the end status : fail - could not restart the
// context listeners
setEndStatus(false, props);
AdminError("Unable to start up servlet.");
props
.setReason("House cleaning was successful but unable to restart the servlet.");
}
} else {
// set the end status: failed - could not halt the
// context listeners
setEndStatus(false, props);
AdminError("Listeners cannot be halted.");
props.setReason("Listeners cannot be halted.");
}
} else {
// set the end status: fail - the runtime has expired
setEndStatus(false, props);
AdminError("Runtime has expired.");
props.setReason("Runtime has expired.");
}
// set the next run date from now
if (!setNextRunDateFromNow(props)) {
AdminError("Failed to update the next run date.");
}
props.write();
} else {
// return since the next run time has not been reached yet.
return;
}
}
// catch any exceptions related to malformed property entries
catch (AdminPropertiesException e) {
// roll back the transaction
rollbackTx(new Transaction[] { as2, ebms });
// resume the context listeners if they were halted
if (HttpDispatcherContext.getDefaultContext().isHalted()) {
HttpDispatcherContext.getDefaultContext().resume();
}
// set the default properties
setDefaultProperties(props);
// set the end status: fail
setEndStatus(false, props);
// log the stack trace
stackTraceToLog(e.getCause());
// set the reason of the exception
props.setReason(e.getMessage());
// set the next run date from now
setNextRunDateFromNow(props);
}
// catch any other exception
catch (Exception e) {
// roll back transaction if the transaction has not been commited
rollbackTx(new Transaction[] { as2, ebms });
// resume context listeners if they are halted
if (HttpDispatcherContext.getDefaultContext().isHalted()) {
HttpDispatcherContext.getDefaultContext().resume();
}
// log the stack trace of the exception
stackTraceToLog(e.getCause());
// set the end status: fail
setEndStatus(false, props);
// set the failure reason
props.setReason(e.getMessage());
// set the next run date
setNextRunDateFromNow(props);
}
try {
if (!props.getEmail().equals("") && !props.getSmtp().equals("")) {
AdminLogging("Sending email...");
sendMail(props, props.getReason().equals(""));
} else {
AdminLogging("Email not set.");
}
} catch (Exception e) {
AdminError("Unable to send email.");
stackTraceToLog(e);
}
}
/**
* Convenience method for logging errors on housecleaning.
*
* @param s
*/
private void AdminError(String s) {
AdminMainProcessor.core.log.error("Housecleaning: " + s);
}
/**
* Convenience method for logging general housecleaning info.
*
* @param s
*/
private void AdminLogging(String s) {
AdminMainProcessor.core.log.info("Housecleaning: " + s);
}
/**
* Convenience method for logging AS2 errors.
*
* @param s
*/
private void AS2Error(String s) {
AdminMainProcessor.core.log.error("AS2 Cleaning: " + s);
}
/**
* Convenience method for logging AS2.
*
* @param s
*/
private void AS2Logging(String s) {
AdminMainProcessor.core.log.info("AS2 Cleaning: " + s);
}
/**
* Checks to see whether the current time has exceeded the parameter 'time'.
*
* @param time
* @return
* @throws Exception
*/
private boolean checkNotExceeded(String time) throws Exception {
Calendar cur = GregorianCalendar.getInstance();
Calendar upperLimit = GregorianCalendar.getInstance();
try {
Date lowerLimit = getDateFormat().parse(time);
upperLimit.setTime(lowerLimit);
upperLimit.add(Calendar.MINUTE, 1);
return upperLimit.after(cur);
} catch (ParseException e) {
AdminError("Parse error when checking the start time.");
throw new Exception("Parse error when checking the start time.", e);
}
}
/**
* Check whether housecleaning should perform or not.
*
* @param time
* @return
* @throws ParseException
*/
private boolean checkStartTime(String time) throws Exception {
Date cur = new Date();
try {
Date run = getDateFormat().parse(time);
return cur.after(run);
} catch (ParseException e) {
AdminError("Parse error when checking the start time.");
throw new Exception("Parse error when checking the start time.", e);
}
}
/**
* Attempt to clean out the AS2 database
*
* @param months
* @throws Exception
* @throws DAOException
* @throws DAOException
*/
protected Transaction cleanAS2(int months) throws Exception {
try {
MessageDAO dao = (MessageDAO) AS2Processor.core.dao
.createDAO(MessageDAO.class);
Transaction tr = ((MessageDataSourceDAO) dao).getFactory()
.createTransaction();
RepositoryDAO repDao = (RepositoryDAO) AS2Processor.core.dao
.createDAO(RepositoryDAO.class);
dao.setTransaction(tr);
repDao.setTransaction(tr);
tr.begin();
List list = dao.findMessagesBeforeTime(months);
AS2Logging(Integer.toString(list.size())
+ " messages will be removed.");
AS2Logging("Initializing...");
Iterator itr = list.iterator();
MessageDVO dvo;
RepositoryDVO repDvo;
while (itr.hasNext()) {
dvo = (MessageDVO) itr.next();
repDvo = (RepositoryDVO) repDao.createDVO();
repDvo.setMessageId(dvo.getMessageId());
repDvo.setMessageBox(dvo.getMessageBox());
repDao.remove(repDvo);
dao.remove(dvo);
}
return tr;
} catch (DAOException e) {
AS2Error("Error encountered while cleaning.");
throw new Exception("Error encountered while cleaning AS2.", e);
}
}
/**
* Attempt to clean the EBMS messages.
*
* @param months
* @return
* @throws Exception
* @throws Exception
* @throws DAOException
*/
protected Transaction cleanEBMS(int months) throws Exception {
try {
hk.hku.cecid.ebms.spa.dao.MessageDAO dao = (hk.hku.cecid.ebms.spa.dao.MessageDAO) EbmsProcessor.core.dao
.createDAO(hk.hku.cecid.ebms.spa.dao.MessageDAO.class);
InboxDAO inboxDao = (InboxDAO) EbmsProcessor.core.dao
.createDAO(InboxDAO.class);
OutboxDAO outboxDao = (OutboxDAO) EbmsProcessor.core.dao
.createDAO(OutboxDAO.class);
hk.hku.cecid.ebms.spa.dao.RepositoryDAO repDao = (hk.hku.cecid.ebms.spa.dao.RepositoryDAO) EbmsProcessor.core.dao
.createDAO(hk.hku.cecid.ebms.spa.dao.RepositoryDAO.class);
Transaction tr = ((hk.hku.cecid.ebms.spa.dao.MessageDataSourceDAO) dao)
.getFactory().createTransaction();
dao.setTransaction(tr);
inboxDao.setTransaction(tr);
outboxDao.setTransaction(tr);
repDao.setTransaction(tr);
tr.begin();
List list = dao.findMessagesBeforeTime(months);
EBMSLogging(Integer.toString(list.size())
+ " messages will be removed.");
EBMSLogging("Initializing...");
Iterator itr = list.iterator();
hk.hku.cecid.ebms.spa.dao.MessageDVO dvo;
InboxDVO inboxDvo;
OutboxDVO outboxDvo;
hk.hku.cecid.ebms.spa.dao.RepositoryDVO repDvo;
while (itr.hasNext()) {
dvo = (hk.hku.cecid.ebms.spa.dao.MessageDVO) itr.next();
if (dvo.getMessageBox().equals("inbox")) {
/**
* delete the inbox entry
*/
inboxDvo = (InboxDVO) inboxDao.createDVO();
inboxDvo.setMessageId(dvo.getMessageId());
inboxDao.remove(inboxDvo);
} else if (dvo.getMessageBox().equals("outbox")) {
/**
* delete the outbox entry
*/
outboxDvo = (OutboxDVO) outboxDao.createDVO();
outboxDvo.setMessageId(dvo.getMessageId());
outboxDao.remove(outboxDvo);
} else {
EBMSError("Unknown value in MessageBox relation.");
throw new Exception(
"Error, unknown value in MessageBox relation.");
}
/**
* remove entry in repository
*/
repDvo = (hk.hku.cecid.ebms.spa.dao.RepositoryDVO) repDao
.createDVO();
repDvo.setMessageId(dvo.getMessageId());
repDvo.setMessageBox(dvo.getMessageBox());
repDao.remove(repDvo);
/**
* finally remove from message table
*/
dao.remove(dvo);
}
return tr;
} catch (DAOException e) {
EBMSError("Error encountered while cleaning.");
throw new Exception("Error encountered while cleaning EBmS.", e);
}
}
/**
* Commit the transactions. If null do nothing. Note: Tx should never be
* null as commitTx should only be called when all txs needing commit are
* valid.
*
* @param txs
* @throws Exception
*/
private void commitTx(Transaction[] txs) throws Exception {
try {
for (int i = 0; i < txs.length; i++) {
if (txs[i] != null) {
txs[i].commit();
}
}
} catch (DAOException e) {
AdminError("Exception thrown during the commit.");
throw new Exception("Exception thrown during commit.", e);
}
}
/**
* Convenience method to create a message associated with the session ses.
* If success is true, String reasonFailed is ignored. If success is false
* and reasonFailed is null, it will be set to "Unknown".
*
* @param ses
* @param time
* @param attempt
* @param result
* @return
* @throws MessagingException
* @throws MessagingException
* @throws MessagingException
* @throws AddressException
*/
private SMTPMessage createMessage(Session ses, Date time, boolean success,
String result, String reasonFailed) throws MessagingException {
SMTPMessage message = new SMTPMessage(ses);
String tag;
if (success) {
tag = "[Success]";
} else {
tag = "[Failed]";
}
try {
InternetAddress from = new InternetAddress();
from.setAddress(ses.getProperty("mail.smtp.from"));
from.setPersonal("Hermes2 Admin");
message.setFrom(from);
message.setSubject(tag + "Hermes2 housecleaning");
String theContent = "======== Report for Hermes2 Housecleaning ======== "
+ "\n"
+ "\n Hermes2 housecleaning has recently been invoked"
+ "\n Date: " + time + "\n Result: " + result;
if (success) {
message.setText(theContent);
} else {
if (reasonFailed == null) {
reasonFailed = "Unknown";
}
message.setText(theContent + "\n Reason for failure: "
+ reasonFailed + "\n\n"
+ " Please refer to the log files for more details.");
}
} catch (MessagingException e) {
AdminError("Unable to create the message. Messaging Exception thrown.");
stackTraceToLog(e);
throw e;
} catch (UnsupportedEncodingException e) {
AdminError("Unsupported encoding in email 'from' name.");
}
return message;
}
/**
* Convenience method for logging errors for EbMS cleaning.
*
* @param msg
*/
private void EBMSError(String msg) {
AdminMainProcessor.core.log.error("EBmS Cleaning: " + msg);
}
/**
* Convenience method for logging general messages for EbMS cleaning.
*
* @param msg
*/
private void EBMSLogging(String msg) {
AdminMainProcessor.core.log.info("EBmS Cleaning: " + msg);
}
/**
* Return the date formatter.
*
* @return
*/
private SimpleDateFormat getDateFormat() {
return new SimpleDateFormat(DATE_FORMAT);
}
/**
* Return the time formatter.
*
* @return
*/
private SimpleDateFormat getTimeFormat() {
return new SimpleDateFormat(TIME_FORMAT);
}
/**
* Load the admin properties.
*
* @return
* @throws Exception
*/
private AdminProperties loadProps() {
return new AdminProperties(
(PropertyTree) AdminMainProcessor.core.properties);
}
/**
* Rollback the transactions. If null, do nothing.
*
* @param txs
* @throws Exception
*/
private void rollbackTx(Transaction[] txs) throws Exception {
try {
for (int i = 0; i < txs.length; i++) {
if (txs[i] != null) {
txs[i].rollback();
}
}
} catch (DAOException e) {
AdminError("Exception thrown during rollback.");
throw new Exception("Exception thrown during rollback.", e);
}
}
/**
* Convenience method to send the notification mail.
*
* @param p
* @param success
* @throws Exception
*/
private void sendMail(AdminProperties p, boolean success) throws Exception {
try {
SmtpMailProperties mailProps = new SmtpMailProperties();
mailProps.setHost(p.getSmtp());
mailProps.setFrom(p.getEmail());
mailProps.setUsername(p.getUsername());
mailProps.setPassword(p.getPassword());
mailProps.setPort(p.getPort());
SmtpMail mail = new SmtpMail(mailProps, false);
SMTPMessage msg;
if (success) {
msg = createMessage(mail.getSession(), new Date(), success, p
.getStatus(), null);
} else {
msg = createMessage(mail.getSession(), new Date(), success, p
.getStatus(), p.getReason());
}
mail.transportConnect();
mail.send(msg, new Address[] { new InternetAddress(p.getEmail()) });
mail.transportClose();
} catch (AdminPropertiesException e) {
AdminError("Invalid mail properties.");
throw new Exception(
"Invalid mail properties. Unable to send mail.", e);
} catch (SmtpMailException e) {
AdminError("SMTP Mail exception thrown.");
throw new Exception("Unable to send mail.", e);
} catch (MessagingException e) {
AdminError("Messaging exception thrown.");
throw new Exception(
"Messaging exception thrown. Unable to send mail.", e);
}
}
/**
* Set and write default properties to the properties file.
*
* @throws Exception
*
* @throws AdminPropertiesException
*/
private void setDefaultProperties(AdminProperties p) throws Exception {
p.setOn(true);
p.setDay(DEFAULT_DAY);
p.setLastRun("");
p.setCutoff(DEFAULT_CUTOFF);
p.setStatus("");
p.setTime(DEFAULT_TIME);
if (p.getEmail() == null) {
p.setEmail("");
}
if (p.getPassword() == null) {
p.setPassword("");
}
p.setPort(25);
if (p.getReason() == null) {
p.setReason("The property file was invalid and had to be reset.");
}
if (p.getUsername() == null) {
p.setUsername("");
}
if (p.getSmtp() == null) {
p.setSmtp("");
}
try {
if (!setNextRunDateFromNow(p)) {
AdminError("Could not set the next run time.");
}
p.write();
AdminLogging("Certain values were missing or null from the properties file. Resetting default values.");
} catch (AdminPropertiesException e) {
AdminError("Exception occured while updating the properties.");
throw new Exception(
"Exception occured while updating the properties.", e);
}
}
/**
* Set and write properties to indicate completion or failure.
*
* @throws AdminPropertiesException
*/
private void setEndStatus(boolean success, AdminProperties p) {
if (success) {
p.setStatus(COMPLETE);
AdminLogging("Housecleaning has completed successfully.");
} else {
p.setStatus(FAILED);
AdminError("Housecleaning was unsuccessful.");
}
}
/**
* Set and write properties to indicate initialization of housecleaning.
*
* @throws Exception
*
* @throws Exception
*
*/
private void setStartStatus() throws Exception {
props.setStatus(IN_PROGESS);
props.setLastRun(getDateFormat().format(new Date()));
try {
props.write();
} catch (AdminPropertiesException e) {
AdminError("Exception occured while updating the properties.");
throw new Exception(
"Exception occured while updating the properties.", e);
}
}
/**
* Convenience method for printing the stack trace to the log file.
*
* @param e
*/
private void stackTraceToLog(Throwable e) {
StackTraceElement[] list = e.getStackTrace();
for (int index = 0; index < list.length; index++) {
AdminMainProcessor.core.log.debug("Housecleaning: "
+ list[index].toString());
}
}
public boolean isRetryEnabled() {
return false;
}
public long getRetryInterval() {
return 0;
}
public int getMaxRetries() {
return 0;
}
public void setRetried(int retried) {
// this.retried = retried;
}
public void onFailure(Throwable e) {
}
public void onAwake() {
}
public boolean isSucceedFast() {
return false;
}
public boolean setNextRunDateFromNow(AdminProperties properties) {
try {
Calendar runTime = GregorianCalendar.getInstance();
runTime.setTime(getTimeFormat().parse(properties.getTime()));
int day_of_week = properties.getDay();
int hour = runTime.get(Calendar.HOUR_OF_DAY);
int min = runTime.get(Calendar.MINUTE);
int sec = runTime.get(Calendar.SECOND);
// get an instance of the calendar
Calendar newTime = GregorianCalendar.getInstance();
int dif = day_of_week - newTime.get(Calendar.DAY_OF_WEEK);
if (dif < 0) {
dif = 7 + dif;
}
if (dif == 0) {
if (hour < newTime.get(Calendar.HOUR_OF_DAY)
|| min < newTime.get(Calendar.MINUTE)
|| sec < newTime.get(Calendar.SECOND)) {
dif = 7 + dif;
} else {
}
}
// add the difference of days
newTime.add(Calendar.DATE, dif);
// set the time
newTime.set(Calendar.HOUR_OF_DAY, hour);
newTime.set(Calendar.MINUTE, min);
newTime.set(Calendar.SECOND, sec);
properties.setNextRun(getDateFormat().format(newTime.getTime()));
return true;
} catch (AdminPropertiesException e) {
return false;
} catch (ParseException e) {
return false;
}
}
public static Date getNextRunDateFromNow(int day_of_week, int hour, int min, int sec) {
// get an instance of the calendar
Calendar newTime = GregorianCalendar.getInstance();
int dif = day_of_week - newTime.get(Calendar.DAY_OF_WEEK);
if (dif < 0) {
dif = 7 + dif;
}
if (dif == 0) {
if (hour < newTime.get(Calendar.HOUR_OF_DAY)
|| min < newTime.get(Calendar.MINUTE)
|| sec < newTime.get(Calendar.SECOND)) {
dif = 7 + dif;
} else {
}
}
// add the difference of days
newTime.add(Calendar.DATE, dif);
// set the time
newTime.set(Calendar.HOUR_OF_DAY, hour);
newTime.set(Calendar.MINUTE, min);
newTime.set(Calendar.SECOND, sec);
return newTime.getTime();
}
}