package org.kisst.flow4j;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.LinkedHashMap;
import org.kisst.gft.RetryableException;
import org.kisst.gft.action.Action;
import org.kisst.gft.admin.WritesHtml;
import org.kisst.gft.task.Task;
import org.kisst.props4j.MultiProps;
import org.kisst.props4j.Props;
import org.kisst.util.ReflectionUtil;
import org.kisst.util.ThreadUtil;
import org.kisst.util.exception.BasicFunctionalException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jamonapi.Monitor;
import com.jamonapi.MonitorFactory;
public class BasicLinearFlow implements WritesHtml, Action {
final static Logger logger=LoggerFactory.getLogger(BasicLinearFlow.class);
protected final static boolean SKIPPED=true;
protected final LinkedHashMap<String, Action> actions= new LinkedHashMap<String, Action>();;
private final HashSet<String> skippedActions = new HashSet<String>();
private final int maxNrofTries;
private final long retryDelay;
private final boolean retryNonFunctionalExceptions;
private final Props props;
public BasicLinearFlow(Props props) {
this.props=props;
maxNrofTries = props.getInt("maxNrofTries", 3);
retryDelay = props.getLong("retryDelay", 30000);
retryNonFunctionalExceptions = props.getBoolean("retryNonFunctionalExceptions", false);
}
public Props getProps() { return props; }
@Override public boolean safeToRetry() {
for (Action a: actions.values()) {
if (! a.safeToRetry())
return false;
}
return true;
}
/*
protected void initFlow() {
LinkedHashMap<String, Action> result = ReflectionUtil.findFieldsImplementing(this, Action.class);
for (String name: result.keySet()) {
addActions.put(name, result.get(name));
}
}*/
protected boolean isSkippedAction(String name) { return skippedActions .contains(name); }
private<T> T addAction(String name, T act) {
if (! (act instanceof Action))
throw new IllegalArgumentException("Trying to add action "+act+" of type"+act.getClass().getName()+", which does not implement the Action interface");
Action act2 = actions.get(name);
if (act2!=null)
throw new RuntimeException("Action name "+name+" is already in use with "+act2+" when trying to add action "+act+" to Flow "+this);
actions.put(name, (Action) act);
return act;
}
protected<T> T addAction(String name, Class<T> cls) { return addAction(name,cls, ! SKIPPED); }
@SuppressWarnings("unchecked")
protected<T> T addAction(String name, Class<T> cls, boolean skipped) {
Props props = getActionConstructorProps((Class<? extends Action>) cls);
if (props.getBoolean("skip", skipped))
skippedActions.add(name);
T act=(T) myCreateAction(cls, props);
return addAction(name, act);
}
protected<T> T addAction(Class<T> cls, boolean skipped) { return this.addAction(cls.getSimpleName(), cls, skipped); }
protected<T> T addAction(Class<T> cls) { return addAction(cls, ! SKIPPED); }
private Props getActionConstructorProps(Class<? extends Action> cls) {
String actionName= cls.getSimpleName();
Props actionprops = props.getProps(actionName,null);
if (actionprops==null)
return props;
return new MultiProps(actionprops,props);
}
@Override public void execute(Task task) {
logger.info("Flow start "+this.getClass().getCanonicalName());
for (String name: actions.keySet()) {
if (isSkippedAction(name)) {
logger.info("action {} skipped",name);
continue;
}
Action a=actions.get(name);
logger.info("action {} started",name);
boolean done=false;
int nrofTries=0;
while (! done){
Monitor mon1=MonitorFactory.start("action:"+name);
Monitor mon2=null;
String channelName= task.getTaskDefinition().getName();
mon2=MonitorFactory.start("channel:"+channelName+":action:"+name);
try {
task.setCurrentAction(a);
a.execute(task);
done=true;
}
catch (RuntimeException e) {
if (e instanceof BasicFunctionalException) {
logger.error("action "+name+" had functional error: ",e);
throw e;
}
if (! a.safeToRetry()) {
logger.error("action "+name+" (which is not safe to retry) had error: ",e);
throw e;
}
if ( (!retryNonFunctionalExceptions) && ! (e instanceof RetryableException)) {
logger.error("action "+name+" had non-functional error: ",e);
throw e;
}
if (nrofTries <= maxNrofTries) {
logger.warn("Error during action "+name+", try number "+nrofTries+", will retry after "+retryDelay/1000+" seconds, error was ", e);
nrofTries++;
ThreadUtil.sleep(retryDelay);
}
else {
logger.error("action "+name+" had "+(nrofTries+1)+" tries, last error: ",e);
throw e;
}
}
finally {
mon1.stop();
if (mon2!=null) mon2.stop();
}
if (done && logger.isInfoEnabled())
logger.info("action "+name+" succesful");
}
}
logger.info("Flow succeeded "+this.getClass().getCanonicalName());
}
protected Action myCreateAction(Class<?> clz, Props props) {
Constructor<?> c=ReflectionUtil.getConstructor(clz, new Class<?>[] {BasicLinearFlow.class, Props.class} );
if (c!=null)
return (Action) ReflectionUtil.createObject(c, new Object[] {this, props} );
c=ReflectionUtil.getConstructor(clz, new Class<?>[] {Props.class} );
if (c!=null)
return (Action) ReflectionUtil.createObject(c, new Object[] {props} );
return (Action) ReflectionUtil.createObject(clz);
}
@Override public void writeHtml(PrintWriter out) {
out.println("<h2>Flow</h2>");
out.println("<table>");
out.print("<tr>");
out.print("<td><b>skipped?</b></td>");
out.print("<td><b>name</b></td>");
out.print("<td><b>details</b></td>");
out.println("</tr>");
for (String name: actions.keySet()) {
Action act=actions.get(name);
if (isSkippedAction(name))
out.print("<tr bgcolor=\"orange\"><td><b>SKIPPED</b></td>");
else
out.print("<tr><td></td>");
out.print("<td>"+name+"</td>");
out.print("<td>");
if (act instanceof WritesHtml)
((WritesHtml)act).writeHtml(out);
else
out.print(act.toString());
out.print("</td>");
out.println("</tr>");
}
out.println("</table>");
}
}