com.heisenberg.mongo.MongoWorkflowInstanceStore.java Source code

Java tutorial

Introduction

Here is the source code for com.heisenberg.mongo.MongoWorkflowInstanceStore.java

Source

/*
 * Copyright 2014 Heisenberg Enterprises Ltd.
 * 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.heisenberg.mongo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import org.bson.types.ObjectId;
import org.slf4j.Logger;

import com.heisenberg.impl.Time;
import com.heisenberg.impl.WorkflowEngineImpl;
import com.heisenberg.impl.WorkflowInstanceQueryImpl;
import com.heisenberg.impl.WorkflowInstanceStore;
import com.heisenberg.impl.WorkflowQueryImpl.Representation;
import com.heisenberg.impl.definition.WorkflowImpl;
import com.heisenberg.impl.instance.ActivityInstanceImpl;
import com.heisenberg.impl.instance.LockImpl;
import com.heisenberg.impl.instance.ScopeInstanceImpl;
import com.heisenberg.impl.instance.VariableInstanceImpl;
import com.heisenberg.impl.instance.WorkflowInstanceImpl;
import com.heisenberg.impl.instance.WorkflowInstanceUpdates;
import com.heisenberg.impl.plugin.ServiceRegistry;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.WriteConcern;

/**
 * @author Walter White
 */
public class MongoWorkflowInstanceStore extends MongoCollection implements WorkflowInstanceStore {

    public static final Logger log = MongoWorkflowEngine.log;

    protected WorkflowEngineImpl processEngine;
    protected MongoWorkflowEngineConfiguration.WorkflowInstanceFields fields;
    protected WriteConcern writeConcernStoreProcessInstance;
    protected WriteConcern writeConcernFlushUpdates;

    public MongoWorkflowInstanceStore() {
    }

    public MongoWorkflowInstanceStore(ServiceRegistry serviceRegistry) {
    }

    @Override
    public String createWorkflowInstanceId(WorkflowImpl processDefinition) {
        return new ObjectId().toString();
    }

    @Override
    public String createActivityInstanceId() {
        return new ObjectId().toString();
    }

    @Override
    public String createVariableInstanceId() {
        return new ObjectId().toString();
    }

    @Override
    public void insertWorkflowInstance(WorkflowInstanceImpl workflowInstance) {
        BasicDBObject dbProcessInstance = writeProcessInstance(workflowInstance);
        insert(dbProcessInstance, writeConcernStoreProcessInstance);
        workflowInstance.trackUpdates(false);
    }

    @Override
    public void flush(WorkflowInstanceImpl workflowInstance) {
        if (log.isDebugEnabled())
            log.debug("Flushing...");

        WorkflowInstanceUpdates updates = workflowInstance.getUpdates();

        DBObject query = BasicDBObjectBuilder.start().add(fields._id, new ObjectId(workflowInstance.id))
                .add(fields.lock, writeLock(workflowInstance.lock)).get();

        BasicDBObject sets = new BasicDBObject();
        BasicDBObject unsets = new BasicDBObject();
        BasicDBObject update = new BasicDBObject();

        if (updates.isEndChanged) {
            if (log.isDebugEnabled())
                log.debug("  Workflow instance ended");
            sets.append(fields.end, workflowInstance.end);
            sets.append(fields.duration, workflowInstance.duration);
        }
        // MongoDB can't combine updates of array elements together with 
        // adding elements to that array.  That's why we overwrite the whole
        // activity instance array when an update happened in there.
        // We do archive the ended (and joined) activity instances into a separate collection 
        // that doesn't have to be loaded.
        if (updates.isActivityInstancesChanged) {
            if (log.isDebugEnabled())
                log.debug("  Activity instances changed");
            List<BasicDBObject> activityInstances = new ArrayList<>();
            List<BasicDBObject> archivedActivityInstances = new ArrayList<>();
            collectActivities(workflowInstance, activityInstances, archivedActivityInstances);
            sets.append(fields.activityInstances, activityInstances);
            if (!archivedActivityInstances.isEmpty()) {
                update.append("$push",
                        new BasicDBObject(fields.archivedActivityInstances, archivedActivityInstances));
            }
        } else {
            if (log.isDebugEnabled())
                log.debug("  No activity instances changed");
        }

        if (updates.isVariableInstancesChanged) {
            if (log.isDebugEnabled())
                log.debug("  Variable instances changed");
            writeVariables(sets, workflowInstance);
        } else {
            if (log.isDebugEnabled())
                log.debug("  No variable instances changed");
        }

        if (updates.isWorkChanged) {
            if (log.isDebugEnabled())
                log.debug("  Work changed");
            List<ObjectId> work = writeWork(workflowInstance.work);
            if (work != null) {
                sets.put(fields.work, work);
            } else {
                unsets.put(fields.work, 1);
            }
        } else {
            if (log.isDebugEnabled())
                log.debug("  No work changed");
        }

        if (updates.isAsyncWorkChanged) {
            if (log.isDebugEnabled())
                log.debug("  Aync work changed");
            List<ObjectId> workAsync = writeWork(workflowInstance.workAsync);
            if (workAsync != null) {
                sets.put(fields.workAsync, workAsync);
            } else {
                unsets.put(fields.workAsync, 1);
            }
        } else {
            if (log.isDebugEnabled())
                log.debug("  No async work changed");
        }

        if (!sets.isEmpty()) {
            update.append("$set", sets);
        } else {
            if (log.isDebugEnabled())
                log.debug("  No sets");
        }
        if (!unsets.isEmpty()) {
            update.append("$unset", unsets);
        } else {
            if (log.isDebugEnabled())
                log.debug("  No unsets");
        }

        if (!update.isEmpty()) {
            update(query, update, false, false, writeConcernFlushUpdates);
        } else {
            if (log.isDebugEnabled())
                log.debug("  Nothing to flush");
        }

        // reset the update tracking as all changes have been saved
        workflowInstance.trackUpdates(false);
    }

    @Override
    public void flushAndUnlock(WorkflowInstanceImpl processInstance) {
        processInstance.lock = null;
        BasicDBObject dbProcessInstance = writeProcessInstance(processInstance);
        saveProcessInstance(dbProcessInstance);
        processInstance.trackUpdates(false);
    }

    @Override
    public List<WorkflowInstanceImpl> findWorkflowInstances(WorkflowInstanceQueryImpl workflowInstanceQuery) {
        BasicDBObject query = buildQuery(workflowInstanceQuery);
        DBCursor workflowInstanceCursor = find(query);
        List<WorkflowInstanceImpl> workflowInstances = new ArrayList<>();
        while (workflowInstanceCursor.hasNext()) {
            BasicDBObject dbWorkflowInstance = (BasicDBObject) workflowInstanceCursor.next();
            WorkflowInstanceImpl workflowInstance = readProcessInstance(dbWorkflowInstance);
            workflowInstances.add(workflowInstance);
        }
        return workflowInstances;
    }

    @Override
    public long countWorkflowInstances(WorkflowInstanceQueryImpl workflowInstanceQueryImpl) {
        return 0;
    }

    @Override
    public void deleteWorkflowInstances(WorkflowInstanceQueryImpl workflowInstanceQuery) {
        BasicDBObject query = buildQuery(workflowInstanceQuery);
        remove(query);
    }

    protected BasicDBObject buildQuery(WorkflowInstanceQueryImpl workflowInstanceQuery) {
        BasicDBObject query = new BasicDBObject();
        if (workflowInstanceQuery.workflowInstanceId != null) {
            query.append(fields._id, new ObjectId(workflowInstanceQuery.workflowInstanceId));
        }
        if (workflowInstanceQuery.activityInstanceId != null) {
            query.append(fields.activityInstances + "." + fields._id,
                    new ObjectId(workflowInstanceQuery.workflowInstanceId));
        }
        return query;
    }

    public void saveProcessInstance(BasicDBObject dbProcessInstance) {
        save(dbProcessInstance, writeConcernStoreProcessInstance);
    }

    public WorkflowInstanceImpl lockWorkflowInstance(WorkflowInstanceQueryImpl processInstanceQuery) {
        BasicDBObjectBuilder builder = BasicDBObjectBuilder.start();
        if (processInstanceQuery.workflowInstanceId != null) {
            builder.add(fields._id, new ObjectId(processInstanceQuery.workflowInstanceId));
        }
        if (processInstanceQuery.activityInstanceId != null) {
            builder.add(fields.activityInstances + "." + fields._id,
                    new ObjectId(processInstanceQuery.activityInstanceId));
        }

        DBObject query = builder.push(fields.lock).add("$exists", false).pop().get();
        DBObject update = BasicDBObjectBuilder.start().push("$set").push(fields.lock)
                .add(fields.time, Time.now().toDate()).add(fields.owner, processEngine.getId()).pop().pop().get();
        DBObject retrieveFields = new BasicDBObject().append(fields.archivedActivityInstances, false);

        BasicDBObject dbProcessInstance = findAndModify(query, update, retrieveFields);
        if (dbProcessInstance == null) {
            return null;
        }

        WorkflowInstanceImpl workflowInstance = readProcessInstance(dbProcessInstance);
        workflowInstance.trackUpdates(false);
        return workflowInstance;
    }

    public BasicDBObject writeProcessInstance(WorkflowInstanceImpl workflowInstance) {
        BasicDBObject dbProcess = new BasicDBObject();
        writeId(dbProcess, fields._id, workflowInstance.id);
        writeStringOpt(dbProcess, fields.organizationId, workflowInstance.organizationId);
        writeId(dbProcess, fields.workflowId, workflowInstance.workflow.id);
        writeIdOpt(dbProcess, fields.callerWorkflowInstanceId, workflowInstance.callerWorkflowInstanceId);
        writeIdOpt(dbProcess, fields.callerActivityInstanceId, workflowInstance.callerActivityInstanceId);
        writeTimeOpt(dbProcess, fields.start, workflowInstance.start);
        writeTimeOpt(dbProcess, fields.end, workflowInstance.end);
        writeLongOpt(dbProcess, fields.duration, workflowInstance.duration);
        writeObjectOpt(dbProcess, fields.lock, writeLock(workflowInstance.lock));
        List<BasicDBObject> activityInstances = new ArrayList<>();
        List<BasicDBObject> archivedActivityInstances = new ArrayList<>();
        collectActivities(workflowInstance, activityInstances, archivedActivityInstances);
        writeObjectOpt(dbProcess, fields.activityInstances, activityInstances);
        if (!archivedActivityInstances.isEmpty()) {
            writeObjectOpt(dbProcess, fields.archivedActivityInstances, archivedActivityInstances);
        }
        writeObjectOpt(dbProcess, fields.work, writeWork(workflowInstance.work));
        writeObjectOpt(dbProcess, fields.workAsync, writeWork(workflowInstance.workAsync));
        return dbProcess;
    }

    protected List<ObjectId> writeWork(Queue<ActivityInstanceImpl> workQueue) {
        List<ObjectId> workActivityInstanceIds = null;
        if (workQueue != null && !workQueue.isEmpty()) {
            workActivityInstanceIds = new ArrayList<ObjectId>();
            for (ActivityInstanceImpl workActivityInstance : workQueue) {
                workActivityInstanceIds.add(new ObjectId(workActivityInstance.id));
            }
        }
        return workActivityInstanceIds;
    }

    public WorkflowInstanceImpl readProcessInstance(BasicDBObject dbWorkflowInstance) {
        WorkflowInstanceImpl workflowInstance = new WorkflowInstanceImpl();
        workflowInstance.workflowEngine = processEngine;
        workflowInstance.workflowInstance = workflowInstance;
        workflowInstance.id = readId(dbWorkflowInstance, fields._id);
        workflowInstance.organizationId = readString(dbWorkflowInstance, fields.organizationId);
        workflowInstance.callerWorkflowInstanceId = readId(dbWorkflowInstance, fields.callerWorkflowInstanceId);
        workflowInstance.callerActivityInstanceId = readId(dbWorkflowInstance, fields.callerActivityInstanceId);
        workflowInstance.start = readTime(dbWorkflowInstance, fields.start);
        workflowInstance.end = readTime(dbWorkflowInstance, fields.end);
        workflowInstance.duration = readLong(dbWorkflowInstance, fields.duration);
        workflowInstance.lock = readLock((BasicDBObject) dbWorkflowInstance.get(fields.lock));
        workflowInstance.workflowId = readId(dbWorkflowInstance, fields.workflowId);
        WorkflowImpl workflow = processEngine.newWorkflowQuery().representation(Representation.EXECUTABLE)
                .id(workflowInstance.workflowId).get();
        if (workflow != null) {
            workflowInstance.workflow = workflow;
            workflowInstance.workflowId = workflow != null ? workflow.id : null;
            workflowInstance.scopeDefinition = workflowInstance.workflow;
        } else {
            throw new RuntimeException("No workflow for instance " + workflowInstance.id);
        }

        Map<Object, ActivityInstanceImpl> allActivityInstances = new LinkedHashMap<>();
        Map<Object, Object> parentIds = new HashMap<>();
        List<BasicDBObject> dbActivityInstances = readList(dbWorkflowInstance, fields.activityInstances);
        if (dbActivityInstances != null) {
            for (BasicDBObject dbActivityInstance : dbActivityInstances) {
                ActivityInstanceImpl activityInstance = readActivityInstance(workflowInstance, dbActivityInstance);
                allActivityInstances.put(activityInstance.id, activityInstance);
                parentIds.put(activityInstance.id, dbActivityInstance.get(fields.parent));
            }
        }

        for (ActivityInstanceImpl activityInstance : allActivityInstances.values()) {
            Object parentId = parentIds.get(activityInstance.id);
            activityInstance.parent = (parentId != null ? allActivityInstances.get(parentId.toString())
                    : workflowInstance);
            activityInstance.parent.addActivityInstance(activityInstance);
        }

        workflowInstance.variableInstances = readVariableInstances(dbWorkflowInstance, workflowInstance);
        workflowInstance.work = readWork(dbWorkflowInstance, fields.work, workflowInstance);
        workflowInstance.workAsync = readWork(dbWorkflowInstance, fields.workAsync, workflowInstance);

        return workflowInstance;
    }

    @SuppressWarnings("unchecked")
    protected Queue<ActivityInstanceImpl> readWork(BasicDBObject dbWorkflowInstance, String fieldName,
            WorkflowInstanceImpl workflowInstance) {
        Queue<ActivityInstanceImpl> workQueue = null;
        List<ObjectId> workActivityInstanceIds = (List<ObjectId>) dbWorkflowInstance.get(fieldName);
        if (workActivityInstanceIds != null) {
            workQueue = new LinkedList<>();
            for (ObjectId workActivityInstanceId : workActivityInstanceIds) {
                ActivityInstanceImpl workActivityInstance = workflowInstance
                        .findActivityInstance(workActivityInstanceId.toString());
                workQueue.add(workActivityInstance);
            }
        }
        return workQueue;
    }

    private List<VariableInstanceImpl> readVariableInstances(BasicDBObject dbWorkflowInstance,
            ScopeInstanceImpl parent) {
        List<BasicDBObject> dbVariableInstances = readList(dbWorkflowInstance, fields.variableInstances);
        if (dbVariableInstances != null) {
            for (BasicDBObject dbVariableInstance : dbVariableInstances) {
                VariableInstanceImpl variableInstance = new VariableInstanceImpl();
                variableInstance.processEngine = processEngine;
                variableInstance.processInstance = parent.workflowInstance;
                variableInstance.id = readId(dbVariableInstance, fields._id);
                variableInstance.variableDefinitionId = readString(dbVariableInstance, fields.variableId);
                WorkflowImpl workflow = parent.workflowInstance.workflow;
                if (workflow != null) {
                    variableInstance.variableDefinition = workflow
                            .findVariable(variableInstance.variableDefinitionId);
                    variableInstance.variableDefinitionId = variableInstance.variableDefinition.id;
                    variableInstance.dataType = variableInstance.variableDefinition.dataType;
                    variableInstance.value = variableInstance.dataType
                            .convertJsonToInternalValue(dbVariableInstance.get(fields.value));
                }
                parent.addVariableInstance(variableInstance);
            }
        }

        return null;
    }

    protected BasicDBObject writeLock(LockImpl lock) {
        if (lock == null) {
            return null;
        }
        BasicDBObject dbLock = new BasicDBObject();
        writeTimeOpt(dbLock, fields.time, lock.time);
        writeObjectOpt(dbLock, fields.owner, lock.owner);
        return dbLock;
    }

    protected LockImpl readLock(BasicDBObject dbLock) {
        if (dbLock == null) {
            return null;
        }
        LockImpl lock = new LockImpl();
        lock.owner = readString(dbLock, fields.owner);
        lock.time = readTime(dbLock, fields.time);
        return lock;
    }

    protected void collectActivities(ScopeInstanceImpl scopeInstance, List<BasicDBObject> dbActivityInstances,
            List<BasicDBObject> dbArchivedActivityInstances) {
        if (scopeInstance.activityInstances != null) {
            List<ActivityInstanceImpl> activeActivityInstances = new ArrayList<>();
            for (ActivityInstanceImpl activity : scopeInstance.activityInstances) {
                BasicDBObject dbActivity = writeActivityInstance(activity);
                if (activity.workState != null) { // null means ready to be archived
                    dbActivityInstances.add(dbActivity);
                    activeActivityInstances.add(activity);
                } else {
                    dbArchivedActivityInstances.add(dbActivity);
                }
                collectActivities(activity, dbActivityInstances, dbArchivedActivityInstances);
            }
            scopeInstance.activityInstances = activeActivityInstances;
        }
    }

    protected BasicDBObject writeActivityInstance(ActivityInstanceImpl activityInstance) {
        String parentId = (activityInstance.parent.isProcessInstance() ? null : activityInstance.parent.getId());
        BasicDBObject dbActivity = new BasicDBObject();
        writeId(dbActivity, fields._id, activityInstance.id);
        writeStringOpt(dbActivity, fields.activityId, activityInstance.activityId);
        writeStringOpt(dbActivity, fields.workState, activityInstance.workState);
        writeIdOpt(dbActivity, fields.parent, parentId);
        writeIdOpt(dbActivity, fields.calledWorkflowInstanceId, activityInstance.calledWorkflowInstanceId);
        writeTimeOpt(dbActivity, fields.start, activityInstance.start);
        writeTimeOpt(dbActivity, fields.end, activityInstance.end);
        writeLongOpt(dbActivity, fields.duration, activityInstance.duration);
        return dbActivity;
    }

    protected ActivityInstanceImpl readActivityInstance(WorkflowInstanceImpl processInstance,
            BasicDBObject dbActivityInstance) {
        ActivityInstanceImpl activityInstance = new ActivityInstanceImpl();
        activityInstance.id = readId(dbActivityInstance, fields._id);
        activityInstance.start = readTime(dbActivityInstance, fields.start);
        activityInstance.end = readTime(dbActivityInstance, fields.end);
        activityInstance.calledWorkflowInstanceId = readId(dbActivityInstance, fields.calledWorkflowInstanceId);
        activityInstance.duration = readLong(dbActivityInstance, fields.duration);
        activityInstance.workState = readString(dbActivityInstance, fields.workState);
        activityInstance.activityId = readString(dbActivityInstance, fields.activityId);
        activityInstance.workflowEngine = processEngine;
        WorkflowImpl workflow = processInstance.workflow;
        if (workflow != null) {
            activityInstance.workflow = workflow;
            activityInstance.activityDefinition = workflow.findActivity(activityInstance.activityId);
            activityInstance.activityId = activityInstance.activityDefinition.id;
            activityInstance.scopeDefinition = activityInstance.activityDefinition;
        }
        activityInstance.workflowInstance = processInstance;
        activityInstance.variableInstances = readVariableInstances(dbActivityInstance, activityInstance);
        return activityInstance;
    }

    protected void writeVariables(BasicDBObject dbScope, ScopeInstanceImpl scope) {
        if (scope.variableInstances != null) {
            ScopeInstanceImpl parent = scope.getParent();
            String parentId = (parent != null ? parent.getId() : null);
            for (VariableInstanceImpl variable : scope.variableInstances) {
                BasicDBObject dbVariable = new BasicDBObject();
                writeId(dbVariable, fields._id, variable.id);
                writeString(dbVariable, fields.variableId, variable.variableDefinitionId);
                writeIdOpt(dbVariable, fields.parent, parentId);
                Object jsonValue = variable.dataType.convertInternalToJsonValue(variable.value);
                writeObjectOpt(dbVariable, fields.value, jsonValue);
                writeListElementOpt(dbScope, fields.variableInstances, dbVariable);
            }
        }
    }

    public WorkflowEngineImpl getProcessEngine() {
        return processEngine;
    }

    public MongoWorkflowEngineConfiguration.WorkflowInstanceFields getFields() {
        return fields;
    }

    public WriteConcern getWriteConcernStoreProcessInstance() {
        return writeConcernStoreProcessInstance;
    }

    public WriteConcern getWriteConcernFlushUpdates() {
        return writeConcernFlushUpdates;
    }
}