All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.centurylink.mdw.services.task.TaskWorkflowHelper Maven / Gradle / Ivy

There is a newer version: 6.1.39
Show newest version
/*
 * Copyright (C) 2017 CenturyLink, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.centurylink.mdw.services.task;

import com.centurylink.mdw.activity.types.TaskActivity;
import com.centurylink.mdw.app.ApplicationContext;
import com.centurylink.mdw.cache.CachingException;
import com.centurylink.mdw.cache.asset.PackageCache;
import com.centurylink.mdw.common.StrategyException;
import com.centurylink.mdw.common.service.ServiceException;
import com.centurylink.mdw.common.service.types.StatusMessage;
import com.centurylink.mdw.config.PropertyManager;
import com.centurylink.mdw.constant.OwnerType;
import com.centurylink.mdw.constant.PropertyNames;
import com.centurylink.mdw.constant.TaskAttributeConstant;
import com.centurylink.mdw.dataaccess.DataAccessException;
import com.centurylink.mdw.dataaccess.DatabaseAccess;
import com.centurylink.mdw.model.JsonObject;
import com.centurylink.mdw.model.asset.AssetVersionSpec;
import com.centurylink.mdw.model.event.EventInstance;
import com.centurylink.mdw.model.event.EventType;
import com.centurylink.mdw.model.monitor.ScheduledEvent;
import com.centurylink.mdw.model.task.*;
import com.centurylink.mdw.model.user.User;
import com.centurylink.mdw.model.user.UserAction;
import com.centurylink.mdw.model.user.UserAction.Entity;
import com.centurylink.mdw.model.variable.Variable;
import com.centurylink.mdw.model.variable.VariableInstance;
import com.centurylink.mdw.model.workflow.Package;
import com.centurylink.mdw.model.workflow.Process;
import com.centurylink.mdw.model.workflow.*;
import com.centurylink.mdw.monitor.MonitorRegistry;
import com.centurylink.mdw.monitor.TaskMonitor;
import com.centurylink.mdw.observer.ObserverException;
import com.centurylink.mdw.observer.task.*;
import com.centurylink.mdw.service.Action;
import com.centurylink.mdw.service.ActionRequestDocument;
import com.centurylink.mdw.service.ActionRequestDocument.ActionRequest;
import com.centurylink.mdw.service.Parameter;
import com.centurylink.mdw.service.data.process.ProcessCache;
import com.centurylink.mdw.service.data.task.TaskDataAccess;
import com.centurylink.mdw.service.data.task.TaskTemplateCache;
import com.centurylink.mdw.service.data.user.UserDataAccess;
import com.centurylink.mdw.service.data.user.UserGroupCache;
import com.centurylink.mdw.services.*;
import com.centurylink.mdw.services.asset.CustomPageLookup;
import com.centurylink.mdw.services.event.ScheduledEventQueue;
import com.centurylink.mdw.services.process.ActivityLogger;
import com.centurylink.mdw.services.task.factory.TaskInstanceNotifierFactory;
import com.centurylink.mdw.services.task.factory.TaskInstanceStrategyFactory;
import com.centurylink.mdw.task.SubTask;
import com.centurylink.mdw.task.SubTaskPlanDocument;
import com.centurylink.mdw.task.SubTaskPlanDocument.SubTaskPlan;
import com.centurylink.mdw.task.types.TaskServiceRegistry;
import com.centurylink.mdw.util.HttpHelper;
import com.centurylink.mdw.util.log.LoggerUtil;
import com.centurylink.mdw.util.log.StandardLogger;
import com.centurylink.mdw.util.timer.CodeTimer;
import org.apache.commons.lang.StringUtils;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.json.JSONObject;

import java.io.IOException;
import java.net.URL;
import java.sql.SQLException;
import java.time.Instant;
import java.util.*;

public class TaskWorkflowHelper {

    private static StandardLogger logger = LoggerUtil.getStandardLogger();

    private TaskInstance taskInstance;
    TaskInstance getTaskInstance() { return taskInstance; }

    TaskWorkflowHelper(TaskInstance taskInstance) {
        this.taskInstance = taskInstance;
    }

    TaskWorkflowHelper(Long taskInstanceId) throws DataAccessException, IOException {
        this.taskInstance = getTaskInstance(taskInstanceId);
    }

    /**
     * Convenience method for below.
     */
    static TaskInstance createTaskInstance(Long taskId, String masterRequestId, Long procInstId,
            String secOwner, Long secOwnerId, String title, String comments)
            throws ServiceException, DataAccessException, IOException {
        return createTaskInstance(taskId, masterRequestId, procInstId, secOwner, secOwnerId, title, comments, 0, null, null);
    }

    /**
     * Creates a task instance. This is the main version. There is another version
     * for creating independent task instance directly from task manager,
     * and a third version for creating detail-only task instances.
     *
     * The method does the following:
     * - create task instance entry in database
     * - create SLA if passed in or specified in template
     * - create groups for new TEMPLATE based tasks
     * - create indices for new TEMPLATE based general tasks
     * - send notification if specified in template
     * - invoke old-style observer if specified in template
     * - auto-assign if specified in template
     * - record in audit log
     *
     * @param taskId    task template ID
     * @param masterRequestId
     * @param processInstanceId    process instance ID
     * @param secondaryOwner    can be DOCUMENT (general task), TASK_INSTANCE (subtask) or WORK_TRANSITION_INSTANCE (for classic task)
     * @param secondaryOwnerId  document ID or transition instance ID
     * @param title  Taken from task template name when not populated
     * @param comments  message (typically stacktrace for fallout tasks) of the activity for classic task
     * @param dueInSeconds  SLA. When it is 0, check if template has specified SLA
     * @param indexes   indices for general task based on templates
     * @param assignee  assignee CUID if this is to be auto-assigned by process variable
     * @return TaskInstance
     */
    static TaskInstance createTaskInstance(Long taskId, String masterRequestId, Long processInstanceId,
                String secondaryOwner, Long secondaryOwnerId, String title, String comments,
                int dueInSeconds, Map indexes, String assignee)
        throws ServiceException, DataAccessException, IOException {
        TaskTemplate task = TaskTemplateCache.getTaskTemplate(taskId);
        String label = task.getLabel();
        Package taskPkg = PackageCache.getPackage(task.getPackageName());
        if (taskPkg != null)
            label = taskPkg.getLabel() + "/" + label;
        Instant due = null;
        dueInSeconds = dueInSeconds > 0 ? dueInSeconds : task.getSlaSeconds();
        if (dueInSeconds > 0)
            due = Instant.now().plusSeconds(dueInSeconds);
        int pri = 0;
        // use the prioritization strategy if one is defined for the task
        try {
            PrioritizationStrategy prioritizationStrategy = getPrioritizationStrategy(task, processInstanceId, indexes);
            if (prioritizationStrategy != null) {
                Date calcDueDate = prioritizationStrategy.determineDueDate(task);
                if (calcDueDate != null)
                    due = calcDueDate.toInstant();
                pri = prioritizationStrategy.determinePriority(task, due == null ? null : Date.from(due));
            }
        }
        catch (Exception ex) {
            throw new ServiceException(ex.getMessage(), ex);
        }

        TaskInstance ti = createTaskInstance(taskId, masterRequestId, OwnerType.PROCESS_INSTANCE,
                processInstanceId, secondaryOwner, secondaryOwnerId, label, title, comments, due, pri);
        ti.setTaskName(task.getTaskName()); // Reset task name back (without package name pre-pended)
        TaskWorkflowHelper helper = new TaskWorkflowHelper(ti);
        if (due != null) {
            int alertInterval = 0; //initialize
            String alertIntervalString = ""; //initialize

            alertIntervalString = task.getAttribute(TaskAttributeConstant.ALERT_INTERVAL);
            alertInterval = StringUtils.isBlank(alertIntervalString)?0:Integer.parseInt(alertIntervalString);
            helper.scheduleTaskSlaEvent(Date.from(due), alertInterval, false);
        }

        // create instance indices for template based general tasks (MDW 5.1) and all template based tasks (MDW 5.2)
        if (indexes != null && !indexes.isEmpty()) {
            new TaskDataAccess().setTaskInstanceIndices(helper.getTaskInstance().getTaskInstanceId(), indexes);
        }
        // create instance groups for template based tasks
        List groups = helper.determineWorkgroups(indexes);
        if (groups != null && groups.size() > 0) {
            new TaskDataAccess().setTaskInstanceGroups(ti.getTaskInstanceId(), groups.toArray(new String[0]));
            helper.getTaskInstance().setWorkgroups(groups);
        }

        if (assignee != null) {
            try
            {
               User user =  UserGroupCache.getUser(assignee);
               if(user == null)
                 throw new ServiceException("Assignee user not found to perform auto-assign : " + assignee);
               helper.assign(user.getId()); // Performing auto assign on summary
            }
            catch (CachingException e)
            {
              logger.error(e.getMessage(), e);
            }
        }

        helper.getTaskInstance().setTaskInstanceUrl(helper.getTaskInstanceUrl());
        helper.notifyTaskAction(TaskAction.CREATE, null, null);   // notification/observer/auto-assign
        helper.auditLog("Create", "MDW");

        TaskRuntimeContext runtimeContext = helper.getContext();
        helper.setIndexes(runtimeContext);
        List subtasks = helper.getSubtaskList(runtimeContext);
        if (subtasks != null && !subtasks.isEmpty())
            helper.createSubtasks(subtasks, runtimeContext);

        return ti;
    }

    /**
     * This version is used by the task manager to create a task instance
     * not associated with a process instance.
     *
     * @param taskId
     * @param masterOwnerId
     * @param comment optional
     * @param due optional
     * @param userId
     * @param secondaryOwner (optional)
     * @return TaskInstance
     */
    static TaskInstance createTaskInstance(Long taskId, String masterOwnerId,
            String title, String comment, Instant due, Long userId, Long secondaryOwner)
    throws ServiceException, DataAccessException, IOException {
        CodeTimer timer = new CodeTimer("createTaskInstance()", true);
        TaskTemplate task = TaskTemplateCache.getTaskTemplate(taskId);
        TaskInstance ti = createTaskInstance(taskId,  masterOwnerId, OwnerType.USER, userId,
                (secondaryOwner != null ? OwnerType.DOCUMENT : null), secondaryOwner,
                task.getTaskName(), title, comment, due, 0);

        TaskWorkflowHelper helper = new TaskWorkflowHelper(ti);

        if (due!=null) {
            String alertIntervalString = task.getAttribute(TaskAttributeConstant.ALERT_INTERVAL);
            int alertInterval = StringUtils.isBlank(alertIntervalString) ? 0 : Integer.parseInt(alertIntervalString);
            helper.scheduleTaskSlaEvent(Date.from(due), alertInterval, false);
        }
        // create instance groups for template based tasks
        List groups = helper.determineWorkgroups(null);
        if (groups != null && groups.size() > 0)
            new TaskDataAccess().setTaskInstanceGroups(ti.getTaskInstanceId(), groups.toArray(new String[0]));

        helper.notifyTaskAction(TaskAction.CREATE, null, null);   // notification/observer/auto-assign
        helper.auditLog("Create", "MDW");
        timer.stopAndLogTiming("");
        return ti;
    }

    static TaskInstance createTaskInstance(Long taskId, String masterRequestId, String owner, Long ownerId,
            String secondaryOwner, Long secondaryOwnerId, String label, String title, String message, Instant due, int priority)
    throws ServiceException, DataAccessException {

        TaskInstance ti = new TaskInstance();
        ti.setTaskId(taskId);
        ti.setOwnerType(owner);
        ti.setOwnerId(ownerId);
        ti.setSecondaryOwnerType(secondaryOwner);
        ti.setSecondaryOwnerId(secondaryOwnerId);
        ti.setStatusCode(TaskStatus.STATUS_OPEN);
        ti.setStateCode(TaskState.STATE_OPEN);
        ti.setComments(message);
        ti.setTaskName(label); // actually populates referred_as
        ti.setTitle(title);
        ti.setMasterRequestId(masterRequestId);
        ti.setPriority(priority);
        ti.setDue(due);
        Long id = new TaskDataAccess().createTaskInstance(ti, due == null ? null : Date.from(due));
        ti.setTaskInstanceId(id);
        return ti;
    }

    static PrioritizationStrategy getPrioritizationStrategy(TaskTemplate taskTemplate, Long processInstanceId, Map indexes)
    throws StrategyException, ServiceException {
        String priorityStrategyAttr = taskTemplate.getAttribute(TaskAttributeConstant.PRIORITY_STRATEGY);
        if (StringUtils.isBlank(priorityStrategyAttr)) {
            return null;
        }
        else {
            PrioritizationStrategy strategy = TaskInstanceStrategyFactory.getPrioritizationStrategy(priorityStrategyAttr, processInstanceId);
            if (strategy instanceof ParameterizedStrategy) {
                populateStrategyParams((ParameterizedStrategy)strategy, taskTemplate, processInstanceId, indexes);
            }
            return strategy;
        }
    }

    static TaskInstance getTaskInstance(Long taskInstanceId) throws DataAccessException, IOException {
        TaskInstance taskInstance = new TaskDataAccess().getTaskInstance(taskInstanceId);
        if (taskInstance == null)
            return null;
        if (taskInstance.getAssigneeId() != null && taskInstance.getAssigneeId() != 0 && taskInstance.getAssigneeCuid() == null) {
            try {
                User user = UserGroupCache.getUser(taskInstance.getAssigneeId());
                if (user != null) {
                    taskInstance.setAssigneeCuid(user.getCuid());
                    taskInstance.setAssignee(user.getName());
                }
            }
            catch (CachingException ex) {
                throw new DataAccessException(ex.getMessage(), ex);
            }
        }
        Long activityInstanceId = new TaskWorkflowHelper(taskInstance).getActivityInstanceId(false);
        if (activityInstanceId != null) {
            taskInstance.setActivityInstanceId(activityInstanceId);
        }
        TaskTemplate template = TaskTemplateCache.getTaskTemplate(taskInstance.getTaskId());
        if (template != null)
            taskInstance.setTemplate(template.getPackageName() + "/" + template.getName());
        return taskInstance;
    }

    void setIndexes(TaskRuntimeContext runtimeContext) throws DataAccessException, IOException {
        TaskIndexProvider indexProvider = getIndexProvider(runtimeContext);
        if (indexProvider != null) {
            Map indexes = indexProvider.collect(runtimeContext);
            if (indexes != null)
                new TaskDataAccess().setTaskInstanceIndices(runtimeContext.getTaskInstanceId(), indexes);
        }
    }

    /**
     * Updates the due date for a task instance.
     * The method should only be called in summary (or summary-and-detail) task manager.
     */
    void updateDue(Instant due, String cuid, String comment)
    throws ServiceException, DataAccessException, IOException {
        boolean hasOldSlaInstance;
        EventServices eventManager = ServiceLocator.getEventServices();
        EventInstance event = eventManager.getEventInstance(ScheduledEvent.SPECIAL_EVENT_PREFIX + "TaskDueDate." + taskInstance.getId());
        boolean isEventExist = event == null ? false : true;
        hasOldSlaInstance = !isEventExist;
        Map changes = new HashMap();
        changes.put("DUE_DATE", Date.from(due));
        changes.put("COMMENTS", comment);
        TaskDataAccess dataAccess = new TaskDataAccess();
        dataAccess.updateTaskInstance(taskInstance.getTaskInstanceId(), changes, false);
        if (due == null) {
            unscheduleTaskSlaEvent();
        }
        else {
            String alertIntervalString = getTemplate().getAttribute(TaskAttributeConstant.ALERT_INTERVAL);
            int alertInterval = StringUtils.isBlank(alertIntervalString)?0:Integer.parseInt(alertIntervalString);
            scheduleTaskSlaEvent(Date.from(due), alertInterval, !hasOldSlaInstance);
        }
        auditLog(UserAction.Action.Change.toString(), cuid, null, "change due date / comments");
    }

    void updateComments(String comments)
    throws ServiceException, DataAccessException {
        Map changes = new HashMap();
        changes.put("COMMENTS", comments);
        new TaskDataAccess().updateTaskInstance(taskInstance.getTaskInstanceId(), changes, false);
    }

    public void updateWorkgroups(List groups) throws DataAccessException {
        new TaskDataAccess().setTaskInstanceGroups(taskInstance.getTaskInstanceId(), groups.toArray(new String[0]));
    }

    public void updatePriority(Integer priority) throws DataAccessException {
        new TaskDataAccess().setTaskInstancePriority(taskInstance.getTaskInstanceId(), priority);
    }

    private TaskIndexProvider getIndexProvider(TaskRuntimeContext runtimeContext) throws IOException {
        String indexProviderClass = runtimeContext.getTaskAttributes().get(TaskAttributeConstant.INDEX_PROVIDER);
        if (indexProviderClass == null) {
            return TaskTemplateCache.getTaskTemplate(runtimeContext.getTaskId()).isAutoformTask() ?
                    new AutoFormTaskIndexProvider() : new CustomTaskIndexProvider();
        }
        else {
            if (indexProviderClass.equals(AutoFormTaskIndexProvider.class.getName()))
                return new AutoFormTaskIndexProvider();
            else if (indexProviderClass.equals(CustomTaskIndexProvider.class.getName()))
                return new CustomTaskIndexProvider();

            TaskIndexProvider provider = TaskServiceRegistry.getInstance().getIndexProvider(runtimeContext.getPackage(), indexProviderClass);
            if (provider == null)
                logger.error("ERROR: cannot create TaskIndexProvider: " + indexProviderClass);
            return provider;
        }
    }

    void performAction(String action, Long userId, Long assigneeId, String comment,
            String destination, boolean notifyEngine, boolean allowResumeEndpoint)
            throws ServiceException, DataAccessException, IOException {

        if (logger.isInfoEnabled())
            logger.info("task action '" + action + "' on instance " + taskInstance.getId());

        if (taskInstance.isShallow())
            getTaskInstanceAdditionalInfo();
        // verifyPermission(ti, action, userId);
        Integer prevStatus = taskInstance.getStatusCode();
        Integer prevState = taskInstance.getStateCode();
        String assigneeCuid = null;

        boolean isComplete = false;

        // special behavior for some types of actions
        if (action.equalsIgnoreCase(TaskAction.ASSIGN) || action.equalsIgnoreCase(TaskAction.CLAIM)) {
            if (assigneeId == null || assigneeId.longValue() == 0) {
                assigneeId = userId;
            }
            assign(assigneeId);
            try {
                User user = UserGroupCache.getUser(assigneeId);
                if (user != null)
                    assigneeCuid = user.getCuid();
            }
            catch (CachingException ex) {
                logger.error(ex.getMessage(), ex);
            }
        }
        else if (action.equalsIgnoreCase(TaskAction.RELEASE)) {
            release();
        }
        else if (action.equalsIgnoreCase(TaskAction.WORK)) {
            work();
        }
        else if (action.equalsIgnoreCase(TaskAction.FORWARD)) {
            forward(destination, comment);
            auditLog(action, userId, destination, comment);
            return;  // forward notifications are handled in forwardTaskInstance()
        }
        else if (action.equalsIgnoreCase(TaskAction.CLOSE)) {
            close(userId, comment);
        }
        else {
            isComplete = true;
            TaskRuntimeContext runtimeContext = getContext();
            // update the indexes
            setIndexes(runtimeContext);
            // option to notify through service (eg: to offload to workflow server instance)
            String taskResumeEndpoint = null;
            if (taskInstance.isProcessOwned() && allowResumeEndpoint)
                taskResumeEndpoint = runtimeContext.getProperty(PropertyNames.TASK_RESUME_NOTIFY_ENDPOINT);
            if (taskResumeEndpoint == null) // otherwise it will be closed via service
                close(action, comment);
            if (notifyEngine && !taskInstance.isSubTask()) {
                if (taskResumeEndpoint == null) {
                    resume(action);
                }
                else {
                    // resume through service
                    resumeThroughService(taskResumeEndpoint, action, userId, comment);
                    return; // notify and audit log at endpoint destination
                }
            }
        }

        notifyTaskAction(action, prevStatus, prevState);
        auditLog(action, userId, assigneeCuid, comment);

        if (isComplete) {
            // in case subtask
            if (taskInstance.isSubTask()) {
                Long masterTaskInstId = taskInstance.getMasterTaskInstanceId();
                boolean allCompleted = true;
                for (TaskInstance subtask : getSubtasks(masterTaskInstId)) {
                    if (!subtask.isInFinalStatus()) {
                        allCompleted = false;
                        break;
                    }
                }
                if (allCompleted) {
                    TaskInstance masterTaskInst = ServiceLocator.getTaskServices().getInstance(masterTaskInstId);
                    TaskWorkflowHelper masterHelper = new TaskWorkflowHelper(masterTaskInst);
                    if ("true".equalsIgnoreCase(masterHelper.getTemplate().getAttribute(TaskAttributeConstant.SUBTASKS_COMPLETE_MASTER)))
                        masterHelper.performAction(TaskAction.COMPLETE, userId, null, null, null, notifyEngine, false);
                }
            }

            // in case master task
            for (TaskInstance subtask : getSubtasks(taskInstance.getTaskInstanceId())) {
                if (!subtask.isInFinalStatus())
                    new TaskWorkflowHelper(subtask).cancel();
            }
        }
    }

    List getSubtaskList(TaskRuntimeContext runtimeContext) throws ServiceException, IOException {
        String subtaskStrategyAttr = getTemplate().getAttribute(TaskAttributeConstant.SUBTASK_STRATEGY);
        if (StringUtils.isBlank(subtaskStrategyAttr)) {
            return null;
        }
        else {
            try {
                SubTaskStrategy strategy = TaskInstanceStrategyFactory.getSubTaskStrategy(subtaskStrategyAttr,
                        OwnerType.PROCESS_INSTANCE.equals(taskInstance.getOwnerType()) ? taskInstance.getOwnerId() : null);
                if (strategy instanceof ParameterizedStrategy) {
                    populateStrategyParams((ParameterizedStrategy)strategy, getTemplate(), taskInstance.getOwnerId(), null);
                }
                XmlOptions xmlOpts = new XmlOptions().setDocumentType(SubTaskPlanDocument.type);
                SubTaskPlanDocument subTaskPlanDoc = SubTaskPlanDocument.Factory.parse(strategy.getSubTaskPlan(runtimeContext), xmlOpts);
                SubTaskPlan plan = subTaskPlanDoc.getSubTaskPlan();
                return plan.getSubTaskList();
            }
            catch (Exception ex) {
                throw new ServiceException(ex.getMessage(), ex);
            }
        }
    }

    void createSubtasks(List subtasks, TaskRuntimeContext masterTaskContext)
            throws ServiceException, DataAccessException, IOException {
        for (SubTask subtask : subtasks) {
            // TODO using schemas field logicalId to store subtask asset path
            TaskTemplate subtaskTemplate = TaskTemplateCache.getTaskTemplate(subtask.getLogicalId());
            if (subtaskTemplate == null)
                throw new ServiceException("Task Template '" + subtask.getLogicalId() + "' does not exist");

            TaskInstance subtaskInstance = createTaskInstance(subtaskTemplate.getTaskId(), masterTaskContext.getMasterRequestId(),
                    masterTaskContext.getProcessInstanceId(), OwnerType.TASK_INSTANCE, masterTaskContext.getTaskInstanceId(), null, null);
            logger.info("Subtask instance created - ID " + subtaskInstance.getTaskInstanceId());
            // TODO recursive subtask creation
        }
    }

    List getSubtasks(Long masterTaskInstanceId) throws DataAccessException {
        return new TaskDataAccess().getSubtaskInstances(masterTaskInstanceId);
    }

    void resume(String action) throws ServiceException, IOException {
        // resume through engine
        if (getTemplate().isAutoformTask())
            resumeAutoForm(action);
        else
            resumeCustom(action);
    }

    public void resumeThroughService(String taskResumeEndpoint, String action, Long userId,
            String comment) throws ServiceException {
        UserTaskAction taskAction = new UserTaskAction();
        taskAction.setTaskInstanceId(taskInstance.getTaskInstanceId());
        taskAction.setTaskAction(action);
        try {
            taskAction.setUser(UserGroupCache.getUser(userId).getCuid());
            taskAction.setComment(comment);
            // Send request to new endpoint, while preventing infinte loop with
            // new Query parameter
            HttpHelper helper = new HttpHelper(new URL(taskResumeEndpoint + "/Services/Tasks/"
                    + taskInstance.getId() + "/" + action + "?disableEndpoint=true"));
            String response = helper.post(taskAction.getJson().toString(2));
            StatusMessage statusMessage = new StatusMessage(new JsonObject(response));
            if (statusMessage.getCode() != 0)
                throw new ServiceException(
                        "Failure response resuming task instance " + taskInstance.getId() + " at "
                                + taskResumeEndpoint + ": " + statusMessage.getMessage());
        }
        catch (Exception ex) {
            throw new ServiceException("Failed to resume task instance: " + taskInstance.getId(), ex);
        }
    }

    private void resumeAutoForm(String taskAction) throws ServiceException {
        try {
            String eventName = TaskAttributeConstant.TASK_CORRELATION_ID_PREFIX + taskInstance.getId();
            JSONObject jsonMsg = new JsonObject();
            String formAction;
            if (taskAction.equals(TaskAction.CANCEL))
                formAction = "@CANCEL_TASK";
            else if (taskAction.equals(TaskAction.COMPLETE))
                formAction = "@COMPLETE_TASK";
            else {
                formAction = "@COMPLETE_TASK";
                jsonMsg.put(TaskAttributeConstant.URLARG_COMPLETION_CODE, taskAction);
            }
            jsonMsg.put(TaskAttributeConstant.TASK_ACTION, formAction);
            JSONObject jsonMeta = new JsonObject().put("META", jsonMsg);
            Long docId = createDocument(jsonMeta, JSONObject.class.getName());
            String av = PropertyManager.getProperty(PropertyNames.ACTIVITY_RESUME_DELAY);
            int delay = 2;
            if (av!=null) {
                // delay some seconds to avoid race condition
                try {
                    delay = Integer.parseInt(av);
                    if (delay < 0)
                        delay = 0;
                    else if (delay > 300)
                        delay = 300;
                } catch (Exception e) {
                }
            }
            notifyProcess(eventName, docId, jsonMeta.toString(2), delay);
        }
        catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            throw new ServiceException(ex.getMessage(), ex);
        }
    }

    private void resumeCustom(String action) throws ServiceException {
        try {
            Long actInstId = getActivityInstanceId(false);

            String correlationId = "TaskAction-" + actInstId.toString();
            ActionRequestDocument actionRequestDoc = ActionRequestDocument.Factory.newInstance();
            ActionRequest actionRequest = actionRequestDoc.addNewActionRequest();
            Action actionItem = actionRequest.addNewAction();
            actionItem.setName("TaskAction");
            Parameter param = actionItem.addNewParameter();
            param.setName("Action");
            param.setStringValue(action);

            Long docId = createDocument(actionRequestDoc, XmlObject.class.getName());
            int delay = 2;
            String av = PropertyManager.getProperty(PropertyNames.ACTIVITY_RESUME_DELAY);
            if (av!=null) {
                try {
                    delay = Integer.parseInt(av);
                    if (delay<0) delay = 0;
                    else if (delay>300) delay = 300;
                } catch (Exception e) {
                    logger.warn("activity resume delay spec is not an integer");
                }
            }
            notifyProcess(correlationId, docId, actionRequestDoc.xmlText(), delay);
        }
        catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            throw new ServiceException(ex.getMessage(), ex);
        }
    }

    private void notifyProcess(String eventName, Long eventInstId,
            String message, int delay) throws DataAccessException, EventException {
        EventServices eventManager = ServiceLocator.getEventServices();
        eventManager.notifyProcess(eventName, eventInstId, message, delay);
    }

    private Long createDocument(Object value, String variableType) throws DataAccessException, ServiceException {
        if (!OwnerType.PROCESS_INSTANCE.equals(taskInstance.getOwnerType()))
            throw new DataAccessException("Invalid owner for creating task doc: "
                    + taskInstance.getOwnerType() + " (" + taskInstance.getId() + ")");

        WorkflowServices workflowServices = ServiceLocator.getWorkflowServices();
        ProcessInstance proc = workflowServices.getProcess(taskInstance.getOwnerId());
        Package pkg = PackageCache.getPackage(proc.getPackageName());
        EventServices eventMgr = ServiceLocator.getEventServices();
        return eventMgr.createDocument(variableType, OwnerType.TASK_INSTANCE, taskInstance.getTaskInstanceId(), value, pkg);
    }

    public Long getActivityInstanceId(boolean sourceActInst)
    {
      Long activityInstanceId = null;
      TaskInstance taskInst = taskInstance;
      try
      {
        if (!OwnerType.PROCESS_INSTANCE.equals(taskInstance.getOwnerType()))
            return null;
        WorkflowServices workflowServices = ServiceLocator.getWorkflowServices();
        ProcessInstance procInst = workflowServices.getProcess(taskInstance.getOwnerId());
        if (sourceActInst && procInst.isEmbedded()) {
            // subprocess secondary owner is activity instance
            activityInstanceId = procInst.getSecondaryOwnerId();
        }
        else {
            // Master/Sub task
            if (OwnerType.TASK_INSTANCE.equals(taskInstance.getSecondaryOwnerType())) {
                // secondary owner id refers to master task instance id
                taskInst = new TaskDataAccess().getTaskInstance(taskInstance.getSecondaryOwnerId());
            }
            // task instance secondary owner is work transition instance
            Long workTransInstId = taskInst.getSecondaryOwnerId();
            TransitionInstance workTransInst = ServiceLocator.getEventServices().getWorkTransitionInstance(workTransInstId);
            activityInstanceId = workTransInst.getDestinationID();
        }
      }
      catch (Exception ex) {
        logger.error(ex.getMessage(), ex);
      }
      return activityInstanceId;
    }

    ActivityInstance getActivityInstance(boolean sourceActInst) throws DataAccessException, ServiceException {
        try {
            Long activityInstanceId = getActivityInstanceId(sourceActInst);
            if (activityInstanceId == null)
                return null;
            return ServiceLocator.getEventServices().getActivityInstance(activityInstanceId);
        }
        catch (ProcessException ex) {
            throw new ServiceException(ex.getMessage(), ex);
        }
    }

    void scheduleTaskSlaEvent(Date dueDate, int alertInterval, boolean isReschedule) {
        ScheduledEventQueue queue = ScheduledEventQueue.getSingleton();
        String eventName = ScheduledEvent.SPECIAL_EVENT_PREFIX
            + "TaskDueDate." + taskInstance.getId();
        boolean isAlert;
        Date eventDate;
        if (alertInterval>0) {
            long eventTime = dueDate.getTime()-alertInterval*1000;
            if (eventTime"
            + (isAlert?"true":"false") + "";
        if (isReschedule) {
            queue.rescheduleExternalEvent(eventName, eventDate, message);
        }
        else {
            queue.scheduleExternalEvent(eventName, eventDate, message, null);
        }
    }

    void unscheduleTaskSlaEvent() throws ServiceException {
        ScheduledEventQueue queue = ScheduledEventQueue.getSingleton();
        String eventName = ScheduledEvent.SPECIAL_EVENT_PREFIX
            + "TaskDueDate." + taskInstance.getId();
        String backwardCompatibleEventName = ScheduledEvent.EXTERNAL_EVENT_PREFIX
            + "TaskDueDate." + taskInstance.getId();
        try {
            queue.unscheduleEvent(eventName);   // this broadcasts
            queue.unscheduleEvent(backwardCompatibleEventName); // this broadcasts
        } catch (Exception e) {
            throw new ServiceException("Failed to unschedule task SLA", e);
        }
    }

    /**
     * Determines a task instance's workgroups based on the defined strategy.  If no strategy exists,
     * default to the workgroups defined in the task template.
     *
     * By default this method propagates StrategyException as ServiceException.  If users wish to continue
     * processing they can override the default strategy implementation to catch StrategyExceptions.
     */
    List determineWorkgroups(Map indexes)
    throws ServiceException, IOException {
        TaskTemplate taskTemplate = getTemplate();
        String routingStrategyAttr = taskTemplate.getAttribute(TaskAttributeConstant.ROUTING_STRATEGY);
        if (StringUtils.isBlank(routingStrategyAttr)) {
            return taskTemplate.getWorkgroups();
        }
        else {
            try {
                RoutingStrategy strategy = TaskInstanceStrategyFactory.getRoutingStrategy(routingStrategyAttr,
                        OwnerType.PROCESS_INSTANCE.equals(taskInstance.getOwnerType()) ? taskInstance.getOwnerId() : null);
                if (strategy instanceof ParameterizedStrategy) {
                    populateStrategyParams((ParameterizedStrategy)strategy, getTemplate(), taskInstance.getOwnerId(), indexes);
                }
                return strategy.determineWorkgroups(taskTemplate, taskInstance);
            }
            catch (Exception ex) {
                throw new ServiceException(ex.getMessage(), ex);
            }
        }
    }

    public TaskTemplate getTemplate() throws IOException {
        return TaskTemplateCache.getTaskTemplate(taskInstance.getTaskId());
    }

    private static void populateStrategyParams(ParameterizedStrategy strategy,
            TaskTemplate template, Long processInstanceId, Map indexes) throws ServiceException {
        if (template.getAttributes() != null) {
            for (String name : template.getAttributes().keySet()) {
                strategy.setParameter(name, template.getAttributes().get(name));
            }
        }
        ProcessRuntimeContext context = ServiceLocator.getWorkflowServices().getContext(processInstanceId);
        for (String name : context.getValues().keySet()) {
            strategy.setParameter(name, context.getValues().get(name));
        }
    }

    // TODO: handle non-standard status changes
    public void notifyTaskAction(String action, Integer previousStatus, Integer previousState)
    throws ServiceException, DataAccessException, IOException {
        CodeTimer timer = new CodeTimer("TaskManager.notifyStatusChange()", true);

        try {
            // new-style notifiers
            String outcome = TaskStatuses.getTaskStatuses().get(taskInstance.getStatusCode());
            notifyMonitors(action, outcome);
            sendNotification(action, outcome);

            // new-style auto-assign
            if (TaskStatus.STATUS_OPEN.intValue() == taskInstance.getStatusCode().intValue()
                    && !action.equals(TaskAction.RELEASE)) {
                AutoAssignStrategy strategy = getAutoAssignStrategy();
                if (strategy != null) {
                    User assignee = strategy.selectAssignee(taskInstance);
                    if (assignee == null)
                        logger.error("No users found for auto-assignment of task instance ID: " + taskInstance.getTaskInstanceId());
                    else
                    {
                      taskInstance.setAssigneeId(assignee.getId());
                      taskInstance.setAssigneeCuid(assignee.getCuid());
                      performAction(TaskAction.ASSIGN, assignee.getId(), assignee.getId(), "Auto-assigned", null, false, false);
                    }
                }
            }
        }
        catch (ObserverException ex) {
            // do not rethrow; observer problems should not prevent task actions
            logger.error(ex.getMessage(), ex);
        }
        timer.stopAndLogTiming("");
    }

    private void notifyMonitors(String action, String outcome) {
        try {
            ActivityInstance activityInstance = getActivityInstance(true);
            if (activityInstance != null) {
                WorkflowServices workflowServices = ServiceLocator.getWorkflowServices();
                ProcessInstance processInstance = workflowServices.getProcess(activityInstance.getProcessInstanceId());
                Package pkg = PackageCache.getPackage(processInstance.getPackageName());
                Process process = ProcessCache.getProcess(processInstance.getProcessId());
                Activity activity = process.getActivity(activityInstance.getActivityId());
                TaskRuntimeContext taskContext = getContext();
                taskContext.getTaskInstance().setActivityInstanceId(activityInstance.getId());
                ActivityRuntimeContext activityContext = new ActivityRuntimeContext(null, pkg, process, processInstance,
                        0, false, activity, TaskActivity.class.getName(), activityInstance, false);
                List monitors = MonitorRegistry.getInstance().getTaskMonitors(activityContext);
                // TODO: add task-registered monitors
                for (TaskMonitor monitor : monitors) {
                    switch (action) {
                        case TaskAction.CREATE:
                            monitor.onCreate(taskContext);
                            break;
                        case TaskAction.ASSIGN:
                        case TaskAction.CLAIM:
                            monitor.onAssign(taskContext);
                            break;
                        case TaskAction.FORWARD:
                            monitor.onForward(taskContext);
                            break;
                        case TaskAction.WORK:
                            monitor.onInProgress(taskContext);
                            break;
                        case TaskAction.COMPLETE:
                            monitor.onComplete(taskContext);
                            break;
                        case TaskAction.CANCEL:
                            monitor.onCancel(taskContext);
                            break;
                        default:
                            if (TaskState.STATE_ALERT.equals(outcome)) {
                                monitor.onAlert(taskContext);
                            } else if (TaskState.STATE_JEOPARDY.equals(outcome)) {
                                monitor.onJeopardy(taskContext);
                            }
                            break;
                    }
                }
            }
        }
        catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
        }
    }
    private void sendNotification(String action, String outcome) {
        try {
            TaskInstanceNotifierFactory notifierFactory = TaskInstanceNotifierFactory.getInstance();
            Long processInstId = OwnerType.PROCESS_INSTANCE.equals(taskInstance.getOwnerType()) ? taskInstance.getOwnerId() : null;
            List notifierSpecs = notifierFactory.getNotifierSpecs(taskInstance.getTaskId(), processInstId, outcome);
            if (notifierSpecs == null || notifierSpecs.isEmpty())
                return;
            if (taskInstance.isShallow())
                getTaskInstanceAdditionalInfo();
            TaskRuntimeContext taskRuntime = getContext();
            for (String notifierSpec : notifierSpecs) {
                try {
                    TaskNotifier notifier = notifierFactory.getNotifier(notifierSpec, processInstId);
                    if (notifier != null) {
                        notifier.sendNotice(taskRuntime, action, outcome);
                    }
                }
                catch (Exception ex) {
                    // don't let one notifier failure prevent others from processing
                    logger.error(ex.getMessage(), ex);
                }
            }
        } catch (Exception ex) {
            logger.error("Failed to send email notification for task instance "
                    + taskInstance.getTaskInstanceId(), ex);
        }
    }

    /**
     * Retrieve additional info for task instance, including
     * assignee user name, due date, groups, master request id, etc.
     */
    void getTaskInstanceAdditionalInfo()
    throws IOException, DataAccessException, ServiceException {
        new TaskDataAccess().getTaskInstanceAdditionalInfoGeneral(taskInstance);
        taskInstance.setTaskInstanceUrl(getTaskInstanceUrl());
    }

    public String getTaskInstanceUrl() throws ServiceException, IOException {
        // check for custom page
        TaskTemplate template = getTemplate();
        if (template != null && template.isHasCustomPage()) {
            String assetSpec = template.getCustomPage();
            if (assetSpec.endsWith(".jsx")) {
                if (template.getCustomPageAssetVersion() != null)
                    assetSpec += " v" + template.getCustomPageAssetVersion();
                AssetVersionSpec customPage = AssetVersionSpec.parse(assetSpec);
                try {
                    CustomPageLookup pageLookup = new CustomPageLookup(customPage, taskInstance.getTaskInstanceId());
                    return pageLookup.getUrl();
                }
                catch (Exception ex) {
                    throw new ServiceException("Cannot determine custom page URL for task: " + template.getLabel(), ex);
                }
            }
        }

        // default url
        String baseUrl = ApplicationContext.getMdwHubUrl();
        if (!baseUrl.endsWith("/"))
          baseUrl += "/";
        return baseUrl + "#/tasks/" + taskInstance.getTaskInstanceId();
    }

    public TaskRuntimeContext getContext() throws ServiceException, IOException {
        User assignee = null;
        if (taskInstance.getAssigneeCuid() != null) {
            try {
                assignee = ServiceLocator.getUserServices().getUser(taskInstance.getAssigneeCuid());
                if (assignee == null)
                    throw new ServiceException(ServiceException.NOT_FOUND, "User not found: " + taskInstance.getAssigneeCuid());
            }
            catch (DataAccessException ex) {
                throw new ServiceException(ex.getMessage(), ex);
            }
        }

        WorkflowServices workflowServices = ServiceLocator.getWorkflowServices();

        ProcessRuntimeContext processContext = null;
        if (OwnerType.PROCESS_INSTANCE.equals(taskInstance.getOwnerType()))
            processContext = workflowServices.getContext(taskInstance.getOwnerId(), true);

        if (processContext != null) {
            return new TaskRuntimeContext(null, processContext, getTemplate(), taskInstance, assignee);
        }
        else {
            return new TaskRuntimeContext(null, null, null, null, new HashMap<>(), getTemplate(), taskInstance, assignee);
        }
    }

    public AutoAssignStrategy getAutoAssignStrategy() throws IOException, ServiceException {
        String autoAssignAttr = getTemplate().getAttribute(TaskAttributeConstant.AUTO_ASSIGN);
        AutoAssignStrategy strategy = null;
        if (StringUtils.isBlank(autoAssignAttr))
            return strategy;
        else{
          try {
            strategy = TaskInstanceStrategyFactory.getAutoAssignStrategy(autoAssignAttr,
                    OwnerType.PROCESS_INSTANCE.equals(taskInstance.getOwnerType()) ? taskInstance.getOwnerId() : null);
            if (strategy instanceof ParameterizedStrategy) {
              // need to check how to pass the indices
              populateStrategyParams((ParameterizedStrategy)strategy, getTemplate(), taskInstance.getOwnerId(), null);
            }
          }
          catch (Exception ex) {
              throw new ServiceException(ex.getMessage(), ex);
          }
        }
        return strategy;
    }

    void assign(Long userId)
    throws IOException, DataAccessException {
        if (isAssignable()) {
            taskInstance.setStatusCode(TaskStatus.STATUS_ASSIGNED);
            taskInstance.setAssigneeId(userId);
            Map changes = new HashMap();
            changes.put("TASK_CLAIM_USER_ID", userId);
            changes.put("TASK_INSTANCE_STATUS", TaskStatus.STATUS_ASSIGNED);
            new TaskDataAccess().updateTaskInstance(taskInstance.getTaskInstanceId(), changes, false);

            // update process variable with assignee - only for local tasks
            TaskTemplate taskVO = getTemplate();
            if (taskVO != null) {
                String assigneeVarSpec = taskVO.getAttribute(TaskAttributeConstant.ASSIGNEE_VAR);
                if (!StringUtils.isBlank(assigneeVarSpec)) {
                    try {
                        User user = UserGroupCache.getUser(userId);
                        String cuid = user == null ? null : user.getCuid();
                        if (cuid == null)
                            throw new DataAccessException("User not found for id: " + userId);
                        TaskRuntimeContext runtimeContext = getContext();
                        String prevCuid;
                        if (TaskRuntimeContext.isExpression(assigneeVarSpec))
                            prevCuid = runtimeContext.evaluateToString(assigneeVarSpec);
                        else {
                            Object varVal = runtimeContext.getValues().get(assigneeVarSpec);
                            prevCuid = varVal == null ? null : varVal.toString();
                        }

                        if (!cuid.equals(prevCuid)) {
                            // need to update variable to match new assignee
                            WorkflowServices workflowServices = ServiceLocator.getWorkflowServices();
                            if (TaskRuntimeContext.isExpression(assigneeVarSpec)) {
                                // create or update document variable referenced by expression
                                runtimeContext.set(assigneeVarSpec, cuid);
                                String rootVar = assigneeVarSpec.substring(2, assigneeVarSpec.indexOf('.'));
                                Variable doc = runtimeContext.getProcess().getVariable(rootVar);
                                VariableInstance varInst = runtimeContext.getProcessInstance().getVariable(rootVar);
                                String stringValue = runtimeContext.getPackage().getStringValue(doc.getType(), runtimeContext.evaluate("${" + rootVar + "}"), true);
                                if (varInst == null) {
                                    workflowServices.createDocument(runtimeContext, rootVar, stringValue);
                                }
                                else {
                                    workflowServices.updateDocument(runtimeContext, rootVar, stringValue);
                                }
                            }
                            else {
                                workflowServices.setVariable(runtimeContext, assigneeVarSpec, cuid);
                            }
                        }
                    }
                    catch (Exception ex) {
                        logger.error(ex.getMessage(), ex);
                    }
                }
            }
        }
        else {
            logger.warn("TaskInstance is not assignable.  ID = " + taskInstance.getTaskInstanceId());
        }
    }

    void release() throws IOException, DataAccessException {
        taskInstance.setStatusCode(TaskStatus.STATUS_OPEN);
        taskInstance.setAssigneeId(null);
        Map changes = new HashMap();
        changes.put("TASK_CLAIM_USER_ID", null);
        changes.put("TASK_INSTANCE_STATUS", TaskStatus.STATUS_OPEN);
        new TaskDataAccess().updateTaskInstance(taskInstance.getTaskInstanceId(), changes, false);

        // clear assignee process variable value
        TaskTemplate taskVO = getTemplate();
        if (taskVO != null) { // not for invalid tasks
            String assigneeVarSpec = taskVO.getAttribute(TaskAttributeConstant.ASSIGNEE_VAR);
            if (!StringUtils.isBlank(assigneeVarSpec)) {
                try {
                    TaskRuntimeContext runtimeContext = getContext();
                    WorkflowServices workflowServices = ServiceLocator.getWorkflowServices();
                    if (TaskRuntimeContext.isExpression(assigneeVarSpec)) {
                        // create or update document variable referenced by expression
                        runtimeContext.set(assigneeVarSpec, null);
                        String rootVar = assigneeVarSpec.substring(2, assigneeVarSpec.indexOf('.'));
                        Variable doc = runtimeContext.getProcess().getVariable(rootVar);
                        VariableInstance varInst = runtimeContext.getProcessInstance().getVariable(rootVar);
                        String stringValue = runtimeContext.getPackage().getStringValue(doc.getType(), runtimeContext.evaluate("${" + rootVar + "}"), true);
                        if (varInst == null) {
                            workflowServices.createDocument(runtimeContext, rootVar, stringValue);
                        }
                        else {
                            workflowServices.updateDocument(runtimeContext, rootVar, stringValue);
                        }
                    }
                    else {
                        workflowServices.setVariable(runtimeContext, assigneeVarSpec, null);
                    }
                }
                catch (Exception ex) {
                    logger.error(ex.getMessage(), ex);
                }
            }
        }
    }

    void work() throws DataAccessException {
        taskInstance.setStatusCode(TaskStatus.STATUS_IN_PROGRESS);
        Map changes = new HashMap<>();
        changes.put("TASK_INSTANCE_STATUS", TaskStatus.STATUS_IN_PROGRESS);
        new TaskDataAccess().updateTaskInstance(taskInstance.getTaskInstanceId(), changes, false);
    }

    void close(Long userId, String comment) throws ServiceException, DataAccessException, IOException {
        close(TaskAction.CLOSE, comment);
        EventServices eventServices = ServiceLocator.getEventServices();
        if (getTemplate().isAutoformTask())
            eventServices.deleteEventWaitInstance("TaskInstance:" + taskInstance.getId());
        ActivityInstance taskActivityInstance = getActivityInstance(false);
        try {
            new TaskDataAccess().setActivityInstanceStatus(taskActivityInstance, WorkStatus.STATUS_CANCELLED,
                    "Task " + taskInstance.getId() + " closed");
            if (!getTemplate().isAutoformTask())
                eventServices.deleteEventWaitInstance("TaskAction-" + taskActivityInstance.getId());
            User user = UserGroupCache.getUser(userId);
            ActivityLogger.persist(taskActivityInstance.getProcessInstanceId(), taskActivityInstance.getId(),
                    StandardLogger.LogLevel.INFO, "Task closed by " + user.getCuid() + (comment == null ? "" : (": " + comment)));
        }
        catch (SQLException ex) {
            logger.error("Error updating activity instance " + taskActivityInstance.getId(), ex);
        }
    }

    private void forward(String destination, String comment)
    throws ServiceException, DataAccessException, IOException {
        List prevWorkgroups = taskInstance.getWorkgroups();
        if (prevWorkgroups == null || prevWorkgroups.isEmpty()) {
            prevWorkgroups = getTemplate().getWorkgroups();
        }

        // change the task instance to be associated with the specified group
        release();

        TaskDataAccess dataAccess = new TaskDataAccess();

        Map changes = new HashMap();
        changes.put("TASK_INSTANCE_STATUS", TaskStatus.STATUS_OPEN);
        changes.put("TASK_CLAIM_USER_ID", null);
        if (comment != null)
            changes.put("COMMENTS", comment);
        dataAccess.updateTaskInstance(taskInstance.getTaskInstanceId(), changes, false);
        String[] destWorkgroups = destination.split(",");
        try {
            for(String groupName:destWorkgroups) {
                if (UserGroupCache.getWorkgroup(groupName) == null) {
                    throw new ServiceException( "Invalid Workgroup: " + groupName);
                }
            }
        } catch (CachingException ex) {
            logger.error(ex.getMessage(), ex);
        }
        dataAccess.setTaskInstanceGroups(taskInstance.getTaskInstanceId(),destWorkgroups );
        taskInstance.setWorkgroups(Arrays.asList(destWorkgroups));

        try {
            // new-style notifiers (registered on destination task)
            sendNotification(TaskAction.FORWARD, TaskAction.FORWARD);

            AutoAssignStrategy strategy = getAutoAssignStrategy();
            if (strategy != null) {
                User assignee = strategy.selectAssignee(taskInstance);
                if (assignee == null)
                    logger.error("No users found for auto-assignment of task instance ID: " + taskInstance.getId());
                else
                    performAction(TaskAction.ASSIGN, assignee.getId(), assignee.getId(), "Auto-assigned", null, false, false);
            }
        }
        catch (ObserverException ex) {
            // do not rethrow; observer problems should not prevent task actions
            logger.error(ex.getMessage(), ex);
        }
    }

    void close(String action, String comment) throws ServiceException, DataAccessException {
        Integer newStatus = TaskStatus.STATUS_COMPLETED;
        if (action.equals(TaskAction.CANCEL) || action.startsWith(TaskAction.CANCEL + "::")
                || action.equals(TaskAction.ABORT) || action.equals(TaskAction.CLOSE))
            newStatus = TaskStatus.STATUS_CANCELLED;
        Map changes = new HashMap<>();
        changes.put("TASK_INSTANCE_STATUS", newStatus);
        changes.put("TASK_INSTANCE_STATE", TaskState.STATE_CLOSED);
        if (comment != null)
            changes.put("COMMENTS", comment);
        TaskDataAccess dataAccess = new TaskDataAccess();
        dataAccess.updateTaskInstance(taskInstance.getTaskInstanceId(), changes, true);
        try {
            Long elapsedMs = dataAccess.getDatabaseTime() - Date.from(taskInstance.getStart()).getTime();
            dataAccess.setElapsedTime(OwnerType.TASK_INSTANCE, taskInstance.getTaskInstanceId(), elapsedMs);
        }
        catch (SQLException ex) {
            logger.error("Failed to set timing for task: " + taskInstance.getId(), ex);
        }
        taskInstance.setStatusCode(newStatus);
    }

    void cancel() throws ServiceException, DataAccessException {
        if (taskInstance.isInFinalStatus()) {
            logger.info("Cannot change the state of the TaskInstance to Cancel.");
            return;
        }
        Integer prevStatus = taskInstance.getStatusCode();
        Integer prevState = taskInstance.getStateCode();
        taskInstance.setStateCode(TaskState.STATE_CLOSED);
        taskInstance.setStatusCode(TaskStatus.STATUS_CANCELLED);
        Map changes = new HashMap<>();
        changes.put("TASK_INSTANCE_STATUS", TaskStatus.STATUS_CANCELLED);
        changes.put("TASK_INSTANCE_STATE", TaskState.STATE_CLOSED);
        TaskDataAccess dataAccess = new TaskDataAccess();
        dataAccess.updateTaskInstance(taskInstance.getTaskInstanceId(), changes, true);
        try {
            Long elapsedMs = dataAccess.getDatabaseTime() - Date.from(taskInstance.getStart()).getTime();
            dataAccess.setElapsedTime(OwnerType.TASK_INSTANCE, taskInstance.getTaskInstanceId(), elapsedMs);
            notifyTaskAction(TaskAction.CANCEL, prevStatus, prevState);
        }
        catch (IOException | SQLException ex) {
            logger.error("Failed to set timing for task: " + taskInstance.getId(), ex);
        }
    }


    private boolean isAssignable() {
        return isOpen();
    }

    private boolean isOpen() {
        if (taskInstance.getStatusCode().intValue() == TaskStatus.STATUS_COMPLETED.intValue()) {
            return false;
        }
        if (taskInstance.getStatusCode().intValue() == TaskStatus.STATUS_CANCELLED.intValue()) {
            return false;
        }
        return true;
    }

    /**
     * Filters according to what's applicable
     * depending on the context of the task activity instance.
     *
     * @param standardTaskActions unfiltered list
     * @return the list of task actions
     */
    public List filterStandardActions(List standardTaskActions)
    throws ServiceException, DataAccessException {

        CodeTimer timer = new CodeTimer("TaskManager.filterStandardTaskActions()", true);

        List filteredTaskActions = standardTaskActions;
        try {
            ActivityInstance activityInstance = getActivityInstance(true);

            if (activityInstance != null) {
                Long processInstanceId = activityInstance.getProcessInstanceId();
                ProcessInstance processInstance = ServiceLocator.getWorkflowServices().getProcess(processInstanceId);

                if (processInstance.isEmbedded()) {
                    // remove RETRY since no default behavior is defined for inline tasks
                    TaskAction retryAction = null;
                    for (TaskAction taskAction : standardTaskActions) {
                        if (taskAction.getTaskActionName().equalsIgnoreCase("Retry"))
                            retryAction = taskAction;
                        }
                    if (retryAction != null)
                        standardTaskActions.remove(retryAction);
                }

                Process processVO = null;
                if (processInstance.getInstanceDefinitionId() > 0L)
                    processVO = ProcessCache.getInstanceDefinition(processInstance.getProcessId(), processInstance.getInstanceDefinitionId());
                if (processVO == null)
                    processVO = ProcessCache.getProcess(processInstance.getProcessId());
                if (processInstance.isEmbedded())
                    processVO = processVO.getSubProcess(new Long(processInstance.getComment()));
                List outgoingWorkTransVOs = processVO.getAllTransitions(activityInstance.getActivityId());
                boolean foundNullResultCode = false;
                for (Transition workTransVO : outgoingWorkTransVOs) {
                    Integer eventType = workTransVO.getEventType();
                    if ((eventType.equals(EventType.FINISH) || eventType.equals(EventType.RESUME))
                        && workTransVO.getCompletionCode() == null) {
                      foundNullResultCode = true;
                      break;
                    }
                }
                if (!foundNullResultCode) {
                    TaskAction cancelAction = null;
                    TaskAction completeAction = null;
                    for (TaskAction taskAction : standardTaskActions) {
                        if (taskAction.getTaskActionName().equalsIgnoreCase("Cancel"))
                            cancelAction = taskAction;
                        if (taskAction.getTaskActionName().equalsIgnoreCase("Complete"))
                            completeAction = taskAction;
                    }
                    if (cancelAction != null)
                        standardTaskActions.remove(cancelAction);
                    if (completeAction != null)
                      standardTaskActions.remove(completeAction);
                }
            }
        }
        catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
        }
        timer.stopAndLogTiming("");
        return filteredTaskActions;
    }

    /**
     * Gets the custom task actions associated with a task instance as determined by
     * the result codes for the possible outgoing work transitions from the associated
     * activity.
     *
     * @return the list of task actions
     */
    public List getCustomActions()
    throws ServiceException, DataAccessException {

        CodeTimer timer = new CodeTimer("getCustomActions()", true);
        List dynamicTaskActions = new ArrayList();

        try {
            ActivityInstance activityInstance = getActivityInstance(true);

            if (activityInstance != null) {
                Long processInstanceId = activityInstance.getProcessInstanceId();
                ProcessInstance processInstance = ServiceLocator.getWorkflowServices().getProcess(processInstanceId);
                Process processVO = null;
                if (processInstance.getInstanceDefinitionId() > 0L)
                    processVO = ProcessCache.getInstanceDefinition(processInstance.getProcessId(), processInstance.getInstanceDefinitionId());
                if (processVO == null)
                    processVO = ProcessCache.getProcess(processInstance.getProcessId());
                if (processInstance.isEmbedded())
                    processVO = processVO.getSubProcess(new Long(processInstance.getComment()));
                List outgoingWorkTransVOs = processVO.getAllTransitions(activityInstance.getActivityId());
                for (Transition workTransVO : outgoingWorkTransVOs) {
                    String resultCode = workTransVO.getCompletionCode();
                    if (resultCode != null) {
                        Integer eventType = workTransVO.getEventType();
                        if (eventType.equals(EventType.FINISH) || eventType.equals(EventType.RESUME) || TaskAction.FORWARD.equals(resultCode)) {
                            TaskAction taskAction = new TaskAction();
                            taskAction.setTaskActionName(resultCode);
                            taskAction.setCustom(true);
                            dynamicTaskActions.add(taskAction);
                        }
                    }
                }
            }
        }
        catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
        }
        timer.stopAndLogTiming("");
        return dynamicTaskActions;
    }

    void auditLog(String action, Long userId, String destination, String comments)
    throws ServiceException, DataAccessException {
        String userCuid = null;
        if (userId != null) {
            UserDataAccess uda = new UserDataAccess();
            User user = uda.getUser(userId);
            userCuid = user.getCuid();
        }
        auditLog(action, userCuid, destination, comments);
    }

    void auditLog(String action, String user) throws ServiceException {
        auditLog(action, user, null, null);
    }

    void auditLog(String action, String user, String destination, String comments)
    throws ServiceException {
        if (user == null)
            user = "N/A";

        Entity entity = Entity.TaskInstance;
        Long entityId = taskInstance.getTaskInstanceId();

        UserAction userAction = new UserAction(user, UserAction.getAction(action), entity, entityId, comments);
        userAction.setSource("Task Services");
        userAction.setDestination(destination);
        if (userAction.getAction().equals(UserAction.Action.Other)) {
            if (action != null && action.startsWith(TaskAction.CANCEL + "::")) {
                userAction.setAction(UserAction.Action.Cancel);
            }
            else {
            userAction.setExtendedAction(action);
            }
        }
        try {
            EventServices eventManager = ServiceLocator.getEventServices();
            eventManager.createAuditLog(userAction);
        }
        catch (DataAccessException ex) {
            throw new ServiceException("Failed to create audit log: " + userAction, ex);
        }
    }

    public void updateState(boolean isAlert) throws DataAccessException {
        if (isAlert) {
            if (taskInstance.getStateCode().equals(TaskState.STATE_OPEN)) {
                Map changes = new HashMap<>();
                changes.put("TASK_INSTANCE_STATE", TaskState.STATE_ALERT);
                new TaskDataAccess().updateTaskInstance(taskInstance.getTaskInstanceId(), changes,
                        false);
                if (taskInstance.getDue() != null) {
                    scheduleTaskSlaEvent(Date.from(taskInstance.getDue()), 0, false);
                }
                notifyMonitors("UPDATE", TaskState.getTaskStateName(TaskState.STATE_ALERT));
                sendNotification("UPDATE", TaskState.getTaskStateName(TaskState.STATE_ALERT));
            }
        }
        else {
            if (taskInstance.getStateCode().equals(TaskState.STATE_OPEN)
                    || taskInstance.getStateCode().equals(TaskState.STATE_ALERT)) {
                Map changes = new HashMap<>();
                changes.put("TASK_INSTANCE_STATE", TaskState.STATE_JEOPARDY);
                new TaskDataAccess().updateTaskInstance(taskInstance.getTaskInstanceId(), changes,false);
                notifyMonitors("UPDATE", TaskState.getTaskStateName(TaskState.STATE_JEOPARDY));
                sendNotification("UPDATE", TaskState.getTaskStateName(TaskState.STATE_JEOPARDY));
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy