/* * Copyright (c) 2010-2013 Evolveum * * 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.evolveum.midpoint.wf.impl.activiti; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskExecutionStatus; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.wf.impl.activiti.dao.WorkItemProvider; import com.evolveum.midpoint.wf.impl.messages.*; import com.evolveum.midpoint.wf.impl.processes.common.ActivitiUtil; import com.evolveum.midpoint.wf.impl.tasks.WfTaskController; import com.evolveum.midpoint.wf.impl.processes.ProcessInterfaceFinder; import com.evolveum.midpoint.wf.impl.processes.common.CommonProcessVariableNames; import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemType; import org.activiti.engine.HistoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.delegate.DelegateExecution; import org.activiti.engine.delegate.DelegateTask; import org.activiti.engine.delegate.TaskListener; import org.activiti.engine.history.HistoricDetail; import org.activiti.engine.history.HistoricDetailQuery; import org.activiti.engine.history.HistoricFormProperty; import org.activiti.engine.history.HistoricVariableUpdate; import org.activiti.engine.impl.persistence.entity.ExecutionEntity; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.runtime.ProcessInstanceBuilder; import org.activiti.engine.task.IdentityLink; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; /** * Transports messages between midPoint and Activiti. (Originally via Camel, currently using direct java calls.) */ @Component public class ActivitiInterface { private static final Trace LOGGER = TraceManager.getTrace(ActivitiInterface.class); private static final String DOT_CLASS = ActivitiInterface.class.getName() + "."; @Autowired private ActivitiEngine activitiEngine; @Autowired private TaskManager taskManager; @Autowired private WfTaskController wfTaskController; @Autowired private ProcessInterfaceFinder processInterfaceFinder; @Autowired private WorkItemProvider workItemProvider; public void startActivitiProcessInstance(StartProcessCommand spic, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { String owner = spic.getProcessOwner(); if (owner != null) { activitiEngine.getIdentityService().setAuthenticatedUserId(owner); } RuntimeService rs = activitiEngine.getProcessEngine().getRuntimeService(); ProcessInstanceBuilder builder = rs.createProcessInstanceBuilder() .processDefinitionKey(spic.getProcessName()) .processInstanceName(spic.getProcessInstanceName()); for (Map.Entry<String, Object> varEntry : spic.getVariables().entrySet()) { builder.addVariable(varEntry.getKey(), varEntry.getValue()); } ProcessInstance pi = builder.start(); if (spic.isSendStartConfirmation()) { // let us send a reply back (useful for listener-free processes) ProcessStartedEvent event = new ProcessStartedEvent( pi.getProcessInstanceId(), ((ExecutionEntity) pi).getVariables(), // a bit of hack... processInterfaceFinder); event.setRunning(!pi.isEnded()); LOGGER.trace("Event to be sent to IDM: {}", event); wfTaskController.onProcessEvent(event, task, result); } } public void queryActivitiProcessInstance(QueryProcessCommand qpc, Task task, OperationResult result) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException { String pid = qpc.getPid(); LOGGER.trace("Querying process instance id {}", pid); HistoryService hs = activitiEngine.getHistoryService(); HistoricDetailQuery hdq = hs.createHistoricDetailQuery() .variableUpdates() .processInstanceId(pid) .orderByTime().desc(); Map<String,Object> variables = new HashMap<>(); for (HistoricDetail hd : hdq.list()) { HistoricVariableUpdate hvu = (HistoricVariableUpdate) hd; String varname = hvu.getVariableName(); Object value = hvu.getValue(); LOGGER.trace(" - found historic variable update: {} <- {}", varname, value); if (!variables.containsKey(varname)) { variables.put(varname, value); } } HistoricDetailQuery hdq2 = hs.createHistoricDetailQuery() .formProperties() .processInstanceId(pid) .orderByVariableRevision().desc(); for (HistoricDetail hd : hdq2.list()) { HistoricFormProperty hfp = (HistoricFormProperty) hd; String varname = hfp.getPropertyId(); Object value = hfp.getPropertyValue(); LOGGER.trace(" - found historic form property: {} <- {}", varname, value); variables.put(varname, value); } QueryProcessResponse qpr = new QueryProcessResponse(pid, variables, processInterfaceFinder); ProcessInstance pi = activitiEngine.getProcessEngine().getRuntimeService().createProcessInstanceQuery().processInstanceId(pid).singleResult(); qpr.setRunning(pi != null && !pi.isEnded()); LOGGER.trace("Running process instance = {}, isRunning: {}", pi, qpr.isRunning()); LOGGER.trace("Response to be sent to midPoint: {}", qpr); wfTaskController.onProcessEvent(qpr, task, result); } public void notifyMidpointAboutProcessFinishedEvent(DelegateExecution execution) { notifyMidpointAboutProcessEvent(new ProcessFinishedEvent(execution, processInterfaceFinder)); } public void notifyMidpointAboutProcessEvent(DelegateExecution execution) { notifyMidpointAboutProcessEvent(new ProcessEvent(execution, processInterfaceFinder)); } private void notifyMidpointAboutProcessEvent(ProcessEvent event) { OperationResult result = new OperationResult(DOT_CLASS + "notifyMidpointAboutProcessEvent"); String taskOid = ActivitiUtil.getTaskOid(event.getVariables()); Task task; try { task = taskManager.getTask(taskOid, result); } catch (ObjectNotFoundException|SchemaException|RuntimeException e) { throw new SystemException("Couldn't get task " + taskOid + " from repository: " + e.getMessage(), e); } if (task.getExecutionStatus() != TaskExecutionStatus.WAITING) { LOGGER.trace("Asynchronous message received in a state different from WAITING (actual state: {}), ignoring it. Task = {}", task.getExecutionStatus(), task); return; } try { wfTaskController.onProcessEvent(event, task, result); } catch (SchemaException|ObjectNotFoundException|ObjectAlreadyExistsException|RuntimeException e) { throw new SystemException("Couldn't process a process-related event: " + e.getMessage(), e); } } public void notifyMidpointAboutTaskEvent(DelegateTask delegateTask) { OperationResult result = new OperationResult(DOT_CLASS + "notifyMidpointAboutTaskEvent"); TaskEvent taskEvent; if (TaskListener.EVENTNAME_CREATE.equals(delegateTask.getEventName())) { taskEvent = new TaskCreatedEvent(); // TODO distinguish created vs. assigned event } else if (TaskListener.EVENTNAME_COMPLETE.equals(delegateTask.getEventName())) { taskEvent = new TaskCompletedEvent(); } else if (TaskListener.EVENTNAME_DELETE.equals(delegateTask.getEventName())) { taskEvent = new TaskDeletedEvent(); } else { return; // ignoring other events } taskEvent.setVariables(delegateTask.getVariables()); taskEvent.setAssigneeOid(delegateTask.getAssignee()); taskEvent.setTaskId(delegateTask.getId()); taskEvent.setTaskName(delegateTask.getName()); taskEvent.setProcessInstanceName((String) delegateTask.getVariable(CommonProcessVariableNames.VARIABLE_PROCESS_INSTANCE_NAME)); taskEvent.setProcessInstanceId(delegateTask.getProcessInstanceId()); taskEvent.setCreateTime(delegateTask.getCreateTime()); taskEvent.setDueDate(delegateTask.getDueDate()); taskEvent.setExecutionId(delegateTask.getExecutionId()); taskEvent.setOwner(delegateTask.getOwner()); for (IdentityLink identityLink : delegateTask.getCandidates()) { if (identityLink.getUserId() != null) { taskEvent.getCandidateUsers().add(identityLink.getUserId()); } else if (identityLink.getGroupId() != null) { taskEvent.getCandidateGroups().add(identityLink.getGroupId()); } else { throw new IllegalStateException("Neither candidate user nor group id is provided in delegateTask: " + delegateTask); } } try { WorkItemType workItem = workItemProvider.taskEventToWorkItemNew(taskEvent, null, true, true, true, result); wfTaskController.onTaskEvent(workItem, taskEvent, result); } catch (Exception e) { // todo fix the exception processing e.g. think about situation where an event cannot be audited - should we allow to proceed? String message = "Couldn't process an event coming from the workflow management system"; LoggingUtils.logUnexpectedException(LOGGER, message, e); result.recordFatalError(message, e); } } }