Java tutorial
/* * 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.impl; import static com.heisenberg.impl.instance.ActivityInstanceImpl.STATE_NOTIFYING; import static com.heisenberg.impl.instance.ActivityInstanceImpl.STATE_STARTING; import static com.heisenberg.impl.instance.ActivityInstanceImpl.STATE_STARTING_MULTI_CONTAINER; import static com.heisenberg.impl.instance.ActivityInstanceImpl.STATE_STARTING_MULTI_INSTANCE; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; import org.joda.time.LocalDateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.heisenberg.api.DataTypes; import com.heisenberg.api.WorkflowEngine; import com.heisenberg.api.WorkflowEngineConfiguration; import com.heisenberg.api.activitytypes.Call; import com.heisenberg.api.builder.DeployResult; import com.heisenberg.api.builder.MessageBuilder; import com.heisenberg.api.builder.ParseIssues; import com.heisenberg.api.builder.WorkflowBuilder; import com.heisenberg.api.definition.Activity; import com.heisenberg.api.instance.WorkflowInstance; import com.heisenberg.api.instance.WorkflowInstanceEventListener; import com.heisenberg.impl.WorkflowQueryImpl.Representation; import com.heisenberg.impl.definition.ActivityImpl; import com.heisenberg.impl.definition.TransitionImpl; import com.heisenberg.impl.definition.VariableImpl; import com.heisenberg.impl.definition.WorkflowImpl; import com.heisenberg.impl.definition.WorkflowValidator; 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.json.JsonService; import com.heisenberg.impl.memory.MemoryJobServiceImpl; import com.heisenberg.impl.memory.MemoryTaskService; import com.heisenberg.impl.memory.MemoryWorkflowInstanceStore; import com.heisenberg.impl.memory.MemoryWorkflowStore; import com.heisenberg.impl.plugin.ServiceRegistry; import com.heisenberg.impl.type.AnyDataType; import com.heisenberg.impl.util.Exceptions; import com.heisenberg.impl.util.Lists; /** * @author Walter White */ public abstract class WorkflowEngineImpl implements WorkflowEngine { public static final Logger log = LoggerFactory.getLogger(WorkflowEngine.class); public String id; public ServiceRegistry serviceRegistry; public DataTypes dataTypes; public JsonService jsonService; public ExecutorService executorService; public WorkflowCache workflowCache; public WorkflowStore workflowStore; public WorkflowInstanceStore workflowInstanceStore; private List<WorkflowInstanceEventListener> listeners; protected WorkflowEngineImpl() { } protected WorkflowEngineImpl(WorkflowEngineConfiguration configuration) { this.serviceRegistry = configuration.getServiceRegistry(); this.serviceRegistry.registerService(this); initializeId(configuration); initializeStorageServices(configuration); this.dataTypes = serviceRegistry.getService(DataTypes.class); this.jsonService = serviceRegistry.getService(JsonService.class); this.executorService = serviceRegistry.getService(ExecutorService.class); this.workflowCache = serviceRegistry.getService(WorkflowCache.class); this.workflowStore = serviceRegistry.getService(WorkflowStore.class); this.workflowInstanceStore = serviceRegistry.getService(WorkflowInstanceStore.class); this.listeners = new ArrayList<>(); } protected void initializeStorageServices(WorkflowEngineConfiguration configuration) { configuration.registerService(new MemoryWorkflowStore(serviceRegistry)); configuration.registerService(new MemoryWorkflowInstanceStore(serviceRegistry)); configuration.registerService(new MemoryTaskService(serviceRegistry)); configuration.registerService(new MemoryJobServiceImpl(serviceRegistry)); } protected void initializeId(WorkflowEngineConfiguration configuration) { this.id = configuration.getId(); if (id == null) { try { id = InetAddress.getLocalHost().getHostAddress(); try { String processName = ManagementFactory.getRuntimeMXBean().getName(); int atIndex = processName.indexOf('@'); if (atIndex > 0) { id += ":" + processName.substring(0, atIndex); } } catch (Exception e) { id += ":?"; } } catch (UnknownHostException e1) { id = UUID.randomUUID().toString(); } } } public void startup() { } public void shutdown() { executorService.shutdown(); } @Override public DataTypes getDataTypes() { return dataTypes; } /// Workflow methods //////////////////////////////////////////////////////////// @Override public WorkflowBuilder newWorkflow() { return new WorkflowImpl(this); } public ParseIssues validateWorkflow(WorkflowImpl workflow) { // throws an exception if there are errors WorkflowValidator validator = new WorkflowValidator(this); workflow.visit(validator); return validator.getIssues(); } public DeployResult validateAndDeploy(WorkflowImpl workflow) { Exceptions.checkNotNull(workflow, "processBuilder"); if (log.isDebugEnabled()) log.debug("Deploying process"); workflow.deployedTime = new LocalDateTime(); DeployResult deployResult = new DeployResult(); // throws an exception if there are errors WorkflowValidator validator = new WorkflowValidator(this); workflow.visit(validator); ParseIssues issues = validator.getIssues(); deployResult.setIssues(issues); if (!issues.hasErrors()) { workflow.id = workflowStore.createWorkflowId(workflow); deployResult.setWorkflowId(workflow.id); workflowStore.insertWorkflow(workflow); workflowCache.put(workflow); } return deployResult; } public String deployWorkflow(WorkflowImpl workflow) { DeployResult result = validateAndDeploy(workflow); result.checkNoErrors(); return result.getWorkflowId(); } public WorkflowQueryImpl newWorkflowQuery() { return new WorkflowQueryImpl(this); } public List<WorkflowImpl> findWorkflows(WorkflowQueryImpl query) { if (query.onlyIdSpecified()) { WorkflowImpl cachedProcessDefinition = workflowCache.get(query.id); if (cachedProcessDefinition != null) { return Lists.of(cachedProcessDefinition); } } List<WorkflowImpl> result = workflowStore.loadWorkflows(query); if (Representation.EXECUTABLE == query.representation) { for (WorkflowImpl processDefinition : result) { WorkflowValidator validator = new WorkflowValidator(this); processDefinition.visit(validator); workflowCache.put(processDefinition); } } return result; } /// Workflow instance methods //////////////////////////////////////////////////////////// @Override public StartImpl newStart() { return new StartImpl(this, jsonService); } @Override public MessageBuilder newMessage() { return new MessageImpl(this, jsonService); } @Override public WorkflowInstanceQueryImpl newWorkflowInstanceQuery() { return new WorkflowInstanceQueryImpl(workflowInstanceStore); } /** caller has to ensure that start.variableValues is not serialized @see VariableRequestImpl#serialize & VariableRequestImpl#deserialize */ public WorkflowInstance startWorkflowInstance(StartImpl start) { WorkflowImpl workflow = newWorkflowQuery().representation(Representation.EXECUTABLE) .id(start.processDefinitionId).name(start.processDefinitionName).orderByDeployTimeDescending() .get(); if (workflow == null) { throw new RuntimeException("Could not find process definition " + start.processDefinitionId + " " + start.processDefinitionName); } WorkflowInstanceImpl workflowInstance = createWorkflowInstance(workflow); workflowInstance.callerWorkflowInstanceId = start.callerWorkflowInstanceId; workflowInstance.callerActivityInstanceId = start.callerActivityInstanceId; workflowInstance.transientContext = start.transientContext; workflowInstance.setVariableValues(start.variableValues); if (log.isDebugEnabled()) log.debug("Starting " + workflowInstance); workflowInstance.setStart(Time.now()); List<Activity> startActivityDefinitions = workflow.getStartActivities(); if (startActivityDefinitions != null) { for (Activity startActivityDefinition : startActivityDefinitions) { workflowInstance.start(startActivityDefinition); } } LockImpl lock = new LockImpl(); lock.setTime(Time.now()); lock.setOwner(getId()); workflowInstance.setLock(lock); workflowInstanceStore.insertWorkflowInstance(workflowInstance); workflowInstance.workflowEngine.executeWork(workflowInstance); return workflowInstance; } public WorkflowInstanceImpl sendActivityInstanceMessage(MessageImpl message) { WorkflowInstanceQueryImpl query = newWorkflowInstanceQuery().workflowInstanceId(message.processInstanceId) .activityInstanceId(message.activityInstanceId); WorkflowInstanceImpl processInstance = lockProcessInstanceWithRetry(query); // TODO set variables and context ActivityInstanceImpl activityInstance = processInstance.findActivityInstance(message.activityInstanceId); if (activityInstance.isEnded()) { throw new RuntimeException("Activity instance " + activityInstance + " is already ended"); } if (log.isDebugEnabled()) log.debug("Signalling " + activityInstance); ActivityImpl activityDefinition = activityInstance.getActivity(); activityDefinition.activityType.message(activityInstance); processInstance.workflowEngine.executeWork(processInstance); return processInstance; } public WorkflowInstanceImpl lockProcessInstanceWithRetry(WorkflowInstanceQueryImpl query) { long wait = 50l; long attempts = 0; long maxAttempts = 4; long backoffFactor = 5; WorkflowInstanceImpl processInstance = workflowInstanceStore.lockWorkflowInstance(query); while (processInstance == null && attempts <= maxAttempts) { try { if (log.isDebugEnabled()) log.debug("Locking failed... retrying"); Thread.sleep(wait); } catch (InterruptedException e) { if (log.isDebugEnabled()) log.debug("Waiting for lock to be released was interrupted"); } wait = wait * backoffFactor; attempts++; processInstance = workflowInstanceStore.lockWorkflowInstance(query); } if (processInstance == null) { throw new RuntimeException("Couldn't lock process instance with " + query); } return processInstance; } protected WorkflowInstanceImpl createWorkflowInstance(WorkflowImpl workflow) { String processInstanceId = workflowInstanceStore.createWorkflowInstanceId(workflow); return new WorkflowInstanceImpl(this, workflow, processInstanceId); } /** instantiates and assign an id. * parent and activityDefinition are only passed for reference. * Apart from choosing the activity instance class to instantiate and assigning the id, * this method does not need to link the parent or the activityDefinition. */ public ActivityInstanceImpl createActivityInstance(ScopeInstanceImpl parent, ActivityImpl activityDefinition) { ActivityInstanceImpl activityInstance = new ActivityInstanceImpl(); activityInstance.id = workflowInstanceStore.createActivityInstanceId(); return activityInstance; } /** instantiates and assign an id. * parent and variableDefinition are only passed for reference. * Apart from choosing the variable instance class to instantiate and assigning the id, * this method does not need to link the parent or variableDefinition. */ public VariableInstanceImpl createVariableInstance(ScopeInstanceImpl parent, VariableImpl variableDefinition) { VariableInstanceImpl variableInstance = new VariableInstanceImpl(); variableInstance.id = workflowInstanceStore.createVariableInstanceId(); return variableInstance; } // process execution methods //////////////////////////////////////////////////////// public String getId() { return id; } public ServiceRegistry getServiceRegistry() { return serviceRegistry; } public JsonService getJsonService() { return jsonService; } public ExecutorService getExecutorService() { return executorService; } public WorkflowCache getProcessDefinitionCache() { return workflowCache; } public WorkflowStore getWorkflowStore() { return workflowStore; } public WorkflowInstanceStore getWorkflowInstanceStore() { return workflowInstanceStore; } public void addListener(WorkflowInstanceEventListener listener) { synchronized (listener) { listeners.add(listener); } } public void removeListener(WorkflowInstanceEventListener listener) { synchronized (listener) { listeners.remove(listener); } } public List<WorkflowInstanceEventListener> getListeners() { return Collections.unmodifiableList(listeners); } public void executeWork(final WorkflowInstanceImpl workflowInstance) { WorkflowInstanceStore workflowInstanceStore = getWorkflowInstanceStore(); boolean isFirst = true; while (workflowInstance.hasWork()) { // in the first iteration, the updates will be empty and hence no updates will be flushed if (isFirst) { isFirst = false; } else { workflowInstanceStore.flush(workflowInstance); } ActivityInstanceImpl activityInstance = workflowInstance.getNextWork(); ActivityImpl activity = activityInstance.getActivity(); if (STATE_STARTING.equals(activityInstance.workState)) { if (log.isDebugEnabled()) log.debug("Starting " + activityInstance); executeStart(activityInstance); } else if (STATE_STARTING_MULTI_INSTANCE.equals(activityInstance.workState)) { if (log.isDebugEnabled()) log.debug("Starting multi instance " + activityInstance); executeStart(activityInstance); } else if (STATE_STARTING_MULTI_CONTAINER.equals(activityInstance.workState)) { List<Object> values = activityInstance.getValue(activity.multiInstance); if (values != null && !values.isEmpty()) { if (log.isDebugEnabled()) log.debug("Starting multi container " + activityInstance); for (Object value : values) { ActivityInstanceImpl elementActivityInstance = activityInstance .createActivityInstance(activity); elementActivityInstance.setWorkState(STATE_STARTING_MULTI_INSTANCE); elementActivityInstance.initializeForEachElement(activity.multiInstanceElement, value); } } else { if (log.isDebugEnabled()) log.debug("Skipping empty multi container " + activityInstance); activityInstance.onwards(); } } else if (STATE_NOTIFYING.equals(activityInstance.workState)) { if (log.isDebugEnabled()) log.debug("Notifying parent of " + activityInstance); activityInstance.parent.ended(activityInstance); activityInstance.workState = null; } } if (workflowInstance.hasAsyncWork()) { if (log.isDebugEnabled()) log.debug("Going asynchronous " + workflowInstance.workflowInstance); workflowInstanceStore.flush(workflowInstance.workflowInstance); ExecutorService executor = getExecutorService(); executor.execute(new Runnable() { public void run() { try { workflowInstance.work = workflowInstance.workAsync; workflowInstance.workAsync = null; workflowInstance.workflowInstance.isAsync = true; if (workflowInstance.updates != null) { workflowInstance.getUpdates().isWorkChanged = true; workflowInstance.getUpdates().isAsyncWorkChanged = true; } executeWork(workflowInstance); } catch (Throwable e) { e.printStackTrace(); } } }); } else { workflowInstanceStore.flushAndUnlock(workflowInstance.workflowInstance); } } public void executeStart(ActivityInstanceImpl activityInstance) { for (WorkflowInstanceEventListener listener : listeners) { listener.started(activityInstance); } ActivityImpl activity = activityInstance.getActivity(); activity.activityType.start(activityInstance); if (ActivityInstanceImpl.START_WORKSTATES.contains(activityInstance.workState)) { activityInstance.setWorkState(ActivityInstanceImpl.STATE_WAITING); } } public void executeWorkflowInstanceEnded(WorkflowInstanceImpl workflowInstance) { if (workflowInstance.callerWorkflowInstanceId != null) { WorkflowInstanceQueryImpl processInstanceQuery = newWorkflowInstanceQuery() .workflowInstanceId(workflowInstance.callerWorkflowInstanceId) .activityInstanceId(workflowInstance.callerActivityInstanceId); WorkflowInstanceImpl callerProcessInstance = lockProcessInstanceWithRetry(processInstanceQuery); ActivityInstanceImpl callerActivityInstance = callerProcessInstance .findActivityInstance(workflowInstance.callerActivityInstanceId); if (callerActivityInstance.isEnded()) { throw new RuntimeException( "Call activity instance " + callerActivityInstance + " is already ended"); } if (log.isDebugEnabled()) log.debug("Notifying caller " + callerActivityInstance); ActivityImpl activityDefinition = callerActivityInstance.getActivity(); Call callActivity = (Call) activityDefinition.activityType; callActivity.calledProcessInstanceEnded(callerActivityInstance, workflowInstance); callerActivityInstance.onwards(); executeWork(callerProcessInstance); } } public void executeOnwards(ActivityInstanceImpl activityInstance) { if (log.isDebugEnabled()) log.debug("Onwards " + this); ActivityImpl activity = activityInstance.activityDefinition; // Default BPMN logic when an activity ends // If there are outgoing transitions (in bpmn they are called sequence flows) if (activity.hasOutgoingTransitionDefinitions()) { // Ensure that each transition is taken // Note that process concurrency does not require java concurrency activityInstance.end(false); for (TransitionImpl transitionDefinition : activity.outgoingDefinitions) { activityInstance.takeTransition(transitionDefinition); } } else { // Propagate completion upwards activityInstance.end(true); } } public void executeEnd(ActivityInstanceImpl activityInstance, boolean notifyParent) { if (activityInstance.end == null) { if (activityInstance.hasOpenActivityInstances()) { throw new RuntimeException( "Can't end this activity instance. There are open activity instances: " + activityInstance); } activityInstance.setEnd(Time.now()); for (WorkflowInstanceEventListener listener : listeners) { listener.ended(activityInstance); } if (notifyParent) { activityInstance.setWorkState(STATE_NOTIFYING); activityInstance.workflowInstance.addWork(activityInstance); } else { activityInstance.setWorkState(null); // means please archive me. } } } public void createVariableInstanceByValue(ScopeInstanceImpl scopeInstance, Object value) { VariableImpl variable = new VariableImpl(); if (value instanceof String) { variable.dataType = DataTypes.TEXT; } else if (value instanceof Number) { variable.dataType = DataTypes.NUMBER; } else { variable.dataType = new AnyDataType(); } VariableInstanceImpl variableInstance = scopeInstance.createVariableInstance(variable); variableInstance.setValue(value); } }