package railo.runtime.spooler;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import railo.commons.io.IOUtil;
import railo.commons.io.SystemUtil;
import railo.commons.io.log.Log;
import railo.commons.io.res.Resource;
import railo.commons.io.res.filter.ResourceNameFilter;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;
import railo.runtime.config.Config;
import railo.runtime.engine.ThreadLocalConfig;
import railo.runtime.exp.DatabaseException;
import railo.runtime.exp.PageException;
import railo.runtime.op.Caster;
import railo.runtime.op.Duplicator;
import railo.runtime.type.Array;
import railo.runtime.type.Collection;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Query;
import railo.runtime.type.QueryImpl;
import railo.runtime.type.Struct;
import railo.runtime.type.dt.DateTimeImpl;
import railo.runtime.type.util.ArrayUtil;
import railo.runtime.type.util.KeyConstants;
public class SpoolerEngineImpl implements SpoolerEngine {
private static final TaskFileFilter FILTER=new TaskFileFilter();
private static final Collection.Key LAST_EXECUTION = KeyImpl.intern("lastExecution");
private static final Collection.Key NEXT_EXECUTION = KeyImpl.intern("nextExecution");
private static final Collection.Key CLOSED = KeyImpl.intern("closed");
private static final Collection.Key TRIES = KeyImpl.intern("tries");
private static final Collection.Key TRIES_MAX = KeyImpl.intern("triesmax");
private String label;
//private LinkedList<SpoolerTask> openTaskss=new LinkedList<SpoolerTask>();
//private LinkedList<SpoolerTask> closedTasks=new LinkedList<SpoolerTask>();
private SpoolerThread thread;
//private ExecutionPlan[] plans;
private Resource persisDirectory;
private long count=0;
private Log log;
private Config config;
private int add=0;
private Resource closedDirectory;
private Resource openDirectory;
private int maxThreads;
public SpoolerEngineImpl(Config config,Resource persisDirectory,String label, Log log, int maxThreads) {
this.config=config;
this.persisDirectory=persisDirectory;
closedDirectory = persisDirectory.getRealResource("closed");
openDirectory = persisDirectory.getRealResource("open");
//calculateSize();
this.maxThreads=maxThreads;
this.label=label;
this.log=log;
//print.ds(persisDirectory.getAbsolutePath());
//load();
if(getOpenTaskCount()>0)start();
}
/*private void calculateSize() {
closedCount=calculateSize(closedDirectory);
openCount=calculateSize(openDirectory);
}*/
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
}
/**
* @return the maxThreads
*/
public int getMaxThreads() {
return maxThreads;
}
private int calculateSize(Resource res) {
return ResourceUtil.directrySize(res,FILTER);
}
@Override
public synchronized void add(SpoolerTask task) {
//openTasks.add(task);
add++;
task.setNextExecution(System.currentTimeMillis());
task.setId(createId(task));
store(task);
start();
}
private void start() {
if(thread==null || !thread.isAlive()) {
thread=new SpoolerThread(this);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
else if(thread.sleeping) {
thread.interrupt();
}
//else print.out("- existing");
}
@Override
public String getLabel() {
return label;
}
private SpoolerTask getTaskById(Resource dir,String id) {
return getTask(dir.getRealResource(id+".tsk"),null);
}
private SpoolerTask getTaskByName(Resource dir,String name) {
return getTask(dir.getRealResource(name),null);
}
private SpoolerTask getTask(Resource res, SpoolerTask defaultValue) {
InputStream is = null;
ObjectInputStream ois = null;
SpoolerTask task=defaultValue;
try {
is = res.getInputStream();
ois = new ObjectInputStream(is);
task = (SpoolerTask) ois.readObject();
}
catch (Throwable t) {//t.printStackTrace();
IOUtil.closeEL(is);
IOUtil.closeEL(ois);
res.delete();
}
IOUtil.closeEL(is);
IOUtil.closeEL(ois);
return task;
}
private void store(SpoolerTask task) {
ObjectOutputStream oos=null;
Resource persis = getFile(task);
if(persis.exists()) persis.delete();
try {
oos = new ObjectOutputStream(persis.getOutputStream());
oos.writeObject(task);
}
catch (IOException e) {}
finally {
IOUtil.closeEL(oos);
}
}
private void unstore(SpoolerTask task) {
Resource persis = getFile(task);
boolean exists=persis.exists();
if(exists) persis.delete();
}
private Resource getFile(SpoolerTask task) {
Resource dir = persisDirectory.getRealResource(task.closed()?"closed":"open");
dir.mkdirs();
return dir.getRealResource(task.getId()+".tsk");
}
private String createId(SpoolerTask task) {
Resource dir = persisDirectory.getRealResource(task.closed()?"closed":"open");
dir.mkdirs();
String id=null;
do{
id=StringUtil.addZeros(++count, 8);
}while(dir.getRealResource(id+".tsk").exists());
return id;
}
public long calculateNextExecution(SpoolerTask task) {
int _tries=0;
ExecutionPlan plan=null;
ExecutionPlan[] plans=task.getPlans();
for(int i=0;i<plans.length;i++) {
_tries+=plans[i].getTries();
if(_tries>task.tries()) {
plan=plans[i];
break;
}
}
if(plan==null)return -1;
return task.lastExecution()+(plan.getIntervall()*1000);
}
public Query getOpenTasksAsQuery(int startrow, int maxrow) throws PageException {
return getTasksAsQuery(createQuery(),openDirectory,startrow, maxrow);
}
public Query getClosedTasksAsQuery(int startrow, int maxrow) throws PageException {
return getTasksAsQuery(createQuery(),closedDirectory,startrow, maxrow);
}
public Query getAllTasksAsQuery(int startrow, int maxrow) throws PageException {
Query query = createQuery();
//print.o(startrow+":"+maxrow);
getTasksAsQuery(query,openDirectory,startrow, maxrow);
int records = query.getRecordcount();
if(maxrow<0) maxrow=Integer.MAX_VALUE;
// no open tasks
if(records==0) {
startrow-=getOpenTaskCount();
if(startrow<1) startrow=1;
}
else {
startrow=1;
maxrow-=records;
}
if(maxrow>0)getTasksAsQuery(query,closedDirectory,startrow, maxrow);
return query;
}
public int getOpenTaskCount() {
return calculateSize(openDirectory);
}
public int getClosedTaskCount() {
return calculateSize(closedDirectory);
}
private Query getTasksAsQuery(Query qry,Resource dir, int startrow, int maxrow) {
String[] children = dir.list(FILTER);
if(ArrayUtil.isEmpty(children)) return qry;
if(children.length<maxrow)maxrow=children.length;
SpoolerTask task;
int to=startrow+maxrow;
if(to>children.length)to=children.length;
if(startrow<1)startrow=1;
for(int i=startrow-1;i<to;i++){
task = getTaskByName(dir, children[i]);
if(task!=null)addQueryRow(qry, task);
}
return qry;
}
private Query createQuery() throws DatabaseException {
String v="VARCHAR";
String d="DATE";
railo.runtime.type.Query qry=new QueryImpl(
new String[]{"type","name","detail","id","lastExecution","nextExecution","closed","tries","exceptions","triesmax"},
new String[]{v,v,"object",v,d,d,"boolean","int","object","int"},
0,"query");
return qry;
}
private void addQueryRow(railo.runtime.type.Query qry, SpoolerTask task) {
int row = qry.addRow();
try{
qry.setAt(KeyConstants._type, row, task.getType());
qry.setAt(KeyConstants._name, row, task.subject());
qry.setAt(KeyConstants._detail, row, task.detail());
qry.setAt(KeyConstants._id, row, task.getId());
qry.setAt(LAST_EXECUTION, row,new DateTimeImpl(task.lastExecution(),true));
qry.setAt(NEXT_EXECUTION, row,new DateTimeImpl(task.nextExecution(),true));
qry.setAt(CLOSED, row,Caster.toBoolean(task.closed()));
qry.setAt(TRIES, row,Caster.toDouble(task.tries()));
qry.setAt(TRIES_MAX, row,Caster.toDouble(task.tries()));
qry.setAt(KeyConstants._exceptions, row,translateTime(task.getExceptions()));
int triesMax=0;
ExecutionPlan[] plans = task.getPlans();
for(int y=0;y<plans.length;y++) {
triesMax+=plans[y].getTries();
}
qry.setAt(TRIES_MAX, row,Caster.toDouble(triesMax));
}
catch(Throwable t){}
}
private Array translateTime(Array exp) {
exp=(Array) Duplicator.duplicate(exp,true);
Iterator<Object> it = exp.valueIterator();
Struct sct;
while(it.hasNext()) {
sct=(Struct) it.next();
sct.setEL(KeyConstants._time,new DateTimeImpl(Caster.toLongValue(sct.get(KeyConstants._time,null),0),true));
}
return exp;
}
class SpoolerThread extends Thread {
private SpoolerEngineImpl engine;
private boolean sleeping;
private final int maxThreads;
public SpoolerThread(SpoolerEngineImpl engine) {
this.maxThreads=engine.getMaxThreads();
this.engine=engine;
try{
this.setPriority(MIN_PRIORITY);
}
// can throw security exceptions
catch(Throwable t){}
}
public void run() {
String[] taskNames;
//SpoolerTask[] tasks;
SpoolerTask task=null;
long nextExection;
ThreadLocalConfig.register(engine.config);
//ThreadLocalPageContext.register(engine.);
List<TaskThread> runningTasks=new ArrayList<TaskThread>();
TaskThread tt;
int adds;
while(getOpenTaskCount()>0) {
adds=engine.adds();
taskNames = openDirectory.list(FILTER);
//tasks=engine.getOpenTasks();
nextExection=Long.MAX_VALUE;
for(int i=0;i<taskNames.length;i++) {
task=getTaskByName(openDirectory, taskNames[i]);
if(task==null) continue;
if(task.nextExecution()<=System.currentTimeMillis()) {
//print.o("- execute");
tt=new TaskThread(engine,task);
tt.start();
runningTasks.add(tt);
}
else if(task.nextExecution()<nextExection &&
nextExection!=-1 &&
!task.closed())
nextExection=task.nextExecution();
nextExection=joinTasks(runningTasks,maxThreads,nextExection);
}
nextExection=joinTasks(runningTasks,0,nextExection);
if(adds!=engine.adds()) continue;
if(nextExection==Long.MAX_VALUE)break;
long sleep = nextExection-System.currentTimeMillis();
//print.o("sleep:"+sleep+">"+(sleep/1000));
if(sleep>0)doWait(sleep);
//if(sleep<0)break;
}
//print.o("end:"+getOpenTaskCount());
}
private long joinTasks(List<TaskThread> runningTasks, int maxThreads,long nextExection) {
if(runningTasks.size()>=maxThreads){
Iterator<TaskThread> it = runningTasks.iterator();
TaskThread tt;
SpoolerTask task;
while(it.hasNext()){
tt = it.next();
SystemUtil.join(tt);
task = tt.getTask();
if(task!=null && task.nextExecution()!=-1 && task.nextExecution()<nextExection && !task.closed()) {
nextExection=task.nextExecution();
}
}
runningTasks.clear();
}
return nextExection;
}
private void doWait(long sleep) {
//long start=System.currentTimeMillis();
try {
sleeping=true;
synchronized (this) {
wait(sleep);
}
} catch (Throwable t) {
//
}
finally {
sleeping=false;
}
}
}
class TaskThread extends Thread {
private SpoolerEngineImpl engine;
private SpoolerTask task;
public TaskThread(SpoolerEngineImpl engine,SpoolerTask task) {
this.engine=engine;
this.task=task;
}
public SpoolerTask getTask() {
return task;
}
public void run() {
ThreadLocalConfig.register(engine.config);
engine.execute(task);
ThreadLocalConfig.release();
}
}
/**
* remove that task from Spooler
* @param task
*/
public void remove(SpoolerTask task) {
unstore(task);
//if(!openTasks.remove(task))closedTasks.remove(task);
}
public void removeAll() {
ResourceUtil.removeChildrenEL(openDirectory);
ResourceUtil.removeChildrenEL(closedDirectory);
SystemUtil.sleep(100);
ResourceUtil.removeChildrenEL(openDirectory);
ResourceUtil.removeChildrenEL(closedDirectory);
}
public int adds() {
//return openTasks.size()>0;
return add;
}
@Override
public void remove(String id) {
SpoolerTask task = getTaskById(openDirectory,id);
if(task==null)task=getTaskById(closedDirectory,id);
if(task!=null)remove(task);
}
/*private SpoolerTask getTaskById(SpoolerTask[] tasks, String id) {
for(int i=0;i<tasks.length;i++) {
if(tasks[i].getId().equals(id)) {
return tasks[i];
}
}
return null;
}*/
/**
* execute task by id and return eror throwd by task
* @param id
* @throws SpoolerException
*/
public PageException execute(String id) {
SpoolerTask task = getTaskById(openDirectory,id);
if(task==null)task=getTaskById(closedDirectory,id);
if(task!=null){
return execute(task);
}
return null;
}
public PageException execute(SpoolerTask task) {
//task.closed();
try {
if(task instanceof SpoolerTaskSupport) // FUTURE this is bullshit, call the execute method directly, but you have to rewrite them for that
((SpoolerTaskSupport)task)._execute(config);
else
task.execute(config);
unstore(task);
log.info("remote-client", task.subject());
task.setLastExecution(System.currentTimeMillis());
task.setNextExecution(-1);
task.setClosed(true);
task=null;
}
catch(Throwable t) {
task.setLastExecution(System.currentTimeMillis());
task.setNextExecution(calculateNextExecution(task));
log.error("remote-client", task.subject()+":"+t.getMessage());
if(task.nextExecution()==-1) {
//openTasks.remove(task);
//if(!closedTasks.contains(task))closedTasks.add(task);
unstore(task);
task.setClosed(true);
store(task);
task=null;
}
else
store(task);
return Caster.toPageException(t);
}
return null;
}
public void setLabel(String label) {
this.label = label;
}
public void setPersisDirectory(Resource persisDirectory) {
this.persisDirectory = persisDirectory;
}
public void setLog(Log log) {
this.log = log;
}
public void setConfig(Config config) {
this.config = config;
}
}
class TaskFileFilter implements ResourceNameFilter {
public boolean accept(Resource parent, String name) {
return name!=null && name.endsWith(".tsk");
}
}