/* * Copyright (c) 2010-2017 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.schema.util; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.schema.SchemaRegistry; import com.evolveum.midpoint.prism.util.CloneUtil; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; import java.util.*; import java.util.stream.Collectors; /** * TODO clean up these formatting methods * * @author mederly */ public class WfContextUtil { @Nullable public static String getStageInfo(WfContextType wfc) { if (wfc == null || hasFinished(wfc)) { return null; } return getStageInfo(wfc.getStageNumber(), getStageCount(wfc), getStageName(wfc), getStageDisplayName(wfc)); } @Nullable public static String getStageInfo(WorkItemType workItem) { if (workItem == null) { return null; } return getStageInfo(getWorkflowContext(workItem)); } public static Integer getStageCount(WorkItemType workItem) { return getStageCount(getWorkflowContext(workItem)); } public static String getStageName(WorkItemType workItem) { return getStageName(getWorkflowContext(workItem)); } public static String getStageName(WfContextType wfc) { ApprovalStageDefinitionType def = getCurrentStageDefinition(wfc); return def != null ? def.getName() : null; } public static String getStageDisplayName(WfContextType wfc) { ApprovalStageDefinitionType def = getCurrentStageDefinition(wfc); return def != null ? def.getDisplayName() : null; } public static ApprovalSchemaType getApprovalSchema(WfContextType wfc) { ItemApprovalProcessStateType info = getItemApprovalProcessInfo(wfc); return info != null ? info.getApprovalSchema() : null; } public static Integer getStageCount(WfContextType wfc) { ApprovalSchemaType schema = getApprovalSchema(wfc); return schema != null ? schema.getStage().size() : null; } public static String getStageDisplayName(WorkItemType workItem) { return getStageDisplayName(getWorkflowContext(workItem)); } // wfc is used to retrieve approval schema (if needed) public static String getStageInfo(Integer stageNumber, Integer stageCount, String stageName, String stageDisplayName) { String name = stageDisplayName != null ? stageDisplayName : stageName; if (name == null && stageNumber == null) { return null; } StringBuilder sb = new StringBuilder(); if (name != null) { sb.append(name); } appendNumber(stageNumber, stageCount, sb); return sb.toString(); } @Nullable public static String getEscalationLevelInfo(AbstractWorkItemType workItem) { if (workItem == null) { return null; } return getEscalationLevelInfo(workItem.getEscalationLevel()); } // TODO move to better place public static String getEscalationLevelInfo(WorkItemEscalationLevelType e) { if (e == null || e.getNumber() == null || e.getNumber() == 0) { return null; } String name = e.getDisplayName() != null ? e.getDisplayName() : e.getName(); if (name != null) { return name + " (" + e.getNumber() + ")"; } else { return String.valueOf(e.getNumber()); } } public static boolean hasFinished(WfContextType wfc) { return wfc.getEndTimestamp() != null; } @Nullable public static String getCompleteStageInfo(WfContextType wfc) { if (wfc == null || hasFinished(wfc)) { return null; } Integer stageNumber = wfc.getStageNumber(); String stageName = getStageName(wfc); String stageDisplayName = getStageDisplayName(wfc); if (stageNumber == null && stageName == null && stageDisplayName == null) { return null; } StringBuilder sb = new StringBuilder(); if (stageName != null && stageDisplayName != null) { sb.append(stageName).append(" (").append(stageDisplayName).append(")"); } else if (stageName != null) { sb.append(stageName); } else if (stageDisplayName != null) { sb.append(stageDisplayName); } appendNumber(stageNumber, getStageCount(wfc), sb); return sb.toString(); } private static void appendNumber(Integer stageNumber, Integer stageCount, StringBuilder sb) { if (stageNumber != null) { boolean parentheses = sb.length() > 0; if (parentheses) { sb.append(" ("); } sb.append(stageNumber); if (stageCount != null) { sb.append("/").append(stageCount); } if (parentheses) { sb.append(")"); } } } public static ItemApprovalProcessStateType getItemApprovalProcessInfo(WfContextType wfc) { if (wfc == null) { return null; } WfProcessSpecificStateType processSpecificState = wfc.getProcessSpecificState(); return processSpecificState instanceof ItemApprovalProcessStateType ? (ItemApprovalProcessStateType) processSpecificState : null; } public static WfPrimaryChangeProcessorStateType getPrimaryChangeProcessorState(WfContextType wfc) { if (wfc == null) { return null; } WfProcessorSpecificStateType state = wfc.getProcessorSpecificState(); return state instanceof WfPrimaryChangeProcessorStateType ? (WfPrimaryChangeProcessorStateType) state : null; } public static ItemApprovalWorkItemPartType getItemApprovalWorkItemInfo(WorkItemType workItem) { return workItem.getProcessSpecificPart() instanceof ItemApprovalWorkItemPartType ? (ItemApprovalWorkItemPartType) workItem.getProcessSpecificPart() : null; } @NotNull public static List<SchemaAttachedPolicyRuleType> getAttachedPolicyRules(WfContextType workflowContext, int order) { ItemApprovalProcessStateType info = getItemApprovalProcessInfo(workflowContext); if (info == null || info.getPolicyRules() == null) { return Collections.emptyList(); } return info.getPolicyRules().getEntry().stream() .filter(e -> e.getStageMax() != null && e.getStageMax() != null && order >= e.getStageMin() && order <= e.getStageMax()) .collect(Collectors.toList()); } public static ApprovalStageDefinitionType getCurrentStageDefinition(WfContextType wfc) { if (wfc == null || wfc.getStageNumber() == null) { return null; } return getStageDefinition(wfc, wfc.getStageNumber()); } // expects already normalized definition (using non-deprecated items, numbering stages from 1 to N) public static ApprovalStageDefinitionType getStageDefinition(WfContextType wfc, int stageNumber) { ItemApprovalProcessStateType info = getItemApprovalProcessInfo(wfc); if (info == null || info.getApprovalSchema() == null) { return null; } ApprovalSchemaType approvalSchema = info.getApprovalSchema(); List<ApprovalStageDefinitionType> stages = approvalSchema.getStage().stream() .filter(level -> level.getNumber() != null && level.getNumber() == stageNumber) .collect(Collectors.toList()); if (stages.size() > 1) { throw new IllegalStateException("More than one level with order of " + stageNumber + ": " + stages); } else if (stages.isEmpty()) { return null; } else { return stages.get(0); } } private static List<ApprovalStageDefinitionType> getStages(ApprovalSchemaType approvalSchema) { return !approvalSchema.getStage().isEmpty() ? approvalSchema.getStage() : approvalSchema.getLevel(); } // we must be strict here; in case of suspicion, throw an exception @SuppressWarnings("unchecked") public static <T extends CaseEventType> List<T> getEventsForCurrentStage(@NotNull WfContextType wfc, @NotNull Class<T> clazz) { if (wfc.getStageNumber() == null) { throw new IllegalArgumentException("No stage number in workflow context; pid = " + wfc.getProcessInstanceId()); } int stageNumber = wfc.getStageNumber(); return wfc.getEvent().stream() .filter(e -> clazz.isAssignableFrom(e.getClass()) && e.getStageNumber() != null && stageNumber == e.getStageNumber()) .map(e -> (T) e) .collect(Collectors.toList()); } @SuppressWarnings("unchecked") public static <T extends CaseEventType> List<T> getEvents(@NotNull WfContextType wfc, @NotNull Class<T> clazz) { return wfc.getEvent().stream() .filter(e -> clazz.isAssignableFrom(e.getClass())) .map(e -> (T) e) .collect(Collectors.toList()); } public static <T extends WorkItemEventType> List<T> getWorkItemEvents(@NotNull WfContextType wfc, @NotNull String workItemId, Class<T> clazz) { return wfc.getEvent().stream() .filter(e -> clazz.isAssignableFrom(e.getClass()) && workItemId.equals(((WorkItemEventType) e).getExternalWorkItemId())) .map(e -> (T) e) .collect(Collectors.toList()); } public static String getBriefDiagInfo(WfContextType wfc) { if (wfc == null) { return "null"; } return "pid: " + wfc.getProcessInstanceId() + ", name: " + wfc.getProcessInstanceName() + ", stage: " + wfc.getStageNumber(); } @NotNull public static String getCurrentStageOutcome(WfContextType wfc, List<StageCompletionEventType> stageEvents) { if (stageEvents.size() > 1) { throw new IllegalStateException("More than one stage-level event in " + getBriefDiagInfo(wfc) + ": " + stageEvents); } StageCompletionEventType event = stageEvents.get(0); if (event.getOutcome() == null) { throw new IllegalStateException("No outcome for stage-level event in " + getBriefDiagInfo(wfc)); } return event.getOutcome(); } // expects normalized definition public static String getStageDiagName(ApprovalStageDefinitionType level) { return level.getNumber() + ":" + level.getName() + (level.getDisplayName() != null ? " (" + level.getDisplayName() + ")" : ""); } public static void normalizeStages(ApprovalSchemaType schema) { // Sorting uses set(..) method which is not available on prism structures. So we do sort on a copy (ArrayList). List<ApprovalStageDefinitionType> stages = new ArrayList<>(getStages(schema)); stages.sort(Comparator.comparing(stage -> getNumber(stage), Comparator.nullsLast(Comparator.naturalOrder()))); for (int i = 0; i < stages.size(); i++) { stages.get(i).setOrder(null); stages.get(i).setNumber(i+1); } schema.getLevel().clear(); schema.getStage().clear(); schema.getStage().addAll(CloneUtil.cloneCollectionMembers(stages)); } private static Integer getNumber(ApprovalStageDefinitionType stage) { return stage.getNumber() != null ? stage.getNumber() : stage.getOrder(); } // public static void checkLevelsOrdering(ApprovalSchemaType schema) { // for (int i = 0; i < schema.getLevel().size(); i++) { // ApprovalStageDefinitionType level = schema.getLevel().get(i); // if (level.getOrder() == null) { // throw new IllegalStateException("Level without order: " + level); // } // if (i > 0 && schema.getLevel().get(i-1).getOrder() >= level.getOrder()) { // throw new IllegalStateException("Level #" + i + " is not before level #" + (i+1) + " in " + schema); // } // } // } // // public static void checkLevelsOrderingStrict(ApprovalSchemaType schema) { // for (int i = 0; i < schema.getLevel().size(); i++) { // Integer order = schema.getLevel().get(i).getOrder(); // if (order == null || order != i+1) { // throw new IllegalStateException("Level #" + (i+1) + " has an incorrect order: " + order + " in " + schema); // } // } // } public static OperationBusinessContextType getBusinessContext(WfContextType wfc) { if (wfc == null) { return null; } for (CaseEventType event : wfc.getEvent()) { if (event instanceof CaseCreationEventType) { return ((CaseCreationEventType) event).getBusinessContext(); } } return null; } // TODO take from the workflow context! public static String getStageInfoTODO(Integer stageNumber) { return getStageInfo(stageNumber, null, null, null); } public static String getProcessInstanceId(WorkItemType workItem) { return getWorkflowContext(workItem).getProcessInstanceId(); } public static WfContextType getWorkflowContext(WorkItemType workItem) { PrismContainerValue<?> parent = PrismContainerValue.getParentContainerValue(workItem.asPrismContainerValue()); if (parent == null) { throw new IllegalStateException("No containing workflow context for " + workItem); } Containerable parentReal = parent.asContainerable(); if (!(parentReal instanceof WfContextType)) { throw new IllegalStateException("WorkItem's parent is not a WfContextType; it is " + parentReal); } return (WfContextType) parentReal; } public static TaskType getTask(WorkItemType workItem) { return getTask(getWorkflowContext(workItem)); } public static TaskType getTask(WfContextType wfc) { PrismContainerValue<?> parent = PrismContainerValue.getParentContainerValue(wfc.asPrismContainerValue()); if (parent == null) { throw new IllegalStateException("No containing task for " + wfc); } Containerable parentReal = parent.asContainerable(); if (!(parentReal instanceof TaskType)) { throw new IllegalStateException("WfContextType's parent is not a TaskType; it is " + parentReal); } return (TaskType) parentReal; } public static ObjectReferenceType getObjectRef(WorkItemType workItem) { return getWorkflowContext(workItem).getObjectRef(); } public static ObjectReferenceType getTargetRef(WorkItemType workItem) { return getWorkflowContext(workItem).getTargetRef(); } public static int getEscalationLevelNumber(AbstractWorkItemType workItem) { return getEscalationLevelNumber(workItem.getEscalationLevel()); } public static int getEscalationLevelNumber(WorkItemEscalationLevelType level) { return level != null && level.getNumber() != null ? level.getNumber() : 0; } public static String getEscalationLevelName(WorkItemEscalationLevelType level) { return level != null ? level.getName() : null; } public static String getEscalationLevelDisplayName(WorkItemEscalationLevelType level) { return level != null ? level.getDisplayName() : null; } public static String getEscalationLevelName(AbstractWorkItemType workItem) { return getEscalationLevelName(workItem.getEscalationLevel()); } public static String getEscalationLevelDisplayName(AbstractWorkItemType workItem) { return getEscalationLevelDisplayName(workItem.getEscalationLevel()); } public static WorkItemEscalationLevelType createEscalationLevel(Integer number, String name, String displayName) { if ((number != null && number != 0) || name != null || displayName != null) { return new WorkItemEscalationLevelType().number(number).name(name).displayName(displayName); } else { return null; } } public static Integer getEscalationLevelNumber(WorkItemEventType event) { return getEscalationLevelNumber(event.getEscalationLevel()); } public static WfContextType getWorkflowContext(PrismObject<TaskType> task) { return task != null ? task.asObjectable().getWorkflowContext() : null; } // TODO better place @NotNull public static WorkItemEventCauseInformationType createCause(AbstractWorkItemActionType action) { WorkItemEventCauseInformationType cause = new WorkItemEventCauseInformationType(); cause.setType(WorkItemEventCauseTypeType.TIMED_ACTION); if (action != null) { cause.setName(action.getName()); cause.setDisplayName(action.getDisplayName()); } return cause; } // TODO better place @Nullable public static WorkItemOperationKindType getOperationKind(AbstractWorkItemActionType action) { WorkItemOperationKindType operationKind; if (action instanceof EscalateWorkItemActionType) { operationKind = WorkItemOperationKindType.ESCALATE; } else if (action instanceof DelegateWorkItemActionType) { operationKind = WorkItemOperationKindType.DELEGATE; } else if (action instanceof CompleteWorkItemActionType) { operationKind = WorkItemOperationKindType.COMPLETE; } else { // shouldn't occur operationKind = null; } return operationKind; } @NotNull public static WorkItemEscalationLevelType createEscalationLevelInformation(DelegateWorkItemActionType delegateAction) { String escalationLevelName; String escalationLevelDisplayName; if (delegateAction instanceof EscalateWorkItemActionType) { escalationLevelName = ((EscalateWorkItemActionType) delegateAction).getEscalationLevelName(); escalationLevelDisplayName = ((EscalateWorkItemActionType) delegateAction).getEscalationLevelDisplayName(); if (escalationLevelName == null && escalationLevelDisplayName == null) { escalationLevelName = delegateAction.getName(); escalationLevelDisplayName = delegateAction.getDisplayName(); } } else { // TODO ... a warning here? escalationLevelName = escalationLevelDisplayName = null; } return new WorkItemEscalationLevelType().name(escalationLevelName).displayName(escalationLevelDisplayName); } // TODO rethink interface of this method // returns parent-less values public static void computeAssignees(List<ObjectReferenceType> newAssignees, List<ObjectReferenceType> delegatedTo, List<ObjectReferenceType> delegates, WorkItemDelegationMethodType method, AbstractWorkItemType workItem) { newAssignees.clear(); delegatedTo.clear(); switch (method) { case ADD_ASSIGNEES: newAssignees.addAll(CloneUtil.cloneCollectionMembers(workItem.getAssigneeRef())); break; case REPLACE_ASSIGNEES: break; default: throw new UnsupportedOperationException("Delegation method " + method + " is not supported yet."); } for (ObjectReferenceType delegate : delegates) { if (delegate.getType() != null && !QNameUtil.match(UserType.COMPLEX_TYPE, delegate.getType())) { throw new IllegalArgumentException("Couldn't use non-user object as a delegate: " + delegate); } if (delegate.getOid() == null) { throw new IllegalArgumentException("Couldn't use no-OID reference as a delegate: " + delegate); } if (!ObjectTypeUtil.containsOid(newAssignees, delegate.getOid())) { newAssignees.add(delegate.clone()); delegatedTo.add(delegate.clone()); } } } public static WorkItemDelegationEventType createDelegationEvent(WorkItemEscalationLevelType newEscalation, List<ObjectReferenceType> assigneesBefore, List<ObjectReferenceType> delegatedTo, @NotNull WorkItemDelegationMethodType method, WorkItemEventCauseInformationType causeInformation) { WorkItemDelegationEventType event; if (newEscalation != null) { WorkItemEscalationEventType escEvent = new WorkItemEscalationEventType(); escEvent.setNewEscalationLevel(newEscalation); event = escEvent; } else { event = new WorkItemDelegationEventType(); } event.getAssigneeBefore().addAll(assigneesBefore); event.getDelegatedTo().addAll(delegatedTo); event.setDelegationMethod(method); event.setCause(causeInformation); return event; } @Nullable public static WorkItemEscalationLevelType createNewEscalation(int escalationLevel, WorkItemEscalationLevelType escalation) { WorkItemEscalationLevelType newEscalation; if (escalation != null) { newEscalation = escalation.clone(); newEscalation.setNumber(escalationLevel + 1); } else { newEscalation = null; } return newEscalation; } @NotNull public static List<TriggerType> createTriggers(int escalationLevel, Date workItemCreateTime, Date workItemDeadline, List<WorkItemTimedActionsType> timedActionsList, PrismContext prismContext, Trace logger, @Nullable String workItemId, @NotNull String handlerUri) throws SchemaException { List<TriggerType> triggers = new ArrayList<>(); for (WorkItemTimedActionsType timedActionsEntry : timedActionsList) { Integer levelFrom; Integer levelTo; if (timedActionsEntry.getEscalationLevelFrom() == null && timedActionsEntry.getEscalationLevelTo() == null) { levelFrom = levelTo = 0; } else { levelFrom = timedActionsEntry.getEscalationLevelFrom(); levelTo = timedActionsEntry.getEscalationLevelTo(); } if (levelFrom != null && escalationLevel < levelFrom) { logger.trace("Current escalation level is before 'escalationFrom', skipping timed actions {}", timedActionsEntry); continue; } if (levelTo != null && escalationLevel > levelTo) { logger.trace("Current escalation level is after 'escalationTo', skipping timed actions {}", timedActionsEntry); continue; } // TODO evaluate the condition List<TimedActionTimeSpecificationType> timeSpecifications = CloneUtil.cloneCollectionMembers(timedActionsEntry.getTime()); if (timeSpecifications.isEmpty()) { timeSpecifications.add(new TimedActionTimeSpecificationType()); } for (TimedActionTimeSpecificationType timeSpec : timeSpecifications) { if (timeSpec.getValue().isEmpty()) { timeSpec.getValue().add(XmlTypeConverter.createDuration(0)); } for (Duration duration : timeSpec.getValue()) { XMLGregorianCalendar mainTriggerTime = computeTriggerTime(duration, timeSpec.getBase(), workItemCreateTime, workItemDeadline); TriggerType mainTrigger = createTrigger(mainTriggerTime, timedActionsEntry.getActions(), null, prismContext, workItemId, handlerUri); triggers.add(mainTrigger); List<Pair<Duration, AbstractWorkItemActionType>> notifyInfoList = getNotifyBefore(timedActionsEntry); for (Pair<Duration, AbstractWorkItemActionType> notifyInfo : notifyInfoList) { XMLGregorianCalendar notifyTime = (XMLGregorianCalendar) mainTriggerTime.clone(); notifyTime.add(notifyInfo.getKey().negate()); TriggerType notifyTrigger = createTrigger(notifyTime, null, notifyInfo, prismContext, workItemId, handlerUri); triggers.add(notifyTrigger); } } } } return triggers; } @NotNull private static TriggerType createTrigger(XMLGregorianCalendar triggerTime, WorkItemActionsType actions, Pair<Duration, AbstractWorkItemActionType> notifyInfo, PrismContext prismContext, @Nullable String workItemId, @NotNull String handlerUri) throws SchemaException { TriggerType trigger = new TriggerType(prismContext); trigger.setTimestamp(triggerTime); trigger.setHandlerUri(handlerUri); ExtensionType extension = new ExtensionType(prismContext); trigger.setExtension(extension); SchemaRegistry schemaRegistry = prismContext.getSchemaRegistry(); if (workItemId != null) { // work item id @SuppressWarnings("unchecked") @NotNull PrismPropertyDefinition<String> workItemIdDef = prismContext.getSchemaRegistry().findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ID); PrismProperty<String> workItemIdProp = workItemIdDef.instantiate(); workItemIdProp.addRealValue(workItemId); trigger.getExtension().asPrismContainerValue().add(workItemIdProp); } // actions if (actions != null) { @NotNull PrismContainerDefinition<WorkItemActionsType> workItemActionsDef = schemaRegistry.findContainerDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ACTIONS); PrismContainer<WorkItemActionsType> workItemActionsCont = workItemActionsDef.instantiate(); workItemActionsCont.add(actions.asPrismContainerValue().clone()); extension.asPrismContainerValue().add(workItemActionsCont); } // time before + action if (notifyInfo != null) { @NotNull PrismContainerDefinition<AbstractWorkItemActionType> workItemActionDef = schemaRegistry.findContainerDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ACTION); PrismContainer<AbstractWorkItemActionType> workItemActionCont = workItemActionDef.instantiate(); workItemActionCont.add(notifyInfo.getValue().asPrismContainerValue().clone()); extension.asPrismContainerValue().add(workItemActionCont); @SuppressWarnings("unchecked") @NotNull PrismPropertyDefinition<Duration> timeBeforeActionDef = schemaRegistry.findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_TIME_BEFORE_ACTION); PrismProperty<Duration> timeBeforeActionProp = timeBeforeActionDef.instantiate(); timeBeforeActionProp.addRealValue(notifyInfo.getKey()); extension.asPrismContainerValue().add(timeBeforeActionProp); } return trigger; } private static List<Pair<Duration,AbstractWorkItemActionType>> getNotifyBefore(WorkItemTimedActionsType timedActions) { List<Pair<Duration,AbstractWorkItemActionType>> rv = new ArrayList<>(); WorkItemActionsType actions = timedActions.getActions(); if (actions.getComplete() != null) { collectNotifyBefore(rv, actions.getComplete()); } if (actions.getDelegate() != null) { collectNotifyBefore(rv, actions.getDelegate()); } if (actions.getEscalate() != null) { collectNotifyBefore(rv, actions.getEscalate()); } return rv; } private static void collectNotifyBefore(List<Pair<Duration,AbstractWorkItemActionType>> rv, CompleteWorkItemActionType complete) { collectNotifyBefore(rv, complete.getNotifyBeforeAction(), complete); } private static void collectNotifyBefore(List<Pair<Duration,AbstractWorkItemActionType>> rv, DelegateWorkItemActionType delegate) { collectNotifyBefore(rv, delegate.getNotifyBeforeAction(), delegate); } private static void collectNotifyBefore(List<Pair<Duration, AbstractWorkItemActionType>> rv, List<Duration> beforeTimes, AbstractWorkItemActionType action) { beforeTimes.forEach(beforeTime -> rv.add(new ImmutablePair<>(beforeTime, action))); } @NotNull private static XMLGregorianCalendar computeTriggerTime(Duration duration, WfTimeBaseType base, Date start, Date deadline) { Date baseTime; if (base == null) { base = duration.getSign() <= 0 ? WfTimeBaseType.DEADLINE : WfTimeBaseType.WORK_ITEM_CREATION; } switch (base) { case DEADLINE: if (deadline == null) { throw new IllegalStateException("Couldn't set timed action relative to work item's deadline because" + " the deadline is not set. Requested interval: " + duration); } baseTime = deadline; break; case WORK_ITEM_CREATION: if (start == null) { throw new IllegalStateException("Task's start time is null"); } baseTime = start; break; default: throw new IllegalArgumentException("base: " + base); } XMLGregorianCalendar rv = XmlTypeConverter.createXMLGregorianCalendar(baseTime); rv.add(duration); return rv; } }