com.globalsight.everest.workflowmanager.WorkflowManagerLocal.java Source code

Java tutorial

Introduction

Here is the source code for com.globalsight.everest.workflowmanager.WorkflowManagerLocal.java

Source

/**
 * Copyright 2009 Welocalize, 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.globalsight.everest.workflowmanager;

import java.io.File;
import java.io.IOException;
import java.rmi.RemoteException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TimeZone;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.jms.JMSException;
import javax.naming.NamingException;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.jbpm.JbpmContext;
import org.jbpm.graph.def.Node;
import org.jbpm.graph.exe.ProcessInstance;
import org.jbpm.taskmgmt.exe.TaskInstance;

import com.globalsight.calendar.BaseFluxCalendar;
import com.globalsight.calendar.CalendarManager;
import com.globalsight.calendar.FluxCalendar;
import com.globalsight.calendar.ReservedTime;
import com.globalsight.calendar.UserFluxCalendar;
import com.globalsight.cxe.adapter.documentum.DocumentumOperator;
import com.globalsight.cxe.entity.fileprofile.FileProfile;
import com.globalsight.cxe.util.EventFlowXmlParser;
import com.globalsight.everest.comment.CommentException;
import com.globalsight.everest.comment.CommentFile;
import com.globalsight.everest.comment.CommentImpl;
import com.globalsight.everest.comment.CommentManager;
import com.globalsight.everest.company.CompanyWrapper;
import com.globalsight.everest.company.MultiCompanySupportedThread;
import com.globalsight.everest.corpus.CorpusManagerWLRemote;
import com.globalsight.everest.costing.AmountOfWork;
import com.globalsight.everest.costing.CostingEngine;
import com.globalsight.everest.costing.Rate;
import com.globalsight.everest.coti.util.COTIUtilEnvoy;
import com.globalsight.everest.edit.offline.AmbassadorDwUpConstants;
import com.globalsight.everest.edit.offline.AmbassadorDwUpException;
import com.globalsight.everest.edit.offline.OEMProcessStatus;
import com.globalsight.everest.edit.offline.OfflineEditManager;
import com.globalsight.everest.edit.offline.download.DownloadParams;
import com.globalsight.everest.edit.offline.download.JobPackageZipper;
import com.globalsight.everest.foundation.EmailInformation;
import com.globalsight.everest.foundation.L10nProfile;
import com.globalsight.everest.foundation.Timestamp;
import com.globalsight.everest.integration.ling.LingServerProxy;
import com.globalsight.everest.jobhandler.Job;
import com.globalsight.everest.jobhandler.JobException;
import com.globalsight.everest.jobhandler.JobImpl;
import com.globalsight.everest.jobhandler.jobcreation.JobCreationMonitor;
import com.globalsight.everest.page.DataSourceType;
import com.globalsight.everest.page.Page;
import com.globalsight.everest.page.PageState;
import com.globalsight.everest.page.PageStateValidator;
import com.globalsight.everest.page.PrimaryFile;
import com.globalsight.everest.page.SourcePage;
import com.globalsight.everest.page.TargetPage;
import com.globalsight.everest.page.pageexport.ExportBatchEvent;
import com.globalsight.everest.page.pageexport.ExportConstants;
import com.globalsight.everest.page.pageexport.ExportEventObserverHelper;
import com.globalsight.everest.page.pageexport.ExportParameters;
import com.globalsight.everest.permission.Permission;
import com.globalsight.everest.permission.PermissionSet;
import com.globalsight.everest.persistence.tuv.SegmentTuTuvIndexUtil;
import com.globalsight.everest.persistence.tuv.SegmentTuUtil;
import com.globalsight.everest.persistence.tuv.SegmentTuvUtil;
import com.globalsight.everest.persistence.tuv.TuvQueryConstants;
import com.globalsight.everest.projecthandler.Project;
import com.globalsight.everest.projecthandler.WorkflowTemplateInfo;
import com.globalsight.everest.projecthandler.WorkflowTypeConstants;
import com.globalsight.everest.request.Request;
import com.globalsight.everest.secondarytargetfile.SecondaryTargetFile;
import com.globalsight.everest.secondarytargetfile.SecondaryTargetFileState;
import com.globalsight.everest.servlet.EnvoyServletException;
import com.globalsight.everest.servlet.util.ServerProxy;
import com.globalsight.everest.taskmanager.Task;
import com.globalsight.everest.taskmanager.TaskImpl;
import com.globalsight.everest.taskmanager.TaskInfo;
import com.globalsight.everest.tuv.TaskTuv;
import com.globalsight.everest.tuv.Tuv;
import com.globalsight.everest.tuv.TuvImpl;
import com.globalsight.everest.tuv.TuvState;
import com.globalsight.everest.usermgr.UserManager;
import com.globalsight.everest.util.jms.JmsHelper;
import com.globalsight.everest.util.system.SystemConfigParamNames;
import com.globalsight.everest.util.system.SystemConfiguration;
import com.globalsight.everest.webapp.WebAppConstants;
import com.globalsight.everest.webapp.javabean.TaskInfoBean;
import com.globalsight.everest.webapp.pagehandler.administration.company.CompanyRemoval;
import com.globalsight.everest.webapp.pagehandler.administration.users.UserHandlerHelper;
import com.globalsight.everest.webapp.pagehandler.administration.users.UserUtil;
import com.globalsight.everest.webapp.pagehandler.offline.OfflineConstants;
import com.globalsight.everest.webapp.pagehandler.offline.download.SendDownloadFileHelper;
import com.globalsight.everest.webapp.pagehandler.projects.l10nprofiles.LocProfileStateConstants;
import com.globalsight.everest.webapp.pagehandler.projects.workflows.JobDataMigration;
import com.globalsight.everest.webapp.pagehandler.tasks.TaskHelper;
import com.globalsight.everest.workflow.Activity;
import com.globalsight.everest.workflow.EventNotificationHelper;
import com.globalsight.everest.workflow.SkipActivityVo;
import com.globalsight.everest.workflow.SystemAction;
import com.globalsight.everest.workflow.SystemActionPerformer;
import com.globalsight.everest.workflow.TaskEmailInfo;
import com.globalsight.everest.workflow.WfTaskInfo;
import com.globalsight.everest.workflow.WorkflowConfiguration;
import com.globalsight.everest.workflow.WorkflowConstants;
import com.globalsight.everest.workflow.WorkflowHelper;
import com.globalsight.everest.workflow.WorkflowInstance;
import com.globalsight.everest.workflow.WorkflowInstanceInfo;
import com.globalsight.everest.workflow.WorkflowJbpmPersistenceHandler;
import com.globalsight.everest.workflow.WorkflowJbpmUtil;
import com.globalsight.everest.workflow.WorkflowMailerConstants;
import com.globalsight.everest.workflow.WorkflowNodeParameter;
import com.globalsight.everest.workflow.WorkflowServerWLRemote;
import com.globalsight.everest.workflow.WorkflowTaskInstance;
import com.globalsight.ling.inprogresstm.InProgressTmManager;
import com.globalsight.ling.tm2.persistence.DbUtil;
import com.globalsight.ling.tw.PseudoConstants;
import com.globalsight.log.GlobalSightCategory;
import com.globalsight.persistence.hibernate.HibernateUtil;
import com.globalsight.scheduling.SchedulerConstants;
import com.globalsight.util.AmbFileStoragePathUtils;
import com.globalsight.util.Entry;
import com.globalsight.util.GeneralException;
import com.globalsight.util.GlobalSightLocale;
import com.globalsight.util.ProcessRunner;
import com.globalsight.util.mail.MailerConstants;
import com.globalsight.util.modules.Modules;
import com.globalsight.util.zip.ZipIt;

public class WorkflowManagerLocal implements WorkflowManager {
    //
    // PRIVATE CONSTANTS
    //
    private static String ACTIVE_NODE_ID = "activeNodeId";

    private static String REASSIGNED_NODE_ID = "reassignedNodeId";

    private static String JOBCREATION_SCRIPT_LOG = null;

    private static String JOBCREATION_SCRIPT_ERR_LOG = null;

    private static final String WF_IMPORT_FAILED = Workflow.IMPORT_FAILED;

    private static final String WF_READY = Workflow.READY_TO_BE_DISPATCHED;

    private static final String WF_DISPATCHED = Workflow.DISPATCHED;

    private static final String WF_LOCALIZED = Workflow.LOCALIZED;

    private static final String WF_EXPORTING = Workflow.EXPORTING;

    private static final String WF_SKIPPING = Workflow.SKIPPING;

    private static final String WF_EXPORTED = Workflow.EXPORTED;

    private static final String WF_ARCHIVED = Workflow.ARCHIVED;

    private static final String WF_CANCELLED = Workflow.CANCELLED;

    private static final String PG_ACTIVE_JOB = PageState.ACTIVE_JOB;

    private static final String PG_LOCALIZED = PageState.LOCALIZED;

    private static final String PG_NOT_LOCALIZED = PageState.NOT_LOCALIZED;

    private static final String PG_EXPORTED = PageState.EXPORTED;

    private static final String PG_OUT_OF_DATE = PageState.OUT_OF_DATE;

    private static final String PG_IMPORT_FAIL = PageState.IMPORT_FAIL;

    private static int ADVANCE_ACTION = 0;

    private static int DISPATCH_ACTION = 1;

    private static int MODIFY_ACTION = 2;

    public static final int LOCALIZED_STATE = 3;

    public static final int EXPORTING_STATE = 4;

    private static final int MAX_THREAD = 5;

    public static final String[] ORDERED_STATES = { WF_IMPORT_FAILED, WF_READY, WF_DISPATCHED, WF_LOCALIZED,
            WF_EXPORTING, WF_SKIPPING, WF_EXPORTED, WF_ARCHIVED };

    private static final Logger s_logger = Logger.getLogger(WorkflowManagerLocal.class.getName());

    private int m_isNotificationActive = -1;
    private Float m_threshold = null;

    static {
        initParas();
    }

    // determines whether the system-wide notification is enabled
    private boolean m_systemNotificationEnabled = EventNotificationHelper.systemNotificationEnabled();

    public WorkflowManagerLocal() throws WorkflowManagerException {
    }

    private static void initParas() {
        try {
            // set the log directory for the error files
            String logDirectory = SystemConfiguration.getInstance()
                    .getStringParameter(SystemConfigParamNames.SYSTEM_LOGGING_DIRECTORY);
            // set the absolute path
            JOBCREATION_SCRIPT_LOG = logDirectory + "/jobCreationScript.log";
            JOBCREATION_SCRIPT_ERR_LOG = logDirectory + "/jobCreationScript.err";
        } catch (Exception e) {
            s_logger.warn("The log directory couldn't be found in the system configuration "
                    + " for JobCreation logging purposes.", e);
        }
    }

    /**
     * Get a list of workflow objects based on a particular workflow id.
     * <p>
     * 
     * @param p_workflowId
     *            - The id of the workflow.
     * @return A vector of only ONE workflow (always a vector is returned since
     *         TOPLink is unaware of querying for one or more objects.)
     * @exception JobException
     *                Component related exception.
     * @exception java.rmi.RemoteException
     *                Network related exception.
     */
    public Workflow getWorkflowById(long p_workflowId) throws RemoteException, WorkflowManagerException {
        Workflow wf = null;
        try {
            wf = HibernateUtil.get(WorkflowImpl.class, p_workflowId);
        } catch (Exception e) {
            s_logger.error(e);
            String[] args = new String[1];
            args[0] = new Long(p_workflowId).toString();
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_GET_WORKFLOW_BY_ID, args, e,
                    WorkflowManagerException.PROPERTY_FILE_NAME);

        }
        return wf;
    }

    /**
     * Get a list of workflow objects based on a particular workflow id. Also
     * ensures that the workflow has a pointer to its WorkflowInstance.
     * <p>
     * 
     * @param p_workflowId
     *            - The id of the workflow.
     * @exception JobException
     *                Component related exception.
     * @exception java.rmi.RemoteException
     *                Network related exception.
     */
    // TODO Is the difference between this and getWorkflowById significant?
    // Can they be combined?
    public Workflow getWorkflowByIdRefresh(long p_workflowId) throws RemoteException, WorkflowManagerException {
        Workflow wf = null;
        try {
            wf = getWorkflowById(p_workflowId);
            refreshWorkflowInstance(wf);
        } catch (Exception e) {
            String[] args = new String[1];
            args[0] = new Long(p_workflowId).toString();
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_GET_WORKFLOW_BY_ID, args, e,
                    WorkflowManagerException.PROPERTY_FILE_NAME);

        }
        return wf;
    }

    /**
     * @see WorkflowManager.cancel(String, Workflow)
     */
    public void cancel(String p_idOfUserRequestingCancel, Workflow p_workflow)
            throws RemoteException, WorkflowManagerException {
        String nameOfUserRequestingCancel = UserUtil.getUserNameById(p_idOfUserRequestingCancel);
        if (allowedToCancelWorkflows(p_idOfUserRequestingCancel)) {
            validateStateOfPages(p_workflow);

            Workflow wf = null;
            Session session = HibernateUtil.getSession();
            Transaction tx = session.beginTransaction();

            try {
                wf = (Workflow) session.get(WorkflowImpl.class, p_workflow.getIdAsLong());

                boolean isDispatched = WF_DISPATCHED.equals(wf.getState());
                String oldWorkflowState = wf.getState();
                wf.setState(WF_CANCELLED);

                JobImpl job = (JobImpl) wf.getJob();
                String oldJobState = job.getState();
                resetJobState(session, job, job.getWorkflows());
                session.saveOrUpdate(job);
                session.saveOrUpdate(wf);

                tx.commit();

                ArrayList msg = new ArrayList();
                msg.add(p_workflow.getIdAsLong());
                msg.add(isDispatched);
                msg.add(oldJobState);
                msg.add(oldWorkflowState);
                JmsHelper.sendMessageToQueue(msg, JmsHelper.JMS_CANCEL_WORKFLOW_QUEUE);

                s_logger.info(
                        "Workflow " + p_workflow.getId() + " was cancelled by user " + nameOfUserRequestingCancel);
            } catch (Exception we) {
                tx.rollback();
                s_logger.error("WorkflowManagerLocal::cancel(Workflow)", we);
                String[] args = new String[1];
                args[0] = new Long(p_workflow.getId()).toString();
                throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_CANCEL_WORKFLOW, args,
                        we);
            }
        } else {
            s_logger.error("The user " + nameOfUserRequestingCancel
                    + " doesn't have the permission to cancel the workflow.");
            String[] args = { Long.toString(p_workflow.getId()), nameOfUserRequestingCancel };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_CANCEL_USER_NOT_ALLOWED, args,
                    null);
        }
    }

    /**
     * For GBS-495, "Discard" Job(s) took long time to complete especially if
     * the job contain large files or too many files.
     * 
     * @param p_job
     * @param reimport
     */
    @SuppressWarnings("unchecked")
    private void cancel(Job p_job, boolean reimport) {
        JobImpl job = HibernateUtil.get(JobImpl.class, new Long(p_job.getId()));
        String oldState = job.getState();
        job.setState(Job.CANCELLED);
        HibernateUtil.saveOrUpdate(job);

        ArrayList msg = new ArrayList();
        msg.add(job.getIdAsLong());
        msg.add(oldState);
        msg.add(reimport);

        try {
            JmsHelper.sendMessageToQueue(msg, JmsHelper.JMS_CANCEL_JOB_QUEUE);
        } catch (JMSException e) {
            job.setState(oldState);
            HibernateUtil.saveOrUpdate(job);
            s_logger.error(e.getMessage(), e);
        } catch (NamingException e) {
            s_logger.error(e.getMessage(), e);
        }
    }

    /**
     * @see WorkflowManager.cancel(String, Job, String)
     */
    public void cancel(String p_idOfUserRequestingCancel, Job p_job, String p_state)
            throws RemoteException, WorkflowManagerException {
        boolean reimport = false;
        cancel(p_idOfUserRequestingCancel, p_job, p_state, reimport);
    }

    /**
     * @see WorkflowManager.cancel(String, Job, String, boolean)
     */
    public void cancel(String p_idOfUserRequestingCancel, Job p_job, String p_state, boolean p_reimport)
            throws RemoteException, WorkflowManagerException {
        if (p_state == null) {
            cancel(p_job, p_reimport);
            return;
        }

        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();

        String nameOfUserRequestingCancel = UserUtil.getUserNameById(p_idOfUserRequestingCancel);
        if (allowedToCancelJobs(p_idOfUserRequestingCancel)) {
            validateStateOfPagesInJob(p_job);
            try {
                Collection<Workflow> wfs = new ArrayList<Workflow>();
                Iterator<Workflow> it = p_job.getWorkflows().iterator();
                Object[] tasks = null;
                List<Object[]> taskList = new ArrayList<Object[]>();
                while (it.hasNext()) {
                    Workflow wf = (Workflow) it.next();

                    boolean updateIFlow = wf.getState().equals(Workflow.DISPATCHED);

                    // if the states are equal - or no state was specified so
                    // cancel all workflows
                    if (p_state == null || p_state.equals(wf.getState())
                            || (p_state.equals(Job.PENDING) && wf.getState().equals(Workflow.IMPORT_FAILED))) {
                        Workflow wfClone = (Workflow) session.get(WorkflowImpl.class, wf.getIdAsLong());
                        wfClone.setState(WF_CANCELLED);

                        // only update the target page state if not LOCALIZED or
                        // EXPORTED yet
                        if ((wf.getState().equals(Workflow.PENDING))
                                || (wf.getState().equals(Workflow.IMPORT_FAILED))
                                || (wf.getState().equals(Workflow.READY_TO_BE_DISPATCHED))
                                || (wf.getState().equals(Workflow.DISPATCHED))
                                || (wf.getState().equals(Workflow.BATCHRESERVED))) {
                            updatePageState(session, wfClone.getTargetPages(), PG_NOT_LOCALIZED);
                            // also update the secondary target files (if any)
                            updateSecondaryTargetFileState(session, wfClone.getSecondaryTargetFiles(),
                                    SecondaryTargetFileState.CANCELLED);
                        }
                        if (updateIFlow) {

                            Map activeTasks = getWFServer().getActiveTasksForWorkflow(wf.getId());
                            if (activeTasks != null) {
                                tasks = activeTasks.values().toArray();
                                taskList.add(tasks);
                                updateTaskState(session, tasks, wfClone.getTasks(), Task.STATE_DEACTIVE);

                                removeReservedTimes(tasks);
                            }
                            WorkflowTemplateInfo wfti = p_job.getL10nProfile()
                                    .getWorkflowTemplateInfo(wfClone.getTargetLocale());

                            TaskEmailInfo emailInfo = new TaskEmailInfo(
                                    p_job.getL10nProfile().getProject().getProjectManagerId(),
                                    wf.getWorkflowOwnerIdsByType(Permission.GROUP_WORKFLOW_MANAGER),
                                    wfti.notifyProjectManager(), p_job.getPriority());
                            emailInfo.setJobName(p_job.getJobName());
                            emailInfo.setProjectIdAsLong(new Long(p_job.getL10nProfile().getProjectId()));
                            emailInfo.setSourceLocale(wfClone.getJob().getSourceLocale().toString());
                            emailInfo.setTargetLocale(wfClone.getTargetLocale().toString());
                            emailInfo.setCompanyId(String.valueOf(p_job.getCompanyId()));

                            getWFServer().suspendWorkflow(wfClone.getId(), emailInfo);
                        }
                        wfs.add(wfClone);
                        session.saveOrUpdate(wfClone);
                    } else {
                        wfs.add(wf);
                    }
                }

                JobImpl jobClone = (JobImpl) session.get(JobImpl.class, new Long(p_job.getId()));
                String lastJobState = resetJobState(session, jobClone, wfs, p_reimport);
                session.saveOrUpdate(jobClone);

                tx.commit();

                // for gbs-1302, cancel interim activities
                // TaskInterimPersistenceAccessor.cancelInterimActivities(taskList);

                if (Job.CANCELLED.equals(lastJobState)) {
                    // cleanCorpus(jobId);
                    // deleteInProgressTmData(jobClone);
                    // GBS-2915, discard a job to remove all job data
                    CompanyRemoval removal = new CompanyRemoval(jobClone.getCompanyId());
                    removal.removeJob(jobClone);
                }

                // remove all export batch events for this job
                // BB 7/9/03 Quick fix: This is disabled because, for an EXPORT
                // SOURCE, it
                // prevents the batch from from being updated (during export)
                // and the export email from being sent. Right now all events
                // are automatically removed after completion. In the future
                // if we enable the history preservation, we need
                // to revisit this issue. Source export history should be
                // removed after the e-mail.
                // ServerProxy.getExportEventObserver()
                // .removeExportBatchEvents(p_job.getId());

                s_logger.info("Job " + p_job.getId() + " was cancelled by user " + nameOfUserRequestingCancel);
            } catch (Exception e) {
                tx.rollback();
                s_logger.error("WorkflowManagerLocal::cancel(Job)", e);
                String[] args = new String[1];
                args[0] = Long.toString(p_job.getId());
                throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_CANCEL_WORKFLOW, args, e,
                        WorkflowManagerException.PROPERTY_FILE_NAME);

            }
        } else {
            s_logger.error("User " + nameOfUserRequestingCancel + " doesn't have permissions to cancel job "
                    + p_job.getId() + " so the cancellation failed.");
            String[] args = { Long.toString(p_job.getId()), nameOfUserRequestingCancel };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_CANCEL_USER_NOT_ALLOWED, args,
                    null);
        }
    }

    /**
     * This method allows a client to archive a single workflow
     * 
     * @param String
     *            Workflow p_workflow
     * @throws RemoteException
     *             , WorkflowManagerException
     */
    public void archiveWorkflow(Workflow p_workflow) throws RemoteException, WorkflowManagerException {
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        boolean canMigrateJobData = false;
        Job job = null;
        try {
            job = (JobImpl) p_workflow.getJob();
            p_workflow.setState(WF_ARCHIVED);
            // If all workflows are in "archived" state...
            if (workflowsHaveState(job.getWorkflows(), WF_ARCHIVED)) {
                job.setState(WF_ARCHIVED);
                canMigrateJobData = true;
            }
            session.saveOrUpdate(p_workflow);
            session.saveOrUpdate(job);
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_ARCHIVE_WORKFLOW, null, e);
        }

        if (canMigrateJobData) {
            // Migrate data for this job here.
            try {
                JobDataMigration.migrateJobData(job);
            } catch (Exception e) {

            }

            deleteFolderForDI(job.getCompanyId(), job.getJobId());
        }
    }

    public boolean archive(Job p_job) throws RemoteException, WorkflowManagerException {
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        boolean canMigrateJobData = false;
        try {
            for (Workflow wf : p_job.getWorkflows()) {
                String wfState = wf.getState();
                if (wfState.equals(WF_EXPORTED)) {
                    wf.setState(WF_ARCHIVED);
                    session.saveOrUpdate(wf);
                }
            }
            if (workflowsHaveState(p_job.getWorkflows(), WF_ARCHIVED)) {
                p_job.setState(WF_ARCHIVED);
                canMigrateJobData = true;
                session.saveOrUpdate(p_job);
            }

            tx.commit();
        } catch (Exception e2) {
            tx.rollback();
            s_logger.error("WorkflowManagerLocal::archive(Job)", e2);
            String[] args = new String[1];
            args[0] = new Long(p_job.getId()).toString();
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_ARCHIVE_WORKFLOW, args, e2,
                    WorkflowManagerException.PROPERTY_FILE_NAME);
        }

        if (canMigrateJobData) {
            // Migrate data for this job here.
            // The migration "CAN" fail, it does not impact normal features.
            try {
                JobDataMigration.migrateJobData(p_job);
            } catch (SQLException e) {

            }

            deleteFolderForDI(p_job.getCompanyId(), p_job.getJobId());
        }
        return canMigrateJobData;
    }

    /**
     * Delete folder
     * "[fileStore]\[companyName]\GlobalSight\DesktopIcon\exported\[jobID]" when
     * job is archived.
     * 
     * This is for GBS-3652 and "getDownloadableJobs()" webservice API.
     */
    private void deleteFolderForDI(long companyId, long jobId) {
        File diExportedDir = AmbFileStoragePathUtils.getDesktopIconExportedDir(companyId);
        File jobDir = new File(diExportedDir, String.valueOf(jobId));
        if (jobDir.exists()) {
            jobDir.delete();
        }
    }

    /**
     * This method allows a client to dispatch a workflow
     * 
     * @param String
     *            Workflow p_workflow
     * @throws RemoteException
     *             , WorkflowManagerException
     */
    public void dispatch(Workflow p_workflow) throws RemoteException, WorkflowManagerException {
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        JbpmContext ctx = null;

        try {
            if (s_logger.isDebugEnabled()) {
                s_logger.debug("dispatch " + p_workflow.getId());
            }

            Workflow wfClone = (Workflow) session.get(WorkflowImpl.class, p_workflow.getIdAsLong());

            long taskId = -1;
            String actionType = null;

            if (WF_READY.equals(wfClone.getState()) || Workflow.PENDING.equals(wfClone.getState())) {
                Job j = wfClone.getJob();
                TaskEmailInfo emailInfo = createTaskEmailInfo(j, wfClone);
                String pm = emailInfo.getProjectManagerId();

                ArrayList returnValue = dispatchWorkflow(wfClone, session, new Date(), emailInfo);

                taskId = ((Long) (returnValue).get(0)).longValue();
                actionType = returnValue.get(3) != null ? (String) returnValue.get(3) : null;

                // For sla issue
                if (wfClone.isEstimatedTranslateCompletionDateOverrided()) {
                    updateEstimatedTranslateCompletionDate(wfClone.getId(),
                            wfClone.getEstimatedTranslateCompletionDate());
                }

                possiblyUpdateJob(session, wfClone, WF_DISPATCHED);
                session.saveOrUpdate(wfClone);
                tx.commit();

                if (((Boolean) returnValue.get(1)).booleanValue()) {
                    exportForStfCreation(new Long(taskId), wfClone, pm);
                }
                // GBS-3002
                if (actionType != null) {
                    SystemActionPerformer.perform(actionType, taskId, pm);
                }

                Task task = (Task) wfClone.getTasks().get(taskId);
                long jobId = task.getJobId();
                L10nProfile l10nProfile = ServerProxy.getJobHandler().getL10nProfileByJobId(jobId);
                long wfStatePostId = l10nProfile.getWfStatePostId();
                if (wfStatePostId != -1) {
                    WfStatePostThread myTask = new WfStatePostThread(task, null, true);
                    Thread t = new MultiCompanySupportedThread(myTask);
                    t.start();
                }

                if (task != null) {
                    task.setProjectManagerName(pm);
                    TaskHelper.autoAcceptTask(task);
                }
            }
        } catch (Exception e2) {
            if (tx != null && tx.isActive()) {
                tx.rollback();
            }
            s_logger.error("Failed to dispatch workflow: " + p_workflow.getId() + " p_workflow="
                    + WorkflowHelper.toDebugString(p_workflow), e2);
            String[] args = new String[1];
            args[0] = new Long(p_workflow.getId()).toString();
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_DISPATCH_WORKFLOW, args, e2,
                    WorkflowManagerException.PROPERTY_FILE_NAME);
        } finally {
            if (ctx != null) {
                ctx.close();
            }
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private DownloadParams getDownloadParams(Task task, Job p_job)
            throws GeneralException, NamingException, IOException {
        SendDownloadFileHelper help = new SendDownloadFileHelper();
        int fileFormat = AmbassadorDwUpConstants.DOWNLOAD_FILE_FORMAT_XLF;
        int editorId = AmbassadorDwUpConstants.EDITOR_XLIFF;
        String encoding = OfflineConstants.ENCODING_DEFAULT;
        int ptagFormat = PseudoConstants.PSEUDO_COMPACT;
        int platformId = AmbassadorDwUpConstants.PLATFORM_WIN32;
        int resInsMode = AmbassadorDwUpConstants.MAKE_RES_ATNS;
        String displayExactMatch = OfflineConstants.DISPLAY_EXACT_MATCH_YES;
        String consolidate = "false";

        List pageIdList = new ArrayList();
        List pageNameList = new ArrayList();
        List<Boolean> canUseUrlList = new ArrayList<Boolean>();
        List pages = task.getSourcePages();

        for (Iterator it = pages.iterator(); it.hasNext();) {
            SourcePage page = (SourcePage) it.next();
            pageIdList.add(new Long(page.getId()));
            pageNameList.add(page.getExternalPageId());
        }

        if (pageIdList != null) {
            for (int i = 0; i < pageIdList.size(); i++) {
                canUseUrlList.add(Boolean.FALSE);
            }
        }

        long workflowId = task.getWorkflow().getId();
        L10nProfile l10nProfile = task.getWorkflow().getJob().getL10nProfile();
        int downloadEditAll = 0;
        if (l10nProfile.getTmChoice() == LocProfileStateConstants.ALLOW_EDIT_TM_USAGE) {
            downloadEditAll = 1;
        }
        Vector excludeTypes = l10nProfile.getTranslationMemoryProfile().getJobExcludeTuTypes();
        List primarySourceFiles = help.getAllPSFList(task);
        List stfList = help.getAllSTFList(task);
        List supportFileList = help.getAllSupportFileList(task);
        String uiLocale = task.getSourceLocale().getLanguage() + "_" + task.getSourceLocale().getCountry();
        DownloadParams downloadParams = new DownloadParams(task.getJobName(), null, "", Long.toString(workflowId),
                Long.toString(task.getId()), pageIdList, pageNameList, canUseUrlList, primarySourceFiles, stfList,
                editorId, platformId, encoding, ptagFormat, uiLocale, task.getSourceLocale(),
                task.getTargetLocale(), true, fileFormat, excludeTypes, downloadEditAll, supportFileList,
                resInsMode, UserHandlerHelper.getUser(task.getAcceptor()));

        // activity type
        Activity act = new Activity();

        try {
            act = ServerProxy.getJobHandler().getActivity(task.getTaskName());
        } catch (Exception e) {
        }

        downloadParams.setJob(p_job);
        downloadParams.setActivityType(act.getDisplayName());

        downloadParams.setConsolidateTmxFiles("yes".equalsIgnoreCase(consolidate));
        downloadParams.setDisplayExactMatch(displayExactMatch);

        return downloadParams;
    }

    public File downloadOfflineFiles(Task task, Job p_job, ArrayList p_nodeEmail, String lockedSegEditType,
            boolean isIncludeXmlNodeContextInformation) throws GeneralException, NamingException, IOException {
        DownloadParams downloadParams = getDownloadParams(task, p_job);
        downloadParams.setIncludeXmlNodeContextInformation(isIncludeXmlNodeContextInformation);
        downloadParams.setAutoActionNodeEmail(p_nodeEmail);
        if (lockedSegEditType != null) {
            try {
                int type = Integer.parseInt(lockedSegEditType);
                if (type >= 1 && type <= 4) {
                    downloadParams.setTMEditType(type);
                }
            } catch (Exception e) {
                // still use default "tmEditType".
            }
        }

        File tmpDir = AmbFileStoragePathUtils.getCustomerDownloadDir(String.valueOf(task.getCompanyId()));
        String fileName = downloadParams.getTruncatedJobName() + "_" + task.getSourceLocale() + "_"
                + task.getTargetLocale() + ".zip";
        File temp = new File(tmpDir, fileName);

        JobPackageZipper zipper = new JobPackageZipper();
        zipper.createZipFile(temp);
        downloadParams.setZipper(zipper);
        downloadParams.setAutoActionNodeEmail(p_nodeEmail);

        try {
            downloadParams.verify();
        } catch (Exception e) {
            throw new EnvoyServletException(e);
        }

        OfflineEditManager odm = ServerProxy.getOfflineEditManager();
        OEMProcessStatus status = new OEMProcessStatus(downloadParams);
        odm.attachListener(status);
        odm.runProcessDownloadRequest(downloadParams);

        zipper.closeZipFile();

        return temp;
    }

    // get a system configuration value for a given name.
    private String getSystemConfigValue(String p_paramName) {
        String value = null;
        try {
            SystemConfiguration config = SystemConfiguration.getInstance();
            value = config.getStringParameter(p_paramName);
        } catch (Exception e) {
            s_logger.error("WorkflowManagerLocal :: getSystemConfigValue for parameter name: " + p_paramName, e);
        }

        return value;
    }

    public void dispatch(Job p_job) throws RemoteException, WorkflowManagerException {
        JobImpl jobClone = null;
        Session session = HibernateUtil.getSession();
        Transaction transaction = null;
        JbpmContext ctx = null;

        try {
            transaction = HibernateUtil.getTransaction();
            jobClone = (JobImpl) session.get(JobImpl.class, new Long(p_job.getId()));
            if (jobClone != null) {
                // refresh job object in the session
                session.evict(jobClone);
                jobClone = (JobImpl) session.get(JobImpl.class, new Long(p_job.getId()));
            }
            Iterator it = jobClone.getWorkflows().iterator();
            // a Map containing task id as key and workflow as value.
            // This is used for possible creation of STF.
            HashMap<Long, Workflow> map = new HashMap<Long, Workflow>(1);
            HashMap<Long, String> etfMap = new HashMap<Long, String>(1);
            Date startDate = new Date();
            ExecutorService pool = Executors.newFixedThreadPool(MAX_THREAD);
            while (it.hasNext()) {
                Workflow wf = (Workflow) it.next();
                if (WF_READY.equals(wf.getState()) || Workflow.PENDING.equals(wf.getState())) {
                    Workflow wfClone = (Workflow) session.get(WorkflowImpl.class, wf.getIdAsLong());
                    TaskEmailInfo emailInfo = createTaskEmailInfo(jobClone, wfClone);

                    ArrayList returnValue = dispatchWorkflow(wfClone, session, startDate, emailInfo);

                    long taskId = ((Long) returnValue.get(0)).longValue();
                    if (taskId != -1) {
                        Object actionType = returnValue.get(3);
                        if (actionType != null) {
                            etfMap.put(taskId, (String) actionType);
                        }

                        Task task = (Task) wfClone.getTasks().get(taskId);
                        long jobId = task.getJobId();
                        L10nProfile l10nProfile = ServerProxy.getJobHandler().getL10nProfileByJobId(jobId);
                        long wfStatePostId = l10nProfile.getWfStatePostId();
                        if (wfStatePostId != -1) {
                            WfStatePostThread myTask = new WfStatePostThread(task, null, true);
                            pool.execute(myTask);
                        }

                        // For sla issue
                        if (wfClone.isEstimatedTranslateCompletionDateOverrided()) {
                            updateEstimatedTranslateCompletionDate(wfClone.getId(),
                                    wfClone.getEstimatedTranslateCompletionDate());
                        }

                        // prepare the map for possible creation of secondary
                        // target
                        // files
                        if (((Boolean) returnValue.get(1)).booleanValue()) {
                            map.put(new Long(taskId), wfClone);
                        }
                    }
                    session.saveOrUpdate(wfClone);
                }
            }
            pool.shutdown();

            jobClone.setState(WF_DISPATCHED);
            updatePageState(session, jobClone.getSourcePages(), PG_ACTIVE_JOB);
            session.saveOrUpdate(jobClone);

            HibernateUtil.commit(transaction);

            String pmId = p_job.getL10nProfile().getProject().getProjectManagerId();
            if (map.size() > 0) {
                Object[] keys = map.keySet().toArray();
                for (int i = 0; i < keys.length; i++) {
                    Long stfTaskId = (Long) keys[i];
                    Workflow wf = map.get(stfTaskId);
                    exportForStfCreation(stfTaskId, wf, pmId);
                }
            }
            // GBS-3002
            if (etfMap.size() > 0) {
                Object[] keys = etfMap.keySet().toArray();
                for (int i = 0; i < keys.length; i++) {
                    Long taskId = (Long) keys[i];
                    String actionType = etfMap.get(taskId);
                    SystemActionPerformer.perform(actionType, taskId, pmId);
                }
            }
        } catch (Exception e2) {
            HibernateUtil.rollback(transaction);
            s_logger.error("Failed to dispatch: " + p_job.getJobName(), e2);
            String[] args = new String[1];
            args[0] = new Long(p_job.getId()).toString();
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_DISPATCH_WORKFLOW, args, e2,
                    WorkflowManagerException.PROPERTY_FILE_NAME);
        } finally {
            if (ctx != null) {
                ctx.close();
            }
        }

        runJobCreationScript(p_job);
    }

    /**
     * This method sets the percentage completion of a particular workflow
     * 
     * @param p_destinationArrow
     *            - The name of the outgoing arrow of a condition node (if the
     *            next node of this task is a condition node). This is used for
     *            making decision.
     * @param skipping
     *            Indicates this task is being skipped.
     * 
     * @throws RemoteException
     *             , WorkflowManagerException
     */
    public void setTaskCompletion(String p_userId, Task p_task, String p_destinationArrow, String skipping)
            throws RemoteException, WorkflowManagerException {
        if (s_logger.isDebugEnabled()) {
            String s = "null task";
            if (p_task != null)
                s = p_task.toString();
        }

        for (int i = 0; i < 3; i++) {
            try {
                completeTask(p_userId, p_task, p_destinationArrow, skipping);
                break;
            } catch (Exception e) {
                s_logger.warn("Ignoring Exception");
                Thread.yield();
            }
        }

        // for COTI api finish job
        try {
            Workflow wfClone = (Workflow) p_task.getWorkflow();
            JobImpl curJob = HibernateUtil.get(JobImpl.class, wfClone.getJob().getId());
            String jobStatus = curJob.getState();
            if (Job.LOCALIZED.equals(jobStatus) || Job.EXPORTED.equals(jobStatus)
                    || Job.EXPORTING.equals(jobStatus)) {
                COTIUtilEnvoy.finishCOTIJob(curJob);
            }
        } catch (Throwable t) {
            // log the error but don't let it affect job completion
            s_logger.error("Error trying to finish COTI job.", t);
        }

        try {
            long jobId = p_task.getJobId();
            L10nProfile l10nProfile = ServerProxy.getJobHandler().getL10nProfileByJobId(jobId);
            long wfStatePostId = l10nProfile.getWfStatePostId();
            if (wfStatePostId != -1) {
                WfStatePostThread myTask = new WfStatePostThread(p_task, p_destinationArrow, false);
                Thread t = new MultiCompanySupportedThread(myTask);
                t.start();
            }
        } catch (Exception e) {
            s_logger.error("Error trying to finish COTI job.", e);
        }
    }

    /**
     * @see WorkflowManager.getTaskInfoByTaskId(Workflow, List, long)
     */
    public TaskInfo getTaskInfoByTaskId(Workflow p_workflow, List p_wfTaskInfos, long p_taskId,
            boolean p_acceptedOnly) throws RemoteException, WorkflowManagerException {
        String state = p_workflow.getState();
        if (Workflow.PENDING.equals(state) || WF_CANCELLED.equals(state) || Workflow.IMPORT_FAILED.equals(state)
                || WF_READY.equals(state)) {
            return null;
        }

        TaskInfo taskInfo = null;
        try {
            int size = p_wfTaskInfos.size();
            Hashtable ht = p_workflow.getTasks();
            Date estimatedDate = p_workflow.getDispatchedDate();
            boolean found = false;
            TaskImpl task = null;
            for (int i = 0; !found && i < size; i++) {
                WfTaskInfo wfTaskInfo = (WfTaskInfo) p_wfTaskInfos.get(i);
                long id = wfTaskInfo.getId();
                found = id == p_taskId;

                task = (TaskImpl) ht.get(new Long(id));

                if (!found) {
                    estimatedDate = (wfTaskInfo.getState() == WorkflowConstants.STATE_COMPLETED)
                            ? task.getCompletedDate()
                            : task.getEstimatedCompletionDate();
                } else {
                    taskInfo = estimateDatesForDefaultPath(estimatedDate, task, wfTaskInfo, p_acceptedOnly);
                }
            }
        } catch (Exception e) {
            s_logger.error(e.getMessage(), e);
            String[] args = { String.valueOf(p_taskId) };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_GET_TASKINFO_IN_DEFAULT_PATH,
                    args, e);
        }
        return taskInfo;
    }

    /**
     * @see WorkflowManager.getTaskInfosInDefaultPath(Workflow)
     */
    public List getTaskInfosInDefaultPath(Workflow p_workflow) throws RemoteException, WorkflowManagerException {
        List<TaskInfo> taskInfos = null;
        String state = p_workflow.getState();
        if (Workflow.PENDING.equals(state) || WF_CANCELLED.equals(state) || Workflow.IMPORT_FAILED.equals(state)
                || WF_READY.equals(state)) {
            return null;
        }

        try {
            taskInfos = new ArrayList<TaskInfo>();
            List wfTaskInfos = ServerProxy.getWorkflowServer().timeDurationsInDefaultPath(null, p_workflow.getId(),
                    -1);

            int size = wfTaskInfos.size();
            Hashtable ht = p_workflow.getTasks();
            Date estimatedDate = p_workflow.getDispatchedDate();
            for (int i = 0; i < size; i++) {
                WfTaskInfo wfTaskInfo = (WfTaskInfo) wfTaskInfos.get(i);
                TaskImpl task = (TaskImpl) ht.get(new Long(wfTaskInfo.getId()));
                if (task != null) {
                    TaskInfo ti = estimateDatesForDefaultPath(estimatedDate, task, wfTaskInfo, true);
                    estimatedDate = (ti.getState() == WorkflowConstants.STATE_COMPLETED) ? ti.getCompletedDate()
                            : ti.getCompleteByDate();

                    taskInfos.add(ti);
                }
            }
        } catch (Exception e) {
            String[] args = { String.valueOf(p_workflow.getId()) };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_GET_TASKS_IN_DEFAULT_PATH,
                    args, e);
        }
        return taskInfos;
    }

    @SuppressWarnings("unchecked")
    public List getTaskInfosInDefaultPathWithSkip(Workflow p_workflow)
            throws RemoteException, WorkflowManagerException {

        JbpmContext ctx = WorkflowConfiguration.getInstance().getJbpmContext();
        List<String> taskList = null;
        try {
            taskList = WorkflowJbpmPersistenceHandler.getSkippedTaskInstance(p_workflow.getId(), ctx);

        } finally {
            ctx.close();
        }

        List<TaskInfo> list = getTaskInfosInDefaultPath(p_workflow);
        TaskInfo lastTaskInfo = null;
        if (list != null) {
            for (TaskInfo taskInfo : list) {
                if (isSkipped(taskInfo, taskList)) {
                    taskInfo.setState(Task.STATE_SKIP);

                }
                lastTaskInfo = taskInfo;
            }
        }

        if (lastTaskInfo != null) {
            lastTaskInfo.setExportDate(((WorkflowImpl) p_workflow).getExportDate());
        }

        return list;
    }

    private boolean isSkipped(TaskInfo taskInfo, List<String> taskList) {
        for (String name : taskList) {
            if (WorkflowJbpmUtil.getActivityName(name).equals(taskInfo.getName())) {
                return true;
            }
        }

        return false;
    }

    /**
     * This modifies an active workflow.
     * 
     * @param p_sessionId
     *            - Users login HTTPSession id
     * @param p_wfInstance
     *            - WorkflowInstance that has been modified.
     * @param p_projectManagerId
     *            - the ProjectManager userId.
     * @param p_modifiedTasks
     *            - A hashtable of the modified tasks. The key is the Task id
     *            and the value is a TaskInfoBean that contains the
     *            modifications.
     * @throws RemoteException
     *             , WorkflowManagerException
     */
    public void modifyWorkflow(String p_sessionId, WorkflowInstance p_wfInstance, String p_projectManagerId,
            Hashtable p_modifiedTasks) throws RemoteException, WorkflowManagerException {
        Map addedAndDeleted = null;
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        try {
            Workflow workflow = (Workflow) session.get(WorkflowImpl.class, new Long(p_wfInstance.getId()));
            Job job = workflow.getJob();

            validateStateOfPages(workflow);
            TaskEmailInfo emailInfo = createTaskEmailInfo(job, workflow);

            Date date = new Date();
            addedAndDeleted = modifyWorkflowInstance(date, p_sessionId, p_wfInstance, emailInfo,
                    String.valueOf(workflow.getCompanyId()));

            // update state for a workflow that was in completed state.
            if (Workflow.EXPORT_FAILED.equals(workflow.getState())) {
                workflow.setState(WF_DISPATCHED);
                workflow.setDispatchedDate(date);
                updatePageState(session, workflow.getTargetPages(), PG_ACTIVE_JOB);
                updateSecondaryTargetFileState(session, workflow.getSecondaryTargetFiles(),
                        SecondaryTargetFileState.ACTIVE_JOB);
                possiblyUpdateJob(session, workflow, Workflow.EXPORT_FAILED);
            }

            // get a list of tasks in the workflow's default path
            // -1 indicates that the default path would be from the
            // beginning of the workflow (form START node)
            List wfTaskInfos = getWFServer().timeDurationsInDefaultPath(null, p_wfInstance.getId(), -1);

            if (WF_DISPATCHED.equals(workflow.getState()) || WF_READY.equals(workflow.getState())) {
                updateWorkflowChanges(p_modifiedTasks, p_wfInstance, wfTaskInfos, session, workflow,
                        addedAndDeleted, date);
            }

            updateDuration(p_wfInstance.getId(), wfTaskInfos, session);
            session.update(workflow);
            tx.commit();
        } catch (GeneralException ge) {
            s_logger.error("modifyWorkflow failed. " + ge.toString(), ge);
            // any instance of GeneralException should have its own meaningful
            // message that should be displayed to the user.
            throw new WorkflowManagerException(ge);
        } catch (Exception we) {
            tx.rollback();
            s_logger.error("modifyWorkflow failed.  " + we.toString(), we);

            String[] args = { String.valueOf(p_wfInstance.getId()) };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_MODIFY_WORKFLOW, args, we);
        }
    }

    /**
     * @see WorkflowManager.startStfCreationForWorkflow(long, Workflow, String)
     */
    public void startStfCreationForWorkflow(long p_taskId, Workflow p_workflow, String p_userId)
            throws RemoteException, WorkflowManagerException {
        try {
            exportForStfCreation(new Long(p_taskId), p_workflow, p_userId);
        } catch (Exception e) {
            String[] args = { String.valueOf(p_taskId) };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_START_CSTF_PROCESS, args, e);
        }
    }

    /**
     * @deprecated For sla report issue.
     * @see WorkflowManager.updatePlannedCompletionDate(String, long, Date)
     */
    public void updatePlannedCompletionDate(long p_workflowId, Date p_plannedCompletionDate)
            throws WorkflowManagerException, RemoteException {
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        try {
            Workflow wf = (Workflow) session.get(WorkflowImpl.class, new Long(p_workflowId));
            wf.setPlannedCompletionDate(p_plannedCompletionDate);
            session.saveOrUpdate(wf);
            tx.commit();

            // send notification if the estimate completion date exceeds planned
            // date
            sendNotification(wf);
        } catch (Exception e) {
            tx.rollback();
            String[] args = { String.valueOf(p_workflowId) };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_UPDATE_PCD, args, e);
        }
    }

    /**
     * For sla report issue User can override the estimatedCompletionDate.
     * 
     * @see WorkflowManager.updateEstimatedCompletionDate(String, long, Date)
     */
    public void updateEstimatedCompletionDate(long p_workflowId, Date p_estimatedCompletionDate)
            throws WorkflowManagerException, RemoteException {
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        try {
            Workflow wf = (Workflow) session.get(WorkflowImpl.class, new Long(p_workflowId));
            wf.setEstimatedCompletionDate(p_estimatedCompletionDate);
            wf.setEstimatedCompletionDateOverrided(true);
            session.saveOrUpdate(wf);
            tx.commit();

            // send notification if the estimate completion date exceeds planned
            // date
            // sendNotification(clone);
            sendNotification(wf);
        } catch (Exception e) {
            String[] args = { String.valueOf(p_workflowId) };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_UPDATE_PCD, args, e);
        }
    }

    /**
     * @see WorkflowManager.updateEstimatedTranslateCompletionDate(long, Date)
     */
    public void updateEstimatedTranslateCompletionDate(long p_workflowId, Date p_estimatedTranslateCompletionDate)
            throws WorkflowManagerException, RemoteException {
        Session session = HibernateUtil.getSession();
        Transaction tx = HibernateUtil.getTransaction();
        try {
            Workflow wf = (Workflow) session.get(WorkflowImpl.class, new Long(p_workflowId));
            refreshWorkflowInstance(wf);

            Date oriEstimatedTranslateCompletionDate = wf.getEstimatedTranslateCompletionDate();
            wf.setEstimatedTranslateCompletionDate(p_estimatedTranslateCompletionDate);
            wf.setEstimatedTranslateCompletionDateOverrided(true);

            if (Workflow.READY_TO_BE_DISPATCHED.equals(wf.getState()) && !wf.isEstimatedCompletionDateOverrided()) {
                List wfTaskInfos = ServerProxy.getWorkflowServer().timeDurationsInDefaultPath(null, wf.getId(), -1);
                FluxCalendar defaultCalendar = ServerProxy.getCalendarManager()
                        .findDefaultCalendar(String.valueOf(wf.getCompanyId()));

                Hashtable tasks = wf.getTasks();

                long workflowDuration = 0l;

                for (int i = wfTaskInfos.size() - 1; i >= 0; i--) {
                    WfTaskInfo wfTaskInfo = (WfTaskInfo) wfTaskInfos.get(i);
                    TaskImpl task = (TaskImpl) tasks.get(new Long(wfTaskInfo.getId()));
                    if (task == null)
                        continue;

                    Activity act = ServerProxy.getJobHandler().getActivity(task.getTaskName());

                    if (Activity.isTranslateActivity(act.getType())) {
                        updateTaskTimeToComplete(task, oriEstimatedTranslateCompletionDate,
                                p_estimatedTranslateCompletionDate, session);
                        break;
                    }

                    workflowDuration += wfTaskInfo.getTotalDuration();
                }

                wf.setEstimatedCompletionDate(ServerProxy.getEventScheduler()
                        .determineDate(p_estimatedTranslateCompletionDate, defaultCalendar, workflowDuration));
            } else if (Workflow.DISPATCHED.equals(wf.getState())) {
                Task translateTask = null;
                List taskInfos = getTaskInfosInDefaultPath(wf);
                if (taskInfos != null) {
                    for (int i = taskInfos.size() - 1; i >= 0; i--) {
                        TaskInfo taskInfo = (TaskInfo) taskInfos.get(i);
                        Task task = (Task) wf.getTasks().get(new Long(taskInfo.getId()));
                        Activity act = ServerProxy.getJobHandler().getActivity(task.getTaskName());

                        if (act.isType(Task.TYPE_TRANSLATE)) {
                            translateTask = task;
                            break;
                        }
                    }
                }

                // Update translate task & workflow
                if ((translateTask != null) && (translateTask.getState() != Task.STATE_COMPLETED)) {
                    translateTask.setEstimatedCompletionDate(p_estimatedTranslateCompletionDate);

                    List wfTaskInfos = getWFServer().timeDurationsInDefaultPath(null, p_workflowId, -1);

                    updateDefaultPathTasks(MODIFY_ACTION, new Date(), wfTaskInfos, wf, translateTask.getId(), false,
                            p_estimatedTranslateCompletionDate, session);
                    session.saveOrUpdate(translateTask);

                    updateTaskTimeToComplete(translateTask, oriEstimatedTranslateCompletionDate,
                            p_estimatedTranslateCompletionDate, session);
                }
            }
            session.saveOrUpdate(wf);
            HibernateUtil.commit(tx);
        } catch (Exception e) {
            HibernateUtil.rollback(tx);
            String[] args = { String.valueOf(p_workflowId) };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_UPDATE_PCD, args, e);
        }
    }

    /**
     * Updates the "Time to Complete" in the delegation configuration xml for
     * the task.
     * <p>
     * For GBS-3456.
     */
    private void updateTaskTimeToComplete(Task task, Date originalEstimatedCompletionDate,
            Date estimatedTranslateCompletionDate, Session session) throws Exception {
        long oriEstimatedTranslateCompletionDate = originalEstimatedCompletionDate.getTime();
        long newEstimatedTranslateCompletionDate = estimatedTranslateCompletionDate.getTime();
        if (newEstimatedTranslateCompletionDate == oriEstimatedTranslateCompletionDate) {
            return;
        }
        JbpmContext ctx = null;
        try {
            ctx = WorkflowConfiguration.getInstance().getJbpmContext();

            TaskInstance taskInstance = WorkflowJbpmPersistenceHandler.getTaskInstance(task.getId(), ctx);
            Node n = null;
            if (taskInstance == null) {
                // this task instance has not been initialized
                WorkflowTaskInstance workflowTaskInstance = task.getWorkflow().getIflowInstance()
                        .getWorkflowTaskById(task.getId());
                ProcessInstance processInstance = ctx.getProcessInstance(task.getWorkflow().getId());

                n = WorkflowJbpmUtil.getNodeByNodeName(processInstance.getProcessDefinition(),
                        workflowTaskInstance.getNodeName());
            } else {
                n = taskInstance.getTask().getTaskNode();
            }

            String config = WorkflowJbpmUtil.getConfigure(n);
            WorkflowNodeParameter param = WorkflowNodeParameter.createInstance(config);
            long timeToComplete = param.getLongAttribute(WorkflowConstants.FIELD_COMPLETED_TIME,
                    WorkflowTaskInstance.NO_RATE);

            long changedTime = determineInterval(task, originalEstimatedCompletionDate,
                    estimatedTranslateCompletionDate);
            long updatedTimeToComplete = changedTime + timeToComplete;
            // at least one minute
            if (updatedTimeToComplete <= 60000) {
                updatedTimeToComplete = 60000;
            }

            param.setAttribute(WorkflowConstants.FIELD_COMPLETED_TIME, String.valueOf(updatedTimeToComplete));
            WorkflowJbpmUtil.setConfigure(n, param.restore());

            // update the scheduler with new estimated translate completion date
            // if the task has already been accepted
            if (task.getAcceptedDate() != null) {
                updateReservedTimes(task, session);
                if (isNotificationActive()) {
                    TaskInfo ti = new TaskInfo(task.getId(), task.getTaskName(), task.getState(),
                            task.getEstimatedAcceptanceDate(), task.getEstimatedCompletionDate(),
                            task.getAcceptedDate(), task.getCompletedDate(), task.getType());
                    int actionType = SchedulerConstants.ACCEPT_ACTIVITY;

                    EventNotificationHelper.performSchedulingProcess(new Integer(actionType), task.getId(),
                            (Integer) SchedulerConstants.s_eventTypes.get(SchedulerConstants.ACCEPT_TYPE), n, ti,
                            task.getAcceptedDate().getTime(),
                            (Integer) SchedulerConstants.s_eventTypes.get(SchedulerConstants.COMPLETE_TYPE),
                            getWarningThreshold(),
                            createTaskEmailInfo(task.getWorkflow().getJob(), task.getWorkflow()));
                }
            }
        } finally {
            ctx.close();
        }
    }

    /**
     * Updates the reserved time associated with the given task from the user's
     * calendar.
     */
    private void updateReservedTimes(Task task, Session session) throws Exception {
        removeReservedTime(task.getId());
        createReservedTime(task, ReservedTime.TYPE_ACTIVITY, session);
    }

    /**
     * Creates a reserved time along with a possible buffer and add it to the
     * acceptor's calendar.
     */
    private void createReservedTime(Task task, String reservedTimeType, Session session) throws Exception {
        UserFluxCalendar userCalendar = ServerProxy.getCalendarManager()
                .findUserCalendarByOwner(task.getAcceptor());
        UserFluxCalendar calClone = (UserFluxCalendar) session.get(UserFluxCalendar.class,
                userCalendar.getIdAsLong());
        addReservedTimeToUserCal(task, reservedTimeType, session, calClone, task.getAcceptedDate(),
                task.getEstimatedCompletionDate());

        session.saveOrUpdate(calClone);
    }

    /**
     * Creates and add a reserved time based on the specified type to the given
     * user calendar.
     */
    private void addReservedTimeToUserCal(Task task, String reservedTimeType, Session session,
            UserFluxCalendar calClone, Date baseDate, Date estimatedDate) {
        // if calendaring module is not installed, don't create reserved time.
        if (!Modules.isCalendaringInstalled()) {
            return;
        }

        TimeZone tz = calClone.getTimeZone();
        Timestamp start = new Timestamp(tz);
        start.setDate(baseDate);
        Timestamp end = new Timestamp(tz);
        end.setDate(estimatedDate);

        StringBuffer sb = new StringBuffer();
        sb.append("[");
        sb.append(task.getTaskName());
        sb.append("]");
        sb.append("[");
        sb.append(task.getJobName());
        sb.append("]");
        sb.append("[");
        sb.append(task.getProjectManagerId());
        sb.append("]");

        String taskName = sb.toString();

        ReservedTime rt = new ReservedTime(taskName, reservedTimeType, start, start.getHour(), start.getMinute(),
                end, end.getHour(), end.getMinute(), null, task.getId());

        calClone.addReservedTime(rt);
        session.save(rt);

        // now add the buffer (if not set to zero)
        if (calClone.getActivityBuffer() > 0) {
            Timestamp bufferEnd = new Timestamp(tz);
            bufferEnd.setDate(end.getDate());
            bufferEnd.add(Timestamp.HOUR, calClone.getActivityBuffer());
            ReservedTime buffer = new ReservedTime(taskName, ReservedTime.TYPE_BUFFER, end, end.getHour(),
                    end.getMinute(), bufferEnd, bufferEnd.getHour(), bufferEnd.getMinute(), null, task.getId());

            calClone.addReservedTime(buffer);
            session.save(buffer);
        }
    }

    /**
     * Determines the interval changed from original date to new one.
     */
    private long determineInterval(Task task, Date oriDate, Date newDate) {
        long changedTime = 0;

        try {
            CalendarManager cm = ServerProxy.getCalendarManager();
            FluxCalendar companyCalendar = cm.findDefaultCalendar(String.valueOf(task.getCompanyId()));
            changedTime = cm.computeInterval(companyCalendar, oriDate, newDate);
        } catch (Exception ex) {
            long oriEstimatedCompletionDate = oriDate.getTime();
            changedTime = newDate.getTime() - oriEstimatedCompletionDate;
        }

        return changedTime;
    }

    private Float getWarningThreshold() {
        if (m_threshold == null) {
            try {
                String threshold = getSystemConfigValue(SystemConfigParamNames.TIMER_THRESHOLD);
                m_threshold = Float.valueOf(threshold);
            } catch (NumberFormatException e) {
                s_logger.error("Invalid warning threshold. It's been reset to 75%", e);
                m_threshold = Float.valueOf(".75");
            }
        }
        return m_threshold;
    }

    /**
     * Determines whether the notification feature is active.
     */
    private boolean isNotificationActive() {
        if (m_isNotificationActive == -1) {
            try {
                SystemConfiguration config = SystemConfiguration.getInstance();
                m_isNotificationActive = config.getIntParameter(SystemConfigParamNames.USE_WARNING_THRESHOLDS);
            } catch (Exception e) {
                s_logger.error("Error checking warningThresholds.enabled", e);
            }
        }
        return m_isNotificationActive == 1;
    }

    /**
     * @see WorkflowManager.reassignWorkflowOwners(long, List)
     */
    public void reassignWorkflowOwners(long p_workflowId, List p_workflowOwners)
            throws RemoteException, WorkflowManagerException {
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        try {
            Workflow wf = (Workflow) session.get(WorkflowImpl.class, new Long(p_workflowId));
            // first delete all the previous owners (EXCEPT FOR 'PM')
            for (Iterator it = wf.getWorkflowOwners().iterator(); it.hasNext();) {
                WorkflowOwner owner = (WorkflowOwner) it.next();
                if (!Permission.GROUP_PROJECT_MANAGER.equals(owner.getOwnerType())) {
                    wf.removeWorkflowOwner(owner);
                    session.delete(owner);
                    it.remove();
                }
            }

            // now add the newly assigned owners
            int size = p_workflowOwners.size();
            for (int i = 0; i < size; i++) {
                WorkflowOwner wfo = (WorkflowOwner) p_workflowOwners.get(i);
                wf.addWorkflowOwner(wfo);
            }

            // set the new owners in the iFlow's process instance
            assignWorkflowInstanceOwners(p_workflowId, wf.getWorkflowOwners());

            session.saveOrUpdate(wf);
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            throw new WorkflowManagerException(e);
        }
    }

    /**
     * Assign the new workflow owners.
     * 
     * @param p_workflowId
     *            - The id of the workflow that the new owners should be
     *            assigned.
     * @param p_newWorkflowOwners
     *            - A list of new workflow owners to be assigned.
     */
    private void assignWorkflowInstanceOwners(long p_workflowId, List p_newWorkflowOwners) throws Exception {
        // get all of the owners now and pass the array of ids to iFlow
        int size = p_newWorkflowOwners.size();
        String[] ownerIds = new String[size];
        for (int i = 0; i < size; i++) {
            WorkflowOwner wfo = (WorkflowOwner) p_newWorkflowOwners.get(i);
            ownerIds[i] = wfo.getOwnerId();
        }
        getWFServer().reassignWorkflowOwners(p_workflowId, null, ownerIds);
    }

    /**
     * Update the completion fraction. This is used when the number of tasks has
     * changed.
     * <p>
     * Set the denominator to the reflect this change. Leave the numerator
     * alone.
     */
    private void updateCompletionFraction(Workflow p_workflow, List p_wfTaskInfos) {
        try {
            int size = p_wfTaskInfos.size();

            long longDenominator = 0;
            for (int i = 0; i < size; i++) {
                WfTaskInfo wfTaskInfo = (WfTaskInfo) p_wfTaskInfos.get(i);
                // get the total duration (acceptance + completion)
                longDenominator += wfTaskInfo.getTotalDuration();
            }

            // convert the millisec to minutes since it's the smallest unit
            // of time used for each activity of a workflow.
            int denominator = (int) (longDenominator / 60000);

            // the numerator stays the same or is moved to match the denominator
            // if
            // it is bigger.
            int numerator = (p_workflow.getCompletionNumerator() > denominator ? denominator
                    : p_workflow.getCompletionNumerator());
            p_workflow.setCompletionFraction(numerator, denominator);
        } catch (Exception e) {
            // just log - no exception
            s_logger.error("Failed to update the completion fraction for workflow " + p_workflow.getId(), e);
        }
    }

    /**
     * Update the completion fraction. The task being passed in has been
     * accepted or completed. And updates the numerator and denominator
     * accordingly.
     * 
     * @param p_completed
     *            specifies 'true' if the complete time should be added to the
     *            fraction. 'false' if the accept time should be added to the
     *            fraction.
     */
    private void updateCompletionFraction(Workflow p_workflow, Task p_completedOrAcceptedTask, List p_wfTaskInfos,
            boolean p_isCompleted) {
        try {
            int size = p_wfTaskInfos.size();
            long longDenominator = 0;
            for (int i = 0; i < size; i++) {
                WfTaskInfo wfTaskInfo = (WfTaskInfo) p_wfTaskInfos.get(i);
                // get the total duration (acceptance + completion)
                longDenominator += wfTaskInfo.getTotalDuration();
            }

            // calculate denominator in 'minutes'
            int denominator = (int) (longDenominator / 60000);

            int numerator = p_workflow.getCompletionNumerator();
            // calculate for the completion
            if (p_isCompleted) {
                numerator = denominator;
            } else {
                if (((TaskImpl) p_completedOrAcceptedTask).getWorkflowTask() == null) {
                    return;
                }

                int duration = (int) (p_completedOrAcceptedTask.getTaskDuration() / 60000)
                        + (int) (p_completedOrAcceptedTask.getTaskAcceptDuration() / 60000);

                int totalDuration = duration + numerator;

                numerator = totalDuration < denominator ? totalDuration : numerator;

                // for workflows with condition node, the default path could
                // return a duration that might be less than the current
                // numerator.
                if (numerator >= denominator) {
                    numerator = denominator - duration;
                }
            }
            p_workflow.setCompletionFraction(numerator, denominator);
        } catch (Exception e) {
            // just log - no exception
            s_logger.error("Failed to update the completion percentage for workflow " + p_workflow.getId(), e);
        }
    }

    /* Create tasks for the given workflow (a cloned object) based on the */
    /* i-Flow task instances */
    private void createTasks(Session p_session, Vector p_workflowTaskInstances, Hashtable p_modifiedTasks,
            Workflow p_workflow, List p_wfTaskInfos) throws Exception {
        TaskImpl task = null;
        try {

            int size = p_workflowTaskInstances.size();
            CostingEngine ce = ServerProxy.getCostingEngine();
            for (int i = 0; i < size; i++) {
                WorkflowTaskInstance inst = (WorkflowTaskInstance) p_workflowTaskInstances.elementAt(i);
                TaskInfoBean taskInfo = null;
                taskInfo = (TaskInfoBean) p_modifiedTasks.get(new Long(inst.getSequence()));
                task = new TaskImpl();
                task.setId(inst.getTaskId());
                task.setWorkflow(p_workflow);
                p_workflow.addTask(task);
                task.setName(inst.getActivityName());
                task.setState(TaskImpl.STATE_DEACTIVE);
                task.setType(getActivityType(task.getName()));
                task.setCompanyId(p_workflow.getCompanyId());
                Rate pageBasedRate = null;
                String hourAmount = null;
                // For Expenses
                long rateId = inst.getExpenseRateId();
                task.setRateSelectionCriteria(inst.getRateSelectionCriteria());
                if (rateId > 0) {
                    // need to create a clone because the rate already exists -
                    // so
                    // don't want to try and insert but just set up the
                    // relationship
                    Rate rateClone = (Rate) ce.getRate(rateId);
                    task.setExpenseRate(rateClone);
                    if (rateClone.getRateType().equals(Rate.UnitOfWork.PAGE_COUNT)) {
                        pageBasedRate = rateClone;
                    } else {
                        if (rateClone.getRateType().equals(Rate.UnitOfWork.HOURLY)) {
                            AmountOfWork cloneNewAow = null;
                            if (taskInfo != null) {
                                hourAmount = taskInfo.getEstimatedHours();
                            }
                            if (hourAmount != null) {
                                AmountOfWork newAow = rateClone.createAmountOfWork();
                                // set the hour amount as estimated hours
                                newAow.setEstimatedAmount(
                                        Double.parseDouble((hourAmount.length() == 0) ? "0" : hourAmount));
                                cloneNewAow = newAow;
                                // p_session.save(cloneNewAow);
                            }
                            task.setAmountOfWork(cloneNewAow);
                        }
                    }
                }
                // for Revenue
                rateId = inst.getRevenueRateId();
                if (rateId > 0) {
                    // need to create a clone because the rate already exists -
                    // so
                    // don't want to try and insert but just set up the
                    // relationship
                    // Rate rateClone = (Rate) p_uow.registerObject(ce
                    // .getRate(rateId));
                    Rate rateClone = (Rate) ce.getRate(rateId);
                    task.setRevenueRate(rateClone);
                    if (pageBasedRate == null && rateClone.getRateType().equals(Rate.UnitOfWork.PAGE_COUNT)) {
                        pageBasedRate = rateClone;
                    } else {
                        if (rateClone.getRateType().equals(Rate.UnitOfWork.HOURLY)) {
                            AmountOfWork cloneNewAow = null;
                            if (hourAmount == null) {
                                if (taskInfo != null) {
                                    hourAmount = taskInfo.getEstimatedHours();
                                }
                                if (hourAmount != null) {
                                    AmountOfWork newAow = rateClone.createAmountOfWork();
                                    // set the hour amount as estimated hours
                                    newAow.setEstimatedAmount(
                                            Double.parseDouble((hourAmount.length() == 0) ? "0" : hourAmount));
                                    cloneNewAow = newAow;
                                    // p_session.save(cloneNewAow);
                                }
                                task.setAmountOfWork(cloneNewAow);
                            }
                        }
                    }
                }
                if (s_logger.isDebugEnabled()) {
                    s_logger.debug("createTasks : " + " workflow=" + p_workflow.getId() + " WorkflowTaskInstance="
                            + WorkflowHelper.toDebugString(inst) + GlobalSightCategory.getLineContinuation()
                            + " task=" + task.toString());
                }

                // if a page based rate is assigned to one of the expenses
                // then need to add that amount of work to the task class.
                if (pageBasedRate != null) {
                    AmountOfWork pageAow = pageBasedRate.createAmountOfWork();
                    int numOfPages = task.getWorkflow().getJob().getPageCount();
                    pageAow.setEstimatedAmount(numOfPages);
                    // p_session.save(pageAow);
                    task.setAmountOfWork(pageAow);
                }
                p_session.saveOrUpdate(task);

                for (Object ob : task.getWorkSet()) {
                    if (ob instanceof AmountOfWork) {
                        AmountOfWork aWork = (AmountOfWork) ob;
                        p_session.saveOrUpdate(aWork);
                    }
                }
            }
        } catch (Exception e) {
            s_logger.error("Failed to get a rate for a task.", e);
            // if a task was set then specify the task - otherwise specify all
            // because
            // the costing engine couldn't be retrieved to get rates.
            String taskId = task != null ? Long.toString(task.getId()) : "all";
            String args[] = { taskId };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_FIND_RATE_FOR_TASK, args, e);
        }

        if (s_logger.isDebugEnabled()) {
            s_logger.debug(
                    "createTasks : " + " setCompletionFraction numerator=" + p_workflow.getCompletionNumerator()
                            + " denominator=" + p_workflow.getCompletionDenominator());
        }
    }

    /* modify the tasks of the given workflow (a cloned object) */
    /* based on the i-Flow task instances */
    private void modifyTasks(Session p_session, Hashtable p_modifiedTasks, Workflow p_workflow, List p_wfTaskInfos)
            throws Exception {
        Task task = null;
        try {
            // if there are modified tasks
            if (p_modifiedTasks != null && p_modifiedTasks.size() > 0) {
                Collection modifiedTasks = p_modifiedTasks.values();
                for (Iterator i = modifiedTasks.iterator(); i.hasNext();) {
                    TaskInfoBean taskInfo = (TaskInfoBean) i.next();
                    // Get a task from Toplink
                    task = ServerProxy.getTaskManager().getTask(taskInfo.getTaskId());
                    // since we can also modify a workflow in Ready state, we
                    // need
                    // to check before deletion (in Ready state Task has not
                    // been
                    // created yet).
                    if (task != null) {
                        task = modifyTask(task, taskInfo, p_session);
                    }
                }
                if (s_logger.isDebugEnabled()) {
                    s_logger.debug("modifyTasks : " + " workflow=" + p_workflow.getId() + " Task="
                            + WorkflowHelper.toDebugString(task) + GlobalSightCategory.getLineContinuation()
                            + " task=" + task.toString());
                }
            }
        } catch (Exception e) {
            s_logger.error("Failed to get a rate for a task.", e);
            // if a task was set then specify the task - otherwise specify all
            // because
            // the costing engine couldn't be retrieved to get rates.
            String taskId = task != null ? Long.toString(task.getId()) : "all";
            String args[] = { taskId };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_FIND_RATE_FOR_TASK, args, e);
        }
    }

    /**
     * Modify one task.
     */
    private Task modifyTask(Task p_task, TaskInfoBean p_tib, Session p_session) throws Exception {
        // modify the rate - since it is the only thing on a task
        // that can be modified at this place
        Task taskClone = (Task) p_session.get(TaskImpl.class, new Long(p_task.getId()));
        Rate expenseRate = null;
        Rate oldExpenseRate = null;
        Rate revenueRate = null;
        Rate oldRevenueRate = null;
        boolean allowRemovalOfAmountOfWork = false;

        // Rate pageBasedRate = null;
        // Get the Expense Rate
        expenseRate = p_tib.getExpenseRate();
        oldExpenseRate = p_task.getExpenseRate();
        // Get the Revenue Rate
        revenueRate = p_tib.getRevenueRate();
        oldRevenueRate = p_task.getRevenueRate();

        taskClone.setRateSelectionCriteria(p_tib.getRateSelectionCriteria());
        taskClone.setIsReportUploadCheck(p_tib.getIsReportUploadCheck());
        // update task name and type
        String taskName = p_tib.getActivityName();
        taskClone.setTaskName(taskName);
        Activity activity = ServerProxy.getJobHandler().getActivity(taskName);
        if (activity != null) {
            taskClone.setType(activity.getActivityType());
        }

        if (expenseRate == null && revenueRate == null) {
            allowRemovalOfAmountOfWork = true;
        } else if (expenseRate == null && revenueRate != null) {
            if (oldExpenseRate != null) {
                if (oldExpenseRate.getRateType() != revenueRate.getRateType()) {
                    allowRemovalOfAmountOfWork = true;
                }
            }
        } else if (expenseRate != null && revenueRate == null) {
            if (oldRevenueRate != null) {
                if (oldRevenueRate.getRateType() != expenseRate.getRateType()) {
                    allowRemovalOfAmountOfWork = true;
                }
            }
        }

        // if a expenseRate is being set
        if (expenseRate != null) {
            Rate oldRate = null;
            oldRate = p_task.getExpenseRate();
            AmountOfWork oldAow = (oldRate == null) ? null : p_task.getAmountOfWork(oldRate.getRateType());

            // need to create a clone because the rate already exists - so
            // don't want to try and insert but just set up the relationship
            taskClone.setExpenseRate(expenseRate);
            if (expenseRate.getRateType().equals(Rate.UnitOfWork.PAGE_COUNT)) {
                // pageBasedRate = expenseRate;
            } else {
                if (expenseRate.getRateType().equals(Rate.UnitOfWork.HOURLY)) {
                    AmountOfWork cloneNewAow = null;
                    String hourAmount = null;
                    boolean isEstimatedAmount = false;

                    // if the task is complete then need to be updating the
                    // actual hours - if not complete yet then update the
                    // estimated hours.
                    if ((hourAmount = p_tib.getActualHours()) == null) {
                        isEstimatedAmount = true; // specifies that the
                        // estimated amount
                        // is being updated
                        hourAmount = p_tib.getEstimatedHours();
                    }

                    // if one exists
                    if (oldAow != null) {
                        // if the amount is cleared and it is persisted
                        // delete the amount
                        if (hourAmount == null) {
                            p_session.delete(oldAow);
                        } else
                        // there is a new amount
                        {
                            cloneNewAow = oldAow;
                            // set the unit of work just in case it changed
                            cloneNewAow.setUnitOfWork(expenseRate.getRateType());
                            if (isEstimatedAmount) {
                                cloneNewAow.setEstimatedAmount(
                                        Double.parseDouble((hourAmount.length() == 0) ? "0" : hourAmount));
                            } else {
                                cloneNewAow.setActualAmount(
                                        Double.parseDouble((hourAmount.length() == 0) ? "0" : hourAmount));
                            }
                            p_session.update(cloneNewAow);
                        }
                    } else
                    // no previous uow
                    {
                        // if an estimate is to be set
                        if (hourAmount != null) {
                            AmountOfWork newAow = taskClone.getExpenseRate().createAmountOfWork();
                            if (isEstimatedAmount) {
                                newAow.setEstimatedAmount(
                                        Double.parseDouble((hourAmount.length() == 0) ? "0" : hourAmount));
                            } else {
                                newAow.setActualAmount(
                                        Double.parseDouble((hourAmount.length() == 0) ? "0" : hourAmount));
                            }
                            cloneNewAow = newAow;
                            // p_session.save(cloneNewAow);
                        }
                    }
                    taskClone.setAmountOfWork(cloneNewAow);
                }
            }

        } else
        // NO RATE
        {
            // if there was a rate AND an amount of work specified
            // remove the amount of work
            if (p_task.getExpenseRate() != null
                    && p_task.getAmountOfWork(p_task.getExpenseRate().getRateType()) != null
                    && allowRemovalOfAmountOfWork) {
                AmountOfWork aow = p_task.getAmountOfWork(p_task.getExpenseRate().getRateType());
                // remove relationship and delete
                taskClone.removeAmountOfWork(aow.getUnitOfWork());
                // if persistent
                if (aow != null) {
                    p_session.delete(aow);
                }
            }
            taskClone.setExpenseRate(null);
        }

        // if a revenueRate is being set
        if (revenueRate != null) {
            Rate oldRate = null;
            oldRate = p_task.getRevenueRate();
            AmountOfWork oldAow = (oldRate == null) ? null : p_task.getAmountOfWork(oldRate.getRateType());

            // need to create a clone because the revenueRate already exists -
            // so
            // don't want to try and insert but just set up the relationship
            taskClone.setRevenueRate(revenueRate);
            if (revenueRate.getRateType().equals(Rate.UnitOfWork.PAGE_COUNT)) {
                // pageBasedRate = revenueRate;
            } else {
                if (revenueRate.getRateType().equals(Rate.UnitOfWork.HOURLY)) {
                    AmountOfWork cloneNewAow = null;
                    String hourAmount = null;
                    boolean isEstimatedAmount = false;

                    // if the task is complete then need to be updating the
                    // actual hours - if not complete yet then update the
                    // estimated hours.
                    if ((hourAmount = p_tib.getActualHours()) == null) {
                        isEstimatedAmount = true; // specifies that the
                        // estimated amount
                        // is being updated
                        hourAmount = p_tib.getEstimatedHours();
                    }

                    // if one exists
                    if (oldAow != null) {
                        // if the amount is cleared and it is persisted
                        // delete the amount
                        if (hourAmount == null) {
                            p_session.delete(oldAow);
                        } else
                        // there is a new amount
                        {
                            cloneNewAow = oldAow;
                            // set the unit of work just in case it changed
                            cloneNewAow.setUnitOfWork(revenueRate.getRateType());
                            if (isEstimatedAmount) {
                                cloneNewAow.setEstimatedAmount(
                                        Double.parseDouble((hourAmount.length() == 0) ? "0" : hourAmount));
                            } else {
                                cloneNewAow.setActualAmount(
                                        Double.parseDouble((hourAmount.length() == 0) ? "0" : hourAmount));
                            }
                            p_session.update(cloneNewAow);
                        }
                    } else
                    // no previous uow
                    {
                        // if an estimate is to be set
                        if (hourAmount != null) {
                            AmountOfWork newAow = taskClone.getRevenueRate().createAmountOfWork();
                            if (isEstimatedAmount) {
                                newAow.setEstimatedAmount(
                                        Double.parseDouble((hourAmount.length() == 0) ? "0" : hourAmount));
                            } else {
                                newAow.setActualAmount(
                                        Double.parseDouble((hourAmount.length() == 0) ? "0" : hourAmount));
                            }
                            // cloneNewAow = (AmountOfWork) p_uow
                            // .registerNewObject(newAow);
                            cloneNewAow = newAow;
                            // p_session.save(cloneNewAow);
                        }
                    }
                    taskClone.setAmountOfWork(cloneNewAow);
                }
            }

        } else
        // No Rate
        {
            // if there was a rate AND an amount of work specified
            // remove the amount of work
            if (p_task.getRevenueRate() != null
                    && p_task.getAmountOfWork(p_task.getRevenueRate().getRateType()) != null
                    && allowRemovalOfAmountOfWork) {
                AmountOfWork aow = p_task.getAmountOfWork(p_task.getRevenueRate().getRateType());
                // remove relationship and delete
                taskClone.removeAmountOfWork(aow.getUnitOfWork());
                // if persistent
                if (aow != null) {
                    p_session.delete(aow);
                }
            }
            taskClone.setRevenueRate(null);
        }
        p_session.saveOrUpdate(taskClone);

        if (taskClone instanceof TaskImpl) {
            TaskImpl task = (TaskImpl) taskClone;
            for (Object ob : task.getWorkSet()) {
                if (ob instanceof AmountOfWork) {
                    AmountOfWork aWork = (AmountOfWork) ob;
                    p_session.saveOrUpdate(aWork);
                }
            }
        }

        return p_task;
    }

    /* delete tasks for the given workflow (a cloned object) based on the */
    /* i-Flow task instances */
    private void deleteTasks(Session p_session, Vector p_workflowTaskInstances, Workflow p_workflow,
            List p_wfTaskInfos) throws Exception {
        int size = p_workflowTaskInstances.size();
        for (int i = 0; i < size; i++) {
            WorkflowTaskInstance inst = (WorkflowTaskInstance) p_workflowTaskInstances.elementAt(i);
            // Get a task from Toplink
            Task task = ServerProxy.getTaskManager().getTask(inst.getTaskId());
            if (s_logger.isDebugEnabled()) {
                s_logger.debug("deleteTasks : " + " workflow=" + p_workflow.getId() + " WorkflowTaskInstance="
                        + WorkflowHelper.toDebugString(inst) + GlobalSightCategory.getLineContinuation() + " task="
                        + task.toString());
            }
            // since we can also modify a workflow in Ready state, we need
            // to check before deletion (in Ready state Task has not been
            // created yet).
            if (task != null) {
                p_session.delete(task);
                p_workflow.removeTask(task);
            }
        }
    }

    private WorkflowServerWLRemote getWFServer() throws Exception {
        return ServerProxy.getWorkflowServer();
    }

    /*
     * return a list of WfTaskInfo object. The arrayList value: first is task
     * id, second is boolean value if need create second target file.
     */
    private ArrayList startWorkflow(Workflow p_workflow, DefaultPathTasks p_dpt, TaskEmailInfo p_emailInfo,
            Session p_session) throws Exception {
        List<WfTaskInfo> wfTaskInfos = getWFServer().startWorkflow(p_workflow.getId(), p_dpt, p_emailInfo);

        // set the state of the first task(s) of workflow to ACTIVE.
        // The returned tasks are all cloned.
        List<Task> nextTasks = updateTaskState(p_session, wfTaskInfos, p_workflow.getTasks(), Task.STATE_ACTIVE);

        return possiblyPerformSystemActions(wfTaskInfos, nextTasks, p_workflow, p_emailInfo.getProjectManagerId());
    }

    /**
     * Performs system action for a workflow.
     * <p>
     * The arrayList value: first is task id, second is boolean value if need
     * create second target file, third one is use emails, fourth one is action
     * type.
     */
    private ArrayList possiblyPerformSystemActions(List<WfTaskInfo> p_nextWfTaskInfos, List<Task> p_nextTasksCloned,
            Workflow p_workflow, String p_userId) throws Exception {
        ArrayList array = new ArrayList();
        if (!WorkflowTypeConstants.TYPE_TRANSLATION.equals(p_workflow.getWorkflowType())) {
            array.add(new Long(-1));
            array.add(false);
            array.add(null);
            array.add(null);
            return array;
        }
        Set<SecondaryTargetFile> stfs = p_workflow.getSecondaryTargetFiles();
        List extractedFiles = p_workflow.getTargetPages(PrimaryFile.EXTRACTED_FILE);
        // If the job doesn't have any extracted files in it then
        // don't create STFs. Un-extracted files are already in native/binary
        // format.
        if (extractedFiles.size() == 0) {
            array.add(new Long(-1));
            array.add(false);
            array.add(null);
            array.add(null);
            return array;
        }

        int size = p_nextWfTaskInfos == null ? 0 : p_nextWfTaskInfos.size();
        long taskId = -1;
        boolean flagSTF = false;
        String actionType = null;
        ArrayList userEmails = new ArrayList();
        // fix for GBS-1594
        Boolean stfState = true;
        Hashtable tasks = p_workflow.getTasks();
        Iterator it = tasks.keySet().iterator();
        while (it.hasNext()) {
            Task task = (Task) tasks.get(it.next());
            if (Task.IN_PROGRESS.equals(task.getStfCreationState())) {
                stfState = false;
                break;
            }
        }
        for (int i = 0; i < size; i++) {
            WfTaskInfo wti = (WfTaskInfo) p_nextWfTaskInfos.get(i);
            actionType = wti.getActionType();

            if (((SystemAction.CSTF.equals(actionType) && stfs.size() == 0) || SystemAction.RSTF.equals(actionType))
                    && stfState) {
                flagSTF = true;
            }

            taskId = wti.getId();
            userEmails = wti.userEmail;
        }

        if (flagSTF && taskId > 0) {
            // set the tasks creation of stf state to InProgress
            updateStfCreationStateForTask(p_nextTasksCloned, taskId, Task.IN_PROGRESS);

        }

        array.add(taskId);
        array.add(flagSTF);
        array.add(userEmails);
        array.add(actionType);

        return array;
    }

    /**
     * Get the activity's type.
     */
    private int getActivityType(String p_activityName) {
        // default
        int type = TaskImpl.TYPE_TRANSLATE;
        try {
            Activity act = ServerProxy.getJobHandler().getActivity(p_activityName);
            type = act.getType();

            // for sla report issue
            if ((type == Activity.TYPE_REVIEW) && act.getIsEditable()) {
                type = TaskImpl.TYPE_REVIEW_EDITABLE;
            }
        } catch (Exception e) {
            // do nothing just return the default
        }
        return type;
    }

    /**
     * @param p_dispositionLists
     *            - Map of List of WorkflowTaskInstances keyed by one of
     *            WorkflowConstant.IS_NEW or IS_DELETED.
     * @param p_modified
     *            Tasks Hashtable of modified tasks - keyed by taskid. Value is
     *            TaskInfoBean with modifications
     */
    @SuppressWarnings("unchecked")
    private void persistWorkflowTaskInstanceChanges(Map p_dispositionLists, Hashtable p_modifiedTasks,
            WorkflowInstance p_wfInstance, List p_wfTaskInfos, Session p_session, Workflow p_workflowClone)
            throws Exception {
        List added = (List) p_dispositionLists.get(WorkflowConstants.IS_NEW);
        if (added != null && added.size() > 0) {
            createTasks(p_session, new Vector(added), p_modifiedTasks, p_workflowClone, p_wfTaskInfos);
        }
        List deleted = (List) p_dispositionLists.get(WorkflowConstants.IS_DELETED);
        if (deleted != null && deleted.size() > 0) {
            deleteTasks(p_session, new Vector(deleted), p_workflowClone, p_wfTaskInfos);
        }

        if (p_modifiedTasks != null && p_modifiedTasks.size() > 0) {
            modifyTasks(p_session, p_modifiedTasks, p_workflowClone, p_wfTaskInfos);
        }
    }

    /*
     * Update the duration in the workflow - it may have changed.
     */
    private void updateDuration(long p_wfiId, List p_wfTaskInfos, Session session) throws WorkflowManagerException {
        try {
            long durationInMilli = 0;
            int size = p_wfTaskInfos.size();
            for (int i = 0; i < size; i++) {
                WfTaskInfo wfTaskInfo = (WfTaskInfo) p_wfTaskInfos.get(i);
                // get all the complete durations in the default path (not the
                // accept time)
                durationInMilli += wfTaskInfo.getCompletionDuration();
            }

            // convert the millisec to minutes since it's the smallest unit
            // of time used for each activity of a workflow.
            int duration = (int) (durationInMilli / 60000);

            Workflow wf = (Workflow) session.get(WorkflowImpl.class, new Long(p_wfiId));
            wf.setDuration(duration);
            session.saveOrUpdate(wf);
        } catch (Exception e) {
            String eArgs[] = new String[1];
            eArgs[0] = Long.toString(p_wfiId);
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_UPDATE_WORKFLOW_DURATION,
                    eArgs, e);
        }
    }

    /* Ensure that the given workflow has its i-flow instance set properly */
    private void refreshWorkflowInstance(Workflow p_workflow) throws Exception {
        if (p_workflow.getIflowInstance() == null) {
            p_workflow.setIflowInstance(getWFServer().getWorkflowInstanceById(p_workflow.getId()));
        }
    }

    /* Change each page in the collection to the desired state. */
    private void updatePageState(Session p_session, Collection p_pages, String p_state) throws Exception {
        Iterator it = p_pages.iterator();
        while (it.hasNext()) {
            // check if this is a valid state transition
            // Leave a page as IMPORT_FAIL
            // can't be NOT_LOCALIZED or OUT_OF_DATE because
            // this can be leveraged againt
            // currently can't re-import the file to change
            // to IMPORT_SUCCESS - so leave as a failure
            Page p = (Page) it.next();
            if (p.getPageState().equals(PG_IMPORT_FAIL)) {
                continue; // skip to the next page
                // don't change this page state
            }
            p.setPageState(p_state);
            p_session.update(p);
        }
    }

    /* Change the state of each secondary target file. */
    private void updateSecondaryTargetFileState(Session p_session, Set<SecondaryTargetFile> p_stfs, String p_state)
            throws Exception {
        for (SecondaryTargetFile stf : p_stfs) {
            stf.setState(p_state);
            p_session.update(stf);
        }
    }

    /* Change the STF creation state of tasks. */
    private void updateStfCreationStateForTask(List p_nextTasksCloned, long p_taskId, String p_state)
            throws Exception {
        int size = p_nextTasksCloned.size();
        boolean found = false;
        for (int i = 0; !found && i < size; i++) {
            Task taskClone = (Task) p_nextTasksCloned.get(i);
            found = taskClone.getId() == p_taskId;

            if (found) {
                taskClone.setStfCreationState(p_state);
                // for GBS-3331: createSTF regard as exporting
                if (p_state.equals("IN_PROGRESS")) {
                    ArrayList<Long> workflowIds = new ArrayList<Long>();
                    workflowIds.add(taskClone.getWorkflow().getIdAsLong());
                    WorkflowExportingHelper.setAsExporting(workflowIds);
                }
            }
        }
    }

    /*
     * Update the task state to the specified state. The return a list of cloned
     * tasks (with updated state) which will be used for more possible updates.
     */
    private List<Task> updateTaskState(Session p_session, List<WfTaskInfo> p_nextWfTaskInfos, Hashtable p_tasks,
            int p_state) throws Exception {
        int size = p_nextWfTaskInfos.size();
        List<Task> tasks = new ArrayList<Task>(size);
        for (int i = 0; i < size; i++) {
            WfTaskInfo wfti = p_nextWfTaskInfos.get(i);
            Task task = (Task) p_tasks.get(new Long(wfti.getId()));
            Task taskClone = (Task) p_session.get(TaskImpl.class, new Long(task.getId()));
            taskClone.setState(p_state);
            tasks.add(taskClone);
            p_session.saveOrUpdate(taskClone);
        }

        return tasks;
    }

    /*
     * Update the task state to the specified state. Each element of the object
     * array is of type WorkflowTaskInstance.
     */
    private void updateTaskState(Session p_session, Object[] p_activeTasks, Hashtable p_wfTasks, int p_state)
            throws Exception {
        int size = p_activeTasks == null ? -1 : p_activeTasks.length;
        for (int i = 0; i < size; i++) {
            WorkflowTaskInstance wfti = (WorkflowTaskInstance) p_activeTasks[i];
            Task task = (Task) p_wfTasks.get(new Long(wfti.getTaskId()));
            task.setState(p_state);
            p_session.saveOrUpdate(task);
        }
    }

    /* If the workflows all have the given p_wf state, then update the job */
    /* with the same state, and update the job's source pages with the */
    /* given pgState */
    private void possiblyUpdateJob(Session p_session, Workflow p_wf, String p_wfState) throws Exception {
        JobImpl job = (JobImpl) p_wf.getJob();
        String jobState = job.getState();
        int jobStateIndex = -1;
        for (int i = 0; i < ORDERED_STATES.length && jobStateIndex < 0; i++) {
            if (jobState.equals(ORDERED_STATES[i])) {
                jobStateIndex = i;
            }
        }
        int lowest = findLowestStateIndex(job.getWorkflows());
        if (jobStateIndex < lowest) {
            JobImpl jobClone = (JobImpl) p_session.get(JobImpl.class, job.getIdAsLong());
            jobClone.setState(ORDERED_STATES[lowest]);
            if (!Workflow.EXPORT_FAILED.equals(p_wfState)
                    && WorkflowTypeConstants.TYPE_TRANSLATION.equals(p_wf.getWorkflowType())) {
                updatePageState(p_session, jobClone.getSourcePages(),
                        ((lowest == LOCALIZED_STATE || lowest == EXPORTING_STATE) ? PG_LOCALIZED : PG_ACTIVE_JOB));
            }
            p_session.saveOrUpdate(jobClone);
        }
        if (lowest >= LOCALIZED_STATE) {
            TaskEmailInfo p_emailInfo = createTaskEmailInfo(job, p_wf);
            p_emailInfo.setAssigneesName(job.getCreateUser().getFirstName());
            getWFServer().sendJobActionEmailToUser(job.getCreateUser().getUserId(), p_emailInfo,
                    WorkflowMailerConstants.COMPLETED_JOB);
        }
    }

    /*
     * Return true if all workflows are in the given state.
     * 
     * If the given state is "CANCELLED" or "IMPORT_FAIL", check ALL workflows
     * against it; Otherwise, only check the given state against non-CANCELLED
     * workflows.
     */
    public static boolean workflowsHaveState(Collection p_workflows, String p_state) {
        boolean workflowsHaveState = true;
        Iterator it = p_workflows.iterator();
        if (p_state.equals(Workflow.CANCELLED) || p_state.equals(Workflow.IMPORT_FAILED)) {
            while (workflowsHaveState && it.hasNext()) {
                Workflow wf = (Workflow) it.next();
                workflowsHaveState &= (wf.getState().equals(Workflow.CANCELLED)
                        || wf.getState().equals(Workflow.IMPORT_FAILED));
            }
        } else {
            while (workflowsHaveState && it.hasNext()) {
                String wfState = ((Workflow) it.next()).getState();
                if (!wfState.equals(Workflow.CANCELLED) && !wfState.equals(Workflow.IMPORT_FAILED)) {
                    workflowsHaveState &= wfState.equals(p_state);
                }
            }
        }
        return workflowsHaveState;
    }

    /**
     * Create the task tuvs for the given collection of tuvs and given task;
     * register all objects as part of the transaction of the given unit of work
     */
    private void createTaskTuvs(Workflow p_wf, Task p_task, String p_taskName, Session session) throws Exception {
        List targetPages = p_wf.getTargetPages(PrimaryFile.EXTRACTED_FILE);
        // Touch to load all TUs for pages to improve performance.
        for (Iterator it = targetPages.iterator(); it.hasNext();) {
            TargetPage tp = (TargetPage) it.next();
            SegmentTuUtil.getTusBySourcePageId(tp.getSourcePage().getId());
        }
        // get the TUVs of all the pages associated with extracted files
        Iterator tuvIt = getTuvsOfPages(targetPages).iterator();
        Iterator prevIt = getPreviousTaskTuvs(p_task.getId()).iterator();

        List<Tuv> tuvs = new ArrayList<Tuv>();
        List<TaskTuv> taskTuvs = new ArrayList<TaskTuv>();
        while (tuvIt.hasNext()) {
            TuvImpl tuv = (TuvImpl) tuvIt.next();
            TaskTuv previousTaskTuv = getPreviousTaskTuvForThisTuv(tuv.getId(), prevIt);
            TuvImpl tuvClone = new TuvImpl(tuv);
            tuvClone.setState(TuvState.OUT_OF_DATE);
            tuvClone.setIsIndexed(false);
            tuvClone.setCreatedDate(new Date());
            tuvClone.setTimestamp(new java.sql.Timestamp(System.currentTimeMillis()));
            tuvClone.setLastModified(new Date());
            // set TuvId here to avoid "previousTuvId" = -1 below:
            // taskTuv.setPreviousTuv(tuvClone);
            SegmentTuTuvIndexUtil.setTuvId(tuvClone);
            tuvs.add(tuvClone);

            TaskTuv taskTuv = new TaskTuv();
            taskTuv.setCurrentTuv(tuv);
            taskTuv.setPreviousTuv(tuvClone);
            taskTuv.setVersion(getVersion(previousTaskTuv));
            taskTuv.setTask(p_task);
            taskTuv.setTaskName(p_taskName);
            taskTuvs.add(taskTuv);
        }

        Connection conn = DbUtil.getConnection();
        conn.setAutoCommit(false);
        try {
            SegmentTuvUtil.saveTuvs(conn, tuvs, p_wf.getJob().getId());
            conn.commit();
            HibernateUtil.save(taskTuvs);
        } finally {
            DbUtil.silentReturnConnection(conn);
        }
    }

    private TaskTuv getPreviousTaskTuvForThisTuv(long p_tuvId, Iterator p_it) {
        TaskTuv taskTuv = null;
        while (p_it.hasNext()) {
            taskTuv = (TaskTuv) p_it.next();
            if (taskTuv.getCurrentTuvId() == p_tuvId) {
                return taskTuv;
            }
        }
        return taskTuv;
    }

    /* Get the tuvs of a collection of pages */
    @SuppressWarnings("unchecked")
    private Collection getTuvsOfPages(Collection p_pages) throws Exception {
        Iterator it = p_pages.iterator();
        Collection tuvs = new ArrayList();
        while (it.hasNext()) {
            TargetPage p = (TargetPage) it.next();
            tuvs.addAll(getTuvsOfPage(p));
        }
        return tuvs;
    }

    private Collection getTuvsOfPage(TargetPage p_targetPage) throws Exception {
        return ServerProxy.getTuvManager().getTargetTuvsForStatistics(p_targetPage);
    }

    /* Get the specified number of previous task tuvs for the given tuv */
    private Collection getPreviousTaskTuvs(long p_taskId) throws Exception {
        HashMap<String, Long> map = new HashMap<String, Long>();
        map.put(TuvQueryConstants.TASK_ID_ARG, new Long(p_taskId));
        return HibernateUtil.searchWithSql(TuvQueryConstants.PREVIOUS_TASK_TUV_BY_TASK_ID, map, TaskTuv.class);
    }

    /* Get the version number for the given task tuv */
    private int getVersion(TaskTuv p_tt) {
        return (p_tt == null ? 1 : p_tt.getVersion() + 1);
    }

    // get the task email info for the given job.
    @SuppressWarnings("unchecked")
    private TaskEmailInfo createTaskEmailInfo(Job p_job, Workflow p_workflow)
            throws CommentException, RemoteException {
        WorkflowTemplateInfo wfti = null;
        if (WorkflowTypeConstants.TYPE_TRANSLATION.equals(p_workflow.getWorkflowType())) {
            wfti = p_job.getL10nProfile().getWorkflowTemplateInfo(p_workflow.getTargetLocale());
        } else {
            wfti = p_job.getL10nProfile().getDtpWorkflowTemplateInfo(p_workflow.getTargetLocale());
        }
        // get Job comments
        List commentsList = p_job.getJobComments();

        // comments don't include restricted attachment informations.
        StringBuffer comments = new StringBuffer();

        // restrictComments include restricted attachment informations.
        StringBuffer restrictComments = new StringBuffer();

        ArrayList list = new ArrayList();
        CommentManager manager = ServerProxy.getCommentManager();

        // attachment don't include restricted attachments.
        List attachment = new ArrayList();

        // restrictAttachment include restricted attachments.
        List restrictAttachment = new ArrayList();

        if (commentsList != null && commentsList.size() > 0) {
            list = new ArrayList(commentsList);
            for (int i = 0; i < list.size(); i++) {
                // Adds comment informations to comments.
                comments.append("\r\n " + (i + 1) + " -- ");
                CommentImpl aComment = (CommentImpl) list.get(i);
                String userName = UserUtil.getUserNameById(aComment.getCreatorId());
                comments.append("Comment Creator: " + userName + "    ");
                comments.append("Date Created: " + aComment.getCreatedDate() + "    ");
                comments.append("Comments: " + aComment.getComment() + "    ");
                comments.append("Attached Files: ");

                // Adds comment informations to restrictComments.
                restrictComments.append("\r\n " + (i + 1) + " -- ");
                restrictComments.append("Comment Creator: " + userName + "    ");
                restrictComments.append("Date Created: " + aComment.getCreatedDate() + "    ");
                restrictComments.append("Comments: " + aComment.getComment() + "    ");
                restrictComments.append("Attached Files: ");

                ArrayList reference = manager.getCommentReferences(String.valueOf(aComment.getId()),
                        WebAppConstants.COMMENT_REFERENCE_RESTRICTED_ACCESS, true);

                boolean attachmentAdded = false;
                boolean restrictAttachmentAdded = false;

                // Adds attachments to comments, attachment, restrictComments
                // and restrictAttachment.
                for (Iterator it = reference.iterator(); it.hasNext();) {
                    CommentFile file = (CommentFile) it.next();
                    long filesize = file.getFileSize() < 3 ? 0 : file.getFileSize();
                    if (filesize != 0) {
                        filesize = (filesize % 1024 != 0) ? ((filesize / 1024) + 1) : filesize / 1024;
                    }

                    // Adds all attachments information to restrictComments and
                    // restrictAttachment.
                    if (restrictAttachmentAdded) {
                        restrictComments.append(", ");
                    } else {
                        restrictAttachmentAdded = true;
                    }

                    String fileName = file.getFilename();
                    if (file.getFileAccess().equals(WebAppConstants.COMMENT_REFERENCE_RESTRICTED_ACCESS)) {
                        fileName = fileName + "(" + WebAppConstants.COMMENT_REFERENCE_RESTRICTED_ACCESS + ")";
                    }

                    restrictComments.append(fileName + "  " + "size: " + filesize + "k");
                    restrictAttachment
                            .add(new File(AmbFileStoragePathUtils.getCommentReferenceDir().getAbsoluteFile()
                                    + File.separator + String.valueOf(aComment.getId()) + File.separator
                                    + file.getFileAccess() + File.separator + file.getFilename()));

                    // Adds attachments to comments and attachment only if the
                    // attachment is not restricted.
                    if (!WebAppConstants.COMMENT_REFERENCE_RESTRICTED_ACCESS.equals(file.getFileAccess())) {
                        if (attachmentAdded) {
                            comments.append(", ");
                        } else {
                            attachmentAdded = true;
                        }

                        comments.append(fileName + "  " + "size: " + filesize + "k");
                        attachment.add(new File(AmbFileStoragePathUtils.getCommentReferenceDir().getAbsoluteFile()
                                + File.separator + String.valueOf(aComment.getId()) + File.separator
                                + file.getFileAccess() + File.separator + file.getFilename()));
                    }
                }

                if (!attachmentAdded) {
                    comments.append("N/A");
                }

                if (!restrictAttachmentAdded) {
                    restrictComments.append("N/A");
                }
            }
        }

        TaskEmailInfo emailInfo = new TaskEmailInfo(p_job.getL10nProfile().getProject().getProjectManagerId(),
                p_workflow.getWorkflowOwnerIdsByType(Permission.GROUP_WORKFLOW_MANAGER),
                wfti.notifyProjectManager(), p_job.getPriority(), comments.toString());
        emailInfo.setJobName(p_job.getJobName());
        emailInfo.setProjectIdAsLong(new Long(p_job.getL10nProfile().getProjectId()));
        emailInfo.setWfIdAsLong(p_workflow.getIdAsLong());
        emailInfo.setSourceLocale(p_job.getSourceLocale().toString());
        emailInfo.setTargetLocale(p_workflow.getTargetLocale().toString());
        emailInfo.setAttachment(attachment);
        emailInfo.setRestrictComments(restrictComments.toString());
        emailInfo.setRestrictAttachment(restrictAttachment);

        if (WorkflowTypeConstants.TYPE_TRANSLATION.equals(p_workflow.getWorkflowType())) {
            setWordCountDetails(p_job, p_workflow, emailInfo);
        }
        emailInfo.setJobId(new Long(p_job.getId()).toString());
        String projectName = p_job.getL10nProfile().getProject().getName();
        emailInfo.setProjectName(projectName);
        emailInfo.setCompanyId(String.valueOf(p_job.getCompanyId()));

        return emailInfo;
    }

    // determine exoprt type for creation of export batch event
    private String determineExportType(ExportParameters p_exportParams, Workflow p_workflow) {
        String exportType = null;
        if (p_exportParams.getExportType().equals(ExportConstants.EXPORT_FOR_STF_CREATION)) {
            exportType = ExportBatchEvent.CREATE_STF;
        } else if (p_exportParams.getExportType().equals(ExportConstants.EXPORT_STF)) {
            exportType = p_workflow.getState().equals(Workflow.LOCALIZED) ? ExportBatchEvent.FINAL_SECONDARY
                    : ExportBatchEvent.INTERIM_SECONDARY;
        } else {
            exportType = p_workflow.getState().equals(Workflow.LOCALIZED) ? ExportBatchEvent.FINAL_PRIMARY
                    : ExportBatchEvent.INTERIM_PRIMARY;
        }
        return exportType;
    }

    /**
     * After a workflow has been cancelled, the Job's state must be reset to the
     * lowest state of the remaining workflows, provided in the p_wfs collection
     * 
     * @param p_uow
     * @param p_job
     * @param p_wfs
     * @return job state
     * @exception Exception
     */
    private String resetJobState(Session p_session, JobImpl p_job, Collection p_wfs) throws Exception {
        return resetJobState(p_session, p_job, p_wfs, false);
    }

    /**
     * After a workflow has been cancelled, the Job's state must be reset to the
     * lowest state of the remaining workflows, provided in the p_wfs collection
     * 
     * @param p_uow
     * @param p_job
     * @param p_wfs
     * @param p_reimport
     * @return job state
     * @exception Exception
     */
    private String resetJobState(Session p_session, JobImpl p_job, Collection p_wfs, boolean p_reimport)
            throws Exception {
        if (p_wfs.size() == 0 || workflowsHaveState(p_wfs, WF_CANCELLED)) {
            p_job.setState(WF_CANCELLED);
            if (p_reimport) {
                updatePageState(p_session, p_job.getSourcePages(), PG_NOT_LOCALIZED);
            } else {
                updatePageState(p_session, p_job.getSourcePages(), PG_OUT_OF_DATE);
            }
        } else {
            int lowest = findLowestStateIndex(p_wfs);
            p_job.setState(ORDERED_STATES[lowest]);
            if (lowest >= LOCALIZED_STATE) {
                updatePageState(p_session, p_job.getSourcePages(),
                        (lowest == LOCALIZED_STATE ? PG_LOCALIZED : PG_EXPORTED));
            }
        }
        String jobState = p_job.getState();

        /*
         * for desktop icon download Ambassador.getDownloadableJobs(...)
         */
        if (jobState.equals(Job.EXPORTED) && p_job.getWorkflows().iterator().hasNext()) {
            Workflow wf = p_job.getWorkflows().iterator().next();
            String dataSourceType = DataSourceType.FILE_SYSTEM_AUTO_IMPORT;
            try {
                dataSourceType = (wf.getTargetPages().iterator().next()).getDataSourceType();
            } catch (Exception ignore) {

            }
            boolean isAutoImport = dataSourceType.equals(DataSourceType.FILE_SYSTEM_AUTO_IMPORT);
            if (isAutoImport) {
                File diExportedDir = AmbFileStoragePathUtils.getDesktopIconExportedDir(p_job.getCompanyId());
                File jobDir = new File(diExportedDir, String.valueOf(p_job.getId()));
                if (!jobDir.exists()) {
                    jobDir.mkdirs();
                }
            }
        } else {
            deleteFolderForDI(p_job.getCompanyId(), p_job.getJobId());
        }

        return jobState;
    }

    /* Find the index of the "lowest" state of any of the given workflows */
    public static int findLowestStateIndex(Collection p_wfs) {
        int lowest = ORDERED_STATES.length - 1;
        Iterator it = p_wfs.iterator();
        while (lowest >= 0 && it.hasNext()) {
            String state = ((Workflow) it.next()).getState();
            for (int i = 0; i <= lowest; i++) {
                if (ORDERED_STATES[i].equals(state)) {
                    lowest = i;
                }
            }
        }
        return lowest;
    }

    private void runJobCreationScript(Job p_job) {
        if (p_job.getL10nProfile().getNameOfJobCreationScript() != null) {
            String jobCreationScript = p_job.getL10nProfile().getNameOfJobCreationScript();
            try {
                String command = jobCreationScript + " " + new Long(p_job.getId()).toString();
                s_logger.info("Running job creation script: " + command);

                // execute this job creation script in a separate process and
                // dedicate a
                // separate thread to watch its stdout and stderr
                ProcessRunner pr = new ProcessRunner(command, JOBCREATION_SCRIPT_LOG, JOBCREATION_SCRIPT_ERR_LOG,
                        true);
                Date d = new Date();
                String threadName = "jobCreationScript" + d.getTime();
                Thread t = new Thread(pr, threadName);
                t.start();

                // wait for the job creation script to finish
                try {
                    t.join();
                } catch (InterruptedException e) {
                }
                s_logger.info("Job creation script finished.");

            } catch (Exception ioe) {
                s_logger.error(
                        "Unable to run job creation script " + jobCreationScript + " job=" + p_job.toString(), ioe);
            }
        }
    }

    private void completeTask(String p_userId, Task p_task, String p_destinationArrow, String skipping)
            throws WorkflowManagerException {
        boolean isCompleted = false; // true if all workflows are complete
        boolean isManualImportFileSystemJob = true;
        boolean isTranslationWorkflow = false;
        boolean isDbJob = false;
        Workflow wfClone = null;
        TaskEmailInfo emailInfo = null;
        long jobId = -2;
        long taskId = -2;
        long wfId = -2;

        if (p_task == null) {
            s_logger.error("Task is null in _setTaskCompletion() for user: " + UserUtil.getUserNameById(p_userId));
        } else {
            taskId = p_task.getId();
        }
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        JbpmContext ctx = null;

        try {
            Date currentTime = new Date();
            TaskImpl task = (TaskImpl) session.get(TaskImpl.class, new Long(p_task.getId()));
            task.setCompletedDate(currentTime);
            task.setState(TaskImpl.STATE_COMPLETED);
            wfClone = (Workflow) task.getWorkflow();
            isTranslationWorkflow = WorkflowTypeConstants.TYPE_TRANSLATION.equals(wfClone.getWorkflowType());
            wfId = wfClone.getId();
            if (isTranslationWorkflow) {
                for (TargetPage tPage : wfClone.getTargetPages()) {
                    String type = tPage.getDataSourceType();
                    if (!DataSourceType.FILE_SYSTEM.equals(type)) {
                        isManualImportFileSystemJob = false;
                        isDbJob = DataSourceType.DATABASE.equals(type);
                        break;
                    }

                }
            } else {
                isManualImportFileSystemJob = false;
                isDbJob = false;
            }

            ArrorInfo arrorInfo = new ArrorInfo(p_task.getId(), p_destinationArrow);

            // -1 indicates that the default path would be from the
            // beginning of the workflow (form START node)
            List wfTaskInfos = getWFServer().timeDurationsInDefaultPath(wfClone.getId(), -1, arrorInfo,
                    getWFServer().getWorkflowInstanceById(wfClone.getId()));

            updateDuration(wfClone.getId(), wfTaskInfos, session);

            DefaultPathTasks dpt = updateDefaultPathTasks(ADVANCE_ACTION, currentTime, wfTaskInfos, wfClone,
                    task.getId(), session);

            Job job = wfClone.getJob();
            jobId = job.getId();
            emailInfo = createTaskEmailInfo(job, wfClone);
            if (job != null && p_task != null) {
                if (s_logger.isDebugEnabled()) {
                    s_logger.debug("Advancing task " + p_task.getTaskName() + ", taskId " + taskId + ", wfId "
                            + wfId + ", job " + job.getJobName() + ", jobId " + jobId + ", user "
                            + UserUtil.getUserNameById(p_userId));
                }
            }

            WorkflowInstanceInfo wfInstInfo = getWFServer().advanceTask(wfClone, p_userId, task.getId(),
                    p_destinationArrow, dpt, emailInfo, skipping);

            int wfState = wfInstInfo.getState();
            isCompleted = wfState == WorkflowConstants.STATE_COMPLETED;
            if (isCompleted) {
                if (s_logger.isDebugEnabled()) {
                    s_logger.debug("workflow state is completed for workflow instance " + wfInstInfo.getId());
                }
            }

            // update the completion fraction
            updateCompletionFraction(wfClone, p_task, wfTaskInfos, isCompleted);
            long stfTaskId = -1;
            boolean createSTF = false;
            String actionType = null;
            long nextTaskId = -1;

            if (isCompleted) {
                if (isManualImportFileSystemJob) {
                    wfClone.setState(WF_LOCALIZED);
                } else {
                    if (skipping == null) {
                        wfClone.setState(WF_LOCALIZED);
                    } else {
                        wfClone.setState(WF_EXPORTING);
                    }
                }
                if (WorkflowTypeConstants.TYPE_DTP.equals(wfClone.getWorkflowType())) {
                    wfClone.setState(WF_EXPORTED);
                }
                wfClone.setCompletedDate(currentTime);
                if (isTranslationWorkflow) {
                    updatePageState(session, wfClone.getTargetPages(), PG_LOCALIZED);
                    updateSecondaryTargetFileState(session, wfClone.getSecondaryTargetFiles(),
                            SecondaryTargetFileState.LOCALIZED);
                }
                possiblyUpdateJob(session, wfClone, WF_LOCALIZED);

                // Set up completed date of job
                job.setCompletedDate(currentTime);
                session.saveOrUpdate(job);
            } else {
                if (isTranslationWorkflow) {
                    createTaskTuvs(wfClone, task, p_task.getTaskName(), session);
                }

                List<WfTaskInfo> nextTaskInfos = wfInstInfo.getNextTaskInfos();
                // update the state of the next active tasks
                List<Task> nextTasks = updateTaskState(session, nextTaskInfos, wfClone.getTasks(),
                        Task.STATE_ACTIVE);
                // perform the creation of STF if necessary
                ArrayList returnValue = possiblyPerformSystemActions(nextTaskInfos, nextTasks, wfClone,
                        emailInfo.getProjectManagerId());

                if (isTranslationWorkflow) {
                    stfTaskId = ((Long) (returnValue.get(0))).longValue();
                    createSTF = ((Boolean) returnValue.get(1)).booleanValue();
                    actionType = returnValue.get(3) != null ? (String) returnValue.get(3) : null;
                }

                nextTaskId = ((Long) returnValue.get(0)).longValue();
            }

            session.saveOrUpdate(task);
            tx.commit();

            if (createSTF) {
                s_logger.debug("Exporting for STF creation, taskId=" + stfTaskId);
                exportForStfCreation(new Long(stfTaskId), wfClone, p_userId);
            }
            // GBS-3002
            if (actionType != null && (skipping == null || skipping.startsWith("LAST"))) {
                SystemActionPerformer.perform(actionType, stfTaskId, p_userId);
            }

            // now remove the reserved time from the user's calendar.
            // This does not need to be part of the same transaction
            removeReservedTime(task.getId(), p_userId);
        } catch (AmbassadorDwUpException e) {
            s_logger.error("Failed to create job on GS Edtion", e);
        } catch (Exception e) {
            tx.rollback();
            s_logger.error("Failed to complete task " + taskId + ", workflow " + wfId + ", job " + jobId, e);
            String taskIdString = "null task id!";
            if (p_task != null) {
                Long id = new Long(p_task.getId());
                taskIdString = id.toString();
            }
            String[] args = new String[1];
            args[0] = taskIdString;
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_SET_TASK_COMPLETION, args, e);
        } finally {
            if (ctx != null) {
                ctx.close();
            }
            // despite errors above, if the job is complete, then attempt the
            // export
            // but re-check job state from TopLink instead of relying on the
            // above boolean
            if (!isManualImportFileSystemJob) {
                if (wfClone != null) {
                    String projectMgrUserId = null;
                    if (emailInfo != null)
                        projectMgrUserId = emailInfo.getProjectManagerId();
                    else {
                        s_logger.warn("Project Mgr Id is null for task " + taskId + ", workflow " + wfId + ", job "
                                + jobId);
                    }

                    try {
                        exportLocalizedWorkflow(wfClone.getId(), projectMgrUserId, isDbJob, isCompleted);
                    } catch (Throwable t) {
                        // log the error but don't let it affect job completion
                        s_logger.error("Error trying to export for completed workflow.", t);
                    }
                } else {
                    s_logger.warn("Not exporting wf for task " + taskId + ", workflow " + wfId + ", job " + jobId);
                }
            }
        }
    }

    /**
     * Process the files if the source file is with XLZ file format
     * 
     * @param p_wf
     * @author Vincent Yan, 2011/01/27
     */
    private void processXLZFiles(Workflow p_wf) {
        if (p_wf == null || p_wf.getAllTargetPages().size() == 0)
            return;

        TargetPage tp = null;
        String externalId = "";
        String tmp = "", tmpFile = "";
        String sourceFilename = "", targetFilename = "";
        String sourceDir = "", targetDir = "";
        File sourceFile = null, targetFile = null;
        File sourcePath = null, targetPath = null;
        ArrayList<String> xlzFiles = new ArrayList<String>();

        try {
            Vector targetPages = p_wf.getAllTargetPages();
            String baseDir = AmbFileStoragePathUtils.getCxeDocDirPath().concat(File.separator);

            Job job = p_wf.getJob();
            String companyId = String.valueOf(job.getCompanyId());
            String companyName = CompanyWrapper.getCompanyNameById(companyId);

            if ("1".equals(CompanyWrapper.getCurrentCompanyId()) && !"1".equals(job.getCompanyId())) {
                baseDir += companyName + File.separator;
            }

            for (int i = 0; i < targetPages.size(); i++) {
                tp = (TargetPage) targetPages.get(i);
                externalId = tp.getSourcePage().getExternalPageId();

                if (externalId.toLowerCase().endsWith(".xlf") || externalId.toLowerCase().endsWith(".xliff")) {
                    tmp = externalId.substring(0, externalId.lastIndexOf(File.separator));
                    sourceFilename = baseDir + tmp + ".xlz";
                    sourceFile = new File(sourceFilename);
                    if (sourceFile.exists() && sourceFile.isFile()) {
                        // source file is with xlz file format
                        targetDir = baseDir + tp.getExportSubDir() + tmp.substring(tmp.indexOf(File.separator));
                        if (!xlzFiles.contains(targetDir))
                            xlzFiles.add(targetDir);

                        // Get exported target path
                        targetPath = new File(targetDir);

                        // Get source path
                        sourceDir = baseDir + tmp;
                        sourcePath = new File(sourceDir);

                        // Copy all files extracted from xlz file from source
                        // path to exported target path
                        // Because xliff files can be exported by GS
                        // auotmatically, then ignore them and
                        // just copy the others file to target path
                        File[] files = sourcePath.listFiles();
                        for (File f : files) {
                            if (f.isDirectory())
                                continue;
                            tmpFile = f.getAbsolutePath().toLowerCase();
                            if (tmpFile.endsWith(".xlf") || tmpFile.endsWith(".xliff"))
                                continue;
                            FileUtils.copyFileToDirectory(f, targetPath);
                        }
                    }
                }

                // Verify if the exported file is generated
                targetFilename = baseDir + tp.getExportSubDir() + File.separator;
                targetFilename += externalId.substring(externalId.indexOf(File.separator) + 1);
                targetFile = new File(targetFilename);
                while (!targetFile.exists()) {
                    Thread.sleep(1000);
                }
            }

            // Generate exported XLZ file and remove temporary folders
            for (int i = 0; i < xlzFiles.size(); i++) {
                targetDir = xlzFiles.get(i);
                targetPath = new File(targetDir);

                ZipIt.addEntriesToZipFile(new File(targetDir + ".xlz"), targetPath.listFiles(), true, "");
            }
        } catch (Exception e) {
            s_logger.error("Error in WorkflowManagerLocal.processXLZFiles. " + e.toString());
        }
    }

    /**
     * Set the workflow word count details for email notification.
     */
    private void setWordCountDetails(Job p_job, Workflow p_workflow, TaskEmailInfo emailInfo) {
        int totalFuzzy = p_workflow.getThresholdHiFuzzyWordCount() + p_workflow.getThresholdLowFuzzyWordCount()
                + p_workflow.getThresholdMedFuzzyWordCount() + p_workflow.getThresholdMedHiFuzzyWordCount();
        int repetitionCount = p_workflow.getRepetitionWordCount();

        emailInfo.setWordCountDetails(p_job.getLeverageMatchThreshold(), p_workflow.getThresholdNoMatchWordCount(),
                repetitionCount, totalFuzzy, p_workflow.getSegmentTmWordCount(), p_workflow.getTotalWordCount());
    }

    /**
     * Performs the export if the workflow is localized, otherwise does nothing.
     */
    private void exportLocalizedWorkflow(long p_wfId, String p_userId, boolean p_isDbJob, boolean isCompleted)
            throws Exception {
        if (s_logger.isDebugEnabled()) {
            s_logger.debug("exportLocalizedWorkflow: " + " p_wfId=" + p_wfId);
        }

        Workflow wf = getWorkflowByIdRefresh(p_wfId);
        if (WorkflowTypeConstants.TYPE_DTP.equals(wf.getWorkflowType())) {
            return;
        }
        if (!isCompleted) {
            if (s_logger.isDebugEnabled()) {
                s_logger.debug("Workflow " + p_wfId + " is not localized yet; not exporting it.");
            }
            return;
        }
        if (Workflow.LOCALIZED.equals(wf.getState())) {
            if (s_logger.isDebugEnabled()) {
                s_logger.debug("Workflow " + p_wfId + " is localized; exporting it.");
            }
            // from GBS-2137, add "Exporting" state before export is done
            JobCreationMonitor.updateWorkflowState(wf, Workflow.EXPORTING);
            JobCreationMonitor.updateJobStateToExporting(wf.getJob());
        }

        // since at FileProfile level a flag is set to determine whether
        // primary or secondary target pages should be automatically exported
        // we need to check for this flag here (only for non-DB datasource).
        boolean shouldExportStf = false;
        if (!p_isDbJob) {
            Object[] requests = wf.getJob().getRequestList().toArray();
            long fpId = ((Request) requests[0]).getDataSourceId();
            FileProfile fp = ServerProxy.getFileProfilePersistenceManager().readFileProfile(fpId);
            shouldExportStf = fp.byDefaultExportStf();
        }

        ExportParameters exportParams = shouldExportStf
                ? new ExportParameters(wf, null, null, null, ExportConstants.NOT_SELECTED,
                        ExportConstants.EXPORT_STF)
                : new ExportParameters(wf);

        String exportEncoding = exportParams.getExportCodeset();

        if (exportEncoding != null) {
            exportParams.setExportCodeset(exportEncoding += "_di");
        }

        ArrayList<Long> workflowIds = new ArrayList<Long>();
        workflowIds.add(p_wfId);
        WorkflowExportingHelper.setAsExporting(workflowIds);

        performExport(wf, p_userId, null, exportParams, shouldExportStf);
    }

    // perform an interim export for the purpose of secondary target file
    // creation.
    private void exportForStfCreation(Long p_taskId, Workflow p_workflow, String p_userId) throws Exception {
        try {
            ExportParameters exportParams = new ExportParameters(p_workflow, null, null, null,
                    ExportConstants.NO_UTF_BOM, ExportConstants.EXPORT_FOR_STF_CREATION);

            performExport(p_workflow, p_userId, p_taskId, exportParams, false);
        } catch (Exception e) {
            // update task state to "FAILED"
            ServerProxy.getTaskManager().updateStfCreationState(p_taskId.longValue(), Task.FAILED);
            throw e;
        }
    }

    // perform export process for target pages or Secondary Target Files.
    private void performExport(Workflow p_workflow, String p_userId, Long p_taskId, ExportParameters p_exportParams,
            boolean p_shouldExportStf) throws Exception {
        List genericPages = null;
        List<Long> ids = new ArrayList<Long>();
        boolean doRegularExport = true;
        ExportParameters exportParams = p_exportParams;

        if (p_shouldExportStf) {
            Set<SecondaryTargetFile> stfs = p_workflow.getSecondaryTargetFiles();
            // only perform export if there are stfs
            if (stfs.size() > 0) {
                doRegularExport = false;
                for (SecondaryTargetFile stf : stfs) {
                    ids.add(stf.getIdAsLong());
                }
                // register for export notification
                long exportBatchId = createExportBatchId(exportParams, p_workflow, p_userId, ids, p_taskId);
                ServerProxy.getPageManager().exportSecondaryTargetFiles(exportParams, ids, exportBatchId);
            } else {
                s_logger.warn("The file profile says to export STFs as a default, yet there are no STFs\r\n"
                        + "for this workflow either because an STF stage was bypassed in the path taken\r\n"
                        + "through the workflow, or because of workflow modification and deletion of an STF\r\n"
                        + "task. The primary target files will be exported instead.");
                doRegularExport = true;
                // create new export params since the original export params
                // were wrong
                exportParams = new ExportParameters(p_workflow);
            }
        }

        if (doRegularExport) {
            genericPages = p_workflow.getTargetPages();
            int size = genericPages == null ? 0 : genericPages.size();

            for (int i = 0; i < size; i++) {
                ids.add(((TargetPage) genericPages.get(i)).getIdAsLong());
            }

            // register for export notification
            long exportBatchId = createExportBatchId(exportParams, p_workflow, p_userId, ids, p_taskId);

            // Export Documentum Workflows
            TargetPage trgpage = (TargetPage) genericPages.get(0);
            SourcePage srcPage = trgpage.getSourcePage();
            String category = srcPage.getDataSourceType();
            // Are these documentum workflows.
            if (category.equalsIgnoreCase(DocumentumOperator.DCTM_CATEGORY)) {

                String eventFlowXml = srcPage.getRequest().getEventFlowXml();
                EventFlowXmlParser parser = new EventFlowXmlParser();
                parser.parse(eventFlowXml);

                try {
                    org.w3c.dom.Element msCategory = parser.getCategory(DocumentumOperator.DCTM_CATEGORY);
                    s_logger.debug("Starting to export a documentum workflow......");
                    String srcObjId = parser.getCategoryDaValue(msCategory, DocumentumOperator.DCTM_OBJECTID)[0];
                    String userId = parser.getCategoryDaValue(msCategory, DocumentumOperator.DCTM_USERID)[0];

                    // Copy a file object and return a new object id.
                    String newObjId = DocumentumOperator.getInstance().doCopy(userId, srcObjId, null);
                    if (newObjId == null) {
                        s_logger.error("Failed to execute copy operation for DCTM Object");
                        throw new Exception();
                    }
                    s_logger.debug("New DCTM file object Id :" + newObjId);
                    exportParams.setNewObjectId(newObjId);
                } catch (NoSuchElementException nsex) {
                    s_logger.debug("Not a documentum Workflow");
                }
            }

            boolean isTargetPage = true;
            ServerProxy.getPageManager().exportPage(exportParams, ids, isTargetPage, exportBatchId);
        }
    }

    // create the export batch id for this export process.
    private long createExportBatchId(ExportParameters p_exportParams, Workflow p_workflow, String p_userId,
            List ids, Long p_taskId) throws Exception {
        // just to get a "list" for the notification call below.
        List<Long> workflowIds = new ArrayList<Long>();
        workflowIds.add(p_workflow.getIdAsLong());

        String exportType = determineExportType(p_exportParams, p_workflow);

        long exportBatchId = ExportEventObserverHelper.notifyBeginExportTargetBatch(p_workflow.getJob(),
                ExportEventObserverHelper.getUser(p_userId), ids, workflowIds, p_taskId, exportType);

        return exportBatchId;
    }

    /*
     * Returns a TaskInfo that contains the estimated dates (mainly estimated
     * completion date) based on the user calendar. For a task that has not been
     * accepted yet, it'll associate a list of possible assignees with their
     * respective estimated completion date (as TaskAssignee object added to
     * TaskInfo).
     */
    private TaskInfo estimateDatesForDefaultPath(Date p_baseDate, Task p_task, WfTaskInfo p_wfTaskInfo,
            boolean p_acceptedOnly) throws Exception {
        TaskInfo ti = null;
        // the NodeInstance state (deactive, active, complete)
        int state = p_wfTaskInfo.getState();

        if (state == WorkflowConstants.STATE_INITIAL) {
            ti = getTaskInfobyRole(p_task, p_baseDate, p_wfTaskInfo);

        } else if (state == WorkflowConstants.TASK_ACTIVE) {
            String acceptor = p_task.getAcceptor();
            if (acceptor == null || !p_acceptedOnly) {
                ti = getTaskInfobyRole(p_task, p_baseDate, p_wfTaskInfo);
            } else {
                ti = getTaskInfoForUser(acceptor, p_task, p_wfTaskInfo, WorkflowConstants.TASK_ACCEPTED,
                        p_task.getEstimatedCompletionDate());
            }
        } else if (state == WorkflowConstants.STATE_COMPLETED) {
            if (!p_acceptedOnly) {
                ti = getTaskInfobyRole(p_task, p_baseDate, p_wfTaskInfo);
            } else {
                String user = p_task.getAcceptor();
                ti = getTaskInfoForUser(user, p_task, p_wfTaskInfo, state, p_task.getCompletedDate());
            }
        }

        return ti;
    }

    /*
     * Start the workflow and set the appropriate attributes for dispatch. The
     * arrayList value: first is task id, second is boolean value if need create
     * second target file.
     */
    @SuppressWarnings("deprecation")
    private ArrayList dispatchWorkflow(Workflow p_wfClone, Session p_session, Date p_startDate,
            TaskEmailInfo p_emailInfo) throws Exception {
        p_wfClone.setState(WF_DISPATCHED);
        p_wfClone.setDispatchedDate(p_startDate);
        if (WorkflowTypeConstants.TYPE_TRANSLATION.equals(p_wfClone.getWorkflowType())) {
            updatePageState(p_session, p_wfClone.getTargetPages(), PG_ACTIVE_JOB);
        }
        // '-1' indicates that the default path would be from the
        // beginning of the workflow (form START node)
        List wfTaskInfos = getWFServer().timeDurationsInDefaultPath(null, p_wfClone.getId(), -1);

        DefaultPathTasks dpt = updateDefaultPathTasks(DISPATCH_ACTION, p_startDate, wfTaskInfos, p_wfClone, -1,
                p_session);
        p_wfClone.setPlannedCompletionDate(p_wfClone.getEstimatedCompletionDate());
        updateCompletionFraction(p_wfClone, wfTaskInfos);

        // For sla report issue
        p_wfClone.updateTranslationCompletedDates();

        return startWorkflow(p_wfClone, dpt, p_emailInfo, p_session);
    }

    /*
     * Remove the reserved times for the active tasks of a workflow.
     */
    public static void removeReservedTimes(Object[] p_activeWfTaskInstances) {
        int size = p_activeWfTaskInstances == null ? -1 : p_activeWfTaskInstances.length;

        for (int i = 0; i < size; i++) {
            WorkflowTaskInstance wfti = (WorkflowTaskInstance) p_activeWfTaskInstances[i];

            removeReservedTime(wfti.getTaskId());
        }
    }

    /*
     * Find the task that's before the activity with the given id. @param
     * p_nodeId - The node id used for finding the node before it. @param
     * p_wfTaskInfos - A list of tasks in the default path. @param p_tasks - The
     * workflow tasks.
     */
    private Task findPreviousTask(long p_nodeId, List p_wfTaskInfos, Hashtable p_tasks) {
        Task previousTask = null;

        int size = p_wfTaskInfos.size();
        boolean found = false;
        for (int i = 0; (!found && i < size); i++) {
            WfTaskInfo wfTaskInfo = (WfTaskInfo) p_wfTaskInfos.get(i);
            found = wfTaskInfo.getId() == p_nodeId;
            if (!found) {
                previousTask = (Task) p_tasks.get(new Long(wfTaskInfo.getId()));
            }
        }

        return previousTask;
    }

    /*
     * Get the task info for a particular user.
     */
    private TaskInfo getTaskInfoForUser(String p_userId, Task p_task, WfTaskInfo p_wfTaskInfo, int p_state,
            Date p_completionDate) throws Exception {
        // if accepted, only show acceptor, estimated completion
        EmailInformation ei = ServerProxy.getUserManager().getEmailInformationForUser(p_userId);
        String userFullName = p_userId;
        if (ei != null) {
            userFullName = ei.getUserFullName();
        } else {
            s_logger.warn(
                    "User '" + UserUtil.getUserNameById(p_userId) + "' appears to have no email information.");
        }

        TaskInfo ti = new TaskInfo(p_wfTaskInfo.getId(), p_wfTaskInfo.getName(), p_state,
                p_task.getEstimatedAcceptanceDate(), p_task.getEstimatedCompletionDate(), p_task.getAcceptedDate(),
                p_completionDate, p_task.getType());
        ti.addTaskAssignee(p_userId, userFullName, p_task.getEstimatedCompletionDate());
        return ti;
    }

    /**
     * Get the task info based on a role associated with the task. The task info
     * will have a list of TaskAssignee objects representing each possible
     * assignee of the task.
     */
    private TaskInfo getTaskInfobyRole(Task p_task, Date p_baseDate, WfTaskInfo p_wfTaskInfo) throws Exception {
        Project project = p_task.getWorkflow().getJob().getL10nProfile().getProject();
        UserManager um = ServerProxy.getUserManager();
        String[] p_userIds = um.getUserIdsFromRoles(p_wfTaskInfo.getRoles(), project);
        List info = um.getEmailInformationForUsers(p_userIds);
        int size = 0;
        if (info != null)
            size = info.size();
        TaskInfo ti = new TaskInfo(p_wfTaskInfo.getId(), p_wfTaskInfo.getName(), p_wfTaskInfo.getState(),
                p_task.getEstimatedAcceptanceDate(), p_task.getEstimatedCompletionDate(), null,
                p_task.getEstimatedCompletionDate(), p_task.getType());

        for (int i = 0; i < size; i++) {
            EmailInformation ei = (EmailInformation) info.get(i);
            UserFluxCalendar cal = ServerProxy.getCalendarManager().findUserCalendarByOwner(ei.getUserId());

            Date completeBy = ServerProxy.getEventScheduler().determineDate(p_baseDate, cal,
                    p_wfTaskInfo.getTotalDuration());
            ti.addTaskAssignee(ei.getUserId(), ei.getUserFullName(), completeBy);
        }
        return ti;
    }

    /**
     * Update the default path tasks by setting their estimated acceptance, and
     * completion times. Also set the estimated completion time for the
     * workflow.
     */
    @SuppressWarnings("unchecked")
    private Map modifyWorkflowInstance(Date p_baseDate, String p_sessionId, WorkflowInstance p_wfInstance,
            TaskEmailInfo p_emailInfo, String p_companyId) throws Exception {
        Vector wfTaskInstances = p_wfInstance.getWorkflowInstanceTasks();
        int size = wfTaskInstances.size();
        DefaultPathTasks dpt = null;
        boolean isReassigned = false;
        HashMap<String, Object> map = new HashMap<String, Object>(1);
        for (int i = 0; (!isReassigned && i < size); i++) {
            WorkflowTaskInstance wft = (WorkflowTaskInstance) wfTaskInstances.get(i);
            boolean isActive = wft.getTaskState() == WorkflowConstants.TASK_ACTIVE;

            if (isActive) {
                // add the id of the active node to the map. This is used for
                // updating the default path (so it starts from the active
                // node).
                map.put(ACTIVE_NODE_ID, new Long(wft.getTaskId()));
            }

            isReassigned = isActive && wft.isReassigned();

            if (isReassigned) {
                dpt = new DefaultPathTasks();
                FluxCalendar calendar = ServerProxy.getCalendarManager().findDefaultCalendar(p_companyId);

                Date acceptBy = ServerProxy.getEventScheduler().determineDate(p_baseDate, calendar,
                        wft.getAcceptTime());

                Date completeBy = ServerProxy.getEventScheduler().determineDate(p_baseDate, calendar,
                        (wft.getAcceptTime() + wft.getCompletedTime()));

                dpt.addTaskInfo(new TaskInfo(wft.getTaskId(), wft.getActivityName(), wft.getTaskState(), acceptBy,
                        completeBy, null, null, wft.getActivity().getType()));

                // This is used to determine the base date in updating
                // the default path
                map.put(REASSIGNED_NODE_ID, Boolean.TRUE);
            }
        }
        map.putAll(getWFServer().modifyWorkflowInstance(p_sessionId, p_wfInstance, dpt, p_emailInfo));

        return map;
    }

    /**
     * Update the default path tasks by setting their estimated acceptance, and
     * completion times. Also set the estimated completion time for the
     * workflow. Note that if the p_completedTaskId is <= 0, the dates for all
     * of the nodes in the path would be udpated. However, a valid id for
     * p_completedTaskId means that only the task with the given id and the ones
     * after it will be updated.
     * 
     * @param p_baseDate
     *            - The based date used for finding dates based on durations.
     * @param p_wfTaskInfos
     *            - The tasks (as WfTaskInfo objects) in default path.
     * @param p_wfClone
     *            - The workflow that needs to be udpated.
     * @param p_completedTaskId
     *            - The id of the base task.
     * @param p_uow
     *            - Unit of work for persistance purposes.
     */
    private DefaultPathTasks updateDefaultPathTasks(int p_actionType, Date p_baseDate, List p_wfTaskInfos,
            Workflow p_wfClone, long p_completedTaskId, Session p_session) throws Exception {
        return updateDefaultPathTasks(p_actionType, p_baseDate, p_wfTaskInfos, p_wfClone, p_completedTaskId, false,
                p_baseDate, p_session);
    }

    /*
     * Update the default path tasks by setting their estimated acceptance, and
     * completion times. Also set the estimated completion time for the
     * workflow. Note that if the p_completedTaskId is <= 0, the dates for all
     * of the nodes in the path would be udpated. However, a valid id for
     * p_completedTaskId means that only the task with the given id and the ones
     * after it will be updated.
     * 
     * @param p_baseDate - The based date used for finding dates based on
     * durations. @param p_wfTaskInfos - The tasks (as WfTaskInfo objects) in
     * default path. @param p_wfClone - The workflow that needs to be udpated.
     * 
     * @param p_completedTaskId - The id of the base task. @param
     * p_isActiveTaskReassigned - True if the active task is reassigned. @param
     * p_originalBaseDate - Either the completion date of previous task or
     * simply the workflow's dispatch date. @param p_uow - Unit of work for
     * persistance purposes.
     */
    private DefaultPathTasks updateDefaultPathTasks(int p_actionType, Date p_baseDate, List p_wfTaskInfos,
            Workflow p_wfClone, long p_completedTaskId, boolean p_isActiveTaskReassigned, Date p_originalBaseDate,
            Session p_session) throws Exception {
        int size = p_wfTaskInfos.size();
        Hashtable ht = p_wfClone.getTasks();
        FluxCalendar defaultCalendar = ServerProxy.getCalendarManager()
                .findDefaultCalendar(String.valueOf(p_wfClone.getCompanyId()));
        UserFluxCalendar userCalendar = null;

        DefaultPathTasks dpt = new DefaultPathTasks();
        Date estimatedDate = p_isActiveTaskReassigned ? p_baseDate : p_originalBaseDate;

        boolean found = p_completedTaskId <= 0;
        // loop thru the tasks following the given start task for updating
        // the estimated dates for the tasks and possibly workflow.
        boolean firstTime = true;
        boolean activeTaskAccepted = false;
        for (int i = 0; i < size; i++) {
            WfTaskInfo wfTaskInfo = (WfTaskInfo) p_wfTaskInfos.get(i);
            if (!found) {
                found = p_completedTaskId == wfTaskInfo.getId();
            } else {
                TaskImpl task = (TaskImpl) ht.get(new Long(wfTaskInfo.getId()));

                if (task != null) {
                    Date acceptBy = ServerProxy.getEventScheduler().determineDate(estimatedDate, defaultCalendar,
                            wfTaskInfo.getAcceptanceDuration());

                    task.setEstimatedAcceptanceDate(acceptBy);

                    long duration = wfTaskInfo.getTotalDuration();

                    if (firstTime) {
                        firstTime = false;
                        if (activeTaskAccepted = (task.getAcceptor() != null
                                && task.getAcceptedDate().compareTo(p_originalBaseDate) >= 0)) {
                            removeReservedTime(task.getId(), task.getAcceptor());

                            userCalendar = ServerProxy.getCalendarManager()
                                    .findUserCalendarByOwner(task.getAcceptor());

                            if (p_isActiveTaskReassigned) {
                                // reset acceptor and acceptance date
                                task.setAcceptor(null);
                                task.setAcceptedDate(null);
                                task.setState(TaskImpl.STATE_ACTIVE);
                            } else {
                                estimatedDate = task.getAcceptedDate();
                                duration = wfTaskInfo.getCompletionDuration();
                            }
                        }
                        // possibly create proposed type reserved times
                        createProposedReservedTimes(p_actionType, estimatedDate, wfTaskInfo, task,
                                ReservedTime.TYPE_PROPOSED, p_isActiveTaskReassigned, p_session);
                    }

                    Date completeBy = ServerProxy.getEventScheduler().determineDate(estimatedDate,
                            userCalendar == null ? (BaseFluxCalendar) defaultCalendar
                                    : (BaseFluxCalendar) userCalendar,
                            duration);

                    task.setEstimatedCompletionDate(completeBy);

                    if (activeTaskAccepted && !p_isActiveTaskReassigned) {
                        addReservedTimeToUserCalendar(userCalendar,
                                buildReservedTimeName(wfTaskInfo.getName(), task), ReservedTime.TYPE_ACTIVITY,
                                task.getAcceptedDate(), completeBy, task.getIdAsLong(), p_session);
                    }

                    estimatedDate = completeBy;
                    activeTaskAccepted = false; // reset it
                    TaskInfo ti = new TaskInfo(task.getId(), task.getTaskName(), task.getState(), acceptBy,
                            completeBy, null, null, task.getType());

                    if (wfTaskInfo.getOverdueToPM() != 0) {
                        ti.setOverdueToPM(wfTaskInfo.getOverdueToPM());
                    }

                    if (wfTaskInfo.getOverdueToUser() != 0) {
                        ti.setOverdueToUser(wfTaskInfo.getOverdueToUser());
                    }

                    dpt.addTaskInfo(ti);
                    p_session.saveOrUpdate(task);
                }
            }

            // There's an edge case where all nodes could go thru on common
            // decision node. Therefore, let's start from the START node...
            if (i == (size - 1) && dpt.size() == 0 && !wfTaskInfo.followedByExitNode()) {
                i = -1;
                found = true;
            }
        }

        // For sla report issue.
        // No need to set estimated completion date for a workflow if
        // the advanced task happens to be the last one.
        // User can override the estimatedCompletionDate.
        if ((!estimatedDate.equals(p_baseDate)) && (!p_wfClone.isEstimatedCompletionDateOverrided())) {
            p_wfClone.setEstimatedCompletionDate(estimatedDate);
            sendNotification(p_wfClone);
        }

        // For sla report issue.
        p_wfClone.updateTranslationCompletedDates();

        return dpt;
    }

    /*
     * Remove the reserved time associated with the given task from the user's
     * calendar. The user is the finisher of the activity.
     */
    private void removeReservedTime(long p_taskId, String p_userId) {
        try {
            ServerProxy.getCalendarManager().removeScheduledActivity(p_taskId, p_userId);
        } catch (Exception e) {
            s_logger.error("Could not remove reserved times for task id " + p_taskId, e);
            throw new GeneralException(e);
        }
    }

    /*
     * Remove the reserved time associated with the given task from the user's
     * calendar. The user is the finisher of the activity.
     */
    private static void removeReservedTime(long p_taskId) {
        try {
            ServerProxy.getCalendarManager().removeScheduledActivities(p_taskId);
        } catch (Exception e) {
            s_logger.error("Failed to remove reserved times for task id " + p_taskId, e);
        }
    }

    /**
     * Update the structural changes that may have happened to the modified
     * workflow. Also update the durations and completion fraction of the
     * workflow. If the workflow is in dispatched state, all the estimated
     * acceptance/completion times starting from the active node should be
     * updated.
     * 
     * @param p_modifiedTasks
     *            - A hashtable of the modified tasks. The key is the Task id
     *            and the value is a TaskInfoBean (contians modified
     *            attributes).
     * @param p_wfInstance
     *            - The workflow instance that was modified.
     * @param p_wfTaskInfos
     *            - The list of tasks in the default path.
     * @param p_uow
     *            - Unit of work.
     * @param p_wfClone
     *            - A clones Workflow object (workflow to be modified).
     * @param p_addedAndDeleted
     *            - A map containing the added/deleted nodes.
     * @param p_baseDate
     *            - Current date that might be used as a base date.
     */
    private void updateWorkflowChanges(Hashtable p_modifiedTasks, WorkflowInstance p_wfInstance, List p_wfTaskInfos,
            Session p_session, Workflow p_wfClone, Map p_addedAndDeleted, Date p_baseDate) throws Exception {
        // persist all the structural changes
        persistWorkflowTaskInstanceChanges(p_addedAndDeleted, p_modifiedTasks, p_wfInstance, p_wfTaskInfos,
                p_session, p_wfClone);

        updateCompletionFraction(p_wfClone, p_wfTaskInfos);

        // only update the estimated dates for a dispatched workflow
        if (WF_DISPATCHED.equals(p_wfClone.getState())) {
            Long nodeId = (Long) p_addedAndDeleted.get(ACTIVE_NODE_ID);
            // check for reassignment of the active node
            Object reassigned = p_addedAndDeleted.get(REASSIGNED_NODE_ID);

            Task previousTask = findPreviousTask(nodeId.longValue(), p_wfTaskInfos, p_wfClone.getTasks());
            // if no reassignment is taken place, the based date should
            // be the completion date of the node before the active task
            // or the workflow dispatch date if the active node happens to
            // be the first activity node in the workflow.
            Date originalBaseDate = previousTask == null ? p_wfClone.getDispatchedDate()
                    : previousTask.getCompletedDate();

            if (originalBaseDate != null) {
                // now update the estimated acceptance/completion times
                updateDefaultPathTasks(MODIFY_ACTION, p_baseDate, p_wfTaskInfos, p_wfClone,
                        previousTask == null ? -1 : previousTask.getId(), reassigned != null, originalBaseDate,
                        p_session);
            } else {
                s_logger.error("Not updating default path tasks because originalBaseDate is null.");
            }
        }
    }

    /**
     * Removes the source corpus docs from the corpus and any applicable corpus
     * mappings, and removes the link between the source page and the corpus
     * doc. The assumption is that there is nothing to do with target pages
     * because they only get created in the corpus at TM population time, and
     * then it is too late to remove them.
     * 
     * @param p_job
     *            canceled Job
     */
    public static void cleanCorpus(Long p_jobId) {
        if (!Modules.isCorpusInstalled())
            return;

        try {
            Job job = ServerProxy.getJobHandler().getJobById(p_jobId.longValue());
            Iterator iter = job.getSourcePages().iterator();
            CorpusManagerWLRemote corpusMgr = ServerProxy.getCorpusManager();
            while (iter.hasNext()) {
                SourcePage sp = (SourcePage) iter.next();
                try {
                    corpusMgr.removeSourceCorpusDoc(sp);
                } catch (Exception e) {
                    s_logger.error("Could not remove corpus doc for page " + sp.getExternalPageId());
                }
            }
        } catch (Exception e) {
            s_logger.error("Could not remove corpus docs for job " + p_jobId, e);
        }
    }

    /*
     * Create 'proposed' reserved time for the assignees of the specified task.
     */
    private void createProposedReservedTimes(int p_actionType, Date p_baseDate, WfTaskInfo p_wfTaskInfo,
            TaskImpl p_taskClone, String p_type, boolean p_isActiveTaskReassigned, Session p_session)
            throws Exception {
        // if calendaring module is not installed, don't create reserved times.
        if (!Modules.isCalendaringInstalled()) {
            return;
        }

        if (p_actionType == ADVANCE_ACTION || p_actionType == DISPATCH_ACTION) {
            createReservedTimes(p_baseDate, p_wfTaskInfo, p_taskClone, p_type, p_session);
        } else if (p_actionType == MODIFY_ACTION && p_isActiveTaskReassigned) {
            // remove the old proposed/actual reserved times
            ServerProxy.getCalendarManager().removeScheduledActivities(p_taskClone.getId());

            createReservedTimes(p_baseDate, p_wfTaskInfo, p_taskClone, p_type, p_session);
        }
    }

    /*
     * Create reserved times based on the given type.
     */
    private void createReservedTimes(Date p_baseDate, WfTaskInfo p_wfTaskInfo, TaskImpl p_taskClone, String p_type,
            Session p_session) throws Exception {
        String[] userIds = ServerProxy.getUserManager().getUserIdsFromRoles(p_wfTaskInfo.getRoles(),
                p_taskClone.getWorkflow().getJob().getL10nProfile().getProject());
        for (int i = 0; i < userIds.length; i++) {
            UserFluxCalendar cal = ServerProxy.getCalendarManager().findUserCalendarByOwner(userIds[i]);
            Date completeBy = ServerProxy.getEventScheduler().determineDate(p_baseDate, cal,
                    p_wfTaskInfo.getTotalDuration());

            addReservedTimeToUserCalendar(cal, buildReservedTimeName(p_wfTaskInfo.getName(), p_taskClone), p_type,
                    p_baseDate, completeBy, p_taskClone.getIdAsLong(), p_session);
        }
    }

    /*
     * Build a user friendly name for an activity type reserved time.
     */
    private String buildReservedTimeName(String p_activityName, TaskImpl p_task) {
        StringBuffer taskName = new StringBuffer();
        taskName.append("[");
        taskName.append(p_activityName);
        taskName.append("]");
        taskName.append("[");
        taskName.append(p_task.getJobName());
        taskName.append("]");
        taskName.append("[");
        taskName.append(p_task.getProjectManagerId());
        taskName.append("]");
        return taskName.toString();
    }

    /*
     * Create a reserved time and add it to the specified user calendar.
     */
    private void addReservedTimeToUserCalendar(UserFluxCalendar p_userCalendar, String p_name, String p_type,
            Date p_startDate, Date p_completeBy, Long p_taskId, Session p_session) throws Exception {
        // if calendaring module is not installed, don't create reserved times.
        if (!Modules.isCalendaringInstalled()) {
            return;
        }

        // create new reserved time for user
        TimeZone tz = p_userCalendar.getTimeZone();
        Timestamp start = new Timestamp(tz);
        start.setDate(p_startDate);
        Timestamp end = new Timestamp(tz);
        end.setDate(p_completeBy);

        ReservedTime rt = new ReservedTime(p_name, p_type, start, start.getHour(), start.getMinute(), end,
                end.getHour(), end.getMinute(), null, p_taskId);

        // cloneCalendar.addReservedTime(rtClone);
        p_userCalendar.addReservedTime(rt);
        p_session.save(rt);

        // now add the buffer (if not set to zero)
        if (ReservedTime.TYPE_ACTIVITY.equals(p_type) && p_userCalendar.getActivityBuffer() > 0) {
            Timestamp bufferEnd = new Timestamp(tz);
            bufferEnd.setDate(end.getDate());
            bufferEnd.add(Timestamp.HOUR, p_userCalendar.getActivityBuffer());
            ReservedTime buffer = new ReservedTime(p_name, ReservedTime.TYPE_BUFFER, end, end.getHour(),
                    end.getMinute(), bufferEnd, bufferEnd.getHour(), bufferEnd.getMinute(), null, p_taskId);
            p_userCalendar.addReservedTime(buffer);
            p_session.save(buffer);
        }
        p_session.saveOrUpdate(p_userCalendar);
    }

    /*
     * Notify the PM and WFM if the estimated completion date exceeds planned
     * date. It's deprecated For sla report issue.
     */
    @SuppressWarnings("deprecation")
    private void sendNotification(Workflow p_workflow) {
        if (!m_systemNotificationEnabled) {
            return;
        }

        if (p_workflow.getPlannedCompletionDate() != null
                && p_workflow.getEstimatedCompletionDate().after(p_workflow.getPlannedCompletionDate())) {
            try {
                SystemConfiguration config = SystemConfiguration.getInstance();
                GlobalSightLocale targetLocale = p_workflow.getTargetLocale();
                Job job = p_workflow.getJob();
                String companyIdStr = String.valueOf(job.getCompanyId());

                WorkflowTemplateInfo wfti = job.getL10nProfile().getWorkflowTemplateInfo(targetLocale);
                Date estimatedCompletionDate = p_workflow.getEstimatedCompletionDate();
                Date plannedDate = p_workflow.getPlannedCompletionDate();
                List wfManagerIds = p_workflow.getWorkflowOwnerIdsByType(Permission.GROUP_WORKFLOW_MANAGER);

                // Job -> name (id)
                StringBuffer jobInfo = new StringBuffer();
                jobInfo.append(job.getJobName());
                jobInfo.append(" (");
                jobInfo.append(job.getId());
                jobInfo.append(")");

                String[] messageArguments = new String[5];
                messageArguments[0] = jobInfo.toString();
                messageArguments[4] = config.getStringParameter(SystemConfigParamNames.CAP_LOGIN_URL);

                EmailInformation emailInfo = null;
                Timestamp ts = null;
                int size = wfManagerIds.size();
                // notify workflow managers (if any)
                for (int i = 0; i < size; i++) {
                    emailInfo = ServerProxy.getUserManager()
                            .getEmailInformationForUser((String) wfManagerIds.get(i));

                    Locale userLocale = emailInfo.getEmailLocale();
                    ts = new Timestamp(emailInfo.getUserTimeZone());
                    ts.setDate(estimatedCompletionDate);
                    ts.setLocale(userLocale);
                    // workflow name (target locale)
                    messageArguments[1] = targetLocale.getDisplayName(userLocale);
                    // estimated completion date
                    messageArguments[2] = ts.toString();

                    ts.setDate(plannedDate);
                    // planned date
                    messageArguments[3] = ts.toString();

                    ServerProxy.getMailer().sendMail((EmailInformation) null, emailInfo,
                            MailerConstants.ESTIMATED_EXCEEDS_PLANNED_DATE, "estimatedExceedsPlanned",
                            messageArguments, companyIdStr);
                }

                if (wfti.notifyProjectManager()) {
                    emailInfo = ServerProxy.getUserManager().getEmailInformationForUser(wfti.getProjectManagerId());

                    Locale userLocale = emailInfo.getEmailLocale();
                    ts = new Timestamp(emailInfo.getUserTimeZone());
                    ts.setDate(estimatedCompletionDate);
                    ts.setLocale(userLocale);
                    // workflow name (target locale)
                    messageArguments[1] = targetLocale.getDisplayName(userLocale);
                    // estimated completion date
                    messageArguments[2] = ts.toString();

                    ts.setDate(plannedDate);
                    // planned date
                    messageArguments[3] = ts.toString();

                    ServerProxy.getMailer().sendMail((EmailInformation) null, emailInfo,
                            MailerConstants.ESTIMATED_EXCEEDS_PLANNED_DATE, "estimatedExceedsPlanned",
                            messageArguments, companyIdStr);
                }
            } catch (Exception e) {
                s_logger.error(
                        "Failed to notify Project Manager about workflow's estimated completion date exceeding planned date.",
                        e);
            }
        }
    }

    /**
     * Returns true if the user is allowed to cancel jobs and/or workflows.
     */
    private boolean allowedToCancelJobs(String p_userId) {
        boolean allowedToCancel = false;
        try {
            PermissionSet userPerms = Permission.getPermissionManager().getPermissionSetForUser(p_userId);
            allowedToCancel = userPerms.getPermissionFor(Permission.JOBS_DISCARD);
        } catch (Exception e) {
            s_logger.error("Failed to see if user " + UserUtil.getUserNameById(p_userId)
                    + " has permission to cancel workflows.", e);
            allowedToCancel = false;
        }
        return allowedToCancel;
    }

    /**
     * Returns true if the user is allowed to cancel workflows. Currently this
     * checks if the user can manage workflows.
     * (Permission.PROJECTS_MANAGE_WORKFLOWS)
     */
    private boolean allowedToCancelWorkflows(String p_userId) {
        boolean allowedToCancel = false;
        try {
            PermissionSet userPerms = Permission.getPermissionManager().getPermissionSetForUser(p_userId);
            allowedToCancel = userPerms.getPermissionFor(Permission.PROJECTS_MANAGE_WORKFLOWS);
        } catch (Exception e) {
            s_logger.error("Failed to see if user " + UserUtil.getUserNameById(p_userId)
                    + " has permission to cancel workflows.", e);
            allowedToCancel = false;
        }
        return allowedToCancel;
    }

    public static void deleteInProgressTmData(Job p_job) throws WorkflowManagerException {
        try {
            InProgressTmManager inProgressTmManager = LingServerProxy.getInProgressTmManager();
            inProgressTmManager.deleteSegments(p_job.getId());
        } catch (Exception e) {
            e.printStackTrace();
            throw new WorkflowManagerException(e);
        }
    }

    /**
     * Make sure no page is in UPDATING state for the given workflow.
     */
    private void validateStateOfPages(Workflow p_workflow) throws WorkflowManagerException {
        try {
            PageStateValidator.validateStateOfPagesInWorkflow(p_workflow);
        } catch (Exception e) {
            throw new WorkflowManagerException(e);
        }
    }

    /**
     * Make sure no page is in UPDATING state for the given job.
     */
    private void validateStateOfPagesInJob(Job p_job) throws WorkflowManagerException {
        try {
            PageStateValidator.validateStateOfPagesInJob(p_job);
        } catch (Exception e) {
            throw new WorkflowManagerException(e);
        }
    }

    public List<SkipActivityVo> getLocalActivity(String[] workflowIds)
            throws WorkflowManagerException, RemoteException {
        return getLocalActivity(workflowIds, null);
    }

    public List<SkipActivityVo> getLocalActivity(String[] workflowIds, Locale locale)
            throws WorkflowManagerException, RemoteException {
        List<SkipActivityVo> list = new ArrayList<SkipActivityVo>(workflowIds.length);
        SkipActivityVo vo = null;
        Long workflowId = null;

        for (String sworkflowId : workflowIds) {
            vo = new SkipActivityVo();
            workflowId = Long.parseLong(sworkflowId);
            Workflow workflow = getWorkflowById(workflowId);
            List<WorkflowTaskInstance> workflowInstances = ServerProxy.getWorkflowServer()
                    .getUnVisitedTasksForWorkflow(workflowId);
            List<Entry> entryList = getEntryList(workflowInstances);
            vo.setList(entryList);
            String targetLocale;
            if (locale == null) {
                targetLocale = workflow.getTargetLocale().getDisplayName();
            } else {
                targetLocale = workflow.getTargetLocale().getDisplayName(locale);
            }
            vo.setTargetLocale(targetLocale);
            vo.setWorkflowId(workflowId);
            list.add(vo);

        }
        return list;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.globalsight.everest.workflowmanager.WorkflowManager#setSkip(java.
     * util.List)
     */
    public void setSkip(List<Entry> list, String userId) throws RemoteException, WorkflowManagerException {
        try {
            ServerProxy.getWorkflowServer().setSkipActivity(list, userId, true);
        } catch (Exception e) {
            s_logger.error("Skip activity error", e);
        }
    }

    private List<Entry> getEntryList(List<WorkflowTaskInstance> workflowInstances) {
        List<Entry> list = new ArrayList<Entry>(workflowInstances.size() + 1);
        Entry<String, String> entry;

        for (WorkflowTaskInstance workflowTaskInstance : workflowInstances) {
            String value = workflowTaskInstance.getActivityName();
            String key = WorkflowConstants.END_NODE.equals(value) ? value
                    : workflowTaskInstance.getActivity().getDisplayName();
            entry = new Entry<String, String>(key, value);
            list.add(entry);
        }

        return list;
    }

    /**
     * Change workflow priority.
     * 
     */
    public void updatePriority(long p_workflowId, int p_priority) throws WorkflowManagerException, RemoteException {
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();

        try {
            Workflow wf = (Workflow) session.get(WorkflowImpl.class, new Long(p_workflowId));
            wf.setPriority(p_priority);
            session.saveOrUpdate(wf);
            tx.commit();
        } catch (Exception e) {
            String[] args = { String.valueOf(p_workflowId) };
            throw new WorkflowManagerException(WorkflowManagerException.MSG_FAILED_TO_UPDATE_PCD, args, e);
        }
    }

    public static TaskInstance getCurrentTask(long wfid) {
        List<TaskInstance> tasks = getTaskHistoryByWorkflowId(wfid);
        TaskInstance task = null;
        if (tasks.size() > 0) {
            task = tasks.get(tasks.size() - 1);

            if (task.getEnd() != null) {
                task = null;
            }
        }

        return task;
    }

    /**
     * Return tasks in sequence for specified workflow, skipped tasks are
     * ignored.
     * 
     * @param id
     *            -- workflow ID.
     * @return -- List<TaskInstance>
     */
    public static List<TaskInstance> getTaskHistoryByWorkflowId(long id) {
        JbpmContext ctx = WorkflowConfiguration.getInstance().getJbpmContext();
        try {
            Session dbSession = ctx.getSession();
            String hql = "from TaskInstance t where t.token.id = :wid order by t.id asc";
            Query query = dbSession.createQuery(hql);
            query.setParameter("wid", id);
            List<TaskInstance> tasks = query.list();
            if (tasks != null && tasks.size() > 0) {
                Set<Long> skipTaskIds = getSkippedTaskIds(tasks);
                for (Iterator<TaskInstance> it = tasks.iterator(); it.hasNext();) {
                    TaskInstance task = it.next();
                    if (skipTaskIds.contains(task.getId())) {
                        it.remove();
                    }
                }
            }

            return tasks;
        } finally {
            ctx.close();
        }
    }

    /**
     * Return task IDs that are skipped.
     * 
     * @param tasks
     * @return
     */
    private static Set<Long> getSkippedTaskIds(List<TaskInstance> tasks) {
        Set<Long> skippedTaskIds = new HashSet<Long>();

        Connection con = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            StringBuilder taskIds = new StringBuilder();
            for (TaskInstance task : tasks) {
                taskIds.append(task.getId()).append(",");
            }
            String taskIdsInStr = taskIds.substring(0, taskIds.length() - 1);

            String sql = "SELECT DISTINCT taskinstance_id FROM jbpm_gs_variable " + "WHERE taskinstance_id IN ("
                    + taskIdsInStr + ") " + "AND name = 'skip' ";

            con = DbUtil.getConnection();
            ps = con.prepareStatement(sql);
            rs = ps.executeQuery();
            while (rs != null && rs.next()) {
                skippedTaskIds.add(rs.getLong(1));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DbUtil.silentClose(rs);
            DbUtil.silentClose(ps);
            DbUtil.silentReturnConnection(con);
        }

        return skippedTaskIds;
    }

    /* Get email address of a user based on the user name */
    private EmailInformation getEmailInfo(String p_userName) throws Exception {
        return ServerProxy.getUserManager().getEmailInformationForUser(p_userName);
    }
}