package org.juxtasoftware.util;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.time.DateUtils;
import org.juxtasoftware.Constants;
import org.juxtasoftware.util.BackgroundTaskStatus.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* Singleton manager of outstanding tasks for the web service. This manager
* ensures that only 1 task is executing at a time
*
* @author loufoster
*
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class TaskManager {
@Autowired @Qualifier("executor") private TaskExecutor taskExecutor;
@Autowired @Qualifier("collate-executor") private TaskExecutor collateExecutor;
private ConcurrentHashMap<String, BackgroundTask> taskMap = new ConcurrentHashMap<String, BackgroundTask>(50);
private final SimpleDateFormat dateFormater = new SimpleDateFormat("MM/dd H:mm:ss:SSS");
private static final Logger LOG = LoggerFactory.getLogger( Constants.WS_LOGGER_NAME );
/**
* Submit a named task to be executed in a new thread and have its status monitored
* Tasks are named based upon operation and id of operand. If a task already exists
* with this name, don't create another unless the existing task is done.
*
* @param newTask
*/
public void submit( BackgroundTask newTask ) {
LOG.info("Task "+newTask.getName()+" submitted to manager");
BackgroundTask task = this.taskMap.get(newTask.getName());
boolean createNewTask = false;
if ( task == null ) {
LOG.info("Task "+newTask.getName()+" does not exist. Create");
createNewTask = true;
} else {
if ( isDone(task) ) {
LOG.info("Task "+newTask.getName()+" exists, but is done");
this.taskMap.remove( task.getName() );
createNewTask = true;
} else {
LOG.info("Task "+task.getName()+" already exists; not creating another");
}
}
if ( createNewTask ) {
LOG.info("Create NEW task "+newTask.getName());
this.taskMap.put( newTask.getName(), newTask );
// exec collate requests in thread pool that only allows a
// small number of concurrent tasks
if ( newTask.getType().equals(BackgroundTask.Type.COLLATE) || newTask.getType().equals(BackgroundTask.Type.VISUALIZE) ) {
this.collateExecutor.execute(newTask);
} else {
// All other tasks are streamed and need less bandwidth. Use thread pool that alows
// more simultaneous tasks
this.taskExecutor.execute(newTask);
}
}
}
@Scheduled(fixedRate=30000)
public void manageQueue() {
// every 30 secs, check for completed tasks that are more than
// 30 minutes old. remove them.
List<String> killList = new ArrayList<String>();
for ( Entry<String, BackgroundTask> entry : this.taskMap.entrySet() ) {
BackgroundTask task = entry.getValue();
if ( isDone(task) ) {
Date endPlus30 = DateUtils.addMinutes(task.getEndTime(), 30);
Date now = new Date();
if ( endPlus30.before( now) ) {
LOG.info("Expiring completed task "+entry.getKey());
killList.add(entry.getKey());
}
}
}
for ( String key : killList ) {
this.taskMap.remove(key);
}
killList.clear();
}
private boolean isDone( final BackgroundTask task ) {
return (task.getStatus().equals(Status.CANCELLED) ||
task.getStatus().equals(Status.COMPLETE) ||
task.getStatus().equals(Status.FAILED) );
}
public void cancel( final String taskName ) {
BackgroundTask task = this.taskMap.get( taskName );
if ( task != null ) {
task.cancel();
}
}
public boolean exists( final String name ) {
BackgroundTask task = this.taskMap.get( name );
return (task != null && task.getStatus().equals(Status.PROCESSING) );
}
public String getStatus( final String name ) {
BackgroundTask task = this.taskMap.get( name );
if ( task != null ) {
String start = this.dateFormater.format( task.getStartTime() );
String end = "";
if ( task.getEndTime() != null ) {
end = this.dateFormater.format( task.getEndTime() );
}
return "{\"status\": \""+task.getStatus()+
"\", \"note\": \"" + task.getMessage() +
"\", \"started\": \""+start+"\", \"finished\": \""+end+"\"}";
} else {
return "{\"status\": \"UNAVAILABLE\"}";
}
}
}