package org.syrup.workers;
import org.apache.xml.serializer.SerializationHandler;
import org.syrup.Data;
import org.syrup.LogEntry;
import org.syrup.LogEntryTemplate;
import org.syrup.PTask;
import org.syrup.PTaskTemplate;
import org.syrup.WorkSpace;
import org.syrup.helpers.DataImpl;
import org.syrup.helpers.LogEntryTemplateImpl;
import org.syrup.helpers.PTaskTemplateImpl;
import org.syrup.helpers.XMLOutput;
import org.syrup.sql.SQLWorkSpace;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.InitialContext;
/**
* The default Worker implementation that executes PTasks taken from a
* WorkSpace.
*
* @author Robbert van Dalen
*/
public class DefaultWorker
{
static final String COPYRIGHT = "Copyright 2005 Robbert van Dalen."
+ "At your option, you may copy, distribute, or make derivative works under "
+ "the terms of The Artistic License. This License may be found at "
+ "http://www.opensource.org/licenses/artistic-license.php. "
+ "THERE IS NO WARRANTY; USE THIS PRODUCT AT YOUR OWN RISK.";
private final static Logger logger = Logger.getLogger("org.syrup.workers.DefaultWorker");
/**
* The main entry to start the Worker as a daemon, or to execute a Syrup
* command.
*
* @param args
* The command + qualifier.
* @param i
* The InputStream to read input from.
* @param o
* The OutputStream to write output to.
* @return The number of results (matched/stopped PTasks, matched
* LogEntries)
*/
public static int operate(String[] args, InputStream i, OutputStream o)
throws Exception
{
WorkSpace sp = null;
try
{
sp = (WorkSpace) (new InitialContext()).lookup("syrupWorkSpace");
}
catch (Exception e)
{
logger.log(Level.INFO, "Did not get syrupWorkSpace key via JNDI. Reverted to default SQLWorkSpace implementation");
sp = new SQLWorkSpace();
}
if (args.length > 0)
{
// The first argument is the command.
if (args[0].equals("reset"))
{
sp.reset();
}
else if (args[0].equals("in1"))
{
sp.set_in_1(read(i));
}
else if (args[0].equals("in2"))
{
sp.set_in_2(read(i));
}
else if (args[0].equals("out1"))
{
write(sp.get_out_1(), o);
}
else if (args[0].equals("out2"))
{
write(sp.get_out_2(), o);
}
else if (args[0].equals("match"))
{
PTaskTemplate template = new PTaskTemplateImpl(args);
return match(sp, template, o);
}
else if (args[0].equals("get"))
{
PTaskTemplate template = new PTaskTemplateImpl(args);
return get(sp, template, o);
}
else if (args[0].equals("execute"))
{
PTaskTemplate template = new PTaskTemplateImpl(args);
execute(sp, template, -1, 10000);
}
else if (args[0].equals("step"))
{
PTaskTemplate template = new PTaskTemplateImpl(args);
execute(sp, template, 1, 0);
}
else if (args[0].equals("stop"))
{
PTaskTemplate template = new PTaskTemplateImpl(args);
return stop(sp, template);
}
else if (args[0].equals("get-log"))
{
// Parses the arguments into a LogEntryTemplate, used by the
// command.
LogEntryTemplate template = new LogEntryTemplateImpl(args);
return match(sp, template, o);
}
else
{
throw new Exception("operation '"
+ args[0] + "' not supported");
}
}
return 0;
}
/**
* Start the Worker as a daemon or to execute a Syrup command using
* System.in and System.out for input and output.
*
* @param args
* The command + qualifier.
*/
public static void main(String[] args) throws Exception
{
operate(args, System.in, System.out);
}
/**
* Return PTasks that match a PTaskTemplate by writing them out in XML
* format.
*
* @param sp
* The WorkSpace to be used.
* @param template
* The PTaskTemplate to be matched.
* @param out
* The OutputStream to write PTasks to.
* @return The number of matched PTasks.
*/
public static int match(WorkSpace sp, PTaskTemplate template,
OutputStream out) throws Exception
{
XMLOutput o = new XMLOutput();
SerializationHandler h = o.wrap(out);
PTask p[] = sp.match(template);
// Outputs the fetched PTasks to the OutputStream in XML format.
o.startDocument("match", h);
for (int i = 0; i < p.length; i++)
{
o.output(p[i], h);
}
o.endDocument("match", h);
return p.length;
}
/**
* Return LogEntries that match a LogEntryTemplate by writing them out in
* XML format.
*
* @param sp
* The WorkSpace to be used.
* @param template
* The LogEntryTemplate to be matched.
* @param out
* The OutputStream to write LogEntries to.
* @return The number of matched LogEntries.
*/
public static int match(WorkSpace sp, LogEntryTemplate template,
OutputStream out) throws Exception
{
XMLOutput o = new XMLOutput();
SerializationHandler h = o.wrap(out);
LogEntry l[] = sp.match(template);
// Outputs the fetched PTasks to the OutputStream in XML format.
o.startDocument("log", h);
for (int i = 0; i < l.length; i++)
{
o.output(l[i], h);
}
o.endDocument("log", h);
return l.length;
}
/**
*/
private static void output(Hashtable tree, XMLOutput xmlout,
SerializationHandler handler) throws Exception
{
Iterator keys = tree.keySet().iterator();
while (keys.hasNext())
{
String key = (String) keys.next();
if (!key.equals("_parent"))
{
Object o = tree.get(key);
if (o instanceof Hashtable)
{
Hashtable subtree = (Hashtable) o;
org.syrup.Context c = (org.syrup.Context) subtree.get("_parent");
xmlout.start(c, handler);
output(subtree, xmlout, handler);
xmlout.end(c, handler);
}
else
{
xmlout.output((org.syrup.Context) o, handler);
}
}
}
}
/**
* Get the Contexts that match a PTaskTemplate by writing them out in XML
* format.
*
* @param sp
* The WorkSpace to be used.
* @param template
* The PTaskTemplate to be matched.
* @param out
* The OutputStream to write Contexts to.
* @return The number of matched Contexts.
*/
public static int get(WorkSpace sp, PTaskTemplate template, OutputStream out)
throws Exception
{
XMLOutput o = new XMLOutput();
SerializationHandler h = o.wrap(out);
org.syrup.Context c[] = sp.get(template);
Hashtable parents = new Hashtable();
// Make a parent Task table
for (int i = 0; i < c.length; i++)
{
org.syrup.Context pc = c[i];
if (pc.task().isParent())
{
Hashtable pn = new Hashtable();
pn.put("_parent", pc);
parents.put(pc.task().key(), pn);
}
}
// Put the child Tasks underneath the parent Tasks.
for (int i = 0; i < c.length; i++)
{
org.syrup.Context ct = c[i];
if (!ct.task().isParent())
{
Hashtable pn = (Hashtable) parents.get(ct.task().parentKey());
if (pn != null)
{
pn.put(ct.task().key(), ct);
}
else
{
parents.put(ct.task().key(), ct);
}
}
}
Hashtable cl = new Hashtable(parents);
Iterator keys = cl.keySet().iterator();
// Build hierarchy
while (keys.hasNext())
{
String k = (String) keys.next();
Object ph = (Object) cl.get(k);
if (ph instanceof Hashtable)
{
Hashtable phh = (Hashtable) ph;
org.syrup.Context ct = (org.syrup.Context) phh.get("_parent");
Hashtable oo = (Hashtable) cl.get(ct.task().parentKey());
if (oo != null)
{
parents.remove(k);
oo.put(k, phh);
}
}
}
// Outputs the fetched Contexts to the OutputStream in XML format.
o.startDocument("get", h);
output(parents, o, h);
o.endDocument("get", h);
return c.length;
}
/**
* Executes PTasks that match a PTaskTemplate by writing them out in XML
* format. This method will continue to execute forever, waiting for new
* PTasks to be executed until the calling Thread or JVM is stopped.
*
* @param sp
* The WorkSpace to be used.
* @param template
* The PTaskTemplate to be matched.
*/
public static void execute(WorkSpace sp, PTaskTemplate template,
long totalIterations, long pollInterval) throws Exception
{
try
{
while (totalIterations > 0
|| totalIterations < 0)
{
PTask p[] = sp.match(template);
int k = 1;
int i = 0;
int executionRuns = 0;
// Go through all matching PTasks sequentially.
while (i < p.length)
{
logger.log(Level.INFO, "starting "
+ p[i], p[i]);
PTask p2 = null;
int ii = 0;
// Retry execution (5 times) upon failure.
while (ii++ < 5)
{
try
{
p2 = sp.execute(p[i]);
executionRuns++;
break;
}
catch (InterruptedException ie)
{
throw ie;
}
catch (Exception e)
{
logger.log(Level.INFO, "execution failed "
+ p[i], e);
}
// This block is only entered when execution fails.
try
{
sp.stop(new PTaskTemplateImpl(new String[]
{
"stop", "-key=equal", " "
+ p[i]
}));
}
catch (Exception e)
{
logger.log(Level.INFO, "stopping failed "
+ p[i], e);
}
logger.log(Level.INFO, "... retrying execution "
+ p[i]);
}
// Indicates that execution has failed 5 times
if (p2 == null)
{
// Bail out to top level caller.
throw new Exception("execution cannot continue - exhausted all retries");
}
// Indicates that the execution was succesful.
if (p2 != p[i])
{
logger.log(Level.INFO, "executed "
+ p2, p2);
}
// Indicates that the execution was not succesful.
else
{
logger.log(Level.INFO, "dropped "
+ p2, p2);
}
// Indicates that the PTask was already taken by another
// Worker.
if (p[i].modifications() == p2.modifications())
{
// Increase the step taken through the fetched list.
// This will lower the chance of hitting a PTask
// that has been taken by another Worker.
k += 1;
logger.log(Level.INFO, "increasing step to "
+ k);
}
// Instead of stepping x time, make it x+1, reducing the
// chance of colliding with another Worker.
// Ideally, the selectopm of executable Tasks from the list
// should be random, but this alternative interleaving
// scheme is nearly as efficient [TODO: prove this!]
i += k;
}
try
{
if (executionRuns == 0)
{
// No more PTasks to be executed. Wait for a while.
logger.log(Level.INFO, "sleeping "
+ pollInterval);
Thread.sleep(pollInterval);
}
}
catch (InterruptedException ie)
{
throw ie;
}
catch (Exception e)
{
logger.log(Level.SEVERE, Thread.currentThread().toString(), e);
}
if (totalIterations > 0)
{
totalIterations--;
}
}
}
catch (InterruptedException ie)
{
logger.log(Level.WARNING, "interrupted ",
ie);
}
catch (Throwable e)
{
logger.log(Level.SEVERE, Thread.currentThread().toString(), e);
}
}
/**
* Stops a non-progressing PTasks matching the PTaskTemplate.
*
* @param sp
* The WorkSpace that is used.
* @param template
* The PTaskTemplate to be matched.
* @return The number of stopped PTasks.
*/
private static int stop(WorkSpace sp, PTaskTemplate template)
throws Exception
{
int stopped = 0;
PTask p[] = sp.match(template);
// Stops the matching PTasks sequentially and one by one.
for (int i = 0; i < p.length; i++)
{
PTask pp = sp.stop(p[i]);
if (pp != p[i])
{
logger.log(Level.INFO, "stopped "
+ pp);
stopped++;
}
}
return stopped;
}
/**
* Encapsulates the data read from an InputStream with a Data object.
*
* @param i
* The InputStream to be encapsulated.
* @return The encapsulated InputStream.
*/
private final static Data read(InputStream i) throws Exception
{
byte b[] = new byte[8192];
ByteArrayOutputStream o = new ByteArrayOutputStream(8192);
int l = 0;
while ((l = i.read(b)) >= 0)
{
o.write(b, 0, l);
}
return new DataImpl(o.toByteArray());
}
/**
* Writes the data to an OutputStream using a Data object.
*
* @param d
* The Data object to be written.
* @param o
* The OutputStream to be written to.
*/
private final static void write(Data d, OutputStream o) throws Exception
{
if (d != null)
{
byte[] b = d.bytes();
o.write(b, 0, b.length);
}
}
}