package com.knowgate.scheduler;
import java.sql.DriverManager;
import java.sql.SQLException ;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.util.Properties;
import java.util.LinkedList;
import java.util.ListIterator;
import javax.mail.MessagingException;
import com.knowgate.jdc.JDCConnection;
import com.knowgate.dataobjs.DB;
import com.knowgate.dataobjs.DBBind;
import com.knowgate.dataobjs.DBSubset;
import com.knowgate.debug.DebugFile;
import com.knowgate.debug.StackTraceUtil;
import com.knowgate.misc.Gadgets;
/**
* <p>Single Thread Scheduler Executor</p>
* <p>SingleThreadExecutor is a class that processes jobs and atoms in a simple way,
* unlike SchedulerDaemon witch is based on an AtomQueue and a WorkerThreadPool,
* SingleThreadExecutor uses directly the database for tracking execution progress
* for a single thread.</p>
* @author Sergio Montoro Ten
* @version 1.0
*/
public class SingleThreadExecutor extends Thread {
private DBBind oGlobalDbb;
private String sEnvProps;
private Properties oEnvProps;
private boolean bContinue;
private String sLastError;
private String sJob;
private Job oJob;
private Atom oAtm;
private LinkedList oCallbacks;
private int iCallbacks;
// ---------------------------------------------------------------------------
private static class SystemOutNotify extends WorkerThreadCallback {
public SystemOutNotify() {
super("SystemOutNotify");
}
public void call (String sThreadId, int iOpCode, String sMessage, Exception oXcpt, Object oParam) {
if (WorkerThreadCallback.WT_EXCEPTION==iOpCode)
System.out.println("Thread " + sThreadId + ": ERROR " + sMessage);
else
System.out.println("Thread " + sThreadId + ": " + sMessage);
}
}
// ---------------------------------------------------------------------------
/**
* <p>Create new SingleThreadExecutor</p>
* @param sPropertiesFilePath Absolute path to hipergate.cnf properties file
* @throws FileNotFoundException
* @throws IOException
*/
public SingleThreadExecutor (String sPropertiesFilePath)
throws FileNotFoundException, IOException {
if (DebugFile.trace) DebugFile.writeln("new SingleThreadExecutor("+sPropertiesFilePath+")");
oGlobalDbb = null;
sJob = null;
bContinue = true;
if (sPropertiesFilePath.lastIndexOf(System.getProperty("file.separator"))==-1)
sEnvProps = sPropertiesFilePath;
else
sEnvProps = sPropertiesFilePath.substring(sPropertiesFilePath.lastIndexOf(System.getProperty("file.separator"))+1);
FileInputStream oInProps = new FileInputStream (sPropertiesFilePath);
oEnvProps = new Properties();
oEnvProps.load (oInProps);
oInProps.close ();
oCallbacks = new LinkedList();
}
/**
* <p>Create new SingleThreadExecutor for a single Job</p>
* @param sPropertiesFilePath Absolute path to hipergate.cnf properties file
* @param sJobId GUID of Job for which to process atoms,
* if <b>null</b> the executor will process atoms for all pending jobs
* @throws FileNotFoundException
* @throws IOException
*/
public SingleThreadExecutor (String sPropertiesFilePath, String sJobId)
throws FileNotFoundException, IOException {
if (DebugFile.trace) DebugFile.writeln("new SingleThreadExecutor("+sPropertiesFilePath+","+sJobId+")");
oGlobalDbb = null;
sJob = sJobId;
bContinue = true;
if (sPropertiesFilePath.lastIndexOf(System.getProperty("file.separator"))==-1)
sEnvProps = sPropertiesFilePath;
else
sEnvProps = sPropertiesFilePath.substring(sPropertiesFilePath.lastIndexOf(System.getProperty("file.separator"))+1);
FileInputStream oInProps = new FileInputStream (sPropertiesFilePath);
oEnvProps = new Properties();
oEnvProps.load (oInProps);
oInProps.close ();
oCallbacks = new LinkedList();
} // SingleThreadExecutor
// ---------------------------------------------------------------------------
/**
* <p>Create new SingleThreadExecutor for a single Job</p>
* @param oProps Environment properties (usually taken from hipergate.cnf)
* @param sJobId GUID of Job for which to process atoms,
* if <b>null</b> the executor will process atoms for all pending jobs
*/
public SingleThreadExecutor (Properties oProps, String sJobId) {
if (DebugFile.trace) DebugFile.writeln("new SingleThreadExecutor([Properties],"+sJobId+")");
oGlobalDbb = null;
sJob = sJobId;
bContinue = true;
oEnvProps = oProps;
oGlobalDbb = new DBBind(oProps);
sEnvProps = oGlobalDbb.getProfileName();
oCallbacks = new LinkedList();
} // SingleThreadExecutor
// ---------------------------------------------------------------------------
/**
* <p>Create new SingleThreadExecutor for a single Job</p>
* @param oDbb DBBind used for accesing the satabase
* @param sJobId GUID of Job for which to process atoms,
* if <b>null</b> the executor will process atoms for all pending jobs
*/
public SingleThreadExecutor (DBBind oDbb, String sJobId) {
oGlobalDbb = oDbb;
sJob = sJobId;
bContinue = true;
sEnvProps = oDbb.getProfileName();
oEnvProps = oDbb.getProperties();
oCallbacks = new LinkedList();
} // SingleThreadExecutor
// ---------------------------------------------------------------------------
public Atom activeAtom() {
return oAtm;
}
// ---------------------------------------------------------------------------
public Job activeJob() {
return oJob;
}
// ---------------------------------------------------------------------------
public String lastError() {
return sLastError;
}
// ---------------------------------------------------------------------------
/**
* Register a thread callback object
* @param oNewCallback WorkerThreadCallback subclass instance
* @throws IllegalArgumentException If a callback with same name has oNewCallback was already registered
*/
public void registerCallback(WorkerThreadCallback oNewCallback)
throws IllegalArgumentException {
WorkerThreadCallback oCallback;
ListIterator oIter = oCallbacks.listIterator();
while (oIter.hasNext()) {
oCallback = (WorkerThreadCallback) oIter.next();
if (oCallback.name().equals(oNewCallback.name())) {
throw new IllegalArgumentException("Callback " + oNewCallback.name() + " is already registered");
} // fi
} // wend
oCallbacks.addLast(oNewCallback);
iCallbacks++;
} // registerCallback
// ---------------------------------------------------------------------------
/**
* Unregister a thread callback object
* @param sCallbackName Name of callback to be unregistered
* @return <b>true</b> if a callback with such name was found and unregistered,
* <b>false</b> otherwise
*/
public boolean unregisterCallback(String sCallbackName) {
WorkerThreadCallback oCallback;
ListIterator oIter = oCallbacks.listIterator();
while (oIter.hasNext()) {
oCallback = (WorkerThreadCallback) oIter.next();
if (oCallback.name().equals(sCallbackName)) {
oIter.remove();
iCallbacks--;
return true;
} // fi
} // wend
return false;
} // unregisterCallback
// ---------------------------------------------------------------------------
private void callBack (int iOpCode, String sMessage, Exception oXcpt, Object oParam) {
WorkerThreadCallback oCallback;
ListIterator oIter = oCallbacks.listIterator();
while (oIter.hasNext()) {
oCallback = (WorkerThreadCallback) oIter.next();
oCallback.call(getName(), iOpCode, sMessage, oXcpt, oParam);
} // wend
} // callBack
// ---------------------------------------------------------------------------
public void run() {
Statement oStm;
AtomFeeder oFdr;
DBSubset oDBS;
String sSQL;
String sJId;
ResultSet oRst;
ResultSetMetaData oMDt;
DBBind oDBB = null;
JDCConnection oCon = null;
if (DebugFile.trace) {
DebugFile.writeln("Begin SingleThreadExecutor.run("+String.valueOf(currentThread().getId())+")");
DebugFile.writeln("environment is "+sEnvProps);
}
try {
if (oGlobalDbb==null) {
// Disable connection reaper to avoid connections being closed in the middle of job execution
oDBB = new DBBind(sEnvProps);
oDBB.connectionPool().setReaperDaemonDelay(0l);
}
else {
oDBB = oGlobalDbb;
}
oCon = oDBB.getConnection("SingleThreadExecutor_"+String.valueOf(currentThread().getId()));
oCon.setAutoCommit(true);
bContinue = true;
sLastError = "";
oFdr = new AtomFeeder();
while (bContinue) {
if (oCon.isClosed()) {
oCon = oDBB.getConnection("SingleThreadExecutor_"+String.valueOf(currentThread().getId()));
oCon.setAutoCommit(true);
}
if (sJob==null)
oDBS = oFdr.loadAtoms(oCon,1);
else
oDBS = oFdr.loadAtoms(oCon, sJob);
if (oDBS.getRowCount()>0) {
sJId = oDBS.getString(0,0);
oJob = Job.instantiate(oCon, sJId, oEnvProps);
oStm = oCon.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
sSQL = "SELECT a.*, j." + DB.tx_parameters + " FROM " + DB.k_job_atoms + " a, " + DB.k_jobs + " j WHERE a." + DB.id_status + "=" + String.valueOf(Atom.STATUS_PENDING) + " AND j." + DB.gu_job + "=a." + DB.gu_job + " AND j." + DB.gu_job + "='" + sJId + "'";
if (DebugFile.trace) {
DebugFile.writeln("Statement.executeQuery("+sSQL+")");
}
oRst = oStm.executeQuery(sSQL);
oMDt = oRst.getMetaData();
while (oRst.next()) {
oAtm = new Atom(oRst, oMDt);
try {
oJob.process(oAtm);
oAtm.archive(oCon);
if (DebugFile.trace)
DebugFile.writeln("Thread " + String.valueOf(currentThread().getId()) + " consumed Atom " + String.valueOf(oAtm.getInt(DB.pg_atom)));
if (iCallbacks>0) callBack(WorkerThreadCallback.WT_ATOM_CONSUME, oJob.getString(DB.gu_job), null, oAtm.getString(DB.tx_email));
}
catch (Exception e) {
if (DebugFile.trace) {
DebugFile.writeln(getName() + " " + e.getClass().getName() + " job " + oJob.getString(DB.gu_job) + " atom " + String.valueOf(oAtm.getInt(DB.pg_atom)) + e.getMessage());
DebugFile.writeln(StackTraceUtil.getStackTrace(e));
}
sLastError = e.getClass().getName() + ", job " + oJob.getString(DB.gu_job) + " ";
sLastError = "atom " + String.valueOf(oAtm.getInt(DB.pg_atom)) + " ";
sLastError += e.getMessage() + "\n" + StackTraceUtil.getStackTrace(e) + "\n";
try {
oAtm.setStatus(oCon, Atom.STATUS_INTERRUPTED, e.getClass().getName() + " " + e.getMessage());
} catch (SQLException sqle) {
if (DebugFile.trace) DebugFile.writeln("Atom.setStatus() SQLException " + sqle.getMessage());
}
oJob.log(sLastError);
if (iCallbacks>0) callBack(WorkerThreadCallback.WT_EXCEPTION, "Thread " + getName() + " " + sLastError, e, oJob);
}
if (oJob.pending()==0) {
oJob.setStatus(oCon, Job.STATUS_FINISHED);
if (iCallbacks>0) callBack(WorkerThreadCallback.WT_JOB_FINISH, "finish", null, oJob);
if (sJob!=null) bContinue = false;
}
} // wend
oRst.close();
oStm.close();
}
else
bContinue = false;
} // wend
if (!oCon.isClosed())
oCon.close("SingleThreadExecutor_"+String.valueOf(currentThread().getId()));
if (oGlobalDbb==null && oDBB!=null) oDBB.close();
}
catch (SQLException e) {
try { if (oCon!=null) if (!oCon.isClosed()) oCon.close(); } catch (Exception ignore) {}
if (oGlobalDbb==null && oDBB!=null) oDBB.close();
sLastError = "SQLException " + e.getMessage();
if (iCallbacks>0) callBack(-1, sLastError, new SQLException(e.getMessage(), e.getSQLState(), e.getErrorCode()), null);
if (oJob!=null) oJob.log(sLastError + "\n");
}
catch (FileNotFoundException e) {
try { if (oCon!=null) if (!oCon.isClosed()) oCon.close(); } catch (Exception ignore) {}
if (oGlobalDbb==null && oDBB!=null) oDBB.close();
sLastError = "FileNotFoundException " + e.getMessage();
if (iCallbacks>0) callBack(-1, sLastError, new FileNotFoundException(e.getMessage()), null);
if (oJob!=null) oJob.log(sLastError + "\n");
}
catch (IOException e) {
try { if (oCon!=null) if (!oCon.isClosed()) oCon.close(); } catch (Exception ignore) {}
if (oGlobalDbb==null && oDBB!=null) oDBB.close();
sLastError = "IOException " + e.getMessage();
if (iCallbacks>0) callBack(-1, sLastError, new IOException(e.getMessage()), null);
if (oJob!=null) oJob.log(sLastError + "\n");
}
catch (ClassNotFoundException e) {
try { if (oCon!=null) if (!oCon.isClosed()) oCon.close(); } catch (Exception ignore) {}
if (oGlobalDbb==null && oDBB!=null) oDBB.close();
sLastError = "ClassNotFoundException " + e.getMessage();
if (iCallbacks>0) callBack(-1, sLastError, new ClassNotFoundException(e.getMessage()), null);
if (oJob!=null) oJob.log(sLastError + "\n");
}
catch (InstantiationException e) {
try { if (oCon!=null) if (!oCon.isClosed()) oCon.close(); } catch (Exception ignore) {}
if (oGlobalDbb==null && oDBB!=null) oDBB.close();
sLastError = "InstantiationException " + e.getMessage();
if (iCallbacks>0) callBack(-1, sLastError, new InstantiationException(e.getMessage()), null);
if (oJob!=null) oJob.log(sLastError + "\n");
}
catch (IllegalAccessException e) {
try { if (oCon!=null) if (!oCon.isClosed()) oCon.close(); } catch (Exception ignore) {}
if (oGlobalDbb==null && oDBB!=null) oDBB.close();
sLastError = "IllegalAccessException " + e.getMessage();
if (iCallbacks>0) callBack(-1, sLastError, new IllegalAccessException(e.getMessage()), null);
if (oJob!=null) oJob.log(sLastError + "\n");
}
catch (NullPointerException e) {
try { if (oCon!=null) if (!oCon.isClosed()) oCon.close(); } catch (Exception ignore) {}
if (oGlobalDbb==null && oDBB!=null) oDBB.close();
sLastError = "NullPointerException " + e.getMessage();
if (iCallbacks>0) callBack(-1, sLastError, new NullPointerException(e.getMessage()), null);
if (oJob!=null) oJob.log(sLastError + "\n");
}
if (DebugFile.trace) {
DebugFile.writeln("End SingleThreadExecutor.run() : "+String.valueOf(currentThread().getId()));
}
} // run
// ---------------------------------------------------------------------------
/**
* <p>Halt thread execution commiting all operations in course before stopping</p>
* If a thread is dead-locked by any reason halting it will not cause any effect.<br>
* halt() method only sends a signals to the each WokerThread telling it that must
* finish pending operations and stop.
*/
public void halt() {
bContinue = false;
}
// ***************************************************************************
// Static Methods
private static void printUsage() {
System.out.println("");
System.out.println("Usage:");
System.out.println("SingleThreadExecutor {run | lrun} job_type cnf_file_path {gu_job | xml_file_path} [verbose]");
System.out.println("job_type is one of {MAIL | FAX | SAVE | FTP}");
}
public static void main(String[] argv)
throws java.io.FileNotFoundException, java.io.IOException, SQLException,
ClassNotFoundException, IllegalAccessException, InstantiationException,
org.xml.sax.SAXException {
SingleThreadExecutor oExec;
if (argv.length!=4 && argv.length!=5)
printUsage();
else if (argv.length==5 && !argv[4].equals("verbose"))
printUsage();
else if (!argv[0].equals("run") && !argv[0].equals("lrun"))
printUsage();
else if (!argv[1].equalsIgnoreCase("MAIL") && !argv[1].equalsIgnoreCase("FAX") &&
!argv[1].equalsIgnoreCase("SAVE") && !argv[1].equalsIgnoreCase("FTP") )
printUsage();
else {
if (argv[0].equals("run"))
oExec = new SingleThreadExecutor(argv[2], argv[3]);
else {
String sJobGUID = Gadgets.generateUUID();
Job.main(new String[]{"create", argv[1], argv[2], argv[3], sJobGUID });
oExec = new SingleThreadExecutor(argv[2], sJobGUID);
}
if (argv.length==5)
oExec.registerCallback(new SystemOutNotify());
oExec.start();
} // fi
} // main
} // SingleThreadExecutor