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

com.centurylink.mdw.services.process.ProcessEngineDriver Maven / Gradle / Ivy

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

import com.centurylink.mdw.activity.ActivityException;
import com.centurylink.mdw.activity.types.StartActivity;
import com.centurylink.mdw.activity.types.SuspendableActivity;
import com.centurylink.mdw.app.ApplicationContext;
import com.centurylink.mdw.app.WorkflowException;
import com.centurylink.mdw.cache.asset.PackageCache;
import com.centurylink.mdw.common.MdwException;
import com.centurylink.mdw.config.PropertyManager;
import com.centurylink.mdw.constant.OwnerType;
import com.centurylink.mdw.constant.PropertyNames;
import com.centurylink.mdw.constant.VariableConstants;
import com.centurylink.mdw.constant.WorkAttributeConstant;
import com.centurylink.mdw.dataaccess.DataAccessException;
import com.centurylink.mdw.model.event.EventType;
import com.centurylink.mdw.model.event.InternalEvent;
import com.centurylink.mdw.model.listener.Listener;
import com.centurylink.mdw.model.monitor.ScheduledEvent;
import com.centurylink.mdw.model.request.Response;
import com.centurylink.mdw.model.variable.Document;
import com.centurylink.mdw.model.variable.DocumentReference;
import com.centurylink.mdw.model.variable.Variable;
import com.centurylink.mdw.model.variable.VariableInstance;
import com.centurylink.mdw.model.workflow.Package;
import com.centurylink.mdw.model.workflow.Process;
import com.centurylink.mdw.model.workflow.*;
import com.centurylink.mdw.model.workflow.WorkStatus.InternalLogMessage;
import com.centurylink.mdw.service.data.process.EngineDataAccess;
import com.centurylink.mdw.service.data.process.EngineDataAccessCache;
import com.centurylink.mdw.service.data.process.EngineDataAccessDB;
import com.centurylink.mdw.service.data.process.ProcessCache;
import com.centurylink.mdw.services.ProcessException;
import com.centurylink.mdw.services.messenger.InternalMessenger;
import com.centurylink.mdw.services.messenger.MessengerFactory;
import com.centurylink.mdw.util.TransactionUtil;
import com.centurylink.mdw.util.TransactionWrapper;
import com.centurylink.mdw.util.log.LoggerUtil;
import com.centurylink.mdw.util.log.StandardLogger;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;

import java.io.IOException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ProcessEngineDriver {

    public static final int DEFAULT_PERFORMANCE_LEVEL = 3;

    private static Integer defaultPerformanceLevelRegular;
    private static Integer defaultPerformanceLevelService;
    private static String useTransactionOnExecute = "not_loaded";

    private static StandardLogger logger = LoggerUtil.getStandardLogger();
    private EngineLogger engineLogger;

    private Exception lastException;    // used by service process to throw exception back to event handler
    private Long mainProcessInstanceId;    // used by service process to remember main process instance ID which caller may query
    private int eventConsumeRetrySleep;

    public ProcessEngineDriver() {
        if (defaultPerformanceLevelRegular == null)
            loadDefaultPerformanceLevel();
        eventConsumeRetrySleep = PropertyManager.getIntegerProperty(PropertyNames.MDW_INTERNAL_EVENT_CONSUME_RETRY_SLEEP, 2);
        engineLogger = new EngineLogger(logger, DEFAULT_PERFORMANCE_LEVEL);  // perf level to be updated in processEvents()
    }

    /**
     * Checks whether the process instance has been canceled or completed
     * @return false if process has been canceled or completed
     */
    private boolean processInstanceIsActive(ProcessInstance processInst) {
        Integer status = processInst.getStatusCode();
        if (WorkStatus.STATUS_CANCELLED.equals(status)) {
            logger.info("ProcessInstance has been cancelled. ProcessInstanceId = " + processInst.getId());
            return false;
        } else if (WorkStatus.STATUS_COMPLETED.equals(status)) {
            logger.info("ProcessInstance has been completed. ProcessInstanceId = " + processInst.getId());
            return false;
        } else return true;
    }

    private String[] getStackTrace() {
        StackTraceElement[] stack = (new Throwable()).getStackTrace();
        String[] ret = new String[stack.length];
        for (int i=0; i 0) {
                            if (!messageDoc.isProcess()) {
                                secondaryOwnerId = secondOwnerId;
                                secondaryOwnerType = OwnerType.ACTIVITY_INSTANCE;
                            }
                            // Update the Process Variable "exception" with the exception handler's triggering Activity exception
                            if (process.getVariable("exception") != null &&
                                    messageDoc.getSecondaryOwnerId() != null && messageDoc.getSecondaryOwnerId() > 0) {
                                VariableInstance exceptionVar = processInstance.getVariable("exception");
                                if (exceptionVar == null)
                                    engine.createVariableInstance(processInstance, "exception", new DocumentReference(messageDoc.getSecondaryOwnerId()));
                                else
                                    engine.updateVariableInstance(exceptionVar, getPackage(process));
                            }
                        }
                        else {
                            if (eventType.equals(EventType.ERROR)) {
                                logger.warn("Creating fallout embedded process without activity instance as secondary owner");
                                logger.warn("--- completion code " + messageDoc.getCompletionCode());
                                logger.warn("--- trans inst ID " + messageDoc.getTransitionInstanceId());
                                logger.warn("--- work ID " + messageDoc.getWorkId());
                                String[] stack = this.getStackTrace();
                                for (int i = 0; i < stack.length; i++) {
                                    logger.warn("--- stack " + i + ": " + stack[i]);
                                }
                            }
                            messageDoc.setSecondaryOwnerType(null);
                        }
                    }
                    else {
                        messageDoc.setSecondaryOwnerType(null);
                    }
                    String ownerType = OwnerType.MAIN_PROCESS_INSTANCE;
                    ProcessInstance procInst = engine.createProcessInstance(
                            embeddedHandlerProc.getId(), ownerType, processInstance.getId(),
                            secondaryOwnerType, secondaryOwnerId, processInstance.getMasterRequestId(), null);
                    engine.startProcessInstance(procInst, 0);
                }
            }
            else {
                // try package-level handler
                Process packageHandlerProc = null;
                // TODO ugly test to avoid dup errors for service subproc invokes
                if (messageDoc.getStatusMessage() == null || !messageDoc.getStatusMessage().startsWith("com.centurylink.mdw.activity.ActivityException: At least one subprocess is not completed\n"))
                    packageHandlerProc = getPackageHandler(processInstance, eventType);
                if (packageHandlerProc != null) {
                    Map params = new HashMap<>();
                    Variable exVar = packageHandlerProc.getVariable("exception");
                    if (exVar == null || !exVar.isInput()) {
                        logger.warn("Handler proc " + packageHandlerProc.getQualifiedLabel() + " does not declare input var: 'exception'");
                    }
                    else {
                        params.put("exception", new DocumentReference(messageDoc.getSecondaryOwnerId()).toString());
                    }
                    if (packageHandlerProc.isService()) {
                        invoke(packageHandlerProc.getId(), OwnerType.ERROR,
                                messageDoc.getSecondaryOwnerId(),
                                originatingInstance.getMasterRequestId(), null, params, null, 0,
                                messageDoc.isProcess() ? OwnerType.PROCESS_INSTANCE : OwnerType.ACTIVITY_INSTANCE, messageDoc.getWorkInstanceId(), null);
                    }
                    else {
                        start(packageHandlerProc.getId(), originatingInstance.getMasterRequestId(), OwnerType.ERROR,
                                messageDoc.getSecondaryOwnerId(), params, messageDoc.isProcess() ? OwnerType.PROCESS_INSTANCE : OwnerType.ACTIVITY_INSTANCE, messageDoc.getWorkInstanceId(), null);
                    }
                }
                else if (eventType.equals(EventType.ABORT)) {
                    // abort the root process instance
                    InternalEvent event = InternalEvent.createProcessAbortMessage(processInstance.getId());
                    engine.abortProcessInstance(event);
                }
                else {
                    logger.info("Transition has not been defined for event of type " + eventType);
                }
            }
        }
        catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            throw new ProcessException(ex.getMessage());
        }
    }

    /**
     * Finds the relevant package handler for a master process instance.
     */
    private Process getPackageHandler(ProcessInstance masterInstance, Integer eventType) throws ProcessException {
        Process process = getProcessDefinition(masterInstance);
        Process handler = getPackageHandler(process.getPackageName(), eventType);
        if (handler != null && handler.getName().equals(process.getName())) {
            logger.warn("Package handler recursion is not allowed. "
                    + "Define an embedded handler in package handler: " + handler.getLabel());
        }
        return handler;
    }

    private Process getPackageHandler(String packageName, Integer eventType) {
        String handlerProcName = EventType.getHandlerName(eventType);
        if (handlerProcName != null) {
            Process packageHandlerProc = null;
            if (PackageCache.getPackage(packageName) != null) {
                packageHandlerProc = ProcessCache.getProcess(packageName + "/" + handlerProcName);
                if (packageHandlerProc == null)  // try lower case
                    packageHandlerProc = ProcessCache.getProcess(packageName + "/" + handlerProcName.toLowerCase());
            }
            if (packageHandlerProc == null && packageName.indexOf('.') > 0) {
                packageHandlerProc = getPackageHandler(packageName.substring(0, packageName.lastIndexOf('.')), eventType);
            }
            return packageHandlerProc;
        }
        return null;
    }

    private void retryActivityStartWhenInstanceExists(ProcessExecutor engine,
            InternalEvent event, ProcessInstance pi) {
        int count = event.getDeliveryCount();
        String av = PropertyManager.getProperty(PropertyNames.MDW_ACTIVITY_ACTIVE_MAX_RETRY);
        int max_retry = 5;
        if (av!=null) {
            // delay some seconds to avoid race condition
            try {
                max_retry = Integer.parseInt(av);
                if (max_retry<0) max_retry = 0;
                else if (max_retry>20) max_retry = 20;
            } catch (Exception e) {
            }
        }
        int initial_delay = 5;
        if (count < max_retry) {
            int delayInSeconds = initial_delay;
            count++;
            event.setDeliveryCount(count);
            for (int i = 0; i < count; i++)
                delayInSeconds = delayInSeconds * 2;
            String tag = EngineLogger.logtag(pi.getProcessId(), pi.getId(), event.getWorkId(), 0L);
            String msg = "Active instance exists, retry in " + delayInSeconds + " seconds";
            engineLogger.info(tag,pi.getId(), null, msg);
            try {
                String msgid = ScheduledEvent.INTERNAL_EVENT_PREFIX + pi.getId() + "start" + event.getWorkId();
                engine.sendDelayedInternalEvent(event, delayInSeconds, msgid, false);
            } catch (MdwException e) {
                tag = EngineLogger.logtag(pi.getProcessId(), pi.getId(), event.getWorkId(), 0L);
                msg = "Failed to send retry jms message";
                engineLogger.error(tag, pi.getId(), null, msg, new Exception(msg));
            }
        } else {
            // only log exception w/o creating a fall out task. Do we need to?
            String tag = EngineLogger.logtag(pi.getProcessId(),pi.getId(),event.getWorkId(),0L);
            String msg = "Active instance exists - fail after " + max_retry + " retries";
            engineLogger.error(tag, pi.getId(), null, msg, new Exception(msg));
        }
    }

    private void resumeActivity(ProcessExecutor engine, InternalEvent event,
            ProcessInstance procInst, boolean resumeOnHold) {
        Long actInstId = event.getWorkInstanceId();
        ActivityRuntime ar = null;
        try {
            ar = engine.resumeActivityPrepare(procInst, event, resumeOnHold);
            if (ar.getStartCase()!=ActivityRuntime.RESUMECASE_NORMAL) return;
            boolean finished;
            if (ar.getActivity() instanceof SuspendableActivity) {
                if ("true".equalsIgnoreCase(useTransactionOnExecute)) {
                    finished = engine.resumeActivityExecute(ar, event, resumeOnHold);
                } else {
                    if (resumeOnHold) finished = ((SuspendableActivity)ar.activity).resumeWaiting(event);
                    else finished = ((SuspendableActivity)ar.activity).resume(event);
                }
            } else finished = true;
            engine.resumeActivityFinish(ar, finished, event, resumeOnHold);
        } catch (Exception e) {
            logger.error("Resume failed", e);
            lastException = e;
            engine.resumeActivityException(procInst, actInstId, ar == null ? null : ar.getActivity(), e);
        }
    }

    private void executeActivity(ProcessExecutor engine, InternalEvent event, ProcessInstance procInst) {
        ActivityRuntime ar = null;
        try {
            // Step 1. check, create and prepare activity instance
            ar = engine.prepareActivityInstance(event, procInst);
            switch (ar.getStartCase()) {
            case ActivityRuntime.STARTCASE_PROCESS_TERMINATED:
                logger.info("ProcessInstance is already terminated. ProcessInstanceId = "
                        + ar.getProcessInstance().getId());
                break;
            case ActivityRuntime.STARTCASE_ERROR_IN_PREPARE:
                // error already reported
                break;
            case ActivityRuntime.STARTCASE_INSTANCE_EXIST:
                retryActivityStartWhenInstanceExists(engine, event, ar.getProcessInstance());
                break;
            case ActivityRuntime.STARTCASE_SYNCH_COMPLETE:
                String msg = "The synchronization activity is already completed";
                engineLogger.info(ar.getProcessInstance().getProcessId(), ar.getProcessInstance().getId(),
                        ar.getActivityInstance().getActivityId(), ar.getActivityInstance().getId(), msg);
                break;
            case ActivityRuntime.STARTCASE_SYNCH_HOLD:
                String message = "The synchronization activity is on-hold - ignore incoming transition";
                engineLogger.info(ar.getProcessInstance().getProcessId(), ar.getProcessInstance().getId(),
                        ar.getActivityInstance().getActivityId(), ar.getActivityInstance().getId(), message);
                break;
            case ActivityRuntime.STARTCASE_SYNCH_WAITING:
                event.setWorkInstanceId(ar.getActivityInstance().getId());
                event.setEventType(EventType.RESUME);
                resumeActivity(engine, event, procInst, false);
                break;
            case ActivityRuntime.STARTCASE_RESUME_WAITING:
                event.setWorkInstanceId(ar.getActivityInstance().getId());
                event.setEventType(EventType.RESUME);
                resumeActivity(engine, event, procInst, true);
                break;
            case ActivityRuntime.STARTCASE_NORMAL:
            default:
                // Step 2. invoke execute() of the activity
                String resCode = ar.activity.notifyMonitors(InternalLogMessage.ACTIVITY_EXECUTE);
                if (resCode == null || resCode.equals("(EXECUTE_ACTIVITY)")) {
                    if (ar.getActivity() instanceof StartActivity) {
                        engine.setProcessInstanceStartTime(ar.getProcessInstance().getId());
                    }
                    // proceed with normal activity execution
                    if ("not_loaded".equals(useTransactionOnExecute)) {
                        useTransactionOnExecute = PropertyManager.getProperty(PropertyNames.MDW_ENGINE_USE_TRANSACTION);
                    }
                    if ("true".equalsIgnoreCase(useTransactionOnExecute))
                        engine.executeActivityInstance(ar.getActivity());
                    else {
                        if (ar.getActivity().getTimer() != null)
                            ar.getActivity().executeTimed(engine);
                        else
                            ar.getActivity().execute(engine);
                    }
                }
                else {
                    // bypass execution due to monitor
                    if (!"null".equals(resCode))
                        ar.getActivity().setReturnCode(resCode);
                    if (ar.getActivity() instanceof SuspendableActivity) {
                        engine.finishActivityInstance(ar.getActivity(), ar.getProcessInstance(), ar.getActivityInstance(), event, true);
                        return;
                    }
                }
                // Step 3. finish activity (complete, suspend or others) or process
                engine.finishActivityInstance(ar.getActivity(),
                        ar.getProcessInstance(), ar.getActivityInstance(), event, false);
                break;

            }
        } catch (Exception ex) {
            lastException = ex;
            engine.failActivityInstance(event, procInst,
                    event.getWorkId(),        // act ID
                    (ar==null || ar.getActivityInstance()==null) ? 0L : ar.getActivityInstance().getId(),
                    ar==null?null:ar.getActivity(), ex);
        }
    }

    private void handleDelay(ProcessExecutor engine, InternalEvent event,
            ProcessInstance processInstance) throws Exception {
        if (!processInstanceIsActive(processInstance)) return;

        ActivityInstance ai = null;
        if (OwnerType.SLA.equals(event.getSecondaryOwnerType())) {
            // new way to handle SLA through JMS message delay rather than timer demon
            Long actInstId = event.getWorkInstanceId();
            ai = engine.getActivityInstance(actInstId);
            if (ai.getStatusCode() != WorkStatus.STATUS_WAITING) {
                // ignore the message when the status is not waiting.
                return;
            }
            String msg = "Activity in waiting status times out";
            engineLogger.info(processInstance.getProcessId(), processInstance.getId(), ai.getActivityId(), actInstId, msg);

            Integer actInstStatus = WorkStatus.STATUS_CANCELLED;
            Process procdef = getProcessDefinition(processInstance);
            Activity activity = procdef.getActivity(ai.getActivityId());
            String status = activity.getAttribute(WorkAttributeConstant.STATUS_AFTER_TIMEOUT);
            if (status!=null) {
                for (int i=0; i workTransitionVOs = processVO.getTransitions(event.getWorkId(),
                EventType.DELAY, event.getCompletionCode());
        if (workTransitionVOs != null && !workTransitionVOs.isEmpty()) {
            engine.createTransitionInstances(processInstance, workTransitionVOs,
                    event.isProcess()?null:event.getWorkInstanceId());
        } else {
            if (ai != null) {
                // This creates the exception document used by Package level Delay handler
                event.setSecondaryOwnerType(OwnerType.ERROR);
                event.setSecondaryOwnerId(engine.createActivityExceptionDocument(processInstance, ai, null, new ActivityException("Activity timeout")).getDocumentId());
            }
            handleInheritedEvent(engine, processInstance, processVO, event, EventType.DELAY);
        }
    }

    private ProcessInstance findProcessInstance(ProcessExecutor engine,
            InternalEvent event) throws ProcessException, DataAccessException {
        Long procInstId;
        if (event.isProcess()) procInstId = event.getWorkInstanceId();    // can be null or populated for process start message
        else procInstId = event.getOwnerId();
        if (procInstId==null) return null;        // must be process start event
        return engine.getProcessInstance(procInstId);
    }

    /**
     * Executes the flow
     */
    public void processEvents(String msgid, String textMessage) {
        try {
            if (logger.isDebugEnabled())
                logger.debug("executeFlow: " + textMessage);
            InternalEvent event = new InternalEvent(textMessage);

            // a. find the process instance (looking for memory only first, then regular)
            Long procInstId;
            ProcessInstance procInst;
            if (event.isProcess()) {
                if (event.getEventType().equals(EventType.FINISH)) {
                    procInstId = null;    // not needed, and for remote process returns, will not be able to find it
                } else {
                    procInstId = event.getWorkInstanceId();
                }
            } else {
                procInstId = event.getOwnerId();
            }
            if (procInstId != null) {
                EngineDataAccess temp_edao = EngineDataAccessCache.getInstance(false, 9);
                procInst = temp_edao.getProcessInstance(procInstId);
                if (procInst == null) {
                    TransactionWrapper transaction = null;
                    EngineDataAccessDB edbao = new EngineDataAccessDB();
                    try {
                        transaction = edbao.startTransaction();
                        procInst = edbao.getProcessInstance(procInstId);
                    }
                    catch (SQLException ex) {
                        if (("Failed to load process instance: " + procInstId).equals(ex.getMessage())) {
                            if (ApplicationContext.isDevelopment()) {
                                logger.error("Unable to load process instance id=" + procInstId + ".  Was this instance deleted?");
                                return;
                            } else {
                                throw ex;
                            }
                        }
                        throw ex;
                    }
                    finally {
                        edbao.stopTransaction(transaction);
                    }
                }
            } else {
                procInst = null;
            }

            // b. now determine performance level here
            int perfLevel;
            if (procInst == null) {        // must be process start message
                if (event.isProcess() && event.getEventType().equals(EventType.START)) {
                    Process procdef = getProcessDefinition(event.getWorkId());
                    if (procdef == null)
                        throw new WorkflowException("Unable to load process id " + event.getWorkId() + " for " + msgid);
                    perfLevel = procdef.getPerformanceLevel();
                } else {
                    perfLevel = 0;
                }
            } else {
                Process procdef = getProcessDefinition(procInst.getProcessId());
                if (procdef == null) {
                    String msg = "Unable to load process id " + procInst.getProcessId() + " (instance id=" + procInst.getId() + ") for " + msgid;
                    if (ApplicationContext.isDevelopment()) {
                        // referential integrity not always enforced for VCS assets
                        if (PropertyManager.getBooleanProperty(PropertyNames.MDW_INTERNAL_EVENT_DEV_CLEANUP, true)) {
                            logger.error(msg + " (event will be deleted)");
                            EngineDataAccess edao = EngineDataAccessCache.getInstance(false, defaultPerformanceLevelRegular);
                            InternalMessenger msgBroker = MessengerFactory.newInternalMessenger();
                            ProcessExecutor engine = new ProcessExecutor(edao, msgBroker, false);
                            engine.deleteInternalEvent(msgid);
                            return;
                        }
                        else {
                            logger.error(msg);
                        }
                    }
                    else {
                        throw new WorkflowException(msg);
                    }
                }
                perfLevel = procdef.getPerformanceLevel();
            }
            if (perfLevel <= 0)
                perfLevel = defaultPerformanceLevelRegular;
            engineLogger.setPerformanceLevel(perfLevel);

            // c. create engine
            EngineDataAccess edao = EngineDataAccessCache.getInstance(false, perfLevel);
            InternalMessenger messenger = MessengerFactory.newInternalMessenger();
            ProcessExecutor engine = new ProcessExecutor(edao, messenger, false);
            if (msgid != null) {
                boolean success = engine.deleteInternalEvent(msgid);
                if (!success) {
                    // retry two times to get around race condition (internal event inserted
                    // into EVENT_INSTANCE table but not committed yet)
                    int retries = 0;
                    while (!success && retries < 2) {
                        logger.debug("Failed to consume internal event " + msgid + " - retry in 2 seconds");
                        Thread.sleep(eventConsumeRetrySleep * 1000L);
                        retries++;
                        success = engine.deleteInternalEvent(msgid);
                    }
                }
                if (!success) {
                    logger.warn("Fail to consume internal event " + msgid + " - assuming the event is already processed by another thread");
                    return;    // already processed;
                }
            }
            if (perfLevel >= 9)
                messenger.setCacheOption(InternalMessenger.CACHE_ONLY);
            else if (perfLevel >= 3)
                messenger.setCacheOption(InternalMessenger.CACHE_ON);
            // d. process event(s)
            if (perfLevel >= 3) {
                // TODO cache proc inst
                processEvent(engine, event, procInst);
                while ((event = messenger.getNextMessageFromQueue(engine)) != null) {
                    procInst = this.findProcessInstance(engine, event);
                    processEvent(engine, event, procInst);
                }
            } else {
                processEvent(engine, event, procInst);
            }
        } catch (XmlException e) {
            logger.error("Unparsable xml message: " + textMessage, e);
        } catch (Throwable ex) {
            logger.error(ex.getMessage(), ex);
        }
    }

    private void processEvent(ProcessExecutor engine, InternalEvent event, ProcessInstance procInst) {
        try {
            if (event.isProcess()) {
                if (event.getEventType().equals(EventType.START)) {
                    if (procInst == null) {
                        procInst = engine.createProcessInstance(
                                event.getWorkId(), event.getOwnerType(), event.getOwnerId(),
                                event.getSecondaryOwnerType(), event.getSecondaryOwnerId(),
                                event.getMasterRequestId(), new HashMap<>(event.getParameters()));
                    }
                    engine.startProcessInstance(procInst, 0);
                } else if (event.getEventType().equals(EventType.FINISH)) {
                    // do not check status - process is already in completed status
                    engine.handleProcessFinish(event);
                } else if (event.getEventType().equals(EventType.ABORT)) {
                    if (!processInstanceIsActive(procInst)) return;
                    engine.abortProcessInstance(event);
                } else if (event.getEventType().equals(EventType.DELAY)) {
                    if (!processInstanceIsActive(procInst)) return;
                    event.setSecondaryOwnerType(OwnerType.ERROR);
                    event.setSecondaryOwnerId(engine.createProcessExceptionDocument(procInst, new ProcessException("Process SLA timeout")).getDocumentId());
                    handleInheritedEvent(engine, procInst, getProcessDefinition(procInst), event, EventType.DELAY);
                }
            } else {
                if (!processInstanceIsActive(procInst))
                    return;
                if (event.getEventType().equals(EventType.START)) {
                    this.executeActivity(engine, event, procInst);
                } else if (event.getEventType().equals(EventType.RESUME)) {
                    resumeActivity(engine, event, procInst, false);
                } else if (event.getEventType().equals(EventType.DELAY)) {
                    handleDelay(engine, event, procInst);
                } else {
                    Process process = getProcessDefinition(procInst);
                    procInst.setProcessName(process.getName());
                    List transition = process.getTransitions(event.getWorkId(),
                            event.getEventType(), event.getCompletionCode());
                    if (transition != null && !transition.isEmpty()) {
                        engine.createTransitionInstances(procInst, transition,
                                event.isProcess() ? null : event.getWorkInstanceId());
                    } else if (event.getEventType().equals(EventType.FINISH)) {
                            // do nothing
                    } else if (event.getEventType().equals(EventType.ERROR)) {
                        if (!process.isEmbeddedExceptionHandler()) {
                            engine.updateProcessInstanceStatus(procInst.getId(), WorkStatus.STATUS_WAITING);
                            handleInheritedEvent(engine, procInst, process, event, EventType.ERROR);
                        } else {
                            logger.info("Error occurred inside an error handler!!!");
                        }
                    } else if (event.getEventType().equals(EventType.CORRECT)) {
                        handleInheritedEvent(engine, procInst, process, event, EventType.CORRECT);
                    } else if (event.getEventType().equals(EventType.ABORT)) {
                        handleInheritedEvent(engine, procInst, process, event, EventType.ABORT);
                    }
                }
            }
        } catch (Throwable ex) {
            logger.error("Fatal exception in executeFlow - cannot generate fallout task", ex);
        }
        finally {
            // Check for any non-stopped transactions (i.e. locked "for update" document rows)
            TransactionUtil transUtil = TransactionUtil.getInstance();
            if (transUtil.getTransaction() != null) {
                try {
                    TransactionWrapper transaction = new TransactionWrapper();
                    engine.stopTransaction(transaction);  // This will stop transaction and close DB connection
                }
                catch (Throwable ex) {
                    logger.error("Fatal exception stopping transaction - cannot close DB connection and stop transaction", ex);
                }
            }
        }
    }

    private void addDocumentToCache(ProcessExecutor engine, Long docId, String variableType, String docType, Object docObj, Package pkg) {
        if (docObj != null) {
            if (docId == 0L) {
                try {
                    engine.createDocument(variableType, OwnerType.LISTENER_REQUEST, 0L, docObj, pkg);
                } catch (DataAccessException e) {
                    // should never happen, as this is cache only
                }
            } else {
                Document doc = new Document();
                doc.setObject(docObj);
                doc.setId(docId);
                doc.setType(docType);
                doc.setVariableType(variableType);
                engine.addDocumentToCache(doc);
            }
        }
    }

    /**
     * @deprecated use {@link #invoke(Long, String, Long, String, Object, Map, String, Map)}
     */
    @Deprecated
    public Response invokeService(Long processId, String ownerType,
            Long ownerId, String masterRequestId, String masterRequest,
            Map parameters, String responseVarName, Map headers) throws Exception {
        return invoke(processId, ownerType, ownerId, masterRequestId, masterRequest, new HashMap<>(parameters),
                responseVarName, 0, null, null, headers);
    }

    /**
     * Invoke a service process synchronously.
     * @return the service response
     */
    public Response invoke(Long processId, String ownerType,
            Long ownerId, String masterRequestId, Object masterRequest,
            Map values, String responseVarName, Map headers) throws Exception {
        return invoke(processId, ownerType, ownerId, masterRequestId, masterRequest, values, responseVarName, 0, null, null, headers);
    }

    /**
     * @deprecated user {@link #invoke(Long, String, Long, String, Object, Map, String, int, String, Long, Map)}
     */
    @Deprecated
    public Response invokeService(Long processId, String ownerType,
            Long ownerId, String masterRequestId, Object masterRequest,
            Map parameters, String responseVarName, int performanceLevel,
            String secondaryOwnerType, Long secondaryOwnerId, Map headers)
            throws ProcessException, DataAccessException {
        return invoke(processId, ownerType, ownerId, masterRequestId, masterRequest, new HashMap<>(parameters),
                responseVarName, performanceLevel, secondaryOwnerType, secondaryOwnerId, headers);
    }

    /**
     * Invoke a service (synchronous) process. The method cannot be used
     * if the process is not a service process.
     * Performance level:
     *    0 - to be determined by global property or process attribute, which will set the level to one of the following
     *    9 - all cache options CACHE_ONLY
     *    5 - CACHE_OFF for activity/transition, CACHE_ONLY for variable/document, CACHE_ON for internal event queue
     *    3 - CACHE_OFF for activity/transition, CACHE_ON for variable/document, CACHE_ON for internal event queue
     *    1 - CACHE_OFF for activity/transition, CACHE_OFF for variable/document, CACHE_OFF for internal event queue
     *
     * @param processId ID of the process definition
     * @param ownerType Owner of the Service Process - DOCUMENT or PROCESS_INSTANCE
     * @param ownerId owner ID of the request event
     * @param masterRequestId master request ID
     * @param masterRequest content of the request event
     * @param values Input parameter bindings for the process instance to be created
     * @param responseVarName the name of the variable where the response is to be obtained.
     *         If you leave this null, the response will be taken from "response"
     *         if one is defined, and null otherwise
     * @param performanceLevel the performance level to be used to run the process.
     *         When a 0 is passed in, the default performance level for service processes will be used,
     *         unless the performance level attribute is configured for the process.
     * @return response message, which is obtained from the variable named ie responseVarName
     *      of the process.
     */
    public Response invoke(Long processId, String ownerType,
            Long ownerId, String masterRequestId, Object masterRequest,
            Map values, String responseVarName, int performanceLevel,
            String secondaryOwnerType, Long secondaryOwnerId, Map headers)
            throws ProcessException, DataAccessException {
        Process process = getProcessDefinition(processId);
        Package pkg = getPackage(process);
        long startMilli = System.currentTimeMillis();
        if (performanceLevel <= 0)
            performanceLevel = process.getPerformanceLevel();
        if (performanceLevel <= 0)
            performanceLevel = defaultPerformanceLevelService;
        EngineDataAccess edao = EngineDataAccessCache.getInstance(true, performanceLevel);
        InternalMessenger msgBroker = MessengerFactory.newInternalMessenger();
        msgBroker.setCacheOption(InternalMessenger.CACHE_ONLY);
        ProcessExecutor engine = new ProcessExecutor(edao, msgBroker, true);
        engineLogger.setPerformanceLevel(performanceLevel);
        if (performanceLevel >= 5) {
            if (OwnerType.DOCUMENT.equals(ownerType)) {
                addDocumentToCache(engine, ownerId, XmlObject.class.getName(), XmlObject.class.getName(), masterRequest, pkg);
            }
            if (values != null) {
                for (String key : values.keySet()) {
                    Object value = values.get(key);
                    if (value instanceof String && ((String)value).startsWith("DOCUMENT:")) {
                        DocumentReference docRef = new DocumentReference((String)value);
                        if (!docRef.getDocumentId().equals(ownerId)) {
                            Document doc = engine.loadDocument(docRef, false);
                            if (doc != null) {
                                String docContent = doc.getContent(pkg);
                                if (docContent != null) {
                                    addDocumentToCache(engine, docRef.getDocumentId(), process.getVariable(key).getType(), doc.getType(), docContent, pkg);
                                }
                            }
                        }
                    }
                }
            }
        }
        ProcessInstance mainProcessInst = executeServiceProcess(engine, processId,
                ownerType, ownerId, masterRequestId, values, secondaryOwnerType, secondaryOwnerId, headers);
        boolean completed = mainProcessInst.getStatusCode().equals(WorkStatus.STATUS_COMPLETED);
        if (headers != null)
            headers.put(Listener.METAINFO_MDW_PROCESS_INSTANCE_ID, mainProcessInst.getId().toString());
        Response response = null;
        if (completed) {
            response = engine.getSynchronousProcessResponse(mainProcessInst.getId(), responseVarName, pkg);
        }
        long stopMilli = System.currentTimeMillis();
        logger.info("Synchronous process executed in " +
                ((stopMilli - startMilli) / 1000.0) + " seconds at performance level " + performanceLevel);
        if (completed)
            return response;
        if (lastException == null)
            throw new ProcessException("Process instance not completed");
        throw new ProcessException(lastException.getMessage(), lastException);
    }

    /**
     * Called internally by invoke subprocess activities to call service processes as
     * subprocesses of regular processes.
     * @return map of output parameters (can be empty hash, but not null);
     */
    public Map invokeSubprocess(Long processId, Long parentInstanceId,
            String masterRequestId, Map values, int performanceLevel)
            throws Exception {
        long startMilli = System.currentTimeMillis();
        if (performanceLevel <= 0)
            performanceLevel = getProcessDefinition(processId).getPerformanceLevel();
        if (performanceLevel <=0 )
            performanceLevel = defaultPerformanceLevelService;
        EngineDataAccess edao = EngineDataAccessCache.getInstance(true, performanceLevel);
        engineLogger.setPerformanceLevel(performanceLevel);
        InternalMessenger msgBroker = MessengerFactory.newInternalMessenger();
        msgBroker.setCacheOption(InternalMessenger.CACHE_ONLY);
        ProcessExecutor engine = new ProcessExecutor(edao, msgBroker, true);
        ProcessInstance mainProcessInst = executeServiceProcess(engine, processId,
                OwnerType.PROCESS_INSTANCE, parentInstanceId, masterRequestId, values, null, null, null);
           boolean completed = mainProcessInst.getStatusCode().equals(WorkStatus.STATUS_COMPLETED);
        Map resp = completed?engine.getOutPutParameters(mainProcessInst.getId(), processId):null;
        long stopMilli = System.currentTimeMillis();
        logger.info("Synchronous process executed in " +
                ((stopMilli-startMilli)/1000.0) + " seconds at performance level " + performanceLevel);
        if (completed)
            return resp;
        if (lastException == null)
            throw new Exception("Process instance not completed");
        throw lastException;
    }

    /**
     * execute service process using asynch engine
     */
    private ProcessInstance executeServiceProcess(ProcessExecutor engine, Long processId,
            String ownerType, Long ownerId, String masterRequestId, Map values,
            String secondaryOwnerType, Long secondaryOwnerId, Map headers)
            throws ProcessException, DataAccessException {
        Process procdef = getProcessDefinition(processId);
        Long startActivityId = procdef.getStartActivity().getId();
        if (masterRequestId == null)
            masterRequestId = genMasterRequestId();
        ProcessInstance mainProcessInst = engine.createProcessInstance(
                processId, ownerType, ownerId, secondaryOwnerType, secondaryOwnerId,
                masterRequestId, values);
        mainProcessInstanceId = mainProcessInst.getId();
        engine.updateProcessInstanceStatus(mainProcessInst.getId(), WorkStatus.STATUS_PENDING_PROCESS);
        if (OwnerType.DOCUMENT.equals(ownerType) && ownerId != 0L) {
            setOwnerDocumentProcessInstanceId(engine, ownerId, mainProcessInst.getId(), masterRequestId);
            bindRequestVariable(procdef, ownerId, engine, mainProcessInst);
        }
        if (headers != null) {
            bindRequestHeadersVariable(procdef, headers, engine, mainProcessInst);
        }
        String msg = InternalLogMessage.PROCESS_START.message + " - " + procdef.getQualifiedName() + "/" + procdef.getVersionString();
        engineLogger.info(processId, mainProcessInst.getId(), masterRequestId, msg);
        engineLogger.info(processId, mainProcessInst.getId(), masterRequestId, "Performance level = " + engineLogger.getPerformanceLevel());
        engine.notifyMonitors(mainProcessInst, InternalLogMessage.PROCESS_START);
        // setProcessInstanceStatus will really set to STATUS_IN_PROGRESS - hint to set START_DT as well
        InternalEvent event = InternalEvent.createActivityStartMessage(startActivityId,
                mainProcessInst.getId(), 0L, masterRequestId, EventType.EVENTNAME_START);
        InternalMessenger messenger = engine.getInternalMessenger();
        lastException = null;
        processEvent(engine, event, mainProcessInst);
        while ((event = messenger.getNextMessageFromQueue(engine)) != null) {
            ProcessInstance procInst = findProcessInstance(engine, event);
            processEvent(engine, event, procInst);
        }
        mainProcessInst = engine.getProcessInstance(mainProcessInst.getId());
        return mainProcessInst;
    }

    public Long getMainProcessInstanceId() {
        return mainProcessInstanceId;
    }

    private void setOwnerDocumentProcessInstanceId(ProcessExecutor engine,
            Long msgDocId, Long procInstId, String masterRequestId) {
        // update document's OWNER_ID with the processInstanceId (OWNER_TYPE will stay LISTENER_REQUEST)
        try {
            if (msgDocId != 0L)
                engine.updateDocumentInfo(new DocumentReference(msgDocId),
                        null, null, procInstId, null, null);
        } catch (Exception e) {
            // this is possible for race condition - document was just created
            logger.warn("Failed to update document for process instance id");
        }
    }

    private void bindRequestVariable(Process process, Long requestDocId,
            ProcessExecutor engine, ProcessInstance pi)
    throws DataAccessException {
        Variable requestVar = process.getVariable(VariableConstants.REQUEST);
        if (requestVar == null)
            return;
        int cat = requestVar.getVariableCategory();
        String vartype = requestVar.getType();
        if (cat != Variable.CAT_INPUT && cat != Variable.CAT_INOUT)
            return;
        if (!getPackage(process).getTranslator(vartype).isDocumentReferenceVariable())
            return;
        List viList = pi.getVariables();
        if (viList != null) {
            for (VariableInstance vi : viList) {
                if (vi.getName().equals(VariableConstants.REQUEST))
                    return;
            }
        }
        DocumentReference docRef = new DocumentReference(requestDocId);
        engine.createVariableInstance(pi, VariableConstants.REQUEST, docRef);
    }

    private void bindRequestHeadersVariable(Process process, Map headers,
            ProcessExecutor engine, ProcessInstance pi) throws DataAccessException {
        Variable headersVar = process.getVariable(VariableConstants.REQUEST_HEADERS);
        if (headersVar == null)
            return;
        int cat = headersVar.getVariableCategory();
        String varType = headersVar.getType();
        if (cat != Variable.CAT_INPUT && cat != Variable.CAT_INOUT)
            return;
        List viList = pi.getVariables();
        if (viList != null) {
            for (VariableInstance vi : viList) {
                if (vi.getName().equals(VariableConstants.REQUEST_HEADERS))
                    return;
            }
        }

        if (varType.equals("java.util.Map") || varType.equals(Object.class.getName())) {
            DocumentReference docRef = engine.createDocument(varType, OwnerType.VARIABLE_INSTANCE, 0L, headers, getPackage(process));
            VariableInstance varInst = engine.createVariableInstance(pi, VariableConstants.REQUEST_HEADERS, docRef);
            engine.updateDocumentInfo(docRef, null, null, varInst.getId(), null, null);
        }
        else {
            logger.info("Implicit requestHeaders supports variable type java.util.Map");
        }
    }

    /**
     * @deprecated user {@link #start(Long, String, String, Long, Map, Map)}
     */
    @Deprecated
    public Long startProcess(Long processId, String masterRequestId, String ownerType,
            Long ownerId, Map vars, Map headers)
            throws ProcessException, DataAccessException {
        return start(processId, masterRequestId, ownerType, ownerId, new HashMap<>(vars), headers);
    }

    /**
     * Start a process asynchronously.
     * @param processId
     * @param masterRequestId
     * @param ownerType
     * @param ownerId
     * @param values Input variable values for the process instance to be created
     * @param headers
     * @return the process instance ID
     */
    public Long start(Long processId, String masterRequestId, String ownerType,
            Long ownerId, Map values, Map headers)
            throws ProcessException, DataAccessException {
        return start(processId, masterRequestId, ownerType, ownerId, values, null, null, headers);
    }

    /**
     * @deprecated user {@link #start(Long, String, String, Long, Map, String, Long, Map)}
     */
    @Deprecated
    public Long startProcess(Long processId, String masterRequestId, String ownerType, Long ownerId,
            Map vars, String secondaryOwnerType, Long secondaryOwnerId, Map headers)
            throws ProcessException, DataAccessException {
        return start(processId, masterRequestId, ownerType, ownerId, new HashMap<>(vars), secondaryOwnerType,
                secondaryOwnerId, headers);
    }

    /**
     * Start a process asynchronously.
     * @param processId ID of the process to be started
     * @param masterRequestId
     * @param ownerType
     * @param ownerId
     * @param values Input variable values for the process instance to be created
     * @return Process instance ID
     */
    public Long start(Long processId, String masterRequestId, String ownerType, Long ownerId,
            Map values, String secondaryOwnerType, Long secondaryOwnerId, Map headers)
            throws ProcessException, DataAccessException {
        Process procdef = getProcessDefinition(processId);
        int perfLevel = procdef.getPerformanceLevel();
        if (perfLevel <= 0)
            perfLevel = defaultPerformanceLevelRegular;
        EngineDataAccess edao = EngineDataAccessCache.getInstance(false, perfLevel);
        engineLogger.setPerformanceLevel(perfLevel);
        InternalMessenger messenger = MessengerFactory.newInternalMessenger();
        // do not set internal messenger with cache options, as this engine does not process it directly - Unless PL 9
        if (perfLevel >= 9)
            messenger.setCacheOption(InternalMessenger.CACHE_ONLY);
        if (masterRequestId == null)
            masterRequestId = genMasterRequestId();
        ProcessExecutor engine = new ProcessExecutor(edao, messenger, false);
        ProcessInstance processInst = engine.createProcessInstance(processId,
                ownerType, ownerId, secondaryOwnerType, secondaryOwnerId,
                masterRequestId, values);
        if (ownerType.equals(OwnerType.DOCUMENT) && ownerId != 0L) {
            setOwnerDocumentProcessInstanceId(engine, ownerId, processInst.getId(), masterRequestId);
            bindRequestVariable(procdef, ownerId, engine, processInst);
        }
        if (headers != null) {
            bindRequestHeadersVariable(procdef, headers, engine, processInst);
        }
        // Delay for ensuring document document content is available for the processing thread
        // It is also needed to ensure the message is really sent, instead of cached
        int delay = PropertyManager.getIntegerProperty(PropertyNames.MDW_PROCESS_LAUNCH_DELAY, 2);
        engine.startProcessInstance(processInst, delay);
        return processInst.getId();
    }

    private void loadDefaultPerformanceLevel() {
        String pv = PropertyManager.getProperty(PropertyNames.MDW_PERFORMANCE_LEVEL_REGULAR);
        if (pv != null)
            defaultPerformanceLevelRegular = Integer.parseInt(pv);
        else
            defaultPerformanceLevelRegular = DEFAULT_PERFORMANCE_LEVEL;
        pv = PropertyManager.getProperty(PropertyNames.MDW_PERFORMANCE_LEVEL_SERVICE);
        if (pv != null)
            defaultPerformanceLevelService = Integer.parseInt(pv);
        else
            defaultPerformanceLevelService = DEFAULT_PERFORMANCE_LEVEL;
    }

    private Process getProcessDefinition(Long processId) throws ProcessException {
        try {
            return ProcessCache.getProcess(processId);
        } catch (IOException ex) {
            throw new ProcessException("Error loading process " + processId, ex);
        }
    }

    private Process getProcessDefinition(ProcessInstance procinst) throws ProcessException {
        try {
            Process procdef = null;
            if (procinst.getInstanceDefinitionId() > 0L)
                procdef = ProcessCache.getInstanceDefinition(procinst.getProcessId(), procinst.getInstanceDefinitionId());
            if (procdef == null)
                procdef = ProcessCache.getProcess(procinst.getProcessId());
            if (procinst.isEmbedded())
                procdef = procdef.getSubProcess(new Long(procinst.getComment()));
            return procdef;
        } catch (IOException ex) {
            throw new ProcessException("Error loading instance definition" + procinst.getId(), ex);
        }
    }

    private Package getPackage(String packageName) {
        return PackageCache.getPackage(packageName);
    }

    private Package getPackage(Process process) {
        if (process.getPackageName() == null)
            return null;
        else
            return getPackage(process.getPackageName());
    }

    private String genMasterRequestId() {
        return Long.toHexString(System.nanoTime());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy