com.effektif.workflow.impl.workflowinstance.WorkflowInstanceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.effektif.workflow.impl.workflowinstance.WorkflowInstanceImpl.java

Source

/*
 * Copyright 2014 Effektif GmbH.
 *
 * 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.effektif.workflow.impl.workflowinstance;

import com.effektif.workflow.api.Configuration;
import com.effektif.workflow.api.WorkflowEngine;
import com.effektif.workflow.api.model.TriggerInstance;
import com.effektif.workflow.api.model.WorkflowInstanceId;
import com.effektif.workflow.api.query.WorkflowInstanceQuery;
import com.effektif.workflow.api.workflowinstance.TimerInstance;
import com.effektif.workflow.api.workflowinstance.WorkflowInstance;
import com.effektif.workflow.impl.WorkflowEngineImpl;
import com.effektif.workflow.impl.WorkflowInstanceStore;
import com.effektif.workflow.impl.activity.ActivityType;
import com.effektif.workflow.impl.activity.types.SubProcessImpl;
import com.effektif.workflow.impl.job.Job;
import com.effektif.workflow.impl.util.Lists;
import com.effektif.workflow.impl.util.Time;
import com.effektif.workflow.impl.workflow.ActivityImpl;
import com.effektif.workflow.impl.workflow.MultiInstanceImpl;
import com.effektif.workflow.impl.workflow.WorkflowImpl;
import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

import static com.effektif.workflow.impl.workflowinstance.ActivityInstanceImpl.*;

/**
 * @author Tom Baeyens
 */
public class WorkflowInstanceImpl extends ScopeInstanceImpl {

    public static final Logger log = LoggerFactory.getLogger(WorkflowEngine.class);

    public WorkflowInstanceId id;
    public String businessKey;
    public LockImpl lock;
    public Queue<ActivityInstanceImpl> work;
    public Queue<ActivityInstanceImpl> workAsync;
    public WorkflowInstanceId callingWorkflowInstanceId;
    public String callingActivityInstanceId;
    public List<String> startActivityIds;
    public Boolean isAsync;
    public Long nextActivityInstanceId;
    public Long nextVariableInstanceId;
    public Long nextTimerInstanceId;
    public List<Job> jobs;
    public List<UnlockListener> unlockListeners;

    /**
     * local cache of the locked workflow instance for the purpose of the call
     * activity. in case the subprocess is fully synchronous and it finishes and
     * wants to continue the parent, that parent is already locked in the db. the
     * call activity will first check this cache to see if the workflow instance
     * is already locked and use this one instead of going to the db.
     */
    public Map<WorkflowInstanceId, WorkflowInstanceImpl> lockedWorkflowInstances;

    public WorkflowInstanceImpl() {
    }

    public WorkflowInstanceImpl(Configuration configuration, WorkflowImpl workflow,
            WorkflowInstanceId workflowInstanceId, TriggerInstance triggerInstance, LockImpl lock,
            Map<String, Object> transientProperties) {
        this.id = workflowInstanceId;
        this.configuration = configuration;
        this.workflow = workflow;
        this.scope = workflow;
        this.workflowInstance = this;
        this.start = Time.now();
        this.nextActivityInstanceId = 1l;
        this.nextVariableInstanceId = 1l;
        this.nextTimerInstanceId = 1l;
        this.businessKey = triggerInstance.getBusinessKey();
        this.callingWorkflowInstanceId = triggerInstance.getCallingWorkflowInstanceId();
        this.callingActivityInstanceId = triggerInstance.getCallingActivityInstanceId();
        this.startActivityIds = triggerInstance.getStartActivityIds();
        this.lock = lock;
        this.transientProperties = transientProperties;
        this.initializeVariableInstances();
    }

    public WorkflowInstance toWorkflowInstance() {
        return toWorkflowInstance(false);
    }

    public WorkflowInstance toWorkflowInstance(boolean includeWorkState) {
        WorkflowInstance workflowInstance = new WorkflowInstance();
        workflowInstance.setId(id);
        workflowInstance.setBusinessKey(businessKey);
        workflowInstance.setWorkflowId(workflow.id);
        workflowInstance.setCallingWorkflowInstanceId(callingWorkflowInstanceId);
        workflowInstance.setCallingActivityInstanceId(callingActivityInstanceId);

        if (jobs != null) {
            List<TimerInstance> timerInstances = new ArrayList<>();
            for (Job job : jobs) {
                TimerInstance timerInstance = new TimerInstance();
                timerInstance.setDueDate(job.getDueDate());
                timerInstances.add(timerInstance);
            }
            workflowInstance.setJobs(timerInstances);
        }

        toScopeInstance(workflowInstance, includeWorkState);
        return workflowInstance;
    }

    public static List<WorkflowInstance> toWorkflowInstances(List<WorkflowInstanceImpl> workflowInstanceImpls) {
        if (workflowInstanceImpls == null) {
            return null;
        }
        List<WorkflowInstance> workflowInstances = new ArrayList<>();
        for (WorkflowInstanceImpl workflowInstance : workflowInstanceImpls) {
            workflowInstances.add(workflowInstance.toWorkflowInstance());
        }
        return workflowInstances;
    }

    public WorkflowInstance executeWork() {
        boolean isFirst = true;
        while (hasWork()) {
            ActivityInstanceImpl activityInstance = getNextWork();
            ActivityImpl activity = activityInstance.getActivity();
            ActivityType activityType = activity.activityType;

            // in the first iteration, the updates will be empty and hence no updates
            // will be flushed
            if (isFirst || activityType.isFlushSkippable()) {
                isFirst = false;
            } else {
                flushDbUpdates();
            }

            if (STATE_STARTING.equals(activityInstance.workState)) {
                if (log.isDebugEnabled())
                    log.debug("Starting " + activityInstance);
                activityInstance.execute();

            } else if (STATE_STARTING_MULTI_INSTANCE.equals(activityInstance.workState)) {
                if (log.isDebugEnabled())
                    log.debug("Starting multi instance " + activityInstance);
                activityInstance.execute();

            } else if (STATE_STARTING_MULTI_CONTAINER.equals(activityInstance.workState)) {
                Collection<Object> values = null;
                MultiInstanceImpl multiInstance = activityType.getMultiInstance();
                if (multiInstance != null && multiInstance.valuesBindings != null) {
                    Object value = activityInstance.getValues(multiInstance.valuesBindings);
                    if (value != null) {
                        if (value instanceof Collection) {
                            values = (Collection<Object>) value;
                        } else {
                            values = Lists.of(value);
                        }
                    }
                }
                if (values != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("Starting multi instance container " + activityInstance);
                    }
                    for (Object element : values) {
                        if (element != null) {
                            ActivityInstanceImpl elementActivityInstance = activityInstance
                                    .createActivityInstance(activity);
                            elementActivityInstance.setWorkState(STATE_STARTING_MULTI_INSTANCE);
                            elementActivityInstance.initializeForEachElement(multiInstance.elementVariable,
                                    element);
                        }
                    }
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Skipping empty multi instance container " + activityInstance);
                    }
                    activityInstance.onwards();
                }

            } else if (STATE_PROPAGATE_TO_PARENT.equals(activityInstance.workState)) {
                if (log.isDebugEnabled()) {
                    log.debug("Propagating end of " + activityInstance + " to parent " + activityInstance.parent);
                }
                activityInstance.parent.activityInstanceEnded(activityInstance);
                activityInstance.workState = null;
            } else if (activityInstance.workState == null) {
                if (log.isDebugEnabled()) {
                    log.debug("Activity instance " + activityInstance + " is completely done");
                }
            }
        }
        WorkflowInstance workflowInstanceSnapshot = workflowInstance.toWorkflowInstance();
        if (hasAsyncWork()) {
            if (log.isDebugEnabled())
                log.debug("Going asynchronous " + this);
            flushDbUpdates();
            Runnable asyncContinuation = new Runnable() {
                public void run() {
                    try {
                        work = workAsync;
                        workAsync = null;
                        isAsync = true;
                        if (updates != null) {
                            getUpdates().isWorkChanged = true;
                            getUpdates().isAsyncWorkChanged = true;
                        }
                        executeWork();
                    } catch (Throwable e) {
                        log.error("in workflow execution", e);
                    }
                }
            };
            WorkflowEngineImpl workflowEngine = configuration.get(WorkflowEngineImpl.class);
            workflowEngine.executeAsync(asyncContinuation);
        } else {
            WorkflowInstanceStore workflowInstanceStore = configuration.get(WorkflowInstanceStore.class);
            workflowInstanceStore.flushAndUnlock(this);
            workflow.workflowEngine.notifyUnlocked(this);
        }
        return workflowInstanceSnapshot;
    }

    public void cancel() {
        super.cancel();
        if (updates != null) {
            getUpdates().isActivityInstancesChanged = true;
            getUpdates().isEndStateChanged = true;
            getUpdates().isEndChanged = true;
            WorkflowInstanceStore workflowInstanceStore = configuration.get(WorkflowInstanceStore.class);
            workflowInstanceStore.flushAndUnlock(this);
        }
    }

    protected void flushDbUpdates() {
        workflow.workflowEngine.notifyFlush(this);
        WorkflowInstanceStore workflowInstanceStore = configuration.get(WorkflowInstanceStore.class);
        workflowInstanceStore.flush(this);
    }

    public void addLockedWorkflowInstance(WorkflowInstanceImpl lockedWorkflowInstance) {
        if (lockedWorkflowInstances == null) {
            lockedWorkflowInstances = new HashMap<>();
        }
        lockedWorkflowInstances.put(lockedWorkflowInstance.getId(), lockedWorkflowInstance);
    }

    /**
     * Notifies event listeners tha the workflow instance has finished execution.
     */
    public void workflowInstanceEnded() {
        workflow.workflowEngine.notifyWorkflowInstanceEnded(workflowInstance);

        destroyScopeInstance();

        if (callingWorkflowInstanceId != null) {
            WorkflowInstanceImpl callingWorkflowInstance = getLockedWorkflowInstance(callingWorkflowInstanceId);
            final ActivityInstanceImpl callingActivityInstance = callingWorkflowInstance
                    .findActivityInstance(callingActivityInstanceId);
            if (log.isDebugEnabled())
                log.debug("Notifying calling activity instance " + callingActivityInstance);
            ActivityImpl activityDefinition = callingActivityInstance.getActivity();
            final SubProcessImpl callActivity = (SubProcessImpl) activityDefinition.activityType;

            callActivity.calledWorkflowInstanceEnded(callingActivityInstance, this);
        }
    }

    public WorkflowInstanceImpl getLockedWorkflowInstance(WorkflowInstanceId workflowInstanceId) {
        WorkflowInstanceImpl callingWorkflowInstance = null;
        if (lockedWorkflowInstances != null) {
            // the lockedWorkflowInstances is a local cache of the locked workflow
            // instances which is passed down to the sub workflow instance in the
            // call activity. In case the subprocess is fully synchronous and it
            // finishes and wants to continue the parent, that parent is already
            // locked in the db. the call activity will first check this cache to
            // see if the workflow instance is already locked and use this one
            // instead of going to the db.
            callingWorkflowInstance = lockedWorkflowInstances.get(workflowInstanceId);
        }
        if (callingWorkflowInstance == null) {
            WorkflowEngineImpl workflowEngine = configuration.get(WorkflowEngineImpl.class);
            callingWorkflowInstance = workflowEngine
                    .lockWorkflowInstanceWithRetry(workflowInstance.callingWorkflowInstanceId);
            if (callingWorkflowInstance == null) {
                log.error("Couldn't continue calling activity instance after workflow instance completion");
            }
        }
        return callingWorkflowInstance;
    }

    public void addWork(ActivityInstanceImpl activityInstance) {
        if (isWorkAsync(activityInstance)) {
            addAsyncWork(activityInstance);
        } else {
            addSyncWork(activityInstance);
        }
    }

    protected boolean isWorkAsync(ActivityInstanceImpl activityInstance) {
        // if this workflow instance is already running in an async thread,
        // the new work should be done sync in this thread.
        if (Boolean.TRUE.equals(isAsync)) {
            return false;
        }
        if (!ActivityInstanceImpl.START_WORKSTATES.contains(activityInstance.workState)) {
            return false;
        }
        return activityInstance.getActivity().activityType.isAsync(activityInstance);
    }

    protected void addSyncWork(ActivityInstanceImpl activityInstance) {
        if (work == null) {
            work = new LinkedList<>();
        }
        work.add(activityInstance);
        if (updates != null) {
            getUpdates().isWorkChanged = true;
        }
    }

    protected void addAsyncWork(ActivityInstanceImpl activityInstance) {
        if (workAsync == null) {
            workAsync = new LinkedList<>();
        }
        workAsync.add(activityInstance);
        if (updates != null) {
            getUpdates().isAsyncWorkChanged = true;
        }
    }

    public ActivityInstanceImpl getNextWork() {
        ActivityInstanceImpl nextWork = work != null ? work.poll() : null;
        if (nextWork != null && updates != null) {
            getUpdates().isWorkChanged = true;
        }
        return nextWork;
    }

    public boolean hasAsyncWork() {
        return workAsync != null && !workAsync.isEmpty();
    }

    public boolean hasWork() {
        return work != null && !work.isEmpty();
    }

    /**
     * Instructs the engine to propagate execution forwards after ending the current activity instance.
     */
    public void endAndPropagateToParent() {
        if (this.end == null) {
            if (hasOpenActivityInstances()) {
                throw new RuntimeException(
                        "Can't end this process instance. There are open activity instances: " + this);
            }
            setEnd(Time.now());
            if (log.isDebugEnabled()) {
                log.debug("Ends " + this);
            }
            workflowInstanceEnded();
        }
    }

    public String toString() {
        return "("
                + ((workflow.name != null ? workflow.name + "|"
                        : workflow.sourceWorkflowId != null ? workflow.sourceWorkflowId + "|" : ""))
                + (id != null ? id.toString() : Integer.toString(System.identityHashCode(this))) + ")";
    }

    public void removeLock() {
        setLock(null);
        if (updates != null) {
            getUpdates().isLockChanged = true;
        }
    }

    public void setLock(LockImpl lock) {
        this.lock = lock;
        if (updates != null) {
            getUpdates().isLockChanged = true;
        }
    }

    public void setEnd(LocalDateTime end) {
        this.end = end;
        if (start != null && end != null) {
            this.duration = end.toDate().getTime() - start.toDate().getTime();
        }
        if (updates != null) {
            getUpdates().isEndChanged = true;
        }
    }

    @Override
    public void setProperty(String key, Object value) {
        super.setProperty(key, value);
        if (updates != null) {
            getUpdates().isPropertiesChanged = true;
        }
    }

    @Override
    public void setPropertyOpt(String key, Object value) {
        getUpdates().isPropertiesChanged = true;
        super.setPropertyOpt(key, value);
    }

    @Override
    public void setProperties(Map<String, Object> properties) {
        getUpdates().isPropertiesChanged = true;
        super.setProperties(properties);
    }

    @Override
    public Object removeProperty(String key) {
        getUpdates().isPropertiesChanged = true;
        return super.removeProperty(key);
    }

    /** getter for casting convenience */
    @Override
    public WorkflowInstanceUpdates getUpdates() {
        return (WorkflowInstanceUpdates) updates;
    }

    @Override
    public boolean isWorkflowInstance() {
        return true;
    }

    public void trackUpdates(boolean isNew) {
        if (updates == null) {
            updates = new WorkflowInstanceUpdates(isNew);
        } else {
            updates.reset(isNew);
        }
        super.trackUpdates(isNew);
    }

    /***
     * isIncluded
     * 
     * @param query
     *          , with any combination of ActivityId and WorkflowInstanceId set or
     *          not set. When set, the value is taken into account, otherwise it
     *          is ignored. If both ActivityId and WorkflowInstanceId are null
     *          (empty query), true is returned
     */
    public boolean isIncluded(WorkflowInstanceQuery query) {

        if (query.getActivityId() == null && query.getWorkflowInstanceId() == null)
            return true;

        if (query.getWorkflowInstanceId() != null && query.getWorkflowInstanceId().equals(id)) {
            return true;
        }

        if (query.getActivityId() != null && hasActivityInstances()) {
            for (ActivityInstanceImpl activityInstance : activityInstances) {
                if (activityInstance.activity.getId().equals(query.getActivityId())
                        && !activityInstance.isEnded()) {
                    return true;
                }
            }
        }

        return false;
    }

    public String generateNextActivityInstanceId() {
        if (updates != null) {
            getUpdates().isNextActivityInstanceIdChanged = true;
        }
        return Long.toString(nextActivityInstanceId++);
    }

    public String generateNextVariableInstanceId() {
        if (updates != null) {
            getUpdates().isNextVariableInstanceIdChanged = true;
        }
        return Long.toString(nextVariableInstanceId++);
    }

    public void addJob(Job job) {
        if (jobs == null) {
            jobs = new ArrayList<>();
        }
        jobs.add(job);
        if (updates != null) {
            getUpdates().isJobsChanged = true;
        }
    }

    public void removeJob(Job job) {
        if (jobs != null) {
            jobs.remove(job);
        }
        if (updates != null) {
            getUpdates().isJobsChanged = true;
        }
    }

    public WorkflowInstanceId getId() {
        return this.id;
    }

    public String getEndState() {
        return endState;
    }

    public void addUnlockListener(UnlockListener unlockListener) {
        if (unlockListeners == null) {
            unlockListeners = new ArrayList<>();
        }
        unlockListeners.add(unlockListener);
    }

    public void notifyUnlockListeners() {
        if (unlockListeners != null) {
            WorkflowEngineImpl workflowEngine = configuration.get(WorkflowEngineImpl.class);
            for (final UnlockListener unlockListener : unlockListeners) {
                workflowEngine.executeAsync(new Runnable() {
                    @Override
                    public void run() {
                        unlockListener.unlocked(WorkflowInstanceImpl.this);
                    }
                });
            }
        }
    }
}