/* * Copyright (c) 2010-2016 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.dao; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.util.CloneUtil; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.schema.DeltaConvertor; import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.WfContextUtil; import com.evolveum.midpoint.security.api.MidPointPrincipal; import com.evolveum.midpoint.security.api.SecurityEnforcer; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.wf.api.WorkItemAllocationChangeOperationInfo; import com.evolveum.midpoint.wf.api.WorkItemOperationSourceInfo; import com.evolveum.midpoint.wf.api.WorkflowManager; import com.evolveum.midpoint.wf.impl.activiti.ActivitiEngine; import com.evolveum.midpoint.wf.impl.processes.common.ActivitiUtil; import com.evolveum.midpoint.wf.impl.processes.common.CommonProcessVariableNames; import com.evolveum.midpoint.wf.impl.processes.itemApproval.MidpointUtil; import com.evolveum.midpoint.wf.impl.tasks.WfTaskController; import com.evolveum.midpoint.wf.impl.util.MiscDataUtil; import com.evolveum.midpoint.wf.impl.util.SingleItemSerializationSafeContainerImpl; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import org.activiti.engine.TaskService; import org.activiti.engine.task.IdentityLink; import org.activiti.engine.task.IdentityLinkType; import org.activiti.engine.task.Task; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; import java.util.*; import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.toShortString; import static com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemOperationKindType.DELEGATE; import static com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemOperationKindType.ESCALATE; /** * @author mederly */ @Component public class WorkItemManager { private static final transient Trace LOGGER = TraceManager.getTrace(WorkItemManager.class); @Autowired private ActivitiEngine activitiEngine; @Autowired private MiscDataUtil miscDataUtil; @Autowired private SecurityEnforcer securityEnforcer; @Autowired private PrismContext prismContext; @Autowired private WorkItemProvider workItemProvider; @Autowired private WfTaskController wfTaskController; @Autowired private TaskManager taskManager; private static final String DOT_INTERFACE = WorkflowManager.class.getName() + "."; private static final String OPERATION_COMPLETE_WORK_ITEM = DOT_INTERFACE + "completeWorkItem"; private static final String OPERATION_CLAIM_WORK_ITEM = DOT_INTERFACE + "claimWorkItem"; private static final String OPERATION_RELEASE_WORK_ITEM = DOT_INTERFACE + "releaseWorkItem"; private static final String OPERATION_DELEGATE_WORK_ITEM = DOT_INTERFACE + "delegateWorkItem"; public void completeWorkItem(String workItemId, String outcome, String comment, ObjectDelta additionalDelta, WorkItemEventCauseInformationType causeInformation, OperationResult parentResult) throws SecurityViolationException, SchemaException { OperationResult result = parentResult.createSubresult(OPERATION_COMPLETE_WORK_ITEM); result.addParams(new String[] { "workItemId", "decision", "comment", "additionalDelta" }, workItemId, outcome, comment, additionalDelta); try { final String userDescription = toShortString(securityEnforcer.getPrincipal().getUser()); result.addContext("user", userDescription); LOGGER.trace("Completing work item {} with decision of {} ['{}'] by {}; cause: {}", workItemId, outcome, comment, userDescription, causeInformation); TaskService taskService = activitiEngine.getTaskService(); taskService.setVariableLocal(workItemId, CommonProcessVariableNames.VARIABLE_CAUSE, new SingleItemSerializationSafeContainerImpl<>(causeInformation, prismContext)); //TaskFormData data = activitiEngine.getFormService().getTaskFormData(workItemId); WorkItemType workItem = workItemProvider.getWorkItem(workItemId, result); if (!miscDataUtil.isAuthorized(workItem, MiscDataUtil.RequestedOperation.COMPLETE)) { throw new SecurityViolationException("You are not authorized to complete this work item."); } final Map<String, String> propertiesToSubmit = new HashMap<>(); propertiesToSubmit.put(CommonProcessVariableNames.FORM_FIELD_OUTCOME, outcome); propertiesToSubmit.put(CommonProcessVariableNames.FORM_FIELD_COMMENT, comment); if (additionalDelta != null) { @SuppressWarnings({ "unchecked", "raw" }) ObjectDelta<? extends ObjectType> additionalDeltaCasted = ((ObjectDelta<? extends ObjectType>) additionalDelta); ObjectDeltaType objectDeltaType = DeltaConvertor.toObjectDeltaType(additionalDeltaCasted); String xmlDelta = prismContext.xmlSerializer() .serializeRealValue(objectDeltaType, SchemaConstants.T_OBJECT_DELTA); propertiesToSubmit.put(CommonProcessVariableNames.FORM_FIELD_ADDITIONAL_DELTA, xmlDelta); } LOGGER.trace("Submitting {} properties", propertiesToSubmit.size()); //formService.submitTaskFormData(workItemId, propertiesToSubmit); Map<String, Object> variables = new HashMap<>(propertiesToSubmit); taskService.complete(workItemId, variables, true); } catch (SecurityViolationException|SchemaException|RuntimeException e) { result.recordFatalError("Couldn't complete the work item " + workItemId + ": " + e.getMessage(), e); throw e; } finally { result.computeStatusIfUnknown(); } } public void claimWorkItem(String workItemId, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException { OperationResult result = parentResult.createSubresult(OPERATION_CLAIM_WORK_ITEM); result.addParam("workItemId", workItemId); try { MidPointPrincipal principal = securityEnforcer.getPrincipal(); result.addContext("user", toShortString(principal.getUser())); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Claiming work item {} by {}", workItemId, toShortString(principal.getUser())); } TaskService taskService = activitiEngine.getTaskService(); Task task = taskService.createTaskQuery().taskId(workItemId).singleResult(); if (task == null) { throw new ObjectNotFoundException("The work item does not exist"); } if (task.getAssignee() != null) { String desc = MiscDataUtil.stringToRef(task.getAssignee()).getOid().equals(principal.getOid()) ? "the current" : "another"; throw new SystemException("The work item is already assigned to "+desc+" user"); } if (!miscDataUtil.isAuthorizedToClaim(task.getId())) { throw new SecurityViolationException("You are not authorized to claim the selected work item."); } taskService.claim(workItemId, principal.getOid()); task = taskService.createTaskQuery().taskId(workItemId).singleResult(); if (task == null) { throw new ObjectNotFoundException("The work item does not exist"); } setNewAssignees(task, Collections.singletonList(ObjectTypeUtil.createObjectRef(principal.getOid(), ObjectTypes.USER)), taskService); } catch (ObjectNotFoundException|SecurityViolationException|RuntimeException e) { result.recordFatalError("Couldn't claim the work item " + workItemId + ": " + e.getMessage(), e); throw e; } finally { result.computeStatusIfUnknown(); } } public void releaseWorkItem(String workItemId, OperationResult parentResult) throws ObjectNotFoundException, SecurityViolationException { OperationResult result = parentResult.createSubresult(OPERATION_RELEASE_WORK_ITEM); result.addParam("workItemId", workItemId); try { MidPointPrincipal principal = securityEnforcer.getPrincipal(); result.addContext("user", toShortString(principal.getUser())); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Releasing work item {} by {}", workItemId, toShortString(principal.getUser())); } TaskService taskService = activitiEngine.getTaskService(); Task task = taskService.createTaskQuery().taskId(workItemId).singleResult(); if (task == null) { throw new ObjectNotFoundException("The work item does not exist"); } if (task.getAssignee() == null) { throw new SystemException("The work item is not assigned to a user"); } if (!MiscDataUtil.stringToRef(task.getAssignee()).getOid().equals(principal.getOid())) { throw new SystemException("The work item is not assigned to the current user"); } boolean candidateFound = false; for (IdentityLink link : taskService.getIdentityLinksForTask(workItemId)) { if (IdentityLinkType.CANDIDATE.equals(link.getType())) { candidateFound = true; break; } } if (!candidateFound) { throw new SystemException("It has no candidates to be offered to"); } taskService.unclaim(workItemId); task = taskService.createTaskQuery().taskId(workItemId).singleResult(); if (task == null) { throw new ObjectNotFoundException("The work item does not exist"); } setNewAssignees(task, Collections.emptyList(), taskService); } catch (ObjectNotFoundException|SecurityViolationException|RuntimeException e) { result.recordFatalError("Couldn't release work item " + workItemId + ": " + e.getMessage(), e); throw e; } finally { result.computeStatusIfUnknown(); } } // TODO when calling from model API, what should we put into escalationLevelName+DisplayName ? // Probably the API should look different. E.g. there could be an "Escalate" button, that would look up the // appropriate escalation timed action, and invoke it. We'll solve this when necessary. Until that time, be // aware that escalationLevelName/DisplayName are for internal use only. public void delegateWorkItem(String workItemId, List<ObjectReferenceType> delegates, WorkItemDelegationMethodType method, WorkItemEscalationLevelType escalation, Duration newDuration, WorkItemEventCauseInformationType causeInformation, OperationResult parentResult) throws ObjectNotFoundException, SecurityViolationException, SchemaException { OperationResult result = parentResult.createSubresult(OPERATION_DELEGATE_WORK_ITEM); result.addParam("workItemId", workItemId); result.addParam("escalation", escalation); result.addCollectionOfSerializablesAsParam("delegates", delegates); try { MidPointPrincipal principal = securityEnforcer.getPrincipal(); result.addContext("user", toShortString(principal.getUser())); ObjectReferenceType initiator = causeInformation == null || causeInformation.getType() == WorkItemEventCauseTypeType.USER_ACTION ? ObjectTypeUtil.createObjectRef(principal.getUser()) : null; LOGGER.trace("Delegating work item {} to {}: escalation={}; cause={}", workItemId, delegates, escalation != null ? escalation.getName() + "/" + escalation.getDisplayName() : "none", causeInformation); WorkItemType workItem = workItemProvider.getWorkItem(workItemId, result); if (!miscDataUtil.isAuthorized(workItem, MiscDataUtil.RequestedOperation.DELEGATE)) { throw new SecurityViolationException("You are not authorized to delegate this work item."); } List<ObjectReferenceType> assigneesBefore = CloneUtil.cloneCollectionMembers(workItem.getAssigneeRef()); WorkItemOperationKindType operationKind = escalation != null ? ESCALATE : DELEGATE; com.evolveum.midpoint.task.api.Task wfTask = taskManager.getTask(WfContextUtil.getTask(workItem).getOid(), result); WorkItemAllocationChangeOperationInfo operationInfoBefore = new WorkItemAllocationChangeOperationInfo(operationKind, assigneesBefore, null); WorkItemOperationSourceInfo sourceInfo = new WorkItemOperationSourceInfo(initiator, causeInformation, null); wfTaskController.notifyWorkItemAllocationChangeCurrentActors(workItem, operationInfoBefore, sourceInfo, null, wfTask, result); if (method == null) { method = WorkItemDelegationMethodType.REPLACE_ASSIGNEES; } List<ObjectReferenceType> newAssignees = new ArrayList<>(); List<ObjectReferenceType> delegatedTo = new ArrayList<>(); WfContextUtil.computeAssignees(newAssignees, delegatedTo, delegates, method, workItem); // don't change the current assignee, if not necessary TaskService taskService = activitiEngine.getTaskService(); Task task = taskService.createTaskQuery().taskId(workItemId).singleResult(); setNewAssignees(task, newAssignees, taskService); Date deadline = task.getDueDate(); if (newDuration != null) { deadline = setNewDuration(task.getId(), newDuration, taskService); } Map<String, Object> variables = taskService.getVariables(workItemId); int escalationLevel = WfContextUtil.getEscalationLevelNumber(workItem); WorkItemEscalationLevelType newEscalation = WfContextUtil.createNewEscalation(escalationLevel, escalation); WorkItemDelegationEventType event = WfContextUtil.createDelegationEvent(newEscalation, assigneesBefore, delegatedTo, method, causeInformation); if (newEscalation != null) { escalationLevel++; taskService.setVariableLocal(workItemId, CommonProcessVariableNames.VARIABLE_ESCALATION_LEVEL_NUMBER, newEscalation.getNumber()); taskService.setVariableLocal(workItemId, CommonProcessVariableNames.VARIABLE_ESCALATION_LEVEL_NAME, newEscalation.getName()); taskService.setVariableLocal(workItemId, CommonProcessVariableNames.VARIABLE_ESCALATION_LEVEL_DISPLAY_NAME, newEscalation.getDisplayName()); } ActivitiUtil.fillInWorkItemEvent(event, principal, workItemId, variables, prismContext); MidpointUtil.recordEventInTask(event, null, ActivitiUtil.getTaskOid(variables), result); ApprovalStageDefinitionType level = WfContextUtil.getCurrentStageDefinition(wfTask.getWorkflowContext()); MidpointUtil.createTriggersForTimedActions(workItemId, escalationLevel, XmlTypeConverter.toDate(workItem.getCreateTimestamp()), deadline, wfTask, level.getTimedActions(), result); WorkItemType workItemAfter = workItemProvider.getWorkItem(workItemId, result); com.evolveum.midpoint.task.api.Task wfTaskAfter = taskManager.getTask(wfTask.getOid(), result); WorkItemAllocationChangeOperationInfo operationInfoAfter = new WorkItemAllocationChangeOperationInfo(operationKind, assigneesBefore, workItemAfter.getAssigneeRef()); wfTaskController.notifyWorkItemAllocationChangeNewActors(workItemAfter, operationInfoAfter, sourceInfo, wfTaskAfter, result); } catch (SecurityViolationException|RuntimeException|ObjectNotFoundException|SchemaException e) { result.recordFatalError("Couldn't delegate/escalate work item " + workItemId + ": " + e.getMessage(), e); throw e; } finally { result.computeStatusIfUnknown(); } } private Date setNewDuration(String workItemId, @NotNull Duration newDuration, TaskService taskService) { XMLGregorianCalendar newDeadline = XmlTypeConverter.createXMLGregorianCalendar(new Date()); newDeadline.add(newDuration); Date dueDate = XmlTypeConverter.toDate(newDeadline); taskService.setDueDate(workItemId, dueDate); return dueDate; } private void setNewAssignees(Task task, List<ObjectReferenceType> assigneeRefList, TaskService taskService) { List<String> assignees = MiscDataUtil.refsToStrings(assigneeRefList); // check and optionally set task assignee if (task.getAssignee() != null && !assignees.contains(task.getAssignee()) || task.getAssignee() == null && !assignees.isEmpty()) { // we will nominate the first delegate (if present) as a new assignee taskService.setAssignee(task.getId(), !assignees.isEmpty() ? assignees.get(0) : null); } // set task identity links List<IdentityLink> currentLinks = taskService.getIdentityLinksForTask(task.getId()); for (IdentityLink currentLink : currentLinks) { if (!CommonProcessVariableNames.MIDPOINT_ASSIGNEE.equals(currentLink.getType())) { continue; } String assigneeFromLink = currentLink.getUserId() != null ? currentLink.getUserId() : currentLink.getGroupId(); if (assignees.contains(assigneeFromLink)) { assignees.remove(assigneeFromLink); } else { if (currentLink.getUserId() != null) { taskService.deleteUserIdentityLink(task.getId(), currentLink.getUserId(), CommonProcessVariableNames.MIDPOINT_ASSIGNEE); } else { taskService.deleteGroupIdentityLink(task.getId(), currentLink.getGroupId(), CommonProcessVariableNames.MIDPOINT_ASSIGNEE); } } } // process remaining assignees for (String assignee : assignees) { ObjectReferenceType assigneeRef = MiscDataUtil.stringToRef(assignee); if (assigneeRef.getType() == null || QNameUtil.match(UserType.COMPLEX_TYPE, assigneeRef.getType())) { taskService.addUserIdentityLink(task.getId(), assignee, CommonProcessVariableNames.MIDPOINT_ASSIGNEE); } else { taskService.addGroupIdentityLink(task.getId(), assignee, CommonProcessVariableNames.MIDPOINT_ASSIGNEE); } } } }