/*
* Copyright (C) 2015 Jan Pokorsky
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.cas.lib.proarc.common.workflow;
import cz.cas.lib.proarc.common.dao.ConcurrentModificationException;
import cz.cas.lib.proarc.common.dao.DaoFactory;
import cz.cas.lib.proarc.common.dao.Transaction;
import cz.cas.lib.proarc.common.dao.WorkflowJobDao;
import cz.cas.lib.proarc.common.dao.WorkflowMaterialDao;
import cz.cas.lib.proarc.common.dao.WorkflowParameterDao;
import cz.cas.lib.proarc.common.dao.WorkflowTaskDao;
import cz.cas.lib.proarc.common.user.UserProfile;
import cz.cas.lib.proarc.common.workflow.model.Job;
import cz.cas.lib.proarc.common.workflow.model.Material;
import cz.cas.lib.proarc.common.workflow.model.MaterialFilter;
import cz.cas.lib.proarc.common.workflow.model.MaterialView;
import cz.cas.lib.proarc.common.workflow.model.Task;
import cz.cas.lib.proarc.common.workflow.model.Task.State;
import cz.cas.lib.proarc.common.workflow.model.TaskFilter;
import cz.cas.lib.proarc.common.workflow.model.TaskParameter;
import cz.cas.lib.proarc.common.workflow.model.TaskView;
import cz.cas.lib.proarc.common.workflow.profile.BlockerDefinition;
import cz.cas.lib.proarc.common.workflow.profile.JobDefinition;
import cz.cas.lib.proarc.common.workflow.profile.ParamDefinition;
import cz.cas.lib.proarc.common.workflow.profile.StepDefinition;
import cz.cas.lib.proarc.common.workflow.profile.TaskDefinition;
import cz.cas.lib.proarc.common.workflow.profile.WorkflowDefinition;
import cz.cas.lib.proarc.common.workflow.profile.WorkflowProfiles;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
*
* @author Jan Pokorsky
*/
public class TaskManager {
private final DaoFactory daoFactory;
private final WorkflowProfiles wp;
private final WorkflowManager wmgr;
public TaskManager(DaoFactory daoFactory, WorkflowProfiles wp, WorkflowManager wmgr) {
this.daoFactory = daoFactory;
this.wp = wp;
this.wmgr = wmgr;
}
public List<TaskView> findTask(TaskFilter filter, WorkflowDefinition wd) {
final boolean findJobTasks = filter.getJobId() != null && filter.getId() == null;
if (findJobTasks) {
filter.setMaxCount(1000); // enough to read all tasks of a single job
}
Transaction tx = daoFactory.createTransaction();
WorkflowTaskDao taskDao = daoFactory.createWorkflowTaskDao();
taskDao.setTransaction(tx);
try {
String lang = filter.getLocale().getLanguage();
List<TaskView> tasks = taskDao.view(filter);
for (TaskView task : tasks) {
TaskDefinition taskProfile = wp.getTaskProfile(wd, task.getTypeRef());
if (taskProfile != null) {
task.setProfileLabel(taskProfile.getTitle(lang, taskProfile.getName()));
task.setProfileHint(taskProfile.getHint(lang, null));
} else {
task.setProfileLabel(task.getTypeRef());
task.setProfileHint("Unknown task XML ID: " + task.getTypeRef());
}
}
if (findJobTasks && !tasks.isEmpty()) {
WorkflowJobDao jobDao = daoFactory.createWorkflowJobDao();
jobDao.setTransaction(tx);
Job job = jobDao.find(filter.getJobId());
if (job != null) {
JobDefinition jobDef = wp.getProfile(wd, job.getProfileName());
if (jobDef != null) {
tasks = sortJobTaskByBlockers(jobDef, tasks);
}
}
}
return tasks;
} finally {
tx.close();
}
}
private <T extends Task> List<T> sortJobTaskByBlockers(JobDefinition job, List<T> tasks) {
ArrayList<T> sorted = new ArrayList<T>(tasks.size());
for (String sortTaskName : job.getTaskNamesSortedByBlockers()) {
for (Iterator<T> it = tasks.iterator(); it.hasNext();) {
T task = it.next();
if (sortTaskName.equals(task.getTypeRef())) {
sorted.add(task);
it.remove();
}
}
if (tasks.isEmpty()) {
break;
}
}
// add tasks not found in the workflow definition
sorted.addAll(tasks);
return sorted;
}
public Task updateTask(
Task task, Map<String, Object> paramMap, WorkflowDefinition workflow
) throws ConcurrentModificationException, WorkflowException {
List<TaskParameter> params = null;
if (paramMap != null) {
params = new ArrayList<TaskParameter>();
TaskDefinition taskDef = wp.getTaskProfile(workflow, task.getTypeRef());
if (taskDef != null) {
for (Entry<String, Object> entry : paramMap.entrySet()) {
String paramName = entry.getKey();
Object paramValue = entry.getValue();
if (paramValue == null) {
continue;
}
String paramStringValue = String.valueOf(paramValue);
ParamDefinition paramDef = wp.getParamProfile(taskDef, paramName);
TaskParameter param = new TaskParameter().addParamRef(paramName)
.addTaskId(task.getId());
if (paramDef != null) {
param.addValue(paramDef.getValueType(), paramStringValue);
params.add(param);
} else {
// ignore unknown params
}
}
}
}
return updateTask(task, params, workflow);
}
public Task updateTask(
Task task, List<TaskParameter> params, WorkflowDefinition workflow
) throws ConcurrentModificationException, WorkflowException {
if (task.getId() == null) {
throw new IllegalArgumentException("Missing ID!");
}
Transaction tx = daoFactory.createTransaction();
WorkflowJobDao jobDao = daoFactory.createWorkflowJobDao();
WorkflowTaskDao taskDao = daoFactory.createWorkflowTaskDao();
WorkflowParameterDao paramDao = daoFactory.createWorkflowParameterDao();
jobDao.setTransaction(tx);
taskDao.setTransaction(tx);
paramDao.setTransaction(tx);
try {
Task old = taskDao.find(task.getId());
if (old == null) {
throw new WorkflowException("Task not found: " + task.getId()).addTaskNotFound(task.getId());
}
// keep read-only properties
task.setCreated(old.getCreated());
task.setJobId(old.getJobId());
task.setTypeRef(old.getTypeRef());
Job job = jobDao.find(task.getJobId());
if (job == null) {
throw new WorkflowException("Job not found: " + task.getJobId()).addJobNotFound(task.getJobId());
}
Job.State jobState = job.getState();
if (jobState != Job.State.OPEN ) {
throw new WorkflowException("Job already closed: " + jobState).addJobIsClosed();
}
WorkflowDefinition profiles = wp.getProfiles();
JobDefinition jobDef = wp.getProfile(profiles, job.getProfileName());
if (jobDef == null) {
throw new WorkflowException("Job name definiton not found! " + job.getProfileName())
.addInvalidXmlId(job.getProfileName())
.addUnexpectedError();
}
// check task state
TaskFilter taskFilter = new TaskFilter();
taskFilter.setJobId(job.getId());
List<TaskView> jobTasks = sortJobTaskByBlockers(jobDef, taskDao.view(taskFilter));
updateTaskState(taskDao, getTaskStateUpdates(task.getState(), old.getState(), task, jobDef, jobTasks));
updateJobState(jobTasks, job, jobDao);
taskDao.update(task);
if (params != null) {
paramDao.remove(task.getId());
paramDao.add(task.getId(), params);
}
tx.commit();
return task;
} catch (ConcurrentModificationException t) {
tx.rollback();
throw t;
} catch (WorkflowException t) {
tx.rollback();
throw t;
} catch (Throwable t) {
tx.rollback();
throw new IllegalStateException("Cannot update task: " + task.getId().toString(), t);
} finally {
tx.close();
}
}
private void updateJobState(List<TaskView> jobTasks, Job job, WorkflowJobDao jobDao) throws ConcurrentModificationException {
boolean allCanceled = true;
boolean allClosed = true;
for (TaskView jobTask : jobTasks) {
if (jobTask.getState() != State.CANCELED) {
allCanceled = false;
}
if (!jobTask.isClosed()) {
allClosed = false;
}
}
if (allClosed) {
job.setState(allCanceled ? Job.State.CANCELED : Job.State.FINISHED);
jobDao.update(job);
}
}
private void updateTaskState(WorkflowTaskDao dao, List<Task> updates) {
for (Task update : updates) {
dao.update(update);
}
}
private List<Task> getTaskStateUpdates(State newState, State oldState,
Task t, JobDefinition job, List<? extends Task> sortedTasks
) throws WorkflowException {
if (oldState == newState) {
return Collections.emptyList();
}
if (oldState == State.WAITING) {
if (newState == State.READY || newState == State.STARTED || newState == State.FINISHED) {
if (isBlocked(getStepDefinition(job, t.getTypeRef()), sortedTasks)) {
throw new WorkflowException("Task is blocked by other tasks!").addTaskBlocked();
}
} else {
}
} else if (newState == State.WAITING) {
throw new WorkflowException("The task cannot be waiting again!").addTaskCannotWaitAgain();
} else {
}
// set new state
t.setState(newState);
List<Task> updates = new ArrayList<Task>();
// resolve waiters
for (Task jobTask : sortedTasks) {
if (t.getId().compareTo(jobTask.getId()) == 0) {
jobTask.setState(newState);
continue;
}
if (jobTask.getState() == State.WAITING) {
boolean blocked = isBlocked(getStepDefinition(job, jobTask.getTypeRef()), sortedTasks);
if (!blocked) {
jobTask.setState(State.READY);
updates.add(jobTask);
}
}
}
return updates;
}
private boolean isBlocked(StepDefinition step, List<? extends Task> sortedTasks) {
if (step != null) {
for (BlockerDefinition blocker : step.getBlockers()) {
if (isBlocker(blocker.getTask().getName(), sortedTasks)) {
return true;
}
}
}
return false;
}
private boolean isBlocker(String name, List<? extends Task> tasks) {
for (Task task : tasks) {
if (name.equals(task.getTypeRef()) && !task.isClosed()) {
return true;
}
}
return false;
}
public Task addTask(
BigDecimal jobId, String taskName, WorkflowDefinition workflow, UserProfile defaultUser
) throws WorkflowException {
Map<String, UserProfile> users = wmgr.createUserMap();
Transaction tx = daoFactory.createTransaction();
WorkflowJobDao jobDao = daoFactory.createWorkflowJobDao();
WorkflowTaskDao taskDao = daoFactory.createWorkflowTaskDao();
WorkflowParameterDao paramDao = daoFactory.createWorkflowParameterDao();
WorkflowMaterialDao materialDao = daoFactory.createWorkflowMaterialDao();
jobDao.setTransaction(tx);
taskDao.setTransaction(tx);
paramDao.setTransaction(tx);
materialDao.setTransaction(tx);
Timestamp now = new Timestamp(System.currentTimeMillis());
try {
Job job = jobDao.find(jobId);
if (job == null) {
throw new WorkflowException("Job not found: " + jobId).addJobNotFound(jobId);
}
Job.State jobState = job.getState();
if (jobState != Job.State.OPEN ) {
throw new WorkflowException("Job already closed: " + jobState).addJobIsClosed();
}
JobDefinition jobDef = wp.getProfile(workflow, job.getProfileName());
if (jobDef == null || jobDef.isDisabled()) {
throw new WorkflowException("Job definition not found: " + job.getProfileName())
.addInvalidXmlId(job.getProfileName());
}
StepDefinition step = getStepDefinition(jobDef, taskName);
TaskFilter taskFilter = new TaskFilter();
taskFilter.setJobId(job.getId());
List<TaskView> jobTasks = sortJobTaskByBlockers(jobDef, taskDao.view(taskFilter));
boolean blockedTask = isBlocked(step, jobTasks);
Task task = createTask(taskDao, now, job, step, users, defaultUser, blockedTask);
wmgr.createTaskParams(paramDao, step, task);
if (!step.getTask().getMaterialSetters().isEmpty()) {
Map<String, Material> materialCache = new HashMap<String, Material>();
MaterialFilter mFilter = new MaterialFilter();
mFilter.setJobId(jobId);
List<MaterialView> materials = materialDao.view(mFilter);
for (MaterialView material : materials) {
materialCache.put(material.getName(), material);
}
wmgr.createMaterials(materialDao, step, task, materialCache, null);
}
tx.commit();
return task;
} catch (WorkflowException t) {
tx.rollback();
throw t;
} catch (Throwable t) {
tx.rollback();
throw new WorkflowException("Cannot add task: " + taskName, t).addUnexpectedError();
} finally {
tx.close();
}
}
private StepDefinition getStepDefinition(JobDefinition jobDef, String taskName) throws WorkflowException {
for (StepDefinition step : jobDef.getSteps()) {
if (step.getTask().getName().equals(taskName)) {
if (step.getTask().isDisabled()) {
throw new WorkflowException("Task disabled: " + taskName).addTaskDisabled(taskName);
}
return step;
}
}
throw new WorkflowException("Step definition not found: " + taskName
+ " for " + jobDef.getName())
.addInvalidXmlId(taskName).addUnexpectedError();
}
private Task createTask(WorkflowTaskDao taskDao, Timestamp now, Job job,
StepDefinition step, Map<String, UserProfile> users, UserProfile defaultUser,
boolean blocked
) throws ConcurrentModificationException {
Task task = taskDao.create().addCreated(now)
.addJobId(job.getId())
.addOwnerId(WorkflowManager.resolveUserId(step.getWorker(), users, defaultUser, false))
.addPriority(job.getPriority())
.setState(blocked? Task.State.WAITING : Task.State.READY)
.addTimestamp(now)
.addTypeRef(step.getTask().getName());
taskDao.update(task);
return task;
}
}