diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/ComponentConstants.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/ComponentConstants.java index ee7362da5e0..5a986a5dcc7 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/ComponentConstants.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/ComponentConstants.java @@ -48,6 +48,9 @@ public class ComponentConstants { public static final QName UI_CASE_TAB_WORKITEMS = new QName(NS_COMPONENTS_PREFIX, "caseTabWorkitems"); public static final String UI_CASE_TAB_WORKITEMS_URL = QNameUtil.qNameToUri(UI_CASE_TAB_WORKITEMS); + public static final QName UI_CASE_TAB_CHILD_CASES = new QName(NS_COMPONENTS_PREFIX, "caseTabChildCases"); + public static final String UI_CASE_TAB_CHILD_CASES_URL = QNameUtil.qNameToUri(UI_CASE_TAB_CHILD_CASES); + public static final QName UI_CASE_TAB_APPROVAL = new QName(NS_COMPONENTS_PREFIX, "caseTabApproval"); public static final String UI_CASE_TAB_APPROVAL_URL = QNameUtil.qNameToUri(UI_CASE_TAB_WORKITEMS); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/MainObjectListPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/MainObjectListPanel.java index 2a9b88d25cf..92fb18d0c50 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/MainObjectListPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/MainObjectListPanel.java @@ -22,6 +22,7 @@ import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; import com.evolveum.midpoint.gui.impl.component.icon.IconCssStyle; import com.evolveum.midpoint.web.component.MultifunctionalButton; +import com.evolveum.midpoint.web.component.util.VisibleBehaviour; import com.evolveum.midpoint.xml.ns._public.common.common_3.DisplayType; import com.evolveum.midpoint.xml.ns._public.common.common_3.IconType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; @@ -165,6 +166,7 @@ protected DisplayType getAdditionalButtonDisplayType(S buttonObject){ return getNewObjectButtonAdditionalDisplayType(buttonObject); } }; + createNewObjectButton.add(new VisibleBehaviour(() -> isCreateNewObjectEnabled())); createNewObjectButton.add(AttributeAppender.append("class", "btn-margin-right")); buttonsList.add(createNewObjectButton); @@ -260,6 +262,10 @@ private boolean isRawOrNoFetchOption(Collection getNewObjectInfluencesList(){ return new ArrayList<>(); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java index 85795ce2589..4c33b1afcc4 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java @@ -401,7 +401,12 @@ protected Integer load() { Task task = createSimpleTask(OPERATION_LOAD_WORK_ITEM_COUNT); S_FilterEntryOrEmpty q = getPrismContext().queryFor(CaseWorkItemType.class); ObjectQuery query = QueryUtils.filterForAssignees(q, getPrincipal(), - OtherPrivilegesLimitationType.F_APPROVAL_WORK_ITEMS, getRelationRegistry()).build(); + OtherPrivilegesLimitationType.F_APPROVAL_WORK_ITEMS, getRelationRegistry()) + .and() + .not() + .item(PrismConstants.T_PARENT, CaseType.F_STATE) + .eq(SchemaConstants.CASE_STATE_CLOSED) + .build(); return getModelService().countContainers(CaseWorkItemType.class, query, null, task, task.getResult()); } catch (SchemaException | SecurityViolationException | ExpressionEvaluationException | ObjectNotFoundException | CommunicationException | ConfigurationException e) { LoggingUtils.logExceptionAsWarning(LOGGER, "Couldn't load work item count", e); @@ -1842,29 +1847,15 @@ public String getBubbleLabel() { }; addMenuItem(item, "PageAdmin.menu.top.cases.listAll", GuiStyleConstants.EVO_CASE_OBJECT_ICON, PageCases.class); -// addMenuItem(item, "PageAdmin.menu.top.cases.approvalCases", PageCasesAll.class); -// addMenuItem(item, "PageAdmin.menu.top.cases.myApprovalCases", PageCasesAll.class); -// addMenuItem(item, "PageAdmin.menu.top.cases.myRequests", PageCasesAll.class); -// addMenuItem(item, "PageAdmin.menu.top.cases.requestsAboutMe", PageCasesAll.class); -// addMenuItem(item, "PageAdmin.menu.top.cases.manualProvisionCases", PageCasesAll.class); -// addMenuItem(item, "PageAdmin.menu.top.cases.myManualProvisionCases", PageCasesAll.class); addMenuItem(item, "PageAdmin.menu.top.caseWorkItems.listAll", GuiStyleConstants.CLASS_OBJECT_WORK_ITEM_ICON, PageCaseWorkItemsAll.class); addMenuItem(item, "PageAdmin.menu.top.caseWorkItems.list", PageCaseWorkItemsAllocatedToMe.class); -// addMenuItem(item, "PageAdmin.menu.top.workItems.list", PageWorkItemsAllocatedToMe.class); -// addMenuItem(item, "PageAdmin.menu.top.workItems.listClaimable", PageWorkItemsClaimable.class); -// addMenuItem(item, "PageAdmin.menu.top.workItems.listAttorney", PageAttorneySelection.class); -// addMenuItem(item, "PageAdmin.menu.top.workItems.listAll", PageWorkItemsAll.class); -// addMenuItem(item, "PageAdmin.menu.top.workItems.listProcessInstancesRequestedBy", PageProcessInstancesRequestedBy.class); -// addMenuItem(item, "PageAdmin.menu.top.workItems.listProcessInstancesRequestedFor", PageProcessInstancesRequestedFor.class); -// addMenuItem(item, "PageAdmin.menu.top.workItems.listProcessInstancesAll", PageProcessInstancesAll.class); -// addMenuItem(item, "PageAdmin.menu.top.cases.list", PageCasesAllocatedToMe.class); - createFocusPageViewMenu(item.getItems(), "PageAdmin.menu.top.caseWorkItems.view", PageCaseWorkItem.class); - MenuItem newCaseMenu = new MenuItem(createStringResource("PageAdmin.menu.top.case.new"), GuiStyleConstants.CLASS_PLUS_CIRCLE, PageCase.class, null, - new VisibleEnableBehaviour()); - item.getItems().add(newCaseMenu); + //todo for now disable the possibility to create case manually +// MenuItem newCaseMenu = new MenuItem(createStringResource("PageAdmin.menu.top.case.new"), GuiStyleConstants.CLASS_PLUS_CIRCLE, PageCase.class, null, +// new VisibleEnableBehaviour()); +// item.getItems().add(newCaseMenu); return item; } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java index 28dc8560d70..549ea148b29 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java @@ -43,7 +43,12 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import com.evolveum.midpoint.model.api.visualizer.Scene; +import com.evolveum.midpoint.schema.util.*; +import com.evolveum.midpoint.web.component.prism.show.SceneDto; +import com.evolveum.midpoint.web.component.prism.show.SceneUtil; import com.evolveum.midpoint.web.page.admin.cases.PageCase; +import com.evolveum.midpoint.web.page.admin.workflow.WorkItemDetailsPanel; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; @@ -161,10 +166,6 @@ import com.evolveum.midpoint.schema.processor.ResourceSchema; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; -import com.evolveum.midpoint.schema.util.LocalizationUtil; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.schema.util.ResourceTypeUtil; -import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.security.api.AuthorizationConstants; import com.evolveum.midpoint.security.api.MidPointPrincipal; import com.evolveum.midpoint.task.api.Task; @@ -3859,6 +3860,35 @@ public static List sortDropDownChoices(IModel return sortedList; } + public static SceneDto createSceneDto(CaseWorkItemType caseWorkItem, PageBase pageBase, String operation){ + if (caseWorkItem == null){ + return null; + } + return createSceneDto(CaseTypeUtil.getCase(caseWorkItem), pageBase, operation); + } + + public static SceneDto createSceneDto(CaseType caseObject, PageBase pageBase, String operation){ + if (caseObject == null || caseObject.getWorkflowContext() == null) { + return null; + } + if (!(caseObject.getWorkflowContext().getProcessorSpecificState() instanceof WfPrimaryChangeProcessorStateType)) { + return null; + } + ObjectReferenceType objectRef = caseObject.getObjectRef(); + WfPrimaryChangeProcessorStateType state = (WfPrimaryChangeProcessorStateType) caseObject.getWorkflowContext().getProcessorSpecificState(); + + OperationResult result = new OperationResult(operation); + Task task = pageBase.createSimpleTask(operation); + try { + Scene deltasScene = SceneUtil.visualizeObjectTreeDeltas(state.getDeltasToProcess(), "pageWorkItem.delta", + pageBase.getPrismContext(), pageBase.getModelInteractionService(), objectRef, task, result); + return new SceneDto(deltasScene); + } catch (SchemaException | ExpressionEvaluationException ex){ + LOGGER.error("Unable to create delta visualization for case " + caseObject.getName(), ex.getLocalizedMessage()); + } + return null; + } + public static String getMidpointCustomSystemName(PageBase pageBase, String defaultSystemNameKey){ DeploymentInformationType deploymentInfo = MidPointApplication.get().getDeploymentInfo(); String subscriptionId = deploymentInfo != null ? deploymentInfo.getSubscriptionIdentifier() : null; diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemPathPanelFactory.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemPathPanelFactory.java index 1d9a43f77c4..0655ad400ab 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemPathPanelFactory.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemPathPanelFactory.java @@ -15,6 +15,8 @@ */ package com.evolveum.midpoint.gui.impl.factory; +import java.io.Serializable; + import javax.annotation.PostConstruct; import javax.annotation.Priority; @@ -36,11 +38,11 @@ */ @Component @Priority(1000) -public class ItemPathPanelFactory extends AbstractGuiComponentFactory { +public class ItemPathPanelFactory extends AbstractGuiComponentFactory implements Serializable { private static final long serialVersionUID = 1L; - @Autowired private GuiComponentRegistry registry; + @Autowired private transient GuiComponentRegistry registry; @PostConstruct public void register() { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/PrismObjectWrapperFactoryImpl.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/PrismObjectWrapperFactoryImpl.java index fce66d9b52c..272c74e8df2 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/PrismObjectWrapperFactoryImpl.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/PrismObjectWrapperFactoryImpl.java @@ -76,6 +76,9 @@ public PrismObjectWrapper createObjectWrapper(PrismObject object, ItemStat } PrismObjectWrapper objectWrapper = createObjectWrapper(object, status); + if (context.getReadOnly() != null) { + objectWrapper.setReadOnly(context.getReadOnly().booleanValue()); + } context.setShowEmpty(ItemStatus.ADDED == status ? true : false); PrismContainerValueWrapper valueWrapper = createValueWrapper(objectWrapper, object.getValue(), ItemStatus.ADDED == status ? ValueStatus.ADDED : ValueStatus.NOT_CHANGED, context); objectWrapper.getValues().add(valueWrapper); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/PrismReferenceHeaderPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/PrismReferenceHeaderPanel.html index b98ffce4902..6cafd859e0c 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/PrismReferenceHeaderPanel.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/PrismReferenceHeaderPanel.html @@ -17,7 +17,6 @@ -
@@ -32,6 +31,5 @@
-
diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/component/PolyStringEditorPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/component/PolyStringEditorPanel.html index 52ac11ea02c..7b679ea728e 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/component/PolyStringEditorPanel.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/component/PolyStringEditorPanel.html @@ -21,18 +21,32 @@
- -   - +
+
+ +
+
+
- -   - +
+
+ +
+
+
diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/component/PolyStringEditorPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/component/PolyStringEditorPanel.java index 0450b9a498b..76e5ed6b0dd 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/component/PolyStringEditorPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/component/PolyStringEditorPanel.java @@ -115,7 +115,7 @@ public void onClick(AjaxRequestTarget target) { } }; showHideLanguagesLocalizedButton.setOutputMarkupId(true); - showHideLanguagesLocalizedButton.add(AttributeAppender.append("style", "cursor: pointer;")); +// showHideLanguagesLocalizedButton.add(AttributeAppender.append("style", "cursor: pointer;")); localizedValueWithButton.add(showHideLanguagesLocalizedButton); WebMarkupContainer originValueContainer = new WebMarkupContainer(ID_ORIGIN_VALUE_CONTAINER); @@ -309,7 +309,7 @@ public void onClick(AjaxRequestTarget target) { } }; showHideLanguagesButton.setOutputMarkupId(true); - showHideLanguagesButton.add(AttributeAppender.append("style", "cursor: pointer;")); +// showHideLanguagesButton.add(AttributeAppender.append("style", "cursor: pointer;")); origValueWithButton.add(showHideLanguagesButton); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/AbstractObjectMainPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/AbstractObjectMainPanel.java index 636e45237fc..2c75998b42e 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/AbstractObjectMainPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/objectdetails/AbstractObjectMainPanel.java @@ -275,7 +275,8 @@ public StringResourceModel getTitle() { @Override public boolean isVisible(){ return WebComponentUtil.isAuthorized(AuthorizationConstants.AUTZ_UI_CONFIGURATION_URL, - AuthorizationConstants.AUTZ_UI_CONFIGURATION_DEBUG_URL); + AuthorizationConstants.AUTZ_UI_CONFIGURATION_DEBUG_URL) && + !getObjectWrapper().isReadOnly(); } }); mainForm.add(editXmlButton); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/search/SearchFactory.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/search/SearchFactory.java index 37695889974..1a3a5e22e7d 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/search/SearchFactory.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/search/SearchFactory.java @@ -135,7 +135,7 @@ public class SearchFactory { SEARCHABLE_OBJECTS.put(CaseWorkItemType.class, Arrays.asList( ItemPath.create(CaseWorkItemType.F_ASSIGNEE_REF), ItemPath.create(CaseWorkItemType.F_ORIGINAL_ASSIGNEE_REF), - ItemPath.create(PrismConstants.T_PARENT, CaseType.F_STATE), +// ItemPath.create(PrismConstants.T_PARENT, CaseType.F_STATE), ItemPath.create(CaseWorkItemType.F_PERFORMER_REF) )); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/wf/SwitchableApprovalProcessPreviewsPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/wf/SwitchableApprovalProcessPreviewsPanel.html index 0baec4264a8..075bf32a3b8 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/wf/SwitchableApprovalProcessPreviewsPanel.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/wf/SwitchableApprovalProcessPreviewsPanel.html @@ -17,14 +17,14 @@ -
-
-

-
-
-
-
-
+ + + + + + + +

@@ -33,13 +33,13 @@

-
- -     - - - - -
+ + + + + + + + diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/wf/SwitchableApprovalProcessPreviewsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/wf/SwitchableApprovalProcessPreviewsPanel.java index 55825823405..659f136020e 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/wf/SwitchableApprovalProcessPreviewsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/wf/SwitchableApprovalProcessPreviewsPanel.java @@ -52,10 +52,10 @@ public class SwitchableApprovalProcessPreviewsPanel extends BasePanel { private static final String ID_WHOLE_PROCESS_CONTAINER = "wholeProcessContainer"; private static final String ID_WHOLE_PROCESS = "wholeProcess"; private static final String ID_WHOLE_PROCESS_HELP = "wholeProcessHelp"; - private static final String ID_SHOW_NEXT_STAGES_CONTAINER = "showNextStagesContainer"; +// private static final String ID_SHOW_NEXT_STAGES_CONTAINER = "showNextStagesContainer"; private static final String ID_SHOW_NEXT_STAGES = "showNextStages"; private static final String ID_SHOW_NEXT_STAGES_HELP = "showNextStagesHelp"; - private static final String ID_SHOW_WHOLE_PROCESS_CONTAINER = "showWholeProcessContainer"; +// private static final String ID_SHOW_WHOLE_PROCESS_CONTAINER = "showWholeProcessContainer"; private static final String ID_SHOW_WHOLE_PROCESS = "showWholeProcess"; private static final String ID_SHOW_WHOLE_PROCESS_HELP = "showWholeProcessHelp"; @@ -137,48 +137,48 @@ private ApprovalProcessExecutionInformationDto createApprovalProcessExecutionInf private void initLayout(IModel showNextStagesModel) { setOutputMarkupId(true); - WebMarkupContainer nextStagesContainer = new WebMarkupContainer(ID_NEXT_STAGES_CONTAINER); - nextStagesContainer.add(new ApprovalProcessExecutionInformationPanel(ID_NEXT_STAGES, nextStagesModel)); - nextStagesContainer.add(WebComponentUtil.createHelp(ID_NEXT_STAGES_HELP)); - nextStagesContainer.add(new VisibleBehaviour(() -> displayedProcessInfoBox == ProcessInfoBox.NEXT_STAGES)); - add(nextStagesContainer); +// WebMarkupContainer nextStagesContainer = new WebMarkupContainer(ID_NEXT_STAGES_CONTAINER); +// nextStagesContainer.add(new ApprovalProcessExecutionInformationPanel(ID_NEXT_STAGES, nextStagesModel)); +// nextStagesContainer.add(WebComponentUtil.createHelp(ID_NEXT_STAGES_HELP)); +//// nextStagesContainer.add(new VisibleBehaviour(() -> displayedProcessInfoBox == ProcessInfoBox.NEXT_STAGES)); +// add(nextStagesContainer); WebMarkupContainer wholeProcessContainer = new WebMarkupContainer(ID_WHOLE_PROCESS_CONTAINER); wholeProcessContainer.add(new ApprovalProcessExecutionInformationPanel(ID_WHOLE_PROCESS, wholeProcessModel)); wholeProcessContainer.add(WebComponentUtil.createHelp(ID_WHOLE_PROCESS_HELP)); - wholeProcessContainer.add(new VisibleBehaviour(() -> displayedProcessInfoBox == ProcessInfoBox.WHOLE_PROCESS)); +// wholeProcessContainer.add(new VisibleBehaviour(() -> displayedProcessInfoBox == ProcessInfoBox.WHOLE_PROCESS)); add(wholeProcessContainer); - WebMarkupContainer showNextStagesContainer = new WebMarkupContainer(ID_SHOW_NEXT_STAGES_CONTAINER); - showNextStagesContainer.add(new AjaxFallbackLink(ID_SHOW_NEXT_STAGES) { - - private static final long serialVersionUID = 1L; - - @Override - public void onClick(Optional target) { - displayedProcessInfoBox = ProcessInfoBox.NEXT_STAGES; - ((AjaxRequestTarget) target.get()).add(SwitchableApprovalProcessPreviewsPanel.this); - } - - }); - showNextStagesContainer.add(WebComponentUtil.createHelp(ID_SHOW_NEXT_STAGES_HELP)); - showNextStagesContainer.add(new VisibleBehaviour(() -> - Boolean.TRUE.equals(showNextStagesModel.getObject()) && displayedProcessInfoBox != ProcessInfoBox.NEXT_STAGES)); - add(showNextStagesContainer); - - WebMarkupContainer showWholeProcessContainer = new WebMarkupContainer(ID_SHOW_WHOLE_PROCESS_CONTAINER); - showWholeProcessContainer.add(new AjaxFallbackLink(ID_SHOW_WHOLE_PROCESS) { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(Optional target) { - displayedProcessInfoBox = ProcessInfoBox.WHOLE_PROCESS; - ((AjaxRequestTarget) target.get()).add(SwitchableApprovalProcessPreviewsPanel.this); - } - }); - showWholeProcessContainer.add(new VisibleBehaviour(() -> displayedProcessInfoBox != ProcessInfoBox.WHOLE_PROCESS)); - showWholeProcessContainer.add(WebComponentUtil.createHelp(ID_SHOW_WHOLE_PROCESS_HELP)); - add(showWholeProcessContainer); +// WebMarkupContainer showNextStagesContainer = new WebMarkupContainer(ID_SHOW_NEXT_STAGES_CONTAINER); +// showNextStagesContainer.add(new AjaxFallbackLink(ID_SHOW_NEXT_STAGES) { +// +// private static final long serialVersionUID = 1L; +// +// @Override +// public void onClick(Optional target) { +// displayedProcessInfoBox = ProcessInfoBox.NEXT_STAGES; +// ((AjaxRequestTarget) target.get()).add(SwitchableApprovalProcessPreviewsPanel.this); +// } +// +// }); +// showNextStagesContainer.add(WebComponentUtil.createHelp(ID_SHOW_NEXT_STAGES_HELP)); +//// showNextStagesContainer.add(new VisibleBehaviour(() -> +//// Boolean.TRUE.equals(showNextStagesModel.getObject()) && displayedProcessInfoBox != ProcessInfoBox.NEXT_STAGES)); +// add(showNextStagesContainer); +// +// WebMarkupContainer showWholeProcessContainer = new WebMarkupContainer(ID_SHOW_WHOLE_PROCESS_CONTAINER); +// showWholeProcessContainer.add(new AjaxFallbackLink(ID_SHOW_WHOLE_PROCESS) { +// private static final long serialVersionUID = 1L; +// +// @Override +// public void onClick(Optional target) { +// displayedProcessInfoBox = ProcessInfoBox.WHOLE_PROCESS; +// ((AjaxRequestTarget) target.get()).add(SwitchableApprovalProcessPreviewsPanel.this); +// } +// }); +//// showWholeProcessContainer.add(new VisibleBehaviour(() -> displayedProcessInfoBox != ProcessInfoBox.WHOLE_PROCESS)); +// showWholeProcessContainer.add(WebComponentUtil.createHelp(ID_SHOW_WHOLE_PROCESS_HELP)); +// add(showWholeProcessContainer); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectList.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectList.java index 19e81603bfc..cdada0e9c01 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectList.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectList.java @@ -107,6 +107,11 @@ protected void newObjectPerformed(AjaxRequestTarget target, CompiledObjectCollec newObjectActionPerformed(target, collectionView); } + @Override + protected boolean isCreateNewObjectEnabled(){ + return PageAdminObjectList.this.isCreateNewObjectEnabled(); + } + @Override protected List getNewObjectInfluencesList(){ if (isCollectionViewPage()){ @@ -191,6 +196,10 @@ protected String getStorageKey() { protected void objectDetailsPerformed(AjaxRequestTarget target, O object){} + protected boolean isCreateNewObjectEnabled(){ + return true; + } + protected void newObjectActionPerformed(AjaxRequestTarget target, CompiledObjectCollectionView collectionView){ if (collectionView == null){ collectionView = getCollectionViewObject(); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseEventsTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseEventsTabPanel.java index 4af27fd3fcc..77db7f9cb94 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseEventsTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseEventsTabPanel.java @@ -145,8 +145,7 @@ protected IModel createLinkModel(IModel> rowModel) { - //TODO should we check any authorization? - return true; + return false; } @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseSummaryPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseSummaryPanel.java index 26431ffa604..0ee566ff19a 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseSummaryPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseSummaryPanel.java @@ -17,10 +17,20 @@ import com.evolveum.midpoint.gui.api.GuiStyleConstants; import com.evolveum.midpoint.gui.api.util.ModelServiceLocator; +import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.web.component.ObjectSummaryPanel; +import com.evolveum.midpoint.web.component.util.SummaryTag; +import com.evolveum.midpoint.web.component.util.VisibleBehaviour; +import com.evolveum.midpoint.web.page.admin.server.dto.ApprovalOutcomeIcon; +import com.evolveum.midpoint.wf.util.ApprovalUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import org.apache.commons.lang.StringUtils; import org.apache.wicket.model.IModel; +import java.util.ArrayList; +import java.util.List; + /** * Created by honchar */ @@ -28,10 +38,53 @@ public class CaseSummaryPanel extends ObjectSummaryPanel { private static final long serialVersionUID = 1L; + private static final String DOT_CLASS = CaseSummaryPanel.class.getName() + "."; + private static final String OPERATION_LOAD_PARENT_CASE_DISPLAY_NAME = DOT_CLASS + "loadParentCaseDisplayName"; + public CaseSummaryPanel(String id, Class type, IModel model, ModelServiceLocator serviceLocator) { super(id, type, model, serviceLocator); } + @Override + protected IModel getTitleModel() { + ObjectReferenceType parentRef = getModelObject().getParentRef(); + if (parentRef != null && StringUtils.isNotEmpty(parentRef.getOid())) { + return createStringResource("CaseSummaryPanel.parentCase", + WebComponentUtil.getDisplayNameOrName(getModelObject().getParentRef(), getPageBase(), OPERATION_LOAD_PARENT_CASE_DISPLAY_NAME)); + } else { + return null; + } + } + + @Override + protected List> getSummaryTagComponentList(){ + List> summaryTagList = new ArrayList<>(); + SummaryTag tagOutcome = new SummaryTag(ID_SUMMARY_TAG, getModel()) { + @Override + protected void initialize(CaseType taskType) { + String icon, name; + if (getModelObject().getOutcome() == null) { + // shouldn't occur! + return; + } + + if (ApprovalUtils.approvalBooleanValueFromUri(getModelObject().getOutcome())) { + icon = ApprovalOutcomeIcon.APPROVED.getIcon(); + name = "approved"; + } else { + icon = ApprovalOutcomeIcon.REJECTED.getIcon(); + name = "rejected"; + } + setIconCssClass(icon); + setLabel(createStringResource("TaskSummaryPanel." + name).getString()); + } + }; + tagOutcome.add(new VisibleBehaviour(() -> CaseSummaryPanel.this.getModelObject().getOutcome() != null)); + summaryTagList.add(tagOutcome); + + return summaryTagList; + } + @Override protected String getIconCssClass() { return GuiStyleConstants.EVO_CASE_OBJECT_ICON; diff --git a/model/workflow-impl/src/test/resources/policy/role-superuser.xml b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.html similarity index 54% rename from model/workflow-impl/src/test/resources/policy/role-superuser.xml rename to gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.html index c0e5ee20fc4..a5ac2d16b88 100644 --- a/model/workflow-impl/src/test/resources/policy/role-superuser.xml +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.html @@ -1,5 +1,5 @@ - - Superuser - - http://midpoint.evolveum.com/xml/ns/public/security/authorization-3#all - - + + + + +
+
+
+
+
+ + \ No newline at end of file diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.java new file mode 100644 index 00000000000..201926343ce --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2010-2019 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.web.page.admin.cases; + +import com.evolveum.midpoint.gui.api.component.BasePanel; +import com.evolveum.midpoint.gui.api.component.ObjectBrowserPanel; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.CaseTypeUtil; +import com.evolveum.midpoint.schema.util.CaseWorkItemUtil; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.schema.util.WorkItemId; +import com.evolveum.midpoint.task.api.Task; +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.web.component.AjaxButton; +import com.evolveum.midpoint.web.component.util.VisibleBehaviour; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemDelegationMethodType; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.model.IModel; + +import java.util.Collections; +import java.util.List; + +/** + * Created by honchar + */ +public class CaseWorkItemActionsPanel extends BasePanel { + private static final long serialVersionUID = 1L; + + private static final Trace LOGGER = TraceManager.getTrace(CaseWorkItemListWithDetailsPanel.class); + + private static final String DOT_CLASS = CaseWorkItemActionsPanel.class.getName() + "."; + private static final String OPERATION_SAVE_WORK_ITEM = DOT_CLASS + "saveWorkItem"; + private static final String OPERATION_DELEGATE_WORK_ITEM = DOT_CLASS + "delegateWorkItem"; + + + private static final String ID_WORK_ITEM_APPROVE_BUTTON = "workItemApproveButton"; + private static final String ID_WORK_ITEM_REJECT_BUTTON = "workItemRejectButton"; + private static final String ID_WORK_ITEM_DELEGATE_BUTTON = "workItemDelegateButton"; + private static final String ID_ACTION_BUTTONS = "actionButtons"; + + public CaseWorkItemActionsPanel(String id, IModel caseWorkItemModel){ + super(id, caseWorkItemModel); + } + + @Override + protected void onInitialize(){ + super.onInitialize(); + initLayout(); + } + + private void initLayout(){ + WebMarkupContainer actionButtonsContainer = new WebMarkupContainer(ID_ACTION_BUTTONS); + actionButtonsContainer.setOutputMarkupId(true); + actionButtonsContainer.add(new VisibleBehaviour(() -> !isParentCaseClosed())); + add(actionButtonsContainer); + + AjaxButton workItemApproveButton = new AjaxButton(ID_WORK_ITEM_APPROVE_BUTTON, + createStringResource("pageWorkItem.button.approve")) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget ajaxRequestTarget) { + savePerformed(ajaxRequestTarget, getCaseWorkItemModelObject(), true); + } + }; + workItemApproveButton.setOutputMarkupId(true); + actionButtonsContainer.add(workItemApproveButton); + + AjaxButton workItemRejectButton = new AjaxButton(ID_WORK_ITEM_REJECT_BUTTON, + createStringResource("pageWorkItem.button.reject")) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget ajaxRequestTarget) { + savePerformed(ajaxRequestTarget, getCaseWorkItemModelObject(), false); + } + }; + workItemRejectButton.setOutputMarkupId(true); + actionButtonsContainer.add(workItemRejectButton); + + AjaxButton workItemDelegateButton = new AjaxButton(ID_WORK_ITEM_DELEGATE_BUTTON, + createStringResource("pageWorkItem.button.delegate")) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget ajaxRequestTarget) { + delegatePerformed(ajaxRequestTarget); + } + }; + workItemDelegateButton.setOutputMarkupId(true); + actionButtonsContainer.add(workItemDelegateButton); + } + + private CaseWorkItemType getCaseWorkItemModelObject(){ + return getModelObject(); + } + + private void savePerformed(AjaxRequestTarget target, CaseWorkItemType workItem, boolean approved) { + Task task = getPageBase().createSimpleTask(OPERATION_SAVE_WORK_ITEM); + OperationResult result = task.getResult(); + try { + //todo implement custom panels +// WorkItemDto dto = workItemDtoModel.getObject(); +// if (approved) { +// boolean requiredFieldsPresent = getWorkItemPanel().checkRequiredFields(); +// if (!requiredFieldsPresent) { +// target.add(getFeedbackPanel()); +// return; +// } +// } +// ObjectDelta delta = getWorkItemPanel().getDeltaFromForm(); +// if (delta != null) { +// //noinspection unchecked +// getPrismContext().adopt(delta); +// } + try { + assumePowerOfAttorneyIfRequested(result); + //todo fix comment and delta + getPageBase().getWorkflowService().completeWorkItem(WorkItemId.of(workItem), approved, "", + null, task, result); + } finally { + dropPowerOfAttorneyIfRequested(result); + } + } catch (Exception ex) { + result.recordFatalError("Couldn't save work item.", ex); + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't save work item", ex); + } + getPageBase().processResult(target, result, false); + getPageBase().redirectBack(); + } + + private boolean isParentCaseClosed(){ + return CaseTypeUtil.isClosed(CaseWorkItemUtil.getCase(getCaseWorkItemModelObject())); + } + + private void delegatePerformed(AjaxRequestTarget target) { + ObjectBrowserPanel panel = new ObjectBrowserPanel( + getPageBase().getMainPopupBodyId(), UserType.class, + Collections.singletonList(UserType.COMPLEX_TYPE), false, getPageBase(), null) { + private static final long serialVersionUID = 1L; + + @Override + protected void onSelectPerformed(AjaxRequestTarget target, UserType user) { + CaseWorkItemActionsPanel.this.getPageBase().hideMainPopup(target); + delegateConfirmedPerformed(target, user); + } + + }; + panel.setOutputMarkupId(true); + getPageBase().showMainPopup(panel, target); + } + + private void delegateConfirmedPerformed(AjaxRequestTarget target, UserType delegate) { + Task task = getPageBase().createSimpleTask(OPERATION_DELEGATE_WORK_ITEM); + OperationResult result = task.getResult(); + try { + List delegates = Collections.singletonList(ObjectTypeUtil.createObjectRef(delegate, getPrismContext())); + try { + assumePowerOfAttorneyIfRequested(result); + getPageBase().getWorkflowService().delegateWorkItem(WorkItemId.of(getModelObject()), delegates, WorkItemDelegationMethodType.ADD_ASSIGNEES, + task, result); + } finally { + dropPowerOfAttorneyIfRequested(result); + } + } catch (Exception ex) { + result.recordFatalError("Couldn't delegate work item.", ex); + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't delegate work item", ex); + } + getPageBase().processResult(target, result, false); + getPageBase().redirectBack(); + } + + private void assumePowerOfAttorneyIfRequested(OperationResult result) { +// if (powerDonor != null) { +// WebModelServiceUtils.assumePowerOfAttorney(powerDonor, getModelInteractionService(), getTaskManager(), result); +// } + } + + private void dropPowerOfAttorneyIfRequested(OperationResult result) { +// if (powerDonor != null) { +// WebModelServiceUtils.dropPowerOfAttorney(getModelInteractionService(), getTaskManager(), result); +// } + } + +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemListWithDetailsPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemListWithDetailsPanel.html index 887375d58ba..2bac08ad9ea 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemListWithDetailsPanel.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemListWithDetailsPanel.html @@ -17,10 +17,6 @@ -
-
-
-
-
+
\ No newline at end of file diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemListWithDetailsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemListWithDetailsPanel.java index e28e1de7a03..4ece90e8645 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemListWithDetailsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemListWithDetailsPanel.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2010-2019 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.web.page.admin.cases; import com.evolveum.midpoint.gui.api.component.DisplayNamePanel; @@ -49,16 +64,8 @@ public abstract class CaseWorkItemListWithDetailsPanel extends MultivalueContainerListPanelWithDetailsPanel { private static final long serialVersionUID = 1L; - private static final Trace LOGGER = TraceManager.getTrace(CaseWorkItemListWithDetailsPanel.class); - - private static final String DOT_CLASS = CaseWorkItemListWithDetailsPanel.class.getName() + "."; - private static final String OPERATION_SAVE_WORK_ITEM = DOT_CLASS + "saveWorkItem"; - private static final String OPERATION_DELEGATE_WORK_ITEM = DOT_CLASS + "delegateWorkItem"; - private static final String ID_WORK_ITEM_APPROVE_BUTTON = "workItemApproveButton"; - private static final String ID_WORK_ITEM_REJECT_BUTTON = "workItemRejectButton"; - private static final String ID_WORK_ITEM_DELEGATE_BUTTON = "workItemDelegateButton"; - private static final String ID_ACTION_BUTTONS = "actionButtons"; + private static final String ID_CASE_WORK_ITEM_ACTIONS_PANEL = "caseWorkItemActionsPanel"; public CaseWorkItemListWithDetailsPanel(String id, IModel> model, UserProfileStorage.TableId tableId, PageStorage pageStorage){ super(id, model, tableId, pageStorage); @@ -68,46 +75,9 @@ public CaseWorkItemListWithDetailsPanel(String id, IModel !isParentCaseClosed())); - getDetailsPanelContainer().add(actionButtonsContainer); - - AjaxButton workItemApproveButton = new AjaxButton(ID_WORK_ITEM_APPROVE_BUTTON, - createStringResource("pageWorkItem.button.approve")) { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget ajaxRequestTarget) { - savePerformed(ajaxRequestTarget, unwrapPanelModel(), true); - } - }; - workItemApproveButton.setOutputMarkupId(true); - actionButtonsContainer.add(workItemApproveButton); - - AjaxButton workItemRejectButton = new AjaxButton(ID_WORK_ITEM_REJECT_BUTTON, - createStringResource("pageWorkItem.button.reject")) { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget ajaxRequestTarget) { - savePerformed(ajaxRequestTarget, unwrapPanelModel(), false); - } - }; - workItemRejectButton.setOutputMarkupId(true); - actionButtonsContainer.add(workItemRejectButton); - - AjaxButton workItemDelegateButton = new AjaxButton(ID_WORK_ITEM_DELEGATE_BUTTON, - createStringResource("pageWorkItem.button.delegate")) { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget ajaxRequestTarget) { - delegatePerformed(ajaxRequestTarget); - } - }; - workItemDelegateButton.setOutputMarkupId(true); - actionButtonsContainer.add(workItemDelegateButton); + CaseWorkItemActionsPanel actionsPanel = new CaseWorkItemActionsPanel(ID_CASE_WORK_ITEM_ACTIONS_PANEL, Model.of(unwrapPanelModel())); + actionsPanel.setOutputMarkupId(true); + getDetailsPanelContainer().add(actionsPanel); } @Override @@ -245,98 +215,13 @@ public void onClick(AjaxRequestTarget target, IModel> rowModel){ return rowModel.getObject().getRealValue(); } private CaseWorkItemType unwrapPanelModel(){ - return getModelObject().getItem().getRealValue(); - } - - - private void savePerformed(AjaxRequestTarget target, CaseWorkItemType workItem, boolean approved) { - Task task = getPageBase().createSimpleTask(OPERATION_SAVE_WORK_ITEM); - OperationResult result = task.getResult(); - try { - //todo implement custom panels -// WorkItemDto dto = workItemDtoModel.getObject(); -// if (approved) { -// boolean requiredFieldsPresent = getWorkItemPanel().checkRequiredFields(); -// if (!requiredFieldsPresent) { -// target.add(getFeedbackPanel()); -// return; -// } -// } -// ObjectDelta delta = getWorkItemPanel().getDeltaFromForm(); -// if (delta != null) { -// //noinspection unchecked -// getPrismContext().adopt(delta); -// } - try { - assumePowerOfAttorneyIfRequested(result); - //todo fix comment and delta - getPageBase().getWorkflowService().completeWorkItem(WorkItemId.of(workItem), approved, "", - null, task, result); - } finally { - dropPowerOfAttorneyIfRequested(result); - } - } catch (Exception ex) { - result.recordFatalError("Couldn't save work item.", ex); - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't save work item", ex); - } - getPageBase().processResult(target, result, false); - } - - private void delegatePerformed(AjaxRequestTarget target) { - ObjectBrowserPanel panel = new ObjectBrowserPanel( - getPageBase().getMainPopupBodyId(), UserType.class, - Collections.singletonList(UserType.COMPLEX_TYPE), false, getPageBase(), null) { - private static final long serialVersionUID = 1L; - - @Override - protected void onSelectPerformed(AjaxRequestTarget target, UserType user) { - CaseWorkItemListWithDetailsPanel.this.getPageBase().hideMainPopup(target); - delegateConfirmedPerformed(target, user); - } - - }; - panel.setOutputMarkupId(true); - getPageBase().showMainPopup(panel, target); - } - - private void delegateConfirmedPerformed(AjaxRequestTarget target, UserType delegate) { - Task task = getPageBase().createSimpleTask(OPERATION_DELEGATE_WORK_ITEM); - OperationResult result = task.getResult(); - try { - List delegates = Collections.singletonList(ObjectTypeUtil.createObjectRef(delegate, getPrismContext())); - try { - assumePowerOfAttorneyIfRequested(result); - getPageBase().getWorkflowService().delegateWorkItem(WorkItemId.of(unwrapPanelModel()), delegates, WorkItemDelegationMethodType.ADD_ASSIGNEES, - task, result); - } finally { - dropPowerOfAttorneyIfRequested(result); - } - } catch (Exception ex) { - result.recordFatalError("Couldn't delegate work item.", ex); - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't delegate work item", ex); - } - getPageBase().processResult(target, result, false); - } - - private void assumePowerOfAttorneyIfRequested(OperationResult result) { -// if (powerDonor != null) { -// WebModelServiceUtils.assumePowerOfAttorney(powerDonor, getModelInteractionService(), getTaskManager(), result); -// } - } - - private void dropPowerOfAttorneyIfRequested(OperationResult result) { -// if (powerDonor != null) { -// WebModelServiceUtils.dropPowerOfAttorney(getModelInteractionService(), getTaskManager(), result); -// } + return getModelObject() != null ? (getModelObject().getItem().getValue() != null ? getModelObject().getItem().getValue().asContainerable() : null) + : null; } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.html new file mode 100644 index 00000000000..008e8c0bd9d --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.html @@ -0,0 +1,24 @@ + + + + + +
+ + + \ No newline at end of file diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.java new file mode 100644 index 00000000000..efc3f17d093 --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2010-2019 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.web.page.admin.cases; + +import com.evolveum.midpoint.gui.api.GuiStyleConstants; +import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; +import com.evolveum.midpoint.gui.api.model.LoadableModel; +import com.evolveum.midpoint.gui.api.page.PageBase; +import com.evolveum.midpoint.gui.api.prism.PrismObjectWrapper; +import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; +import com.evolveum.midpoint.prism.PrismConstants; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.query.builder.S_FilterEntryOrEmpty; +import com.evolveum.midpoint.web.component.form.Form; +import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; +import com.evolveum.midpoint.web.component.objectdetails.AbstractObjectTabPanel; +import com.evolveum.midpoint.web.component.util.SelectableBean; +import com.evolveum.midpoint.web.session.UserProfileStorage; +import com.evolveum.midpoint.web.util.OnePageParameterEncoder; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.mapper.parameter.PageParameters; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Created by honchar + */ +public class ChildCasesTabPanel extends AbstractObjectTabPanel { + private static final long serialVersionUID = 1L; + + private static final String ID_CHILD_CASES_PANEL = "childCasesPanel"; + + public ChildCasesTabPanel(String id, Form> mainForm, LoadableModel> objectWrapperModel) { + super(id, mainForm, objectWrapperModel); + } + + @Override + protected void onInitialize(){ + super.onInitialize(); + initLayout(); + } + + private void initLayout() { + setOutputMarkupId(true); + + MainObjectListPanel table = new MainObjectListPanel(ID_CHILD_CASES_PANEL, + CaseType.class, UserProfileStorage.TableId.PAGE_CASE_CHILD_CASES_TAB, Collections.emptyList(), getPageBase()) { + +// @Override +// protected IColumn, String> createCheckboxColumn() { +// return null; +// } + + @Override + protected void objectDetailsPerformed(AjaxRequestTarget target, CaseType caseInstance) { + PageParameters pageParameters = new PageParameters(); + pageParameters.add(OnePageParameterEncoder.PARAMETER, caseInstance.getOid()); + ChildCasesTabPanel.this.getPageBase().navigateToNext(PageCase.class, pageParameters); + } + + @Override + protected List, String>> createColumns() { + List, String>> columns = new ArrayList, String>>(); + + IColumn column = new PropertyColumn(createStringResource("pageCases.table.description"), "value.description"); + columns.add(column); + + column = new PropertyColumn, String>(createStringResource("pageCases.table.state"), CaseType.F_STATE.getLocalPart(), "value.state"); + columns.add(column); + + column = new AbstractExportableColumn, String>( + createStringResource("pageCases.table.workitems")) { + + @Override + public void populateItem(Item>> cellItem, + String componentId, IModel> model) { + cellItem.add(new Label(componentId, + model.getObject().getValue() != null && model.getObject().getValue().getWorkItem() != null ? + model.getObject().getValue().getWorkItem().size() : null)); + } + + @Override + public IModel getDataModel(IModel> rowModel) { + return Model.of(rowModel.getObject().getValue() != null && rowModel.getObject().getValue().getWorkItem() != null ? + Integer.toString(rowModel.getObject().getValue().getWorkItem().size()) : ""); + } + + + }; + columns.add(column); + return columns; + } + + @Override + protected boolean isCreateNewObjectEnabled(){ + return false; + } + + @Override + protected ObjectQuery addFilterToContentQuery(ObjectQuery query) { + if (query == null) { + query = ChildCasesTabPanel.this.getPageBase().getPrismContext().queryFactory().createQuery(); + } + ObjectQuery queryFilter = ChildCasesTabPanel.this.getPageBase().getPrismContext().queryFor(CaseType.class) + .item(CaseType.F_PARENT_REF) + .ref(getObjectWrapper().getOid()) + .build(); + query.addFilter(queryFilter.getFilter()); + return query; + } + + @Override + protected WebMarkupContainer createTableButtonToolbar(String id) { + return null; + } + + @Override + protected List createInlineMenu(){ + return new ArrayList<>(); + } + + }; + table.setOutputMarkupId(true); + add(table); + } +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/OperationRequestCaseTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/OperationRequestCaseTabPanel.java index bf7caafe60c..6eabef97b48 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/OperationRequestCaseTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/OperationRequestCaseTabPanel.java @@ -18,10 +18,17 @@ import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.prism.PrismObjectWrapper; +import com.evolveum.midpoint.gui.api.util.WebComponentUtil; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.web.component.form.Form; import com.evolveum.midpoint.web.component.objectdetails.AbstractObjectTabPanel; +import com.evolveum.midpoint.web.component.prism.show.SceneDto; +import com.evolveum.midpoint.web.component.prism.show.ScenePanel; +import com.evolveum.midpoint.web.page.admin.workflow.WorkItemDetailsPanel; import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.model.IModel; /** * Created by honchar @@ -29,6 +36,13 @@ public class OperationRequestCaseTabPanel extends AbstractObjectTabPanel { private static final long serialVersionUID = 1L; + private static final String DOT_CLASS = OperationRequestCaseTabPanel.class.getName() + "."; + private static final Trace LOGGER = TraceManager.getTrace(OperationRequestCaseTabPanel.class); + private static final String OPERATION_PREPARE_DELTA_VISUALIZATION = DOT_CLASS + "prepareDeltaVisualization"; + + private static String ID_OPERATIONAL_REQUEST_CASE_PANEL = "operationRequestCasePanel"; + private IModel sceneModel; + public OperationRequestCaseTabPanel(String id, Form> mainForm, LoadableModel> objectWrapperModel, PageBase pageBase) { super(id, mainForm, objectWrapperModel); } @@ -36,11 +50,27 @@ public OperationRequestCaseTabPanel(String id, Form @Override protected void onInitialize(){ super.onInitialize(); + initModels(); initLayout(); } + private void initModels(){ + sceneModel = new LoadableModel() { + @Override + protected SceneDto load() { + PageBase pageBase = OperationRequestCaseTabPanel.this.getPageBase(); + return WebComponentUtil.createSceneDto(getObjectWrapper().getObject().asObjectable(), pageBase, OPERATION_PREPARE_DELTA_VISUALIZATION); + } + }; + } + + private void initLayout(){ - add(new WebMarkupContainer("aoperationRequestCasePanel")); +// ScenePanel scenePanel = new ScenePanel(ID_OPERATIONAL_REQUEST_CASE_PANEL, sceneModel); +// scenePanel.setOutputMarkupId(true); +// add(scenePanel); + + add(new WebMarkupContainer(ID_OPERATIONAL_REQUEST_CASE_PANEL)); } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCase.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCase.java index ee12625bbb7..abed83b0715 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCase.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCase.java @@ -20,6 +20,7 @@ import com.evolveum.midpoint.gui.api.component.tabs.PanelTab; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.tabs.AbstractTab; import org.apache.wicket.extensions.markup.html.tabs.ITab; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.model.Model; @@ -57,133 +58,19 @@ public class PageCase extends PageAdminObjectDetails { private static final String ID_SUMMARY_PANEL = "summaryPanel"; public PageCase() { - initialize(null); + this(null, true); } public PageCase(PrismObject unitToEdit, boolean isNewObject) { - initialize(unitToEdit, isNewObject); + initialize(unitToEdit, isNewObject, true); } public PageCase(PageParameters parameters) { getPageParameters().overwriteWith(parameters); - initialize(null); + initialize(null, true, true); } -// @Override -// protected void initializeModel(final PrismObject caseObject, boolean isNewObject, boolean isReadonly) { -// super.initializeModel(loadCase(), isNewObject, isReadonly); -// } - -// private PrismObject loadCase() { -// Task task = createSimpleTask(OPERATION_LOAD_CASE); -// OperationResult result = task.getResult(); -// -// Collection> options = getOperationOptionsBuilder() -// .item(CaseType.F_OBJECT_REF).resolve() -// .build(); -// boolean emptyCase = !isEditingFocus(); -// PrismObject caseInstance = null; -// try { -// if (emptyCase) { -// LOGGER.trace("Loading case: New case (creating)"); -// CaseType newCase = new CaseType(); -// getMidpointApplication().getPrismContext().adopt(newCase); -// caseInstance = newCase.asPrismObject(); -// } else { -// String oid = getObjectOidParameter(); -// -// caseInstance = WebModelServiceUtils.loadObject(CaseType.class, oid, options, -// PageCase.this, task, result); -// -// if (caseInstance == null) { -// LOGGER.trace("caseInstance:[oid]={} was null", oid); -// getSession().error(getString("pageCase.message.cantEditCase")); -// showResult(result); -// throw new RestartResponseException(PageCases.class); -// } -// LOGGER.debug("CASE WORK ITEMS: {}", caseInstance.asObjectable().getWorkItem()); -// } -// } catch (Exception ex) { -// result.recordFatalError("Couldn't get case.", ex); -// LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load case", ex); -// } -// -// if (caseInstance == null) { -// if (isEditingFocus()) { -// getSession().error(getString("pageAdminFocus.message.cantEditFocus")); -// } else { -// getSession().error(getString("pageAdminFocus.message.cantNewFocus")); -// } -// throw new RestartResponseException(PageCases.class); -// } -//// -//// ObjectWrapper wrapper; -//// ObjectWrapperFactory owf = new ObjectWrapperFactory(this); -//// ContainerStatus status = isEditingFocus() ? ContainerStatus.MODIFYING : ContainerStatus.ADDING; -//// try { -//// wrapper = owf.createObjectWrapper("PageCase.details", null, caseInstance, status, task); -//// } catch (Exception ex) { -//// result.recordFatalError("Couldn't get case.", ex); -//// LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load case", ex); -//// try { -//// wrapper = owf.createObjectWrapper("PageCase.details", null, caseInstance, null, null, status, task); -//// } catch (SchemaException e) { -//// throw new SystemException(e.getMessage(), e); -//// } -//// } -//// -//// wrapper.setShowEmpty(emptyCase); -//// -//// //for now decided to make targetRef readonly -//// wrapper.getContainers().forEach(containerWrapper -> { -//// if (containerWrapper.isMain()){ -//// containerWrapper.getValues().forEach(containerValueWrapper -> { -//// PropertyOrReferenceWrapper itemWrapper = containerValueWrapper.findPropertyWrapperByName(CaseType.F_TARGET_REF); -//// if (itemWrapper != null){ -//// itemWrapper.setReadonly(true); -//// } -//// }); -//// } -//// }); -// -// PrismObjectWrapperFactory owf = getRegistry().getObjectWrapperFactory(caseInstance.getDefinition()); -// PrismObjectWrapper wrapper; -//// ObjectWrapperFactory owf = new ObjectWrapperFactory(this); -// ItemStatus status = isEditingFocus() ? ItemStatus.NOT_CHANGED : ItemStatus.ADDED; -// try { -// WrapperContext context = new WrapperContext(task, result); -// wrapper = owf.createObjectWrapper(caseInstance, status, context); -// } catch (Exception ex) { -// result.recordFatalError("Couldn't get case.", ex); -// LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load case", ex); -// try { -// WrapperContext context = new WrapperContext(task, result); -// wrapper = owf.createObjectWrapper(caseInstance,status, context); -// } catch (SchemaException e) { -// throw new SystemException(e.getMessage(), e); -// } -// } -// -//// wrapper.setShowEmpty(emptyCase); -// -// //for now decided to make targetRef readonly -// -// //TODO maybe do it while creating wrappers -//// wrapper.getContainers().forEach(containerWrapper -> { -//// if (containerWrapper.isMain()){ -//// containerWrapper.getValues().forEach(containerValueWrapper -> { -//// PropertyOrReferenceWrapper itemWrapper = containerValueWrapper.findPropertyWrapperByName(CaseType.F_TARGET_REF); -//// if (itemWrapper != null){ -//// itemWrapper.setReadonly(true); -//// } -//// }); -//// } -//// }); -// -// return wrapper.getObject(); -// } - @Override protected AbstractObjectMainPanel createMainPanel(String id) { return new AssignmentHolderTypeMainPanel(id, getObjectModel(), this) { @@ -208,7 +95,7 @@ public WebMarkupContainer createPanel(String panelId) { }); } else if (matchCaseType(SystemObjectsType.ARCHETYPE_OPERATION_REQUEST)) { tabs.add(0, - new PanelTab(parentPage.createStringResource("PageCase.approvalTab"), + new PanelTab(parentPage.createStringResource("PageCase.operationRequestTab"), getTabVisibility(ComponentConstants.UI_CASE_TAB_APPROVAL_URL, true, parentPage)) { private static final long serialVersionUID = 1L; @@ -222,22 +109,38 @@ public WebMarkupContainer createPanel(String panelId) { } else if (matchCaseType(SystemObjectsType.ARCHETYPE_MANUAL_CASE)) { //todo manual case tab } - tabs.add( - new CountablePanelTab(parentPage.createStringResource("PageCase.workitemsTab"), - getTabVisibility(ComponentConstants.UI_CASE_TAB_WORKITEMS_URL, false, parentPage)) { + if (matchCaseType(SystemObjectsType.ARCHETYPE_OPERATION_REQUEST)){ + tabs.add( + new PanelTab(parentPage.createStringResource("PageCase.childCasesTab"), + getTabVisibility(ComponentConstants.UI_CASE_TAB_CHILD_CASES_URL, false, parentPage)) { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - @Override - public WebMarkupContainer createPanel(String panelId) { - return new CaseWorkitemsTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); - } + @Override + public WebMarkupContainer createPanel(String panelId) { + return new ChildCasesTabPanel(panelId, getMainForm(), getObjectModel()); + } + }); + } else { + //todo do manual cases have workitems? + tabs.add( + new CountablePanelTab(parentPage.createStringResource("PageCase.workitemsTab"), + getTabVisibility(ComponentConstants.UI_CASE_TAB_WORKITEMS_URL, false, parentPage)) { + + private static final long serialVersionUID = 1L; + + @Override + public WebMarkupContainer createPanel(String panelId) { + return new CaseWorkitemsTabPanel(panelId, getMainForm(), getObjectModel(), parentPage); + } + + @Override + public String getCount() { + return Integer.toString(countWorkItems()); + } + }); + } - @Override - public String getCount() { - return Integer.toString(countWorkItems()); - } - }); tabs.add( new CountablePanelTab(parentPage.createStringResource("PageCase.events"), getTabVisibility(ComponentConstants.UI_CASE_TAB_EVENTS_URL, false, parentPage)) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.html index 1747870d7ae..719ecfcb89b 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.html @@ -22,6 +22,7 @@
+
diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.java index 747ac75f3ac..57836492388 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.java @@ -19,6 +19,7 @@ import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.GetOperationOptionsBuilder; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.WorkItemId; import com.evolveum.midpoint.security.api.AuthorizationConstants; @@ -30,6 +31,7 @@ import com.evolveum.midpoint.web.application.AuthorizationAction; import com.evolveum.midpoint.web.application.PageDescriptor; import com.evolveum.midpoint.web.component.AjaxButton; +import com.evolveum.midpoint.web.component.util.VisibleBehaviour; import com.evolveum.midpoint.web.page.admin.workflow.CaseWorkItemSummaryPanel; import com.evolveum.midpoint.web.page.admin.workflow.WorkItemDetailsPanel; import com.evolveum.midpoint.web.util.OnePageParameterEncoder; @@ -37,6 +39,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.wicket.RestartResponseException; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.model.Model; import org.apache.wicket.request.mapper.parameter.PageParameters; import java.util.List; @@ -65,6 +68,7 @@ public class PageCaseWorkItem extends PageAdminCaseWorkItems { private static final Trace LOGGER = TraceManager.getTrace(PageCaseWorkItem.class); private static final String ID_WORK_ITEM_DETAILS = "workItemDetails"; private static final String ID_SUMMARY_PANEL = "summaryPanel"; + private static final String ID_CASE_WORK_ITEM_ACTIONS_PANEL = "caseWorkItemActionsPanel"; private static final String ID_DELTA_PANEL = "deltaPanel"; private static final String ID_MAIN_FORM = "mainForm"; private static final String ID_CASE_WORK_ITEM_FORM = "caseWorkItemForm"; @@ -204,6 +208,11 @@ public void onClick(AjaxRequestTarget target) { back.setOutputMarkupId(true); add(back); + CaseWorkItemActionsPanel actionsPanel = new CaseWorkItemActionsPanel(ID_CASE_WORK_ITEM_ACTIONS_PANEL, caseWorkItemModel); + actionsPanel.setOutputMarkupId(true); + actionsPanel.add(new VisibleBehaviour(() -> !SchemaConstants.CASE_STATE_CLOSED.equals(caseModel.getObject().getState()))); + add(actionsPanel); + } private void cancelPerformed() { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItems.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItems.java index a73508c9d68..0fba8c1465c 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItems.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItems.java @@ -28,33 +28,29 @@ import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.impl.prism.PrismContainerValueWrapper; import com.evolveum.midpoint.prism.query.ObjectPaging; +import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.util.CaseTypeUtil; import com.evolveum.midpoint.schema.util.WorkItemId; import com.evolveum.midpoint.web.component.data.column.ColumnUtils; +import com.evolveum.midpoint.web.component.data.column.IconColumn; import com.evolveum.midpoint.web.component.search.Search; import com.evolveum.midpoint.web.component.search.SearchFactory; import com.evolveum.midpoint.web.component.search.SearchFormPanel; import com.evolveum.midpoint.web.component.util.ContainerListDataProvider; -import com.evolveum.midpoint.web.component.util.MultivalueContainerListDataProvider; import com.evolveum.midpoint.web.util.OnePageParameterEncoder; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder; -import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.markup.html.panel.Panel; -import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; @@ -84,14 +80,12 @@ import com.evolveum.midpoint.web.component.data.column.LinkColumn; import com.evolveum.midpoint.web.component.form.multivalue.MultiValueChoosePanel; import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; -import com.evolveum.midpoint.web.page.admin.cases.dto.CaseWorkItemDto; import com.evolveum.midpoint.web.page.admin.cases.dto.CaseWorkItemDtoProvider; import com.evolveum.midpoint.web.page.admin.cases.dto.SearchingUtils; import com.evolveum.midpoint.web.page.admin.dto.ObjectViewDto; import com.evolveum.midpoint.web.page.admin.reports.component.SingleValueChoosePanel; import com.evolveum.midpoint.web.security.SecurityUtils; import com.evolveum.midpoint.web.session.UserProfileStorage; -import com.evolveum.midpoint.web.util.TooltipBehavior; import com.evolveum.midpoint.wf.util.QueryUtils; /** @@ -164,23 +158,22 @@ public ObjectQuery getQuery() { private ObjectQuery createQuery() throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { ObjectQuery query; boolean authorizedToSeeAll = isAuthorized(ModelAuthorizationAction.READ_ALL_WORK_ITEMS.getUrl()); - S_FilterEntryOrEmpty q = getPrismContext().queryFor(CaseWorkItemType.class); -// S_AtomicFilterExit query = queryStart.asc(PrismConstants.T_PARENT, CaseType.F_METADATA, MetadataType.F_CREATE_TIMESTAMP).; -// if (all && authorizedToSeeAll) { - query = q.build(); -// } else { - // not authorized to see all => sees only allocated to him (not quite what is expected, but sufficient for the time being) -// query = QueryUtils.filterForAssignees(q, SecurityUtils.getPrincipalUser(), -// OtherPrivilegesLimitationType.F_APPROVAL_WORK_ITEMS, getRelationRegistry()) -// .and().item(CaseWorkItemType.F_CLOSE_TIMESTAMP).isNull().build(); -// } + S_FilterEntryOrEmpty queryStart = getPrismContext().queryFor(CaseWorkItemType.class); + if (all && authorizedToSeeAll) { + query = queryStart.build(); + } else { +// not authorized to see all => sees only allocated to him (not quite what is expected, but sufficient for the time being) + query = QueryUtils.filterForAssignees(queryStart, SecurityUtils.getPrincipalUser(), + OtherPrivilegesLimitationType.F_APPROVAL_WORK_ITEMS, getRelationRegistry()) + .and().item(CaseWorkItemType.F_CLOSE_TIMESTAMP).isNull().build(); + } // IsolatedCheckBoxPanel includeClosedCases = (IsolatedCheckBoxPanel) getCaseWorkItemsSearchField(ID_SEARCH_FILTER_INCLUDE_CLOSED_CASES); // if (includeClosedCases == null || !includeClosedCases.getValue()) { query.addFilter( getPrismContext().queryFor(CaseWorkItemType.class) .not() .item(PrismConstants.T_PARENT, CaseType.F_STATE) - .eq("closed") + .eq(SchemaConstants.CASE_STATE_CLOSED) .build() .getFilter() ); @@ -201,37 +194,11 @@ private ObjectQuery createQuery() throws SchemaException, ObjectNotFoundExceptio // ); // } // } -// } - - // Assignee Filter -// SingleValueChoosePanel assigneeChoice = (SingleValueChoosePanel) getCaseWorkItemsSearchField(createComponentPath(ID_SEARCH_FILTER_ASSIGNEE_CONTAINER, ID_SEARCH_FILTER_ASSIGNEE)); -// if (assigneeChoice != null) { -// List assignees = assigneeChoice.getModelObject(); -// if (assignees != null && assignees.size() > 0) { -// ObjectType assignee = assignees.get(0); -// if (assignee != null) { -// // TODO MID-3581 -// query.addFilter( -// getPrismContext().queryFor(CaseWorkItemType.class) -// .item(CaseWorkItemType.F_ASSIGNEE_REF).ref(ObjectTypeUtil.createObjectRef(assignee, getPrismContext()).asReferenceValue()).buildFilter() -// ); -// } -// } // } return query; } - private String getCurrentUserOid() { - try { - return getSecurityContextManager().getPrincipal().getOid(); - } catch (SecurityViolationException e) { - // TODO handle more cleanly - throw new SystemException("Couldn't get currently logged user OID", e); - } - } - //endregion - private void initModels(){ searchModel = new LoadableModel(false) { @@ -277,7 +244,6 @@ protected void searchPerformed(ObjectQuery query, AjaxRequestTarget target) { }; table.setShowPaging(true); table.setOutputMarkupId(true); - table.setItemsPerPage(itemsPerPage); // really don't know why this is necessary, as e.g. in PageRoles the size setting works without it add(table); // initSearch(); } @@ -286,31 +252,22 @@ private void searchPerformed(AjaxRequestTarget target){ BoxedTablePanel table = (BoxedTablePanel) get(ID_CASE_WORK_ITEMS_TABLE); table.setCurrentPage(null); target.add((Component) table); - target.add(getFeedbackPanel()); } private List, String>> initColumns(){ List, String>> columns = new ArrayList<>(); -// columns.add(new IconColumn>(Model.of("")) { -// -// private static final long serialVersionUID = 1L; -// -// @Override -// protected IModel createIconModel(IModel> rowModel) { -// return new IModel() { -// -// private static final long serialVersionUID = 1L; -// -// @Override -// public String getObject() { -// return WebComponentUtil.createDefaultBlackIcon(AssignmentsUtil.getTargetType(rowModel.getObject().getContainerValue().asContainerable())); -// } -// }; -// } -// -// }); + columns.add(new IconColumn>(Model.of("")) { + + private static final long serialVersionUID = 1L; + + @Override + protected DisplayType getIconDisplayType(IModel> rowModel) { + return WebComponentUtil.createDisplayType(WebComponentUtil.createDefaultBlackIcon(CaseWorkItemType.COMPLEX_TYPE)); + } + + }); columns.add(new LinkColumn>(createStringResource("PolicyRulesPanel.nameColumn")){ private static final long serialVersionUID = 1L; diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCases.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCases.java index a1cbd5acc00..9eef80ab025 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCases.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCases.java @@ -1,22 +1,28 @@ package com.evolveum.midpoint.web.page.admin.cases; -import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; +import com.evolveum.midpoint.gui.api.GuiStyleConstants; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; -import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; +import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.security.api.AuthorizationConstants; +import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.web.application.AuthorizationAction; import com.evolveum.midpoint.web.application.PageDescriptor; import com.evolveum.midpoint.web.application.Url; -import com.evolveum.midpoint.web.component.form.Form; +import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; +import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; +import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; import com.evolveum.midpoint.web.component.util.SelectableBean; import com.evolveum.midpoint.web.page.admin.PageAdminObjectList; +import com.evolveum.midpoint.web.page.admin.users.component.ExecuteChangeOptionsDto; import com.evolveum.midpoint.web.session.UserProfileStorage; import com.evolveum.midpoint.web.util.OnePageParameterEncoder; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.lang.StringUtils; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; @@ -30,9 +36,7 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import javax.xml.datatype.XMLGregorianCalendar; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; +import java.util.*; /** * @author acope on 9/14/17. @@ -54,6 +58,8 @@ public class PageCases extends PageAdminObjectList { private static final String DOT_CLASS = PageCases.class.getName() + "."; private static final String OPERATION_LOAD_REFERENCE_DISPLAY_NAME = DOT_CLASS + "loadReferenceDisplayName"; + private static final String OPERATION_DELETE_CASE_OBJECT = DOT_CLASS + "deleteCaseObject"; + private static final String OPERATION_STOP_CASE_PROCESS = DOT_CLASS + "stopCaseProcess"; private static final long serialVersionUID = 1L; @@ -64,47 +70,6 @@ public PageCases() { super(); } -// private void initLayout() { -// Form mainForm = new Form(ID_MAIN_FORM); -// add(mainForm); -// -// LOGGER.trace("Creating casePanel"); -// MainObjectListPanel casePanel = -// new MainObjectListPanel( -// ID_CASES_TABLE, -// CaseType.class, -// UserProfileStorage.TableId.TABLE_CASES, -// null, -// this) { -// -// private static final long serialVersionUID = 1L; -// -// @Override -// protected void objectDetailsPerformed(AjaxRequestTarget target, CaseType caseInstance) { -// PageCases.this.caseDetailsPerformed(target, caseInstance); -// } -// -// @Override -// protected void newObjectPerformed(AjaxRequestTarget target, CompiledObjectCollectionView collectionView) { -// navigateToNext(PageCase.class); -// } -// -// @Override -// protected List, String>> createColumns() { -// return PageCases.this.initColumns(); -// } -// -// @Override -// protected List createInlineMenu() { -// return new ArrayList<>(); -// } -// -// }; -// casePanel.setOutputMarkupId(true); -// mainForm.add(casePanel); -// -// } - @Override protected void objectDetailsPerformed(AjaxRequestTarget target, CaseType caseInstance) { LOGGER.trace("caseDetailsPerformed()"); @@ -256,21 +221,134 @@ protected UserProfileStorage.TableId getTableId(){ return UserProfileStorage.TableId.TABLE_CASES; } + + @Override + protected boolean isCreateNewObjectEnabled(){ + return false; + } + + @Override + protected Collection> getQueryOptions(){ + return getOperationOptionsBuilder() + .item(CaseType.F_OBJECT_REF).resolve() + .item(CaseType.F_TARGET_REF).resolve() + .build(); + } + @Override protected List createRowActions() { List menu = new ArrayList<>(); + + menu.add(new ButtonInlineMenuItem(createStringResource("pageCases.button.stopProcess")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new ColumnMenuAction>() { + + @Override + public void onClick(AjaxRequestTarget target) { + if (getRowModel() == null) { + stopCaseProcessConfirmed(target); + } else { + stopCaseProcessConfirmed(target, Arrays.asList(getRowModel().getObject().getValue())); + } + } + }; + } + + @Override + public String getButtonIconCssClass(){ + return GuiStyleConstants.CLASS_STOP_MENU_ITEM; + } + + @Override + public IModel getConfirmationMessageModel(){ + return getObjectListPanel().getSelectedObjectsCount() > 0 ? + createStringResource("pageCases.button.stopProcess.multiple.confirmationMessage", getObjectListPanel().getSelectedObjectsCount()) : + createStringResource("pageCases.button.stopProcess.confirmationMessage"); + } + + }); + menu.add(new ButtonInlineMenuItem(createStringResource("pageCases.button.delete")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new ColumnMenuAction>() { + + @Override + public void onClick(AjaxRequestTarget target) { + if (getRowModel() == null) { + deleteCaseObjectsConfirmed(target); + } else { + deleteCaseObjectsConfirmed(target, Arrays.asList(getRowModel().getObject().getValue())); + } + } + }; + } + + @Override + public String getButtonIconCssClass(){ + return GuiStyleConstants.CLASS_DELETE_MENU_ITEM; + } + + @Override + public IModel getConfirmationMessageModel(){ + return getObjectListPanel().getSelectedObjectsCount() > 0 ? + createStringResource("pageCases.button.delete.multiple.confirmationMessage", getObjectListPanel().getSelectedObjectsCount()) : + createStringResource("pageCases.button.delete.confirmationMessage"); + } + + }); + return menu; } - private String getObjectRef(IModel> caseModel) { + private String getObjectRef(IModel> caseModel) { CaseType caseModelObject = caseModel.getObject().getValue(); - if (caseModelObject.getObjectRef() == null) { + if (caseModelObject == null || caseModelObject.getObjectRef() == null) { return ""; } - if (caseModelObject.getObjectRef().getTargetName() != null && StringUtils.isNotEmpty(caseModelObject.getObjectRef().getTargetName().getOrig())) { - return caseModelObject.getObjectRef().getTargetName().getOrig(); - } else { - return caseModelObject.getObjectRef().getOid(); + return WebComponentUtil.getEffectiveName(caseModelObject.getObjectRef(), AbstractRoleType.F_DISPLAY_NAME, PageCases.this, + OPERATION_LOAD_REFERENCE_DISPLAY_NAME); + } + + private void deleteCaseObjectsConfirmed(AjaxRequestTarget target){ + deleteCaseObjectsConfirmed(target, getObjectListPanel().getSelectedObjects()); + } + + private void deleteCaseObjectsConfirmed(AjaxRequestTarget target, List casesToDelete){ + if (casesToDelete == null){ + return; + } + casesToDelete.forEach(caseObject -> { + WebModelServiceUtils.deleteObject(CaseType.class, caseObject.getOid(), + ExecuteChangeOptionsDto.createFromSystemConfiguration().createOptions(), + new OperationResult(OPERATION_DELETE_CASE_OBJECT), PageCases.this); + }); + target.add(PageCases.this); + } + + private void stopCaseProcessConfirmed(AjaxRequestTarget target){ + stopCaseProcessConfirmed(target, getObjectListPanel().getSelectedObjects()); + } + + private void stopCaseProcessConfirmed(AjaxRequestTarget target, List casesToStop){ + if (casesToStop == null){ + return; } + casesToStop.forEach(caseObject -> { + Task task = createSimpleTask(OPERATION_STOP_CASE_PROCESS); + OperationResult result = new OperationResult(OPERATION_STOP_CASE_PROCESS); + try { + getWorkflowService().stopProcessInstance(caseObject.getOid(), task, result); + } catch (Exception ex){ + LOGGER.error("Couldn't stop case process, ", ex.getLocalizedMessage()); + result.recordFatalError("Couldn't stop case process, ", ex); + showResult(result); + } + }); + target.add(PageCases.this); } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/component/AuditLogViewerPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/component/AuditLogViewerPanel.java index 5247e598a41..5da91d165c1 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/component/AuditLogViewerPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/component/AuditLogViewerPanel.java @@ -214,7 +214,7 @@ private void initParametersPanel(Form mainForm) { AuditSearchDto.F_COLLECTION + ".auditSearch.recordQuery")); usedQuery.setOutputMarkupId(true); usedQuery.setEnabled(false); - parametersPanel.add(usedQuery); + usedQueryContainer.add(usedQuery); WebMarkupContainer usedIntervalContainer = new WebMarkupContainer(ID_USED_INTERVAL_CONTAINER); usedIntervalContainer.setOutputMarkupId(true); @@ -225,7 +225,7 @@ private void initParametersPanel(Form mainForm) { AuditSearchDto.F_COLLECTION + ".auditSearch.interval")); usedInterval.setOutputMarkupId(true); usedInterval.setEnabled(false); - parametersPanel.add(usedInterval); + usedIntervalContainer.add(usedInterval); DropDownChoicePanel eventType = new DropDownChoicePanel<>( ID_EVENT_TYPE, new PropertyModel<>( diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTaskEdit.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTaskEdit.java index fdc92d38ac4..2de56474290 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTaskEdit.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTaskEdit.java @@ -40,7 +40,6 @@ import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContainerDefinition; import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.schema.GetOperationOptions; @@ -466,7 +465,7 @@ private boolean isEditable(ItemDefinition definition, Set> } protected boolean isEditable(ItemPath itemPath) { - ItemDefinition itemDefinition = ((PrismObjectDefinition)objectWrapperModel.getObject()).findItemDefinition(itemPath); + ItemDefinition itemDefinition = objectWrapperModel.getObject().findItemDefinition(itemPath); if (itemDefinition != null) { return itemDefinition.canRead() && itemDefinition.canModify(); } else { @@ -475,7 +474,7 @@ protected boolean isEditable(ItemPath itemPath) { } protected boolean isReadable(ItemPath itemPath) { - ItemDefinition itemDefinition = ((PrismObjectDefinition)objectWrapperModel.getObject()).findItemDefinition(itemPath); + ItemDefinition itemDefinition = objectWrapperModel.getObject().findItemDefinition(itemPath); if (itemDefinition != null) { return itemDefinition.canRead(); } else { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/dto/TaskDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/dto/TaskDto.java index ecf866557f7..62e394815a7 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/dto/TaskDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/dto/TaskDto.java @@ -1218,8 +1218,9 @@ public boolean isWorkflowCategory() { } public boolean isWorkflowChild() { - // TODO-WF - throw new UnsupportedOperationException("TODO"); + return false; +// // TODO-WF +// throw new UnsupportedOperationException("TODO"); // // return isWorkflowCategory() && getWorkflowContext() != null && getWorkflowContext().getCaseOid() != null; } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/PageWorkItem.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/PageWorkItem.java index 0c5125a9e44..dc404d34b44 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/PageWorkItem.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/PageWorkItem.java @@ -379,7 +379,7 @@ private void claimPerformed(AjaxRequestTarget target) { WorkflowService workflowService = getWorkflowService(); try { workflowService.claimWorkItem(workItemDtoModel.getObject().getWorkItemId(), task, result); - } catch (SecurityViolationException | ObjectNotFoundException | RuntimeException | SchemaException e) { + } catch (SecurityViolationException | ObjectNotFoundException | RuntimeException | SchemaException | ObjectAlreadyExistsException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { result.recordFatalError("Couldn't claim work item due to an unexpected exception.", e); } processResult(target, result, true); @@ -391,7 +391,7 @@ private void releasePerformed(AjaxRequestTarget target) { WorkflowService workflowService = getWorkflowService(); try { workflowService.releaseWorkItem(WorkItemId.of(workItemDtoModel.getObject().getWorkItem()), task, result); - } catch (SecurityViolationException | ObjectNotFoundException | RuntimeException | SchemaException e) { + } catch (SecurityViolationException | ObjectNotFoundException | RuntimeException | SchemaException | ObjectAlreadyExistsException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { result.recordFatalError("Couldn't release work item due to an unexpected exception.", e); } processResult(target, result, true); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/PageWorkItems.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/PageWorkItems.java index efabda942dd..0b3a5429e76 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/PageWorkItems.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/PageWorkItems.java @@ -26,9 +26,7 @@ import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.task.api.Task; -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.*; import com.evolveum.midpoint.web.component.AjaxButton; import com.evolveum.midpoint.web.component.util.VisibleBehaviour; import com.evolveum.midpoint.web.component.wf.WorkItemsPanel; @@ -267,7 +265,7 @@ private void claimWorkItemsPerformed(AjaxRequestTarget target) { try { workflowService.claimWorkItem(workItemDto.getWorkItemId(), task, result); result.computeStatusIfUnknown(); - } catch (ObjectNotFoundException | SecurityViolationException | RuntimeException | SchemaException e) { + } catch (ObjectNotFoundException | SecurityViolationException | RuntimeException | SchemaException | ObjectAlreadyExistsException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { result.recordPartialError(createStringResource("pageWorkItems.message.partialError.claimed").getString(), e); } } @@ -304,7 +302,7 @@ private void releaseWorkItemsPerformed(AjaxRequestTarget target) { if (!result.isNotApplicable()) { applicable++; } - } catch (ObjectNotFoundException | SecurityViolationException | RuntimeException | SchemaException e) { + } catch (ObjectNotFoundException | SecurityViolationException | RuntimeException | SchemaException | ObjectAlreadyExistsException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { result.recordPartialError(createStringResource("pageWorkItems.message.partialError.released").getString(), e); } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java index 6c79ecee215..5aad381d41b 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java @@ -18,6 +18,7 @@ import com.evolveum.midpoint.gui.api.component.BasePanel; import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.page.PageBase; +import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.model.api.visualizer.Scene; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.CaseTypeUtil; @@ -77,27 +78,7 @@ private void initModels(){ @Override protected SceneDto load() { PageBase pageBase = WorkItemDetailsPanel.this.getPageBase(); - // todo scene dto is taken from old code. ok? - CaseType aCase = CaseTypeUtil.getCase(WorkItemDetailsPanel.this.getModelObject()); - if (aCase == null || aCase.getWorkflowContext() == null) { - return null; - } - if (!(aCase.getWorkflowContext().getProcessorSpecificState() instanceof WfPrimaryChangeProcessorStateType)) { - return null; - } - ObjectReferenceType objectRef = aCase.getObjectRef(); - WfPrimaryChangeProcessorStateType state = (WfPrimaryChangeProcessorStateType) aCase.getWorkflowContext().getProcessorSpecificState(); - - OperationResult result = new OperationResult(OPERATION_PREPARE_DELTA_VISUALIZATION); - Task task = pageBase.createSimpleTask(OPERATION_PREPARE_DELTA_VISUALIZATION); - try { - Scene deltasScene = SceneUtil.visualizeObjectTreeDeltas(state.getDeltasToProcess(), "pageWorkItem.delta", - pageBase.getPrismContext(), pageBase.getModelInteractionService(), objectRef, task, result); - return new SceneDto(deltasScene); - } catch (SchemaException | ExpressionEvaluationException ex){ - LOGGER.error("Unable to create delta visualization for work item " + WorkItemDetailsPanel.this.getModelObject(), ex.getLocalizedMessage()); - } - return null; + return WebComponentUtil.createSceneDto(WorkItemDetailsPanel.this.getModelObject(), pageBase, OPERATION_PREPARE_DELTA_VISUALIZATION); } }; } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java index 6348a85507a..9ed8d637195 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java @@ -156,24 +156,24 @@ private static void addInformationFromRecordedStage(ApprovalStageExecutionInform } } // not needed after "create work item" events will be implemented - for (CaseWorkItemType workItem : executionRecord.getWorkItem()) { - ObjectReferenceType approver = workItem.getOriginalAssigneeRef(); - if (approver == null) { - LOGGER.warn("No original assignee in work item {} -- ignoring it", workItem); - continue; - } - WorkItemId externalWorkItemId = WorkItemId.of(workItem); // fixme work item has no parent here!!! - if (externalWorkItemId == null) { - LOGGER.warn("No external work item ID in work item {} -- ignoring it", workItem); - continue; - } - ApproverEngagementDto engagement = rv.findApproverEngagement(approver, externalWorkItemId); - if (engagement == null) { - resolve(approver, resolver, session, opTask, result); - engagement = new ApproverEngagementDto(approver, externalWorkItemId); - rv.addApproverEngagement(engagement); - } - } +// for (CaseWorkItemType workItem : executionRecord.getWorkItem()) { +// ObjectReferenceType approver = workItem.getOriginalAssigneeRef(); +// if (approver == null) { +// LOGGER.warn("No original assignee in work item {} -- ignoring it", workItem); +// continue; +// } +// WorkItemId externalWorkItemId = WorkItemId.of(workItem); // fixme work item has no parent here!!! +// if (externalWorkItemId == null) { +// LOGGER.warn("No external work item ID in work item {} -- ignoring it", workItem); +// continue; +// } +// ApproverEngagementDto engagement = rv.findApproverEngagement(approver, externalWorkItemId); +// if (engagement == null) { +// resolve(approver, resolver, session, opTask, result); +// engagement = new ApproverEngagementDto(approver, externalWorkItemId); +// rv.addApproverEngagement(engagement); +// } +// } } private void addApproverEngagement(ApproverEngagementDto engagement) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/session/UserProfileStorage.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/session/UserProfileStorage.java index 9f765b78b50..08af6f25eeb 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/session/UserProfileStorage.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/session/UserProfileStorage.java @@ -98,6 +98,7 @@ public enum TableId { USERS_VIEW_TABLE, FOCUS_PROJECTION_TABLE, PAGE_CASE_WORKITEMS_TAB, + PAGE_CASE_CHILD_CASES_TAB, PAGE_CASE_EVENTS_TAB } diff --git a/gui/admin-gui/src/main/resources/initial-objects/025-archetype-approval-case.xml b/gui/admin-gui/src/main/resources/initial-objects/025-archetype-approval-case.xml index 11b8c549df2..b01c694cc93 100644 --- a/gui/admin-gui/src/main/resources/initial-objects/025-archetype-approval-case.xml +++ b/gui/admin-gui/src/main/resources/initial-objects/025-archetype-approval-case.xml @@ -27,7 +27,7 @@ Approval Cases - fe fe-approver + fe fe-approver-object diff --git a/gui/admin-gui/src/main/resources/localization/Midpoint.properties b/gui/admin-gui/src/main/resources/localization/Midpoint.properties index 579bf1daf1e..dc9fbc0c7be 100755 --- a/gui/admin-gui/src/main/resources/localization/Midpoint.properties +++ b/gui/admin-gui/src/main/resources/localization/Midpoint.properties @@ -2508,7 +2508,9 @@ pageUser.userDetails=User details PageCase.title=Case details PageCase.workitemsTab=Workitems +PageCase.childCasesTab=Child cases PageCase.approvalTab=Approval +PageCase.operationRequestTab=Operation request PageCase.events=Events PageCaseWorkItem.title=Case work item details pageCase.button.save=Save @@ -2545,6 +2547,11 @@ pageCases.table.openTimestamp=Opened pageCases.table.closeTimestamp=Closed pageCases.table.workitems=Workitems pageCases.button.delete=Delete +pageCases.button.stopProcess=Stop process +pageCases.button.stopProcess.confirmationMessage=Do you really want to stop case process? +pageCases.button.stopProcess.multiple.confirmationMessage=Do you really want to stop process for {0} cases? +pageCases.button.delete.confirmationMessage=Do you really want to delete case? +pageCases.button.delete.multiple.confirmationMessage=Do you really want to delete {0} cases? PageCasesAllocatedToMe.title=My Cases PageCasesAll.title=All Cases PageCases.title=Cases List @@ -3639,6 +3646,7 @@ TaskSummaryPanel.progressIfWaiting=(waiting) TaskSummaryPanel.progressIfClosed=(closed) TaskSummaryPanel.progressIfStalled=(stalled since {0}) TaskSummaryPanel.lastProcessed=Last object processed: {0} +CaseSummaryPanel.parentCase=Parent case: {0} ResourceContentResourcePanel.showExisting=Show existing ResourceContentResourcePanel.newTask=Create new SearchPanel.advanced=Advanced diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/Item.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/Item.java index 19b8ac68f8f..c460523a5fd 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/Item.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/Item.java @@ -581,4 +581,5 @@ static Collection getAllValues(Item item, ItemPath path) { void setPrismContext(PrismContext prismContext); // todo remove + Long getHighestId(); } diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/util/CloneUtil.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/util/CloneUtil.java index 7fae8c9eb4e..fe9dcd91b30 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/util/CloneUtil.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/util/CloneUtil.java @@ -36,6 +36,7 @@ import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import com.evolveum.prism.xml.ns._public.types_3.RawType; +import org.jetbrains.annotations.Contract; import org.springframework.util.ClassUtils; import javax.xml.datatype.Duration; @@ -135,6 +136,7 @@ public static T clone(T orig) { throw new IllegalArgumentException("Cannot clone "+orig+" ("+origClass+")"); } + @Contract("!null -> !null; null -> null") public static List cloneCollectionMembers(Collection collection) { if (collection == null) { return null; diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xml/XmlTypeConverter.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xml/XmlTypeConverter.java index 7a2a3e8f5b4..c7fa02c25b1 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xml/XmlTypeConverter.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/xml/XmlTypeConverter.java @@ -24,6 +24,7 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import org.apache.commons.codec.binary.Base64; +import org.jetbrains.annotations.Contract; import org.w3c.dom.Element; import javax.xml.bind.annotation.XmlEnumValue; @@ -126,6 +127,7 @@ public static XMLGregorianCalendar createXMLGregorianCalendar(Long timeInMillis) return createXMLGregorianCalendar(gregorianCalendar); } + @Contract("null -> null") public static XMLGregorianCalendar createXMLGregorianCalendar(Date date) { if (date == null) { return null; diff --git a/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/PolyStringTranslationArgumentType.java b/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/PolyStringTranslationArgumentType.java index a638f4e632a..89e020ed754 100644 --- a/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/PolyStringTranslationArgumentType.java +++ b/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/PolyStringTranslationArgumentType.java @@ -51,6 +51,17 @@ public class PolyStringTranslationArgumentType implements Serializable, Cloneabl protected String value; protected PolyStringTranslationType translation; + public PolyStringTranslationArgumentType() { + } + + public PolyStringTranslationArgumentType(PolyStringTranslationType translation) { + this.translation = translation; + } + + public PolyStringTranslationArgumentType(String value) { + this.value = value; + } + public String getValue() { return value; } diff --git a/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/PolyStringTranslationType.java b/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/PolyStringTranslationType.java index 913ec46b683..6c5eec3a22f 100644 --- a/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/PolyStringTranslationType.java +++ b/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/PolyStringTranslationType.java @@ -24,6 +24,10 @@ package com.evolveum.prism.xml.ns._public.types_3; +import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.SingleLocalizableMessage; +import org.jetbrains.annotations.NotNull; + import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -146,4 +150,33 @@ public PolyStringTranslationType clone() { } return cloned; } + + // TODO move to appropriate place + @NotNull + public static PolyStringTranslationType fromLocalizableMessage(@NotNull SingleLocalizableMessage message) { + PolyStringTranslationType rv = new PolyStringTranslationType(); + rv.setKey(message.getKey()); + rv.setFallback(message.getFallbackMessage()); + LocalizableMessage fallbackLocalizableMessage = message.getFallbackLocalizableMessage(); + if (fallbackLocalizableMessage != null) { + if (fallbackLocalizableMessage instanceof SingleLocalizableMessage) { + rv.setFallbackTranslation(fromLocalizableMessage((SingleLocalizableMessage) fallbackLocalizableMessage)); + } else { + throw new UnsupportedOperationException("Fallback messages other than SingleLocalizableMessage are not supported in PolyString: " + fallbackLocalizableMessage); + } + } + if (message.getArgs() != null) { + for (Object arg : message.getArgs()) { + if (arg instanceof SingleLocalizableMessage) { + rv.getArgument().add(new PolyStringTranslationArgumentType(fromLocalizableMessage((SingleLocalizableMessage) arg))); + } else if (arg instanceof LocalizableMessage) { + throw new UnsupportedOperationException("LocalizableMessages arguments other than SingleLocalizableMessage are not supported in PolyString: " + arg); + } else { + // even null values go here; as arguments in this class cannot be null + rv.getArgument().add(new PolyStringTranslationArgumentType(String.valueOf(arg))); + } + } + } + return rv; + } } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/ItemImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/ItemImpl.java index 248544cb7ad..7b4ac1a524c 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/ItemImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/ItemImpl.java @@ -23,6 +23,7 @@ import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.Holder; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.exception.SchemaException; @@ -1001,4 +1002,18 @@ public Collection getAllValues(ItemPath path) { @Override public abstract Item clone(); + + @Override + public Long getHighestId() { + Holder highest = new Holder<>(); + this.accept(visitable -> { + if (visitable instanceof PrismContainerValue) { + Long id = ((PrismContainerValue) visitable).getId(); + if (id != null && (highest.isEmpty() || id > highest.getValue())) { + highest.setValue(id); + } + } + }); + return highest.getValue(); + } } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java index 274bde213e6..34e43dde638 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java @@ -482,9 +482,27 @@ public abstract class SchemaConstants { public static final String LIFECYCLE_FAILED = "failed"; // Case: generic reusable case states + // Not all cases use all these states; most common are OPEN and CLOSED. + // Case was created but it is not yet open. E.g. there should be no work items. + public static final String CASE_STATE_CREATED = "created"; + public static final QName CASE_STATE_CREATED_QNAME = new QName(NS_CASE, CASE_STATE_CREATED); + + // Case is open - work items are created, completed, delegated, etc. Associated work is carried out. public static final String CASE_STATE_OPEN = "open"; public static final QName CASE_STATE_OPEN_QNAME = new QName(NS_CASE, CASE_STATE_OPEN); + + // All human interaction regarding the case is over. But there might be some actions pending, e.g. + // submitting change execution task, waiting for subtasks to be closed, and so on. + public static final String CASE_STATE_CLOSING = "closing"; + public static final QName CASE_STATE_CLOSING_QNAME = new QName(NS_CASE, CASE_STATE_CLOSING); + + // The case now proceeds by means of automated execution of defined actions (e.g. approved changes); + // or waiting for the execution to start. + public static final String CASE_STATE_EXECUTING = "executing"; + public static final QName CASE_STATE_EXECUTING_QNAME = new QName(NS_CASE, CASE_STATE_EXECUTING); + + // The case is closed. No further actions nor changes are expected. public static final String CASE_STATE_CLOSED = "closed"; public static final QName CASE_STATE_CLOSED_QNAME = new QName(NS_CASE, CASE_STATE_CLOSED); diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java index cbb3d935319..83cf7124088 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java @@ -58,4 +58,13 @@ public static CaseType getCase(CaseWorkItemType workItem) { public static WorkItemId getId(CaseWorkItemType workItem) { return WorkItemId.of(workItem); } + + public static CaseWorkItemType getWorkItem(CaseType aCase, long id) { + for (CaseWorkItemType workItem : aCase.getWorkItem()) { + if (workItem.getId() != null && workItem.getId() == id) { + return workItem; + } + } + return null; + } } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/WfContextUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/WfContextUtil.java index f6f586350e0..22da15fa128 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/WfContextUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/WfContextUtil.java @@ -604,7 +604,7 @@ public static WorkItemDelegationEventType createDelegationEvent(WorkItemEscalati @NotNull public static List createTriggers(int escalationLevel, Date workItemCreateTime, Date workItemDeadline, List timedActionsList, - PrismContext prismContext, Trace logger, @Nullable String workItemId, @NotNull String handlerUri) + PrismContext prismContext, Trace logger, @Nullable Long workItemId, @NotNull String handlerUri) throws SchemaException { List triggers = new ArrayList<>(); for (WorkItemTimedActionsType timedActionsEntry : timedActionsList) { @@ -653,7 +653,7 @@ public static List createTriggers(int escalationLevel, Date workIte @NotNull private static TriggerType createTrigger(XMLGregorianCalendar triggerTime, WorkItemActionsType actions, - Pair notifyInfo, PrismContext prismContext, @Nullable String workItemId, @NotNull String handlerUri) + Pair notifyInfo, PrismContext prismContext, Long workItemId, @NotNull String handlerUri) throws SchemaException { TriggerType trigger = new TriggerType(prismContext); trigger.setTimestamp(triggerTime); @@ -665,9 +665,9 @@ private static TriggerType createTrigger(XMLGregorianCalendar triggerTime, WorkI if (workItemId != null) { // work item id @SuppressWarnings("unchecked") - @NotNull PrismPropertyDefinition workItemIdDef = + @NotNull PrismPropertyDefinition workItemIdDef = prismContext.getSchemaRegistry().findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ID); - PrismProperty workItemIdProp = workItemIdDef.instantiate(); + PrismProperty workItemIdProp = workItemIdDef.instantiate(); workItemIdProp.addRealValue(workItemId); trigger.getExtension().asPrismContainerValue().add(workItemIdProp); } @@ -779,7 +779,13 @@ public static String getOutcome(ApprovalSchemaExecutionInformationType info) { public static List getAllRules(SchemaAttachedPolicyRulesType policyRules) { List rv = new ArrayList<>(); + if (policyRules == null){ + return rv; + } for (SchemaAttachedPolicyRuleType entry : policyRules.getEntry()) { + if (entry == null){ + continue; + } if (!rv.contains(entry.getRule())) { rv.add(entry.getRule()); } diff --git a/infra/schema/src/main/resources/localization/schema.properties b/infra/schema/src/main/resources/localization/schema.properties index e034a2aed2f..aa1fa25aa22 100755 --- a/infra/schema/src/main/resources/localization/schema.properties +++ b/infra/schema/src/main/resources/localization/schema.properties @@ -1250,3 +1250,7 @@ ArchetypePolicyType.conflictResolution=Conflict resolution ArchetypePolicyType.lifecycleStateModel=Lifecycle state model ArchetypePolicyType.applicablePolicies=Applicable policies ConfigurationType.configurationProperties=Configuration properties +CaseType.parentRef=Parent reference +CaseType.requestorRef=Requestor reference +CaseType.stageNumber=Stage number +CaseType.localizableName=Localizable name \ No newline at end of file diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-case-management-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-case-management-3.xsd index a5d829def94..3d7c69fa42a 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-case-management-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-case-management-3.xsd @@ -56,17 +56,6 @@ - - - - Localizable name of the case. To be used e.g. in GUI. - Consider moving this to ObjectType. - - - 4.0 - - - @@ -412,6 +401,7 @@ + diff --git a/infra/schema/src/main/resources/xml/ns/public/model/extension-3.xsd b/infra/schema/src/main/resources/xml/ns/public/model/extension-3.xsd index 1ea118d358d..68b37d12bf9 100644 --- a/infra/schema/src/main/resources/xml/ns/public/model/extension-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/model/extension-3.xsd @@ -420,7 +420,7 @@ - + 0 diff --git a/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/MidPointAsserts.java b/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/MidPointAsserts.java index 939693bfc48..fdd6392c336 100644 --- a/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/MidPointAsserts.java +++ b/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/MidPointAsserts.java @@ -303,9 +303,22 @@ public static void assertHasNoOrg(PrismObject user) { public static void assertHasOrgs(PrismObject user, int expectedNumber) { O userType = user.asObjectable(); - assertEquals("Unexepected number of orgs in "+user+": "+userType.getParentOrgRef(), expectedNumber, userType.getParentOrgRef().size()); + assertEquals("Unexpected number of orgs in "+user+": "+userType.getParentOrgRef(), expectedNumber, userType.getParentOrgRef().size()); } + public static void assertHasArchetypes(PrismObject object, int expectedNumber) { + O objectable = object.asObjectable(); + assertEquals("Unexpected number of archetypes in "+object+": "+objectable.getArchetypeRef(), expectedNumber, objectable.getArchetypeRef().size()); + } + + public static void assertHasArchetype(PrismObject object, String oid) { + for (ObjectReferenceType orgRef: object.asObjectable().getArchetypeRef()) { + if (oid.equals(orgRef.getOid())) { + return; + } + } + AssertJUnit.fail(object + " does not have archetype " + oid); + } public static void assertVersionIncrease(PrismObject objectOld, PrismObject objectNew) { Long versionOld = parseVersion(objectOld); diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/caching/AbstractThreadLocalCache.java b/infra/util/src/main/java/com/evolveum/midpoint/util/caching/AbstractThreadLocalCache.java index 9a434bafef9..a260b3338bb 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/caching/AbstractThreadLocalCache.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/caching/AbstractThreadLocalCache.java @@ -154,4 +154,11 @@ public CacheConfiguration getConfiguration() { public void setConfiguration(CacheConfiguration configuration) { this.configuration = configuration; } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "entryCount=" + entryCount + + '}'; + } } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/WorkflowService.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/WorkflowService.java index 7e8398f7674..0aeb5ac71e7 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/WorkflowService.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/WorkflowService.java @@ -41,10 +41,12 @@ void completeWorkItem(WorkItemId workItemId, boolean decision, String comment, O ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException; void claimWorkItem(WorkItemId workItemId, Task task, OperationResult parentResult) - throws SecurityViolationException, ObjectNotFoundException, SchemaException; + throws SecurityViolationException, ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, ExpressionEvaluationException; void releaseWorkItem(WorkItemId workItemId, Task task, OperationResult parentResult) - throws ObjectNotFoundException, SecurityViolationException, SchemaException; + throws ObjectNotFoundException, SecurityViolationException, SchemaException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, ExpressionEvaluationException; void delegateWorkItem(WorkItemId workItemId, List delegates, WorkItemDelegationMethodType method, Task task, OperationResult parentResult) throws ObjectNotFoundException, SecurityViolationException, diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java index 484039d116d..491f38dc2dc 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java @@ -2034,22 +2034,20 @@ public void completeWorkItem(WorkItemId workItemId, boolean decision, String com public void stopProcessInstance(String caseOid, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, SecurityViolationException, ExpressionEvaluationException, CommunicationException, ConfigurationException, ObjectAlreadyExistsException { - if (!securityEnforcer.isAuthorized(AuthorizationConstants.AUTZ_ALL_URL, null, AuthorizationParameters.EMPTY, null, task, parentResult)) { - PrismObject caseObject = cacheRepositoryService.getObject(CaseType.class, caseOid, null, parentResult); - securityEnforcer.authorize(ModelAuthorizationAction.STOP_APPROVAL_PROCESS_INSTANCE.getUrl(), null, AuthorizationParameters.Builder.buildObject(caseObject), null, task, parentResult); - } getWorkflowManagerChecked().stopProcessInstance(caseOid, task, parentResult); } @Override public void claimWorkItem(WorkItemId workItemId, Task task, OperationResult parentResult) - throws SecurityViolationException, ObjectNotFoundException, SchemaException { + throws SecurityViolationException, ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, ExpressionEvaluationException { getWorkflowManagerChecked().claimWorkItem(workItemId, task, parentResult); } @Override public void releaseWorkItem(WorkItemId workItemId, Task task, OperationResult parentResult) - throws ObjectNotFoundException, SecurityViolationException, SchemaException { + throws ObjectNotFoundException, SecurityViolationException, SchemaException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, ExpressionEvaluationException { getWorkflowManagerChecked().releaseWorkItem(workItemId, task, parentResult); } diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractInternalModelIntegrationTest.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractInternalModelIntegrationTest.java index 2ecc4aa4d6a..dd73706bced 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractInternalModelIntegrationTest.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractInternalModelIntegrationTest.java @@ -192,5 +192,4 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti userTypeElaine = repoAddObjectFromFile(USER_ELAINE_FILE, UserType.class, initResult).asObjectable(); } - } diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractModelImplementationIntegrationTest.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractModelImplementationIntegrationTest.java index e6abbb8adb5..96563abce38 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractModelImplementationIntegrationTest.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/AbstractModelImplementationIntegrationTest.java @@ -31,7 +31,9 @@ import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.util.PrismTestUtil; import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ResourceTypeUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; @@ -46,6 +48,8 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.Collection; +import java.util.List; import java.util.function.Consumer; import static org.testng.AssertJUnit.*; @@ -347,4 +351,13 @@ protected LensContext createContextForAssignment(Class< assertFocusModificationSanity(context); return context; } + + protected List getWorkItemsForCase(String caseOid, + Collection> options, OperationResult result) throws SchemaException { + // TODO use simple getObject(CaseType) + return repositoryService.searchContainers(CaseWorkItemType.class, + prismContext.queryFor(CaseWorkItemType.class).ownerId(caseOid).build(), + options, result); + + } } diff --git a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java index 2ff002303d4..77d2c025384 100644 --- a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java +++ b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java @@ -50,7 +50,6 @@ import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; import com.evolveum.midpoint.prism.path.*; -import com.evolveum.midpoint.provisioning.api.ChangeNotificationDispatcher; import com.evolveum.midpoint.schema.*; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.task.api.TaskDebugUtil; @@ -90,7 +89,6 @@ import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; -import com.evolveum.midpoint.model.api.AssignmentObjectRelation; import com.evolveum.midpoint.model.api.AssignmentCandidatesSpecification; import com.evolveum.midpoint.model.api.ModelAuditService; import com.evolveum.midpoint.model.api.ModelAuthorizationAction; @@ -112,7 +110,6 @@ import com.evolveum.midpoint.model.common.SystemObjectCache; import com.evolveum.midpoint.model.common.stringpolicy.UserValuePolicyOriginResolver; import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; -import com.evolveum.midpoint.model.test.asserter.AssignmentObjectRelationsAsserter; import com.evolveum.midpoint.model.test.asserter.AssignmentCandidatesSpecificationAsserter; import com.evolveum.midpoint.model.test.asserter.CompiledUserProfileAsserter; import com.evolveum.midpoint.model.test.asserter.EvaluatedPolicyRulesAsserter; @@ -159,7 +156,6 @@ import com.evolveum.midpoint.test.DummyAuditService; import com.evolveum.midpoint.test.DummyResourceContoller; import com.evolveum.midpoint.test.IntegrationTestTools; -import com.evolveum.midpoint.test.asserter.AbstractAsserter; import com.evolveum.midpoint.test.asserter.ArchetypePolicyAsserter; import com.evolveum.midpoint.test.asserter.DummyAccountAsserter; import com.evolveum.midpoint.test.asserter.DummyGroupAsserter; @@ -2169,12 +2165,12 @@ protected void assertHasOrg(String userOid, String orgOid, Task task, OperationR assertAssignedOrg(user, orgOid); } - protected void assertHasOrgs(PrismObject user, String... orgOids) throws Exception { - for (String orgOid: orgOids) { - assertHasOrg(user, orgOid); - } - assertHasOrgs(user, orgOids.length); - } + protected void assertHasOrgs(PrismObject user, String... orgOids) throws Exception { + for (String orgOid: orgOids) { + assertHasOrg(user, orgOid); + } + assertHasOrgs(user, orgOids.length); + } protected void assertHasOrg(PrismObject focus, String orgOid) { MidPointAsserts.assertHasOrg(focus, orgOid); @@ -2200,6 +2196,21 @@ protected void assertHasOrgs(PrismObject user, int exp MidPointAsserts.assertHasOrgs(user, expectedNumber); } + protected void assertHasArchetypes(PrismObject object, String... oids) { + for (String oid : oids) { + assertHasArchetype(object, oid); + } + assertHasArchetypes(object, oids.length); + } + + protected void assertHasArchetypes(PrismObject object, int expectedNumber) { + MidPointAsserts.assertHasArchetypes(object, expectedNumber); + } + + protected void assertHasArchetype(PrismObject object, String oid) { + MidPointAsserts.assertHasArchetype(object, oid); + } + protected void assertSubOrgs(String baseOrgOid, int expected) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { Task task = taskManager.createTaskInstance(AbstractModelIntegrationTest.class+".assertSubOrgs"); OperationResult result = task.getResult(); @@ -3776,10 +3787,10 @@ protected void addTriggers(String oid, Collection timestam private TriggerType addRandomValue(TriggerType trigger) { //noinspection unchecked - @NotNull PrismPropertyDefinition workItemIdDef = + @NotNull PrismPropertyDefinition workItemIdDef = prismContext.getSchemaRegistry().findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ID); - PrismProperty workItemIdProp = workItemIdDef.instantiate(); - workItemIdProp.addRealValue(String.valueOf(Math.random())); + PrismProperty workItemIdProp = workItemIdDef.instantiate(); + workItemIdProp.addRealValue((long) (Math.random() * 100000000000L)); try { //noinspection unchecked trigger.asPrismContainerValue().findOrCreateContainer(TriggerType.F_EXTENSION).add(workItemIdProp); diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/WorkflowListener.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/WorkflowListenerImpl.java similarity index 85% rename from model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/WorkflowListener.java rename to model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/WorkflowListenerImpl.java index 570b0b2aef6..47fddd3f11c 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/WorkflowListener.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/WorkflowListenerImpl.java @@ -21,7 +21,6 @@ import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.LightweightIdentifierGenerator; -import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -45,9 +44,9 @@ * @author mederly */ @Component -public class WorkflowListener implements ProcessListener, WorkItemListener { +public class WorkflowListenerImpl implements WorkflowListener { - private static final Trace LOGGER = TraceManager.getTrace(WorkflowListener.class); + private static final Trace LOGGER = TraceManager.getTrace(WorkflowListenerImpl.class); //private static final String DOT_CLASS = WorkflowListener.class.getName() + "."; @@ -63,8 +62,7 @@ public class WorkflowListener implements ProcessListener, WorkItemListener { @PostConstruct public void init() { if (workflowManager != null) { - workflowManager.registerProcessListener(this); - workflowManager.registerWorkItemListener(this); + workflowManager.registerWorkflowListener(this); } else { LOGGER.warn("WorkflowManager not present, notifications for workflows will not be enabled."); } @@ -72,18 +70,16 @@ public void init() { //region Process-level notifications @Override - public void onProcessInstanceStart(CaseType aCase, Task opTask, - OperationResult result) { + public void onProcessInstanceStart(CaseType aCase, OperationResult result) { WorkflowProcessEvent event = new WorkflowProcessEvent(identifierGenerator, ChangeType.ADD, aCase); - initializeWorkflowEvent(event, aCase, opTask); + initializeWorkflowEvent(event, aCase); processEvent(event, result); } @Override - public void onProcessInstanceEnd(CaseType aCase, Task opTask, - OperationResult result) { + public void onProcessInstanceEnd(CaseType aCase, OperationResult result) { WorkflowProcessEvent event = new WorkflowProcessEvent(identifierGenerator, ChangeType.DELETE, aCase); - initializeWorkflowEvent(event, aCase, opTask); + initializeWorkflowEvent(event, aCase); processEvent(event, result); } //endregion @@ -91,34 +87,34 @@ public void onProcessInstanceEnd(CaseType aCase, Task opTask, //region WorkItem-level notifications @Override public void onWorkItemCreation(ObjectReferenceType assignee, @NotNull CaseWorkItemType workItem, - CaseType aCase, Task wfTask, OperationResult result) { + CaseType aCase, OperationResult result) { WorkItemEvent event = new WorkItemLifecycleEvent(identifierGenerator, ChangeType.ADD, workItem, SimpleObjectRefImpl.create(functions, assignee), null, null, null, aCase.getWorkflowContext(), aCase); - initializeWorkflowEvent(event, aCase, wfTask); + initializeWorkflowEvent(event, aCase); processEvent(event, result); } @Override public void onWorkItemDeletion(ObjectReferenceType assignee, @NotNull CaseWorkItemType workItem, WorkItemOperationInfo operationInfo, WorkItemOperationSourceInfo sourceInfo, - CaseType aCase, Task opTask, OperationResult result) { + CaseType aCase, OperationResult result) { WorkItemEvent event = new WorkItemLifecycleEvent(identifierGenerator, ChangeType.DELETE, workItem, SimpleObjectRefImpl.create(functions, assignee), getInitiator(sourceInfo), operationInfo, sourceInfo, aCase.getWorkflowContext(), aCase); - initializeWorkflowEvent(event, aCase, opTask); + initializeWorkflowEvent(event, aCase); processEvent(event, result); } @Override public void onWorkItemCustomEvent(ObjectReferenceType assignee, @NotNull CaseWorkItemType workItem, @NotNull WorkItemNotificationActionType notificationAction, WorkItemEventCauseInformationType cause, - CaseType aCase, Task opTask, OperationResult result) { + CaseType aCase, OperationResult result) { WorkItemEvent event = new WorkItemCustomEvent(identifierGenerator, ChangeType.ADD, workItem, SimpleObjectRefImpl.create(functions, assignee), new WorkItemOperationSourceInfo(null, cause, notificationAction), aCase.getWorkflowContext(), aCase, notificationAction.getHandler()); - initializeWorkflowEvent(event, aCase, opTask); + initializeWorkflowEvent(event, aCase); processEvent(event, result); } @@ -126,11 +122,11 @@ public void onWorkItemCustomEvent(ObjectReferenceType assignee, @NotNull CaseWor public void onWorkItemAllocationChangeCurrentActors(@NotNull CaseWorkItemType workItem, @NotNull WorkItemAllocationChangeOperationInfo operationInfo, @Nullable WorkItemOperationSourceInfo sourceInfo, - Duration timeBefore, CaseType aCase, Task task, + Duration timeBefore, CaseType aCase, OperationResult result) { checkOids(operationInfo.getCurrentActors()); for (ObjectReferenceType currentActor : operationInfo.getCurrentActors()) { - onWorkItemAllocationModifyDelete(currentActor, workItem, operationInfo, sourceInfo, timeBefore, aCase, task, result); + onWorkItemAllocationModifyDelete(currentActor, workItem, operationInfo, sourceInfo, timeBefore, aCase, result); } } @@ -138,13 +134,13 @@ public void onWorkItemAllocationChangeCurrentActors(@NotNull CaseWorkItemType wo public void onWorkItemAllocationChangeNewActors(@NotNull CaseWorkItemType workItem, @NotNull WorkItemAllocationChangeOperationInfo operationInfo, @Nullable WorkItemOperationSourceInfo sourceInfo, - CaseType aCase, Task task, OperationResult result) { + CaseType aCase, OperationResult result) { Validate.notNull(operationInfo.getNewActors()); checkOids(operationInfo.getCurrentActors()); checkOids(operationInfo.getNewActors()); for (ObjectReferenceType newActor : operationInfo.getNewActors()) { - onWorkItemAllocationAdd(newActor, workItem, operationInfo, sourceInfo, aCase, task, result); + onWorkItemAllocationAdd(newActor, workItem, operationInfo, sourceInfo, aCase, result); } } @@ -154,12 +150,12 @@ private void checkOids(List refs) { private void onWorkItemAllocationAdd(ObjectReferenceType newActor, @NotNull CaseWorkItemType workItem, @Nullable WorkItemOperationInfo operationInfo, @Nullable WorkItemOperationSourceInfo sourceInfo, - CaseType aCase, Task task, OperationResult result) { + CaseType aCase, OperationResult result) { WorkItemAllocationEvent event = new WorkItemAllocationEvent(identifierGenerator, ChangeType.ADD, workItem, SimpleObjectRefImpl.create(functions, newActor), getInitiator(sourceInfo), operationInfo, sourceInfo, aCase.getWorkflowContext(), aCase, null); - initializeWorkflowEvent(event, aCase, task); + initializeWorkflowEvent(event, aCase); processEvent(event, result); } @@ -170,14 +166,14 @@ private SimpleObjectRef getInitiator(WorkItemOperationSourceInfo sourceInfo) { private void onWorkItemAllocationModifyDelete(ObjectReferenceType currentActor, @NotNull CaseWorkItemType workItem, @Nullable WorkItemOperationInfo operationInfo, @Nullable WorkItemOperationSourceInfo sourceInfo, - Duration timeBefore, CaseType aCase, Task task, + Duration timeBefore, CaseType aCase, OperationResult result) { WorkItemAllocationEvent event = new WorkItemAllocationEvent(identifierGenerator, timeBefore != null ? ChangeType.MODIFY : ChangeType.DELETE, workItem, SimpleObjectRefImpl.create(functions, currentActor), getInitiator(sourceInfo), operationInfo, sourceInfo, aCase.getWorkflowContext(), aCase, timeBefore); - initializeWorkflowEvent(event, aCase, task); + initializeWorkflowEvent(event, aCase); processEvent(event, result); } //endregion @@ -197,7 +193,7 @@ private void processEvent(WorkflowEvent event, OperationResult result) { result.recordSuccessIfUnknown(); } - private void initializeWorkflowEvent(WorkflowEvent event, CaseType aCase, Task wfTask) { + private void initializeWorkflowEvent(WorkflowEvent event, CaseType aCase) { event.setRequester(SimpleObjectRefImpl.create(functions, aCase.getRequestorRef())); event.setRequestee(SimpleObjectRefImpl.create(functions, aCase.getObjectRef())); // TODO what if requestee is yet to be created? diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/CompleteAction.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/CompleteAction.java deleted file mode 100644 index c25afbf9f67..00000000000 --- a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/CompleteAction.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2010-2019 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.api; - -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.schema.util.WorkItemId; -import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemEventCauseInformationType; -import org.jetbrains.annotations.NotNull; - -import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Describes the "complete work item" action. - */ -public class CompleteAction implements Serializable { - - @NotNull private final WorkItemId workItemId; - @NotNull private final CaseWorkItemType workItem; - @NotNull private final String outcome; - private final String comment; - private final ObjectDelta additionalDelta; - private final WorkItemEventCauseInformationType causeInformation; - - public CompleteAction(@NotNull WorkItemId workItemId, @NotNull CaseWorkItemType workItem, - @NotNull String outcome, String comment, ObjectDelta additionalDelta, - WorkItemEventCauseInformationType causeInformation) { - this.workItemId = workItemId; - this.workItem = workItem; - this.outcome = outcome; - this.comment = comment; - this.additionalDelta = additionalDelta; - this.causeInformation = causeInformation; - } - - @NotNull - public WorkItemId getWorkItemId() { - return workItemId; - } - - @NotNull - public CaseWorkItemType getWorkItem() { - return workItem; - } - - @NotNull - public String getOutcome() { - return outcome; - } - - public String getComment() { - return comment; - } - - public ObjectDelta getAdditionalDelta() { - return additionalDelta; - } - - public WorkItemEventCauseInformationType getCauseInformation() { - return causeInformation; - } - - @Override - public String toString() { - return "CompleteAction{" + - "workItem=" + workItem + - ", outcome='" + outcome + '\'' + - ", comment='" + comment + '\'' + - ", additionalDelta=" + additionalDelta + - ", causeInformation=" + causeInformation + - '}'; - } - - public static List getWorkItems(Collection actions) { - return actions.stream().map(a -> a.getWorkItem()).collect(Collectors.toList()); - } -} diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/ProcessListener.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/ProcessListener.java deleted file mode 100644 index fcb3caded7e..00000000000 --- a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/ProcessListener.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.api; - -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; - -/** - * An interface through which external observers can be notified about wf process related events. - * - * EXPERIMENTAL. This interface will probably change in near future. - * - * @author mederly - */ -public interface ProcessListener { - - /** - * This method is called by wf module when a process instance successfully starts. - * @param instanceState externalized process instance variables - * @param aCase - * @param result implementer should report its result here - */ - void onProcessInstanceStart(CaseType aCase, Task opTask, OperationResult result); - - /** - * This method is called by wf module when a process instance ends. - * @param instanceState externalized process instance variables - * @param aCase - * @param result implementer should report its result here - */ - void onProcessInstanceEnd(CaseType aCase, Task opTask, OperationResult result); -} diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkItemListener.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowListener.java similarity index 68% rename from model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkItemListener.java rename to model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowListener.java index 58df975a4ac..82a0305d4d6 100644 --- a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkItemListener.java +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowListener.java @@ -1,72 +1,77 @@ -/* - * 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.api; - -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.xml.datatype.Duration; - -/** - * An interface through which external observers can be notified about work item related events. - * Used e.g. for implementing workflow-related notifications. - * - * A tricky question is how to let the observer know how to deal with the process instance state - * (e.g. how to construct a notification). Currently, the observer has to use the class of - * the instance state prism object. It is up to the process implementer to provide appropriate - * information through ChangeProcessor.externalizeInstanceState() method. - * - * EXPERIMENTAL. This interface may change in near future. - * - * @author mederly - */ -public interface WorkItemListener { - - /** - * This method is called by wf module when a work item is created. - */ - void onWorkItemCreation(ObjectReferenceType assignee, @NotNull CaseWorkItemType workItem, - CaseType aCase, Task wfTask, OperationResult result); - - /** - * This method is called by wf module when a work item is completed. - */ - void onWorkItemDeletion(ObjectReferenceType assignee, @NotNull CaseWorkItemType workItem, - @Nullable WorkItemOperationInfo operationInfo, @Nullable WorkItemOperationSourceInfo sourceInfo, - CaseType aCase, Task opTask, OperationResult result); - - void onWorkItemCustomEvent(ObjectReferenceType assignee, @NotNull CaseWorkItemType workItem, - @NotNull WorkItemNotificationActionType notificationAction, @Nullable WorkItemEventCauseInformationType cause, - CaseType aCase, Task opTask, OperationResult result); - - /** - * EXPERIMENTAL - */ - void onWorkItemAllocationChangeCurrentActors(@NotNull CaseWorkItemType workItem, - @NotNull WorkItemAllocationChangeOperationInfo operationInfo, - @Nullable WorkItemOperationSourceInfo sourceInfo, - Duration timeBefore, CaseType aCase, Task task, - OperationResult result); - - void onWorkItemAllocationChangeNewActors(@NotNull CaseWorkItemType workItem, - @NotNull WorkItemAllocationChangeOperationInfo operationInfo, - @Nullable WorkItemOperationSourceInfo sourceInfo, CaseType aCase, - Task task, OperationResult result); -} +/* + * 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.api; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.xml.datatype.Duration; + +/** + * An interface through which external observers can be notified about workflow related events. + * Used e.g. for implementing workflow-related notifications. + * + * EXPERIMENTAL. This interface may change in near future. + * + * @author mederly + */ +public interface WorkflowListener { + + /** + * This method is called by wf module when a process instance successfully starts. + * @param aCase + * @param result implementer should report its result here + */ + void onProcessInstanceStart(CaseType aCase, OperationResult result); + + /** + * This method is called by wf module when a process instance ends. + * @param aCase + * @param result implementer should report its result here + */ + void onProcessInstanceEnd(CaseType aCase, OperationResult result); + + /** + * This method is called by wf module when a work item is created. + */ + void onWorkItemCreation(ObjectReferenceType assignee, @NotNull CaseWorkItemType workItem, + CaseType aCase, OperationResult result); + + /** + * This method is called by wf module when a work item is completed. + */ + void onWorkItemDeletion(ObjectReferenceType assignee, @NotNull CaseWorkItemType workItem, + @Nullable WorkItemOperationInfo operationInfo, @Nullable WorkItemOperationSourceInfo sourceInfo, + CaseType aCase, OperationResult result); + + void onWorkItemCustomEvent(ObjectReferenceType assignee, @NotNull CaseWorkItemType workItem, + @NotNull WorkItemNotificationActionType notificationAction, @Nullable WorkItemEventCauseInformationType cause, + CaseType aCase, OperationResult result); + + /** + * EXPERIMENTAL + */ + void onWorkItemAllocationChangeCurrentActors(@NotNull CaseWorkItemType workItem, + @NotNull WorkItemAllocationChangeOperationInfo operationInfo, @Nullable WorkItemOperationSourceInfo sourceInfo, + Duration timeBefore, CaseType aCase, OperationResult result); + + void onWorkItemAllocationChangeNewActors(@NotNull CaseWorkItemType workItem, + @NotNull WorkItemAllocationChangeOperationInfo operationInfo, @Nullable WorkItemOperationSourceInfo sourceInfo, + CaseType aCase, OperationResult result); +} diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java index df9307a0568..dbffe837789 100644 --- a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java @@ -28,7 +28,6 @@ import com.evolveum.midpoint.wf.util.ChangesByState; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import java.util.Collection; import java.util.List; /** @@ -50,10 +49,12 @@ void completeWorkItem(WorkItemId workItemId, boolean decision, String comment, O ExpressionEvaluationException, CommunicationException, ConfigurationException; void claimWorkItem(WorkItemId workItemId, Task task, OperationResult result) - throws ObjectNotFoundException, SecurityViolationException, SchemaException; + throws ObjectNotFoundException, SecurityViolationException, SchemaException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, ExpressionEvaluationException; void releaseWorkItem(WorkItemId workItemId, Task task, OperationResult result) - throws SecurityViolationException, ObjectNotFoundException, SchemaException; + throws SecurityViolationException, ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, ExpressionEvaluationException; void delegateWorkItem(WorkItemId workItemId, List delegates, WorkItemDelegationMethodType method, Task task, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException, @@ -63,7 +64,8 @@ void delegateWorkItem(WorkItemId workItemId, List delegates //region Process instances void stopProcessInstance(String caseOid, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException; + throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException, SecurityViolationException, + CommunicationException, ConfigurationException, ExpressionEvaluationException; //endregion @@ -77,9 +79,7 @@ void stopProcessInstance(String caseOid, Task task, OperationResult parentResult // TODO remove this PrismContext getPrismContext(); - void registerProcessListener(ProcessListener processListener); - - void registerWorkItemListener(WorkItemListener workItemListener); + void registerWorkflowListener(WorkflowListener workflowListener); boolean isCurrentUserAuthorizedToSubmit(CaseWorkItemType workItem, Task task, OperationResult result) throws ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException; diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/CancelCaseRequest.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/CancelCaseRequest.java new file mode 100644 index 00000000000..7975c601d18 --- /dev/null +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/CancelCaseRequest.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2019 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.api.request; + +import org.jetbrains.annotations.NotNull; + +/** + * + */ +public class CancelCaseRequest extends Request { + + public CancelCaseRequest(@NotNull String caseOid) { + super(caseOid, null); + } + +} diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/ClaimWorkItemsRequest.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/ClaimWorkItemsRequest.java new file mode 100644 index 00000000000..5e01f3e6df8 --- /dev/null +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/ClaimWorkItemsRequest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010-2019 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.api.request; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * + */ +public class ClaimWorkItemsRequest extends Request { + + public static class SingleClaim { + private final long workItemId; + + public SingleClaim(long workItemId) { + this.workItemId = workItemId; + } + + public long getWorkItemId() { + return workItemId; + } + + @Override + public String toString() { + return "SingleClaim{" + + "workItemId=" + workItemId + + '}'; + } + } + + @NotNull private final Collection claims = new ArrayList<>(); + + public ClaimWorkItemsRequest(@NotNull String caseOid) { + super(caseOid, null); + } + + @NotNull + public Collection getClaims() { + return claims; + } +} diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/CompleteWorkItemsRequest.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/CompleteWorkItemsRequest.java new file mode 100644 index 00000000000..3eaa4bcab82 --- /dev/null +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/CompleteWorkItemsRequest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2010-2019 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.api.request; + +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemEventCauseInformationType; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * + */ +public class CompleteWorkItemsRequest extends Request { + + public static class SingleCompletion { + private final long workItemId; + @NotNull private final String outcome; + private final String comment; + private final ObjectDelta additionalDelta; + + public SingleCompletion(long workItemId, @NotNull String outcome, String comment, + ObjectDelta additionalDelta) { + this.workItemId = workItemId; + this.outcome = outcome; + this.comment = comment; + this.additionalDelta = additionalDelta; + } + + public long getWorkItemId() { + return workItemId; + } + + @NotNull + public String getOutcome() { + return outcome; + } + + public String getComment() { + return comment; + } + + public ObjectDelta getAdditionalDelta() { + return additionalDelta; + } + + @Override + public String toString() { + return "SingleCompletion{" + + "workItemId=" + workItemId + + ", outcome='" + outcome + '\'' + + ", comment='" + comment + '\'' + + ", additionalDelta=" + additionalDelta + + '}'; + } + } + + @NotNull private final Collection completions = new ArrayList<>(); + + public CompleteWorkItemsRequest(@NotNull String caseOid, WorkItemEventCauseInformationType causeInformation) { + super(caseOid, causeInformation); + } + + @NotNull + public Collection getCompletions() { + return completions; + } +} diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/DelegateWorkItemsRequest.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/DelegateWorkItemsRequest.java new file mode 100644 index 00000000000..8c6ea27faaa --- /dev/null +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/DelegateWorkItemsRequest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2010-2019 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.api.request; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.jetbrains.annotations.NotNull; + +import javax.xml.datatype.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * + */ +public class DelegateWorkItemsRequest extends Request { + + public static class SingleDelegation { + private final long workItemId; + @NotNull private final List delegates; + @NotNull private final WorkItemDelegationMethodType method; + private final WorkItemEscalationLevelType targetEscalationInfo; + private final Duration newDuration; + + public SingleDelegation(long workItemId, + @NotNull List delegates, + @NotNull WorkItemDelegationMethodType method, + WorkItemEscalationLevelType targetEscalationInfo, Duration newDuration) { + this.workItemId = workItemId; + this.delegates = delegates; + this.method = method; + this.targetEscalationInfo = targetEscalationInfo; + this.newDuration = newDuration; + } + + public long getWorkItemId() { + return workItemId; + } + + @NotNull + public List getDelegates() { + return delegates; + } + + @NotNull + public WorkItemDelegationMethodType getMethod() { + return method; + } + + public WorkItemEscalationLevelType getTargetEscalationInfo() { + return targetEscalationInfo; + } + + public Duration getNewDuration() { + return newDuration; + } + + @Override + public String toString() { + return "SingleDelegation{" + + "workItemId=" + workItemId + + ", delegates=" + delegates + + ", method=" + method + + ", targetEscalationInfo=" + targetEscalationInfo + + ", newDuration=" + newDuration + + '}'; + } + } + + @NotNull private final Collection delegations = new ArrayList<>(); + + public DelegateWorkItemsRequest(@NotNull String caseOid, WorkItemEventCauseInformationType causeInformation) { + super(caseOid, causeInformation); + } + + @NotNull + public Collection getDelegations() { + return delegations; + } +} diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/OpenCaseRequest.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/OpenCaseRequest.java new file mode 100644 index 00000000000..144e2bc7021 --- /dev/null +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/OpenCaseRequest.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2010-2019 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.api.request; + +import org.jetbrains.annotations.NotNull; + +/** + * + */ +public class OpenCaseRequest extends Request { + + public OpenCaseRequest(@NotNull String caseOid) { + super(caseOid, null); + } + +} diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/ReleaseWorkItemsRequest.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/ReleaseWorkItemsRequest.java new file mode 100644 index 00000000000..63ffac44868 --- /dev/null +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/ReleaseWorkItemsRequest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010-2019 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.api.request; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * + */ +public class ReleaseWorkItemsRequest extends Request { + + public static class SingleRelease { + private final long workItemId; + + public SingleRelease(long workItemId) { + this.workItemId = workItemId; + } + + public long getWorkItemId() { + return workItemId; + } + + @Override + public String toString() { + return "SingleRelease{" + + "workItemId=" + workItemId + + '}'; + } + } + + @NotNull private final Collection releases = new ArrayList<>(); + + public ReleaseWorkItemsRequest(@NotNull String caseOid) { + super(caseOid, null); + } + + @NotNull + public Collection getReleases() { + return releases; + } +} diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/Request.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/Request.java new file mode 100644 index 00000000000..d64c14d07e2 --- /dev/null +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/Request.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2019 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.api.request; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemEventCauseInformationType; +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; + +/** + * + */ +public abstract class Request implements Serializable { + + @NotNull private final String caseOid; + private final WorkItemEventCauseInformationType causeInformation; + + public Request(@NotNull String caseOid, + WorkItemEventCauseInformationType causeInformation) { + this.caseOid = caseOid; + this.causeInformation = causeInformation; + } + + @NotNull + public String getCaseOid() { + return caseOid; + } + + public WorkItemEventCauseInformationType getCauseInformation() { + return causeInformation; + } +} diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/util/ApprovalUtils.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/util/ApprovalUtils.java index f652945d40d..15e602849d4 100644 --- a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/util/ApprovalUtils.java +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/util/ApprovalUtils.java @@ -48,6 +48,10 @@ public static Boolean approvalBooleanValue(AbstractWorkItemOutputType result) { return result != null ? approvalBooleanValue(fromUri(result.getOutcome())) : null; } + public static Boolean approvalBooleanValue(String uri) { + return uri != null ? approvalBooleanValue(fromUri(uri)) : null; + } + private static Boolean approvalBooleanValue(WorkItemOutcomeType outcome) { if (outcome == null) { return null; @@ -67,6 +71,10 @@ private static boolean isApproved(WorkItemOutcomeType outcome) { return BooleanUtils.isTrue(approvalBooleanValue(outcome)); } + public static boolean isApproved(String result) { + return BooleanUtils.isTrue(approvalBooleanValue(result)); + } + public static String toUri(WorkItemOutcomeType workItemOutcomeType) { if (workItemOutcomeType == null) { return null; diff --git a/model/workflow-impl/pom.xml b/model/workflow-impl/pom.xml index 20cd99cb803..2169903ca48 100644 --- a/model/workflow-impl/pom.xml +++ b/model/workflow-impl/pom.xml @@ -171,12 +171,6 @@ - - com.evolveum.midpoint.model - notifications-api - 4.0-SNAPSHOT - test - com.evolveum.midpoint.infra test-util diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java index c385b6650c6..817fad8cc60 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java @@ -41,6 +41,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import java.util.ArrayList; @@ -61,7 +62,9 @@ public class ApprovalSchemaExecutionInformationHelper { @Autowired private StageComputeHelper computeHelper; @Autowired private PrimaryChangeProcessor primaryChangeProcessor; @Autowired private ConfigurationHelper configurationHelper; - @Autowired private RepositoryService repositoryService; + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInformation(String caseOid, Task opTask, OperationResult result) @@ -169,7 +172,7 @@ private ApprovalStageExecutionRecordType createStageExecutionRecord( ApprovalStageExecutionRecordType rv = new ApprovalStageExecutionRecordType(prismContext); aCase.getEvent().stream() .filter(e -> e.getStageNumber() != null && e.getStageNumber() == stageNumber) - .forEach(e -> rv.getEvent().add(e)); + .forEach(e -> rv.getEvent().add(e.clone())); if (stageNumber == currentStageNumber) { rv.getWorkItem().addAll(CloneUtil.cloneCollectionMembers(aCase.getWorkItem())); } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java index 8a4c9cdaa6c..14c68c0ae55 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java @@ -27,15 +27,14 @@ import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.wf.api.ProcessListener; import com.evolveum.midpoint.schema.util.WorkItemId; -import com.evolveum.midpoint.wf.api.WorkItemListener; +import com.evolveum.midpoint.wf.api.WorkflowListener; import com.evolveum.midpoint.wf.api.WorkflowManager; -import com.evolveum.midpoint.wf.impl.access.ProcessInstanceManager; +import com.evolveum.midpoint.wf.impl.access.CaseManager; import com.evolveum.midpoint.wf.impl.access.WorkItemManager; import com.evolveum.midpoint.wf.impl.processes.common.ExpressionEvaluationHelper; import com.evolveum.midpoint.wf.impl.access.AuthorizationHelper; -import com.evolveum.midpoint.wf.impl.engine.NotificationHelper; +import com.evolveum.midpoint.wf.impl.engine.helpers.NotificationHelper; import com.evolveum.midpoint.wf.impl.util.PerformerCommentsFormatterImpl; import com.evolveum.midpoint.wf.impl.util.ChangesSorter; import com.evolveum.midpoint.wf.util.PerformerCommentsFormatter; @@ -43,6 +42,7 @@ import com.evolveum.midpoint.wf.util.ChangesByState; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @@ -60,14 +60,16 @@ public class WorkflowManagerImpl implements WorkflowManager { @Autowired private PrismContext prismContext; @Autowired private WfConfiguration wfConfiguration; - @Autowired private ProcessInstanceManager processInstanceManager; + @Autowired private CaseManager caseManager; @Autowired private NotificationHelper notificationHelper; @Autowired private WorkItemManager workItemManager; @Autowired private ChangesSorter changesSorter; @Autowired private AuthorizationHelper authorizationHelper; @Autowired private ApprovalSchemaExecutionInformationHelper approvalSchemaExecutionInformationHelper; @Autowired private TaskManager taskManager; - @Autowired private RepositoryService repositoryService; + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; @Autowired private ExpressionEvaluationHelper expressionEvaluationHelper; private static final String DOT_INTERFACE = WorkflowManager.class.getName() + "."; @@ -92,13 +94,15 @@ public void completeWorkItem(WorkItemId workItemId, boolean decision, String com @Override public void claimWorkItem(WorkItemId workItemId, Task task, OperationResult result) - throws ObjectNotFoundException, SecurityViolationException, SchemaException { + throws ObjectNotFoundException, SecurityViolationException, SchemaException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, ExpressionEvaluationException { workItemManager.claimWorkItem(workItemId, task, result); } @Override public void releaseWorkItem(WorkItemId workItemId, Task task, OperationResult result) - throws SecurityViolationException, ObjectNotFoundException, SchemaException { + throws SecurityViolationException, ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, ExpressionEvaluationException { workItemManager.releaseWorkItem(workItemId, task, result); } @@ -112,8 +116,9 @@ public void delegateWorkItem(WorkItemId workItemId, List de //region Process instances (cases) @Override public void stopProcessInstance(String caseOid, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { - processInstanceManager.closeCase(caseOid, task, parentResult); + throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException, SecurityViolationException, + CommunicationException, ConfigurationException, ExpressionEvaluationException { + caseManager.cancelCase(caseOid, task, parentResult); } //endregion @@ -133,13 +138,8 @@ public PrismContext getPrismContext() { } @Override - public void registerProcessListener(ProcessListener processListener) { - notificationHelper.registerProcessListener(processListener); - } - - @Override - public void registerWorkItemListener(WorkItemListener workItemListener) { - notificationHelper.registerWorkItemListener(workItemListener); + public void registerWorkflowListener(WorkflowListener workflowListener) { + notificationHelper.registerWorkItemListener(workflowListener); } @Override diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/ProcessInstanceManager.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/CaseManager.java similarity index 63% rename from model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/ProcessInstanceManager.java rename to model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/CaseManager.java index 03341cc5a49..4b07e05d347 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/ProcessInstanceManager.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/CaseManager.java @@ -17,46 +17,46 @@ package com.evolveum.midpoint.wf.impl.access; import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; 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.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.wf.api.WorkflowManager; +import com.evolveum.midpoint.wf.api.request.CancelCaseRequest; import com.evolveum.midpoint.wf.impl.engine.WorkflowEngine; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** - * @author mederly + * */ -@Component -public class ProcessInstanceManager { +@Component("wfCaseManager") +public class CaseManager { - private static final transient Trace LOGGER = TraceManager.getTrace(ProcessInstanceManager.class); + private static final transient Trace LOGGER = TraceManager.getTrace(CaseManager.class); @Autowired private TaskManager taskManager; @Autowired private PrismContext prismContext; @Autowired private WorkflowEngine workflowEngine; - @Autowired private RepositoryService repositoryService; private static final String DOT_INTERFACE = WorkflowManager.class.getName() + "."; private static final String OPERATION_STOP_PROCESS_INSTANCE = DOT_INTERFACE + "stopProcessInstance"; private static final String OPERATION_DELETE_PROCESS_INSTANCE = DOT_INTERFACE + "deleteProcessInstance"; - public void closeCase(String caseOid, Task task, OperationResult parentResult) - throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException { + public void cancelCase(String caseOid, Task task, OperationResult parentResult) + throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, ConfigurationException, + CommunicationException, SecurityViolationException, ExpressionEvaluationException { OperationResult result = parentResult.createSubresult(OPERATION_STOP_PROCESS_INSTANCE); result.addParam("caseOid", caseOid); try { - workflowEngine.closeCase(caseOid, task, result); - } catch (RuntimeException | SchemaException | ObjectAlreadyExistsException | ObjectNotFoundException e) { + CancelCaseRequest request = new CancelCaseRequest(caseOid); + workflowEngine.executeRequest(request, task, result); + } catch (RuntimeException | SchemaException | ObjectAlreadyExistsException | ObjectNotFoundException | + SecurityViolationException | ExpressionEvaluationException | ConfigurationException | CommunicationException e) { result.recordFatalError("Case couldn't be stopped: " + e.getMessage(), e); throw e; } finally { @@ -64,17 +64,19 @@ public void closeCase(String caseOid, Task task, OperationResult parentResult) } } - private void deleteCase(String caseOid, OperationResult parentResult) { - OperationResult result = parentResult.createSubresult(OPERATION_DELETE_PROCESS_INSTANCE); - result.addParam("caseOid", caseOid); - try { - workflowEngine.deleteCase(caseOid, parentResult); - } catch (RuntimeException e) { - result.recordFatalError("Case couldn't be deleted: " + e.getMessage(), e); - throw e; - } finally { - result.computeStatusIfUnknown(); - } - } + // TODO cleanup and delete cases + +// private void deleteCase(String caseOid, OperationResult parentResult) { +// OperationResult result = parentResult.createSubresult(OPERATION_DELETE_PROCESS_INSTANCE); +// result.addParam("caseOid", caseOid); +// try { +// repositoryService.deleteObject(CaseType.class, ) +// } catch (RuntimeException e) { +// result.recordFatalError("Case couldn't be deleted: " + e.getMessage(), e); +// throw e; +// } finally { +// result.computeStatusIfUnknown(); +// } +// } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/WorkItemManager.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/WorkItemManager.java index a81b18af1ca..980feffeaa1 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/WorkItemManager.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/WorkItemManager.java @@ -16,37 +16,43 @@ package com.evolveum.midpoint.wf.impl.access; -import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.result.OperationResultStatus; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.security.api.MidPointPrincipal; -import com.evolveum.midpoint.security.api.SecurityContextManager; +import com.evolveum.midpoint.schema.util.WorkItemId; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.schema.util.WorkItemId; -import com.evolveum.midpoint.wf.api.CompleteAction; import com.evolveum.midpoint.wf.api.WorkflowManager; +import com.evolveum.midpoint.wf.api.request.ClaimWorkItemsRequest; +import com.evolveum.midpoint.wf.api.request.CompleteWorkItemsRequest; +import com.evolveum.midpoint.wf.api.request.DelegateWorkItemsRequest; +import com.evolveum.midpoint.wf.api.request.ReleaseWorkItemsRequest; import com.evolveum.midpoint.wf.impl.engine.WorkflowEngine; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.xml.datatype.Duration; -import java.util.Collection; import java.util.List; -import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.toShortString; -import static java.util.Collections.singleton; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; /** + * Provides basic work item actions for upper layers. + * * Takes care of - * - authorization (including determination of the user) * - handling operation result + * + * Does NOT take care of + * - authorizations -- these are handled internally by WorkflowEngine, because only at that time + * we read the appropriate objects (cases, work items). */ @Component @@ -54,10 +60,10 @@ public class WorkItemManager { private static final Trace LOGGER = TraceManager.getTrace(WorkItemManager.class); - @Autowired private AuthorizationHelper authorizationHelper; - @Autowired private SecurityContextManager securityContextManager; - @Autowired private PrismContext prismContext; @Autowired private WorkflowEngine workflowEngine; + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; private static final String DOT_INTERFACE = WorkflowManager.class.getName() + "."; @@ -79,13 +85,12 @@ public void completeWorkItem(WorkItemId workItemId, String outcome, String comme result.addParam("additionalDelta", additionalDelta); try { - MidPointPrincipal principal = getAndRecordPrincipal(result); - LOGGER.trace("Completing work item {} with decision of {} ['{}'] by {}; cause: {}", - workItemId, outcome, comment, toShortString(principal.getUser()), causeInformation); - CaseWorkItemType workItem = workflowEngine.getWorkItem(workItemId, result); - CompleteAction completeAction = new CompleteAction(workItemId, workItem, outcome, comment, additionalDelta, causeInformation); - completeWorkItemsInternal(singleton(completeAction), principal, task, result); - } catch (SecurityViolationException | RuntimeException | CommunicationException | ConfigurationException | SchemaException | ObjectAlreadyExistsException e) { + LOGGER.trace("Completing work item {} with decision of {} ['{}']; cause: {}", + workItemId, outcome, comment, causeInformation); + CompleteWorkItemsRequest request = new CompleteWorkItemsRequest(workItemId.caseOid, causeInformation); + request.getCompletions().add(new CompleteWorkItemsRequest.SingleCompletion(workItemId.id, outcome, comment, additionalDelta)); + workflowEngine.executeRequest(request, task, result); + } catch (SecurityViolationException | RuntimeException | SchemaException | ObjectAlreadyExistsException e) { result.recordFatalError("Couldn't complete the work item " + workItemId + ": " + e.getMessage(), e); throw e; } finally { @@ -93,21 +98,16 @@ public void completeWorkItem(WorkItemId workItemId, String outcome, String comme } } - @NotNull - private MidPointPrincipal getAndRecordPrincipal(OperationResult result) throws SecurityViolationException { - MidPointPrincipal principal = securityContextManager.getPrincipal(); - result.addContext("user", toShortString(principal.getUser())); - return principal; - } - - // assuming the work items in actions are fresh enough - public void completeWorkItems(Collection actions, Task task, OperationResult parentResult) + /** + * Bulk version of completeWorkItem method. It's necessary when we need to complete more items at the same time, + * to provide accurate completion information (avoiding completion of the first one and automated closure of other ones). + */ + public void completeWorkItems(CompleteWorkItemsRequest request, Task task, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SchemaException, ObjectAlreadyExistsException { OperationResult result = parentResult.createSubresult(OPERATION_COMPLETE_WORK_ITEMS); try { - MidPointPrincipal principal = getAndRecordPrincipal(result); - completeWorkItemsInternal(actions, principal, task, result); + workflowEngine.executeRequest(request, task, result); } catch (SecurityViolationException | RuntimeException | CommunicationException | ConfigurationException | SchemaException | ObjectAlreadyExistsException e) { result.recordFatalError("Couldn't complete work items: " + e.getMessage(), e); throw e; @@ -116,40 +116,18 @@ public void completeWorkItems(Collection actions, Task task, Ope } } - private void completeWorkItemsInternal(Collection actions, MidPointPrincipal principal, Task task, OperationResult result) - throws SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, - ConfigurationException, SchemaException, ObjectAlreadyExistsException { - LOGGER.trace("Executing complete actions: {}", actions); - for (CompleteAction action : actions) { - if (!authorizationHelper.isAuthorized(action.getWorkItem(), AuthorizationHelper.RequestedOperation.COMPLETE, task, result)) { - throw new SecurityViolationException("You are not authorized to complete the work item."); - } - } - workflowEngine.completeWorkItems(actions, principal, result); - } - + // We can eventually provide bulk version of this method as well. public void claimWorkItem(WorkItemId workItemId, Task task, OperationResult parentResult) - throws SecurityViolationException, ObjectNotFoundException, SchemaException { + throws SecurityViolationException, ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, ExpressionEvaluationException { OperationResult result = parentResult.createSubresult(OPERATION_CLAIM_WORK_ITEM); result.addArbitraryObjectAsParam("workItemId", workItemId); try { - MidPointPrincipal principal = getAndRecordPrincipal(result); - LOGGER.trace("Claiming work item {} by {}", workItemId, toShortString(principal.getUser())); - CaseWorkItemType workItem = workflowEngine.getWorkItem(workItemId, result); - if (!workItem.getAssigneeRef().isEmpty()) { - String desc; - if (workItem.getAssigneeRef().size() == 1 && principal.getOid().equals(workItem.getAssigneeRef().get(0).getOid())) { - desc = "the current"; - } else { - desc = "another"; - } - throw new SystemException("The work item is already assigned to "+desc+" user"); - } - if (!authorizationHelper.isAuthorizedToClaim(workItem)) { - throw new SecurityViolationException("You are not authorized to claim the selected work item."); - } - workflowEngine.claim(workItemId, principal, task, result); - } catch (ObjectNotFoundException | SecurityViolationException | RuntimeException | SchemaException e) { + LOGGER.trace("Claiming work item {}", workItemId); + ClaimWorkItemsRequest request = new ClaimWorkItemsRequest(workItemId.caseOid); + request.getClaims().add(new ClaimWorkItemsRequest.SingleClaim(workItemId.id)); + workflowEngine.executeRequest(request, task, result); + } catch (ObjectNotFoundException | SecurityViolationException | RuntimeException | SchemaException | ObjectAlreadyExistsException | ExpressionEvaluationException | ConfigurationException | CommunicationException e) { result.recordFatalError("Couldn't claim the work item " + workItemId + ": " + e.getMessage(), e); throw e; } finally { @@ -157,31 +135,18 @@ public void claimWorkItem(WorkItemId workItemId, Task task, OperationResult pare } } + // We can eventually provide bulk version of this method as well. public void releaseWorkItem(WorkItemId workItemId, Task task, OperationResult parentResult) - throws ObjectNotFoundException, SecurityViolationException, SchemaException { + throws ObjectNotFoundException, SecurityViolationException, SchemaException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, ExpressionEvaluationException { OperationResult result = parentResult.createSubresult(OPERATION_RELEASE_WORK_ITEM); result.addArbitraryObjectAsParam("workItemId", workItemId); try { - MidPointPrincipal principal = getAndRecordPrincipal(result); - - LOGGER.trace("Releasing work item {} by {}", workItemId, toShortString(principal.getUser())); - - CaseWorkItemType workItem = workflowEngine.getWorkItem(workItemId, result); - if (workItem.getAssigneeRef().isEmpty()) { - throw new SystemException("The work item is not assigned to a user"); - } - if (workItem.getAssigneeRef().size() > 1) { - throw new SystemException("The work item is assigned to more than one user, so it cannot be released"); - } - if (!principal.getOid().equals(workItem.getAssigneeRef().get(0).getOid())) { - throw new SystemException("The work item is not assigned to the current user"); - } - if (workItem.getCandidateRef().isEmpty()) { - result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "There are no candidates this work item can be offered to"); - return; - } - workflowEngine.unclaim(workItemId, principal, task, result); - } catch (ObjectNotFoundException | SecurityViolationException | RuntimeException | SchemaException e) { + LOGGER.trace("Releasing work item {}", workItemId); + ReleaseWorkItemsRequest request = new ReleaseWorkItemsRequest(workItemId.caseOid); + request.getReleases().add(new ReleaseWorkItemsRequest.SingleRelease(workItemId.id)); + workflowEngine.executeRequest(request, task, result); + } catch (ObjectNotFoundException | SecurityViolationException | RuntimeException | SchemaException | ObjectAlreadyExistsException | ExpressionEvaluationException | ConfigurationException | CommunicationException e) { result.recordFatalError("Couldn't release work item " + workItemId + ": " + e.getMessage(), e); throw e; } finally { @@ -193,6 +158,8 @@ public void releaseWorkItem(WorkItemId workItemId, Task task, OperationResult pa // 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. + + // We can eventually provide bulk version of this method as well. public void delegateWorkItem(WorkItemId workItemId, List delegates, WorkItemDelegationMethodType method, WorkItemEscalationLevelType escalation, Duration newDuration, WorkItemEventCauseInformationType causeInformation, Task task, OperationResult parentResult) @@ -202,23 +169,12 @@ public void delegateWorkItem(WorkItemId workItemId, List de result.addArbitraryObjectAsParam("escalation", escalation); result.addArbitraryObjectCollectionAsParam("delegates", delegates); try { - MidPointPrincipal principal = getAndRecordPrincipal(result); - - ObjectReferenceType initiator = - causeInformation == null || causeInformation.getType() == WorkItemEventCauseTypeType.USER_ACTION ? - ObjectTypeUtil.createObjectRef(principal.getUser(), prismContext) : null; - LOGGER.trace("Delegating work item {} to {}: escalation={}; cause={}", workItemId, delegates, escalation != null ? escalation.getName() + "/" + escalation.getDisplayName() : "none", causeInformation); - - CaseWorkItemType workItem = workflowEngine.getWorkItem(workItemId, result); - if (!authorizationHelper.isAuthorized(workItem, AuthorizationHelper.RequestedOperation.DELEGATE, task, result)) { - throw new SecurityViolationException("You are not authorized to delegate this work item."); - } - - workflowEngine - .executeDelegation(workItemId, delegates, method, escalation, newDuration, causeInformation, - result, principal, initiator, task, workItem); + DelegateWorkItemsRequest request = new DelegateWorkItemsRequest(workItemId.caseOid, causeInformation); + request.getDelegations().add(new DelegateWorkItemsRequest.SingleDelegation(workItemId.id, delegates, + defaultIfNull(method, WorkItemDelegationMethodType.REPLACE_ASSIGNEES), escalation, newDuration)); + workflowEngine.executeRequest(request, task, result); } catch (SecurityViolationException | RuntimeException | ObjectNotFoundException | SchemaException | CommunicationException | ConfigurationException e) { result.recordFatalError("Couldn't delegate/escalate work item " + workItemId + ": " + e.getMessage(), e); throw e; @@ -228,4 +184,17 @@ public void delegateWorkItem(WorkItemId workItemId, List de result.computeStatusIfUnknown(); } } + + @NotNull + public CaseWorkItemType getWorkItem(WorkItemId id, OperationResult result) + throws SchemaException, ObjectNotFoundException { + PrismObject caseObject = repositoryService.getObject(CaseType.class, id.caseOid, null, result); + //noinspection unchecked + PrismContainerValue pcv = (PrismContainerValue) + caseObject.find(ItemPath.create(CaseType.F_WORK_ITEM, id.id)); + if (pcv == null) { + throw new ObjectNotFoundException("No work item " + id.id + " in " + caseObject); + } + return pcv.asContainerable(); + } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/EngineInvocationContext.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/EngineInvocationContext.java index d59abc63b5a..e0b481f33ea 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/EngineInvocationContext.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/EngineInvocationContext.java @@ -16,78 +16,110 @@ package com.evolveum.midpoint.wf.impl.engine; +import com.evolveum.midpoint.audit.api.AuditEventRecord; import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.equivalence.ParameterizedEquivalenceStrategy; import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.repo.api.VersionPrecondition; +import com.evolveum.midpoint.schema.ObjectTreeDeltas; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.WfContextUtil; +import com.evolveum.midpoint.schema.util.WorkItemId; +import com.evolveum.midpoint.security.api.MidPointPrincipal; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DebugDumpable; -import com.evolveum.midpoint.wf.impl.processes.common.StageComputeHelper; -import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.WfContextType; +import com.evolveum.midpoint.util.DebugUtil; +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.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.impl.engine.helpers.DelayedNotification; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + /** + * A context for single engine invocation attempt. + * (I.e. this context is created when invocation attempt starts.) * + * todo clean this up */ public class EngineInvocationContext implements DebugDumpable { - @NotNull public CaseType aCase; - @NotNull public final Task opTask; - private boolean done; + private static final Trace LOGGER = TraceManager.getTrace(EngineInvocationContext.class); + + @NotNull private final CaseType originalCase; + @NotNull private final CaseType currentCase; + @NotNull private final Task opTask; + @NotNull private final WorkflowEngine engine; + @NotNull private final MidPointPrincipal principal; + + @NotNull public final List pendingAuditRecords = new ArrayList<>(); + @NotNull public final List pendingNotifications = new ArrayList<>(); - public EngineInvocationContext(@NotNull CaseType aCase, @NotNull Task opTask) { - this.aCase = aCase; + private boolean wasClosed; + + public EngineInvocationContext(@NotNull CaseType originalCase, @NotNull Task opTask, @NotNull WorkflowEngine engine, + @NotNull MidPointPrincipal principal) { + this.originalCase = originalCase; + this.currentCase = originalCase.clone(); this.opTask = opTask; + this.engine = engine; + this.principal = principal; } public WfContextType getWfContext() { - return aCase.getWorkflowContext(); + return currentCase.getWorkflowContext(); } @NotNull public CaseType getCase() { - return aCase; + return currentCase; } @NotNull - public Task getOpTask() { + public Task getTask() { return opTask; } @Override public String debugDump(int indent) { - return aCase.getWorkflowContext().asPrismContainerValue().debugDump(indent); // TODO + return currentCase.getWorkflowContext().asPrismContainerValue().debugDump(indent); // TODO } public String getChannel() { return opTask.getChannel(); } - public boolean isDone() { - return done; - } - - public void setDone(boolean done) { - this.done = done; - } - @Override public String toString() { return "EngineInvocationContext{" + - "case=" + aCase + - ", done=" + done + + "case=" + currentCase + '}'; } public String getCaseOid() { - return aCase.getOid(); + return currentCase.getOid(); } @NotNull public CaseWorkItemType findWorkItemById(long id) { //noinspection unchecked PrismContainerValue workItemPcv = (PrismContainerValue) - aCase.asPrismContainerValue().find(ItemPath.create(CaseType.F_WORK_ITEM, id)); + currentCase.asPrismContainerValue().find(ItemPath.create(CaseType.F_WORK_ITEM, id)); if (workItemPcv == null) { throw new IllegalStateException("No work item " + id + " in " + this); } else { @@ -95,27 +127,165 @@ public CaseWorkItemType findWorkItemById(long id) { } } - private StageComputeHelper.ComputationResult preStageComputationResult; - private String currentStageOutcome; + public String getProcessInstanceName() { + return currentCase.getName().getOrig(); + } - public String getCurrentStageOutcome() { - return currentStageOutcome; + public void addAuditRecord(AuditEventRecord record) { + pendingAuditRecords.add(record); } - public void setCurrentStageOutcome(String currentStageOutcome) { - this.currentStageOutcome = currentStageOutcome; + public void commit(OperationResult result) + throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException, PreconditionViolationException { + + if (currentCase.getOid() == null) { + String newOid = engine.repositoryService.addObject(currentCase.asPrismObject(), null, result); + originalCase.setOid(newOid); + } else { + ObjectDelta diff = originalCase.asPrismObject() + .diff(currentCase.asPrismObject(), ParameterizedEquivalenceStrategy.LITERAL); + assert diff.isModify(); + Collection> modifications = diff.getModifications(); + + LOGGER.trace("Modifications to be applied to case {}:\n{}", getCaseOid(), DebugUtil.debugDumpLazily(modifications)); + + engine.repositoryService.modifyObject(CaseType.class, getCaseOid(), modifications, + new VersionPrecondition<>(originalCase.asPrismObject()), null, result); + } + + if (wasClosed) { + try { + engine.primaryChangeProcessor.onProcessEnd(this, result); + + engine.auditHelper.prepareProcessEndRecord(this, result); + prepareNotification(new DelayedNotification.ProcessEnd(currentCase)); + } catch (PreconditionViolationException e) { + throw new SystemException(e); + } + } + + engine.auditHelper.auditPreparedRecords(this); + engine.notificationHelper.sendPreparedNotifications(this, result); } - public StageComputeHelper.ComputationResult getPreStageComputationResult() { - return preStageComputationResult; + public int getNumberOfStages() { + Integer stageCount = WfContextUtil.getStageCount(getWfContext()); + if (stageCount == null) { + LOGGER.error("Couldn't determine stage count from the workflow context\n{}", debugDump()); + throw new IllegalStateException("Couldn't determine stage count from the workflow context"); + } + return stageCount; } - public void setPreStageComputationResult( - StageComputeHelper.ComputationResult preStageComputationResult) { - this.preStageComputationResult = preStageComputationResult; + public int getCurrentStage() { + int rv = defaultIfNull(currentCase.getStageNumber(), 0); + checkCurrentStage(rv); + return rv; } - public String getProcessInstanceName() { - return aCase.getName().getOrig(); + private void checkCurrentStage(int rv) { + if (rv < 0 || rv > getNumberOfStages()) { + LOGGER.error("Current stage is below 0 or beyond the number of stages: {}\n{}", rv, debugDump()); + throw new IllegalStateException("Current stage is below 0 or beyond the number of stages: " + rv); + } + } + + public ApprovalStageDefinitionType getCurrentStageDefinition() { + return WfContextUtil.getCurrentStageDefinition(currentCase); + } + + public boolean isAnyCurrentStageWorkItemOpen() { + int currentStage = getCurrentStage(); + return currentCase.getWorkItem().stream() + .anyMatch(wi -> wi.getStageNumber() != null && wi.getStageNumber() == currentStage && wi.getCloseTimestamp() == null); + } + + public void addEvent(CaseEventType event) { + currentCase.getEvent().add(event); + } + + private PrismContext getPrismContext() { + return originalCase.asPrismObject().getPrismContext(); + } + + public void updateDelta(ObjectDeltaType additionalDelta) throws SchemaException { + PrismContext prismContext = getPrismContext(); + WfPrimaryChangeProcessorStateType state = WfContextUtil.getPrimaryChangeProcessorState(getWfContext()); + ObjectTreeDeltasType updatedDelta = ObjectTreeDeltas.mergeDeltas(state.getDeltasToProcess(), additionalDelta, prismContext); + state.setDeltasToProcess(updatedDelta); + } + +// @NotNull +// List getOpenWorkItemsForStage(EngineInvocationContext ctx, int stage) { +// return ctx.currentCase.getWorkItem().stream() +// .filter(wi -> wi.getStageNumber() != null && wi.getStageNumber() == stage) +// .filter(wi -> wi.getCloseTimestamp() == null) +// .collect(Collectors.toList()); +// } + + @NotNull + public List getWorkItemsForStage(int stage) { + return currentCase.getWorkItem().stream() + .filter(wi -> wi.getStageNumber() != null && wi.getStageNumber() == stage) + .collect(Collectors.toList()); + } + + +// private boolean isClosed() { +// return SchemaConstants.CASE_STATE_CLOSED.equals(currentCase.getState()); +// } +// +// private boolean isWaiting() { +// return currentCase.getWorkItem().stream().anyMatch(wi -> wi.getCloseTimestamp() == null); +// } + + public void setWasClosed(boolean wasClosed) { + this.wasClosed = wasClosed; + } + + public boolean getWasClosed() { + return wasClosed; + } + +// public void assertNoOpenWorkItems() { +// if (currentCase.getWorkItem().stream().anyMatch(wi -> wi.getCloseTimestamp() == null)) { +// throw new IllegalStateException("Open work item in " + currentCase); +// } +// } + + // todo remove + public WorkItemId createWorkItemId(CaseWorkItemType workItem) { + return WorkItemId.create(getCaseOid(), workItem.getId()); + } + + @NotNull + public MidPointPrincipal getPrincipal() { + return principal; + } + + public void prepareNotification(DelayedNotification notification) { + pendingNotifications.add(notification); + } + + // private void logCtx(EngineInvocationContext ctx, String message, OperationResult result) +// throws SchemaException, ObjectNotFoundException { +// String rootOid = ctx.originalCase.getParentRef() != null ? ctx.originalCase.getParentRef().getOid() : ctx.originalCase.getOid(); +// CaseType rootCase = repositoryService.getObject(CaseType.class, rootOid, null, result).asObjectable(); +// LOGGER.trace("###### [ {} ] ######", message); +// LOGGER.trace("Root case:\n{}", modelHelper.dumpCase(rootCase)); +// for (CaseType subcase : miscHelper.getSubcases(rootCase, result)) { +// LOGGER.trace("Subcase:\n{}", modelHelper.dumpCase(subcase)); +// } +// LOGGER.trace("###### [ END OF {} ] ######", message); +// } + + @NotNull + public WorkflowEngine getEngine() { + return engine; + } + + @NotNull + public CaseType getCurrentCase() { + return currentCase; } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/NotificationHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/NotificationHelper.java deleted file mode 100644 index d376e8495e1..00000000000 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/NotificationHelper.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2010-2019 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.engine; - -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.wf.api.*; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.springframework.stereotype.Component; - -import javax.xml.datatype.Duration; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * - */ -@Component -public class NotificationHelper { - - private static final Trace LOGGER = TraceManager.getTrace(NotificationHelper.class); - - private Set processListeners = ConcurrentHashMap.newKeySet(); - private Set workItemListeners = ConcurrentHashMap.newKeySet(); - - public void notifyProcessStart(CaseType aCase, Task opTask, OperationResult result) { - for (ProcessListener processListener : processListeners) { - processListener.onProcessInstanceStart(aCase, opTask, result); - } - } - - public void notifyProcessEnd(CaseType aCase, Task opTask, OperationResult result) { - for (ProcessListener processListener : processListeners) { - processListener.onProcessInstanceEnd(aCase, opTask, result); - } - } - - public void notifyWorkItemCreated(ObjectReferenceType originalAssigneeRef, CaseWorkItemType workItem, - CaseType aCase, Task opTask, OperationResult result) { - for (WorkItemListener workItemListener : workItemListeners) { - workItemListener.onWorkItemCreation(originalAssigneeRef, workItem, aCase, opTask, result); - } - } - - public void notifyWorkItemDeleted(ObjectReferenceType assignee, CaseWorkItemType workItem, - WorkItemOperationInfo operationInfo, WorkItemOperationSourceInfo sourceInfo, - CaseType aCase, Task opTask, OperationResult result) { - for (WorkItemListener workItemListener : workItemListeners) { - workItemListener.onWorkItemDeletion(assignee, workItem, operationInfo, sourceInfo, aCase, opTask, result); - } - } - - public void notifyWorkItemAllocationChangeCurrentActors(CaseWorkItemType workItem, - @NotNull WorkItemAllocationChangeOperationInfo operationInfo, - WorkItemOperationSourceInfo sourceInfo, Duration timeBefore, - CaseType aCase, Task opTask, OperationResult result) { - for (WorkItemListener workItemListener : workItemListeners) { - workItemListener.onWorkItemAllocationChangeCurrentActors(workItem, operationInfo, sourceInfo, timeBefore, aCase, opTask, result); - } - } - - public void notifyWorkItemAllocationChangeNewActors(CaseWorkItemType workItem, - @NotNull WorkItemAllocationChangeOperationInfo operationInfo, - @Nullable WorkItemOperationSourceInfo sourceInfo, - CaseType aCase, Task opTask, OperationResult result) { - for (WorkItemListener workItemListener : workItemListeners) { - workItemListener.onWorkItemAllocationChangeNewActors(workItem, operationInfo, sourceInfo, aCase, opTask, result); - } - } - - public void notifyWorkItemCustom(@Nullable ObjectReferenceType assignee, CaseWorkItemType workItem, - WorkItemEventCauseInformationType cause, CaseType aCase, Task opTask, - @NotNull WorkItemNotificationActionType notificationAction, - OperationResult result) { - for (WorkItemListener workItemListener : workItemListeners) { - workItemListener.onWorkItemCustomEvent(assignee, workItem, notificationAction, cause, aCase, opTask, result); - } - } - - public void registerProcessListener(ProcessListener processListener) { - LOGGER.trace("Registering process listener {}", processListener); - processListeners.add(processListener); - } - - public void registerWorkItemListener(WorkItemListener workItemListener) { - LOGGER.trace("Registering work item listener {}", workItemListener); - workItemListeners.add(workItemListener); - } - -} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/TriggerHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/TriggerHelper.java deleted file mode 100644 index 96465607ccf..00000000000 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/TriggerHelper.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2010-2019 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.engine; - -import com.evolveum.midpoint.prism.PrismContainerValue; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismProperty; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.util.PrismUtil; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.WfContextUtil; -import com.evolveum.midpoint.schema.util.WorkItemId; -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.processes.common.WfTimedActionTriggerHandler; -import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.TriggerType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemTimedActionsType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * - */ -@Component -public class TriggerHelper { - - private static final Trace LOGGER = TraceManager.getTrace(TriggerHelper.class); - - @Autowired private RepositoryService repositoryService; - @Autowired private PrismContext prismContext; - - public void createTriggersForTimedActions(WorkItemId workItemId, int escalationLevel, Date workItemCreateTime, - Date workItemDeadline, CaseType wfCase, List timedActionsList, OperationResult result) { - LOGGER.trace("Creating triggers for timed actions for work item {}, escalation level {}, create time {}, deadline {}, {} timed action(s)", - workItemId, escalationLevel, workItemCreateTime, workItemDeadline, timedActionsList.size()); - try { - List triggers = WfContextUtil.createTriggers(escalationLevel, workItemCreateTime, workItemDeadline, - timedActionsList, prismContext, LOGGER, workItemId.asString(), WfTimedActionTriggerHandler.HANDLER_URI); - LOGGER.trace("Adding {} triggers to {}:\n{}", triggers.size(), wfCase, - PrismUtil.serializeQuietlyLazily(prismContext, triggers)); - if (!triggers.isEmpty()) { - List> itemDeltas = prismContext.deltaFor(TaskType.class) - .item(TaskType.F_TRIGGER).add(PrismContainerValue.toPcvList(triggers)) - .asItemDeltas(); - repositoryService.modifyObject(CaseType.class, wfCase.getOid(), itemDeltas, result); - } - } catch (ObjectNotFoundException | SchemaException | ObjectAlreadyExistsException | RuntimeException e) { - throw new SystemException("Couldn't add trigger(s) to " + wfCase + ": " + e.getMessage(), e); - } - } - - public void removeTriggersForWorkItem(CaseType aCase, WorkItemId workItemId, OperationResult result) { - List> toDelete = new ArrayList<>(); - for (TriggerType triggerType : aCase.getTrigger()) { - if (WfTimedActionTriggerHandler.HANDLER_URI.equals(triggerType.getHandlerUri())) { - PrismProperty workItemIdProperty = triggerType.getExtension().asPrismContainerValue() - .findProperty(SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ID); - if (workItemIdProperty != null && workItemId.asString().equals(workItemIdProperty.getRealValue())) { - //noinspection unchecked - toDelete.add(triggerType.clone().asPrismContainerValue()); - } - } - } - removeSelectedTriggers(aCase, toDelete, result); - } - - // not necessary any more, as work item triggers are deleted when the work item (task) is deleted - // (and there are currently no triggers other than work-item-related) - public void removeAllStageTriggersForWorkItem(CaseType aCase, OperationResult result) { - List> toDelete = new ArrayList<>(); - for (TriggerType triggerType : aCase.getTrigger()) { - if (WfTimedActionTriggerHandler.HANDLER_URI.equals(triggerType.getHandlerUri())) { - //noinspection unchecked - toDelete.add(triggerType.clone().asPrismContainerValue()); - } - } - removeSelectedTriggers(aCase, toDelete, result); - } - - private void removeSelectedTriggers(CaseType aCase, List> toDelete, OperationResult result) { - LOGGER.trace("About to delete {} triggers from {}: {}", toDelete.size(), aCase, toDelete); - if (!toDelete.isEmpty()) { - try { - List> itemDeltas = prismContext.deltaFor(TaskType.class) - .item(TaskType.F_TRIGGER).delete(toDelete) - .asItemDeltas(); - repositoryService.modifyObject(CaseType.class, aCase.getOid(), itemDeltas, result); - } catch (SchemaException|ObjectNotFoundException|ObjectAlreadyExistsException|RuntimeException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't remove triggers from {}", e, aCase); - } - } - } -} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/WorkflowEngine.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/WorkflowEngine.java index ce4bd3ca020..f9d52438b7a 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/WorkflowEngine.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/WorkflowEngine.java @@ -16,936 +16,120 @@ package com.evolveum.midpoint.wf.impl.engine; -import com.evolveum.midpoint.audit.api.AuditEventRecord; -import com.evolveum.midpoint.audit.api.AuditService; import com.evolveum.midpoint.common.Clock; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.delta.ItemDeltaCollectionsUtil; -import com.evolveum.midpoint.prism.delta.builder.S_ItemEntry; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.util.CloneUtil; -import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.schema.*; -import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.*; import com.evolveum.midpoint.security.api.MidPointPrincipal; import com.evolveum.midpoint.security.api.SecurityUtil; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskManager; -import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.*; -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.api.WorkItemAllocationChangeOperationInfo; -import com.evolveum.midpoint.wf.api.WorkItemOperationSourceInfo; -import com.evolveum.midpoint.wf.api.CompleteAction; +import com.evolveum.midpoint.wf.api.request.Request; +import com.evolveum.midpoint.wf.impl.access.AuthorizationHelper; +import com.evolveum.midpoint.wf.impl.engine.actions.Action; +import com.evolveum.midpoint.wf.impl.engine.actions.ActionFactory; +import com.evolveum.midpoint.wf.impl.engine.helpers.AuditHelper; +import com.evolveum.midpoint.wf.impl.engine.helpers.NotificationHelper; +import com.evolveum.midpoint.wf.impl.engine.helpers.TriggerHelper; +import com.evolveum.midpoint.wf.impl.engine.helpers.WorkItemHelper; import com.evolveum.midpoint.wf.impl.processes.common.ExpressionEvaluationHelper; import com.evolveum.midpoint.wf.impl.processes.common.StageComputeHelper; -import com.evolveum.midpoint.wf.impl.processors.ModelHelper; -import com.evolveum.midpoint.wf.impl.util.MiscHelper; import com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor; -import com.evolveum.midpoint.wf.util.ApprovalUtils; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; -import org.apache.commons.collections4.CollectionUtils; -import org.jetbrains.annotations.NotNull; +import com.evolveum.midpoint.wf.impl.util.MiscHelper; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; -import javax.xml.datatype.Duration; -import javax.xml.datatype.XMLGregorianCalendar; -import java.util.*; -import java.util.stream.Collectors; - -import static com.evolveum.midpoint.wf.api.CompleteAction.getWorkItems; -import static com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType.F_EVENT; -import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType.F_WORKFLOW_CONTEXT; -import static com.evolveum.midpoint.xml.ns._public.common.common_3.WfContextType.F_PROCESSOR_SPECIFIC_STATE; -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; -import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; - /** - * This is a replacement of Activiti. + * As a replacement of Activiti, this class manages execution of approval cases: + * it starts, advances, and stops approval process instances represented by CaseType objects. + * + * Handling of concurrency issues: each request is carried out solely "in memory". Only when committing it the + * case object version is checked in repository. If it was changed in the meanwhile the request is aborted and repeated. */ @Component public class WorkflowEngine { private static final Trace LOGGER = TraceManager.getTrace(WorkflowEngine.class); - @Autowired private Clock clock; - @Autowired private RepositoryService repositoryService; - @Autowired private PrismContext prismContext; - @Autowired private TaskManager taskManager; - @Autowired private AuditService auditService; - @Autowired private AuditHelper auditHelper; - @Autowired private NotificationHelper notificationHelper; - @Autowired private StageComputeHelper stageComputeHelper; - @Autowired private PrimaryChangeProcessor primaryChangeProcessor; // todo - @Autowired private MiscHelper miscHelper; - @Autowired private ModelHelper modelHelper; - @Autowired private TriggerHelper triggerHelper; - @Autowired private ExpressionEvaluationHelper expressionEvaluationHelper; - - public void startProcessInstance(CTX ctx, OperationResult result) throws ObjectAlreadyExistsException, SchemaException, ObjectNotFoundException { - - LOGGER.trace("+++ startProcessInstance ENTER: ctx={}", ctx); - - auditHelper.auditProcessStart(ctx.aCase, ctx.getWfContext(), primaryChangeProcessor, ctx.opTask, result); - notificationHelper.notifyProcessStart(ctx.aCase, ctx.opTask, result); - advanceProcessInstance(ctx, result); - - runTheProcess(ctx, result); - - LOGGER.trace("--- startProcessInstance EXIT: ctx={}", ctx); - } - - private void runTheProcess(CTX ctx, OperationResult result) throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { - - LOGGER.trace("+++ runTheProcess ENTER: ctx={}", ctx); - for (;;) { - refreshContext(ctx, result); - if (isWaiting(ctx) || isClosed(ctx)) { - LOGGER.trace("--- runTheProcess EXIT (waiting or closed): ctx={}", ctx); - return; - } - if (ctx.isDone()) { - recordProcessOutcome(ctx, SchemaConstants.MODEL_APPROVAL_OUTCOME_APPROVE, result); - stopProcessInstance(ctx, result); - LOGGER.trace("--- runTheProcess EXIT (done): ctx={}", ctx); - return; - } - advanceProcessInstance(ctx, result); - } - } - - private boolean isClosed(CTX ctx) { - return SchemaConstants.CASE_STATE_CLOSED.equals(ctx.aCase.getState()); - } - - private boolean isWaiting(CTX ctx) { - return ctx.aCase.getWorkItem().stream().anyMatch(wi -> wi.getCloseTimestamp() == null); - } - - private void refreshContext(CTX ctx, OperationResult result) - throws SchemaException, ObjectNotFoundException { - ctx.aCase = repositoryService.getObject(CaseType.class, ctx.getCaseOid(), null, result).asObjectable(); - } - - public void stopProcessInstance(EngineInvocationContext ctx, OperationResult result) throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - LOGGER.trace("+++ stopProcessInstance ENTER: ctx={}", ctx); - - XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); - List> caseModifications = new ArrayList<>(); - List openWorkItems = ctx.aCase.getWorkItem().stream() - .filter(wi -> wi.getCloseTimestamp() == null) - .collect(Collectors.toList()); - for (CaseWorkItemType workItem : openWorkItems) { - addWorkItemClosureModifications(ctx, workItem.getId(), now, caseModifications); - } - modifyCase(ctx, caseModifications, result); - - // TODO what about outcome? (beware of race conditions!) - - try { - primaryChangeProcessor.onProcessEnd(ctx, result); - } catch (PreconditionViolationException e) { - throw new SystemException(e); // TODO - } - - auditHelper.auditProcessEnd(ctx.getCase(), ctx.getWfContext(), primaryChangeProcessor, ctx.opTask, result); - notificationHelper.notifyProcessEnd(ctx.getCase(), ctx.opTask, result); - - // TODO execute the result!!! - LOGGER.trace("--- stopProcessInstance EXIT: ctx={}", ctx); - } - - public List> prepareCaseClosureModifications(XMLGregorianCalendar now) - throws SchemaException { - return prismContext.deltaFor(CaseType.class) - .item(CaseType.F_STATE).replace(SchemaConstants.CASE_STATE_CLOSED) - .item(CaseType.F_CLOSE_TIMESTAMP).replace(now) - .asItemDeltas(); - } + @Autowired public Clock clock; + @Autowired + @Qualifier("cacheRepositoryService") + public RepositoryService repositoryService; + @Autowired public PrismContext prismContext; + @Autowired public SecurityEnforcer securityEnforcer; + @Autowired public AuditHelper auditHelper; + @Autowired public NotificationHelper notificationHelper; + @Autowired public StageComputeHelper stageComputeHelper; + @Autowired public PrimaryChangeProcessor primaryChangeProcessor; // todo + @Autowired public MiscHelper miscHelper; + @Autowired public TriggerHelper triggerHelper; + @Autowired public ExpressionEvaluationHelper expressionEvaluationHelper; + @Autowired public WorkItemHelper workItemHelper; + @Autowired public AuthorizationHelper authorizationHelper; + @Autowired private ActionFactory actionFactory; + + private static final int MAX_ATTEMPTS = 10; /** - * Closes work item in repository as well as in memory. + * Executes a request. This is the main entry point. */ - private void closeWorkItem(EngineInvocationContext ctx, CaseWorkItemType workItem, - XMLGregorianCalendar now, OperationResult result) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - LOGGER.trace("+++ closeWorkItem ENTER: workItem={}, ctx={}", workItem, ctx); - List> modifications = new ArrayList<>(); - addWorkItemClosureModifications(ctx, workItem.getId(), now, modifications); - repositoryService.modifyObject(CaseType.class, ctx.getCaseOid(), modifications, result); - // removes "workItem[n]" from the item path to be directly applicable to the specific work item - for (ItemDelta itemDelta : modifications) { - itemDelta.setParentPath(itemDelta.getParentPath().rest(2)); - itemDelta.applyTo(workItem.asPrismContainerValue()); - } - LOGGER.trace("--- closeWorkItem EXIT: workItem={}, ctx={}", workItem, ctx); - } - - private void addWorkItemClosureModifications(EngineInvocationContext ctx, Long workItemId, - XMLGregorianCalendar now, List> modifications) throws SchemaException { - modifications.addAll(prismContext.deltaFor(CaseType.class) - .item(CaseType.F_WORK_ITEM, workItemId, CaseWorkItemType.F_CLOSE_TIMESTAMP).replace(now) - .asItemDeltas()); - } - - public void assertNoOpenWorkItems(CaseType aCase) { - if (aCase.getWorkItem().stream().anyMatch(wi -> wi.getCloseTimestamp() == null)) { - throw new IllegalStateException("Open work item in " + aCase); - } - } - // actions should have compatible causeInformation - // TODO orchestrator - public void completeWorkItems(Collection actions, MidPointPrincipal principal, OperationResult result) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - - LOGGER.trace("+++ completeWorkItems ENTER: actions: {}", actions.size()); - if (actions.isEmpty()) { - throw new IllegalArgumentException("No complete actions"); - } - - List alreadyCompleted = new ArrayList<>(); - for (Iterator iterator = actions.iterator(); iterator.hasNext(); ) { - CompleteAction action = iterator.next(); - if (action.getWorkItem().getCloseTimestamp() != null) { - alreadyCompleted.add(action); - iterator.remove(); - } - } - if (!alreadyCompleted.isEmpty()) { - LOGGER.warn("The following work items were already completed: {}", getWorkItems(alreadyCompleted)); - if (alreadyCompleted.size() == 1) { - result.recordWarning("Work item " + alreadyCompleted.get(0).getWorkItemId() + " was already completed on " - + alreadyCompleted.get(0).getWorkItem().getCloseTimestamp()); - } else { - result.recordWarning(alreadyCompleted.size() + " work items were already completed"); - } - if (actions.isEmpty()) { + public void executeRequest(Request request, Task opTask, OperationResult result) + throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException, SecurityViolationException, + ExpressionEvaluationException, ConfigurationException, CommunicationException { + int attempt = 1; + for (;;) { + EngineInvocationContext ctx = createInvocationContext(request.getCaseOid(), opTask, result); + Action firstAction = actionFactory.create(request, ctx); + executeActionChain(firstAction, result); + try { + ctx.commit(result); return; - } - } - - EngineInvocationContext ctx = createInvocationContext(actions.iterator().next().getWorkItemId(), result); - ApprovalStageDefinitionType stageDef = WfContextUtil.getCurrentStageDefinition(ctx.aCase); - LevelEvaluationStrategyType levelEvaluationStrategyType = stageDef.getEvaluationStrategy(); - - boolean stopTheStage = false; - - List> caseModifications = new ArrayList<>(); - - XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); - for (CompleteAction action : actions) { - CaseWorkItemType workItem = action.getWorkItem(); - String outcome = action.getOutcome(); - - String currentCaseOid = action.getWorkItemId().getCaseOid(); - if (!ctx.getCaseOid().equals(currentCaseOid)) { - throw new IllegalArgumentException("Trying to complete work items from unrelated cases: " + ctx.getCaseOid() + " vs " + currentCaseOid); - } - - LOGGER.trace("+++ recordCompletionOfWorkItem ENTER: workItem={}, outcome={}", workItem, outcome); - LOGGER.trace("======================================== Recording individual decision of {}", principal); - - @NotNull WorkItemResultType itemResult = new WorkItemResultType(prismContext); - itemResult.setOutcome(outcome); - itemResult.setComment(action.getComment()); - boolean isApproved = ApprovalUtils.isApproved(itemResult); - if (isApproved && action.getAdditionalDelta() != null) { - ObjectDeltaType additionalDeltaBean = DeltaConvertor.toObjectDeltaType(action.getAdditionalDelta()); - ObjectTreeDeltasType treeDeltas = new ObjectTreeDeltasType(); - treeDeltas.setFocusPrimaryDelta(additionalDeltaBean); - itemResult.setAdditionalDeltas(treeDeltas); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Recording decision for approval process instance {} (case oid {}), stage {}: decision: {}", - ctx.getProcessInstanceName(), ctx.getCaseOid(), - WfContextUtil.getStageDiagName(stageDef), itemResult.getOutcome()); - } - - ObjectReferenceType performerRef = ObjectTypeUtil.createObjectRef(principal.getUser(), prismContext); - caseModifications.addAll(prismContext.deltaFor(CaseType.class) - .item(CaseType.F_WORK_ITEM, workItem.getId(), CaseWorkItemType.F_OUTPUT).replace(itemResult) - .item(CaseType.F_WORK_ITEM, workItem.getId(), CaseWorkItemType.F_PERFORMER_REF).replace(performerRef) - .asItemDeltas()); - addWorkItemClosureModifications(ctx, workItem.getId(), now, caseModifications); - - if (levelEvaluationStrategyType == LevelEvaluationStrategyType.FIRST_DECIDES) { - LOGGER.trace("Finishing the stage, because the stage evaluation strategy is 'firstDecides'."); - stopTheStage = true; - } else if ((levelEvaluationStrategyType == null || levelEvaluationStrategyType == LevelEvaluationStrategyType.ALL_MUST_AGREE) && !isApproved) { - LOGGER.trace("Finishing the stage, because the stage eval strategy is 'allMustApprove' and the decision was 'reject'."); - stopTheStage = true; - } - } - - repositoryService.modifyObject(CaseType.class, ctx.getCaseOid(), caseModifications, result); - refreshContext(ctx, result); - - for (CompleteAction action : actions) { - CaseWorkItemType workItem = ctx.findWorkItemById(action.getWorkItem().getId()); - onWorkItemClosure(ctx, workItem, true, action.getCauseInformation(), result); - } - - refreshContext(ctx, result); - - boolean keepStageOpen; - if (stopTheStage) { - closeOtherWorkItems(ctx, actions.iterator().next().getCauseInformation(), now, result); - refreshContext(ctx, result); - keepStageOpen = false; - } else { - keepStageOpen = ctx.aCase.getWorkItem().stream().anyMatch(wi -> wi.getCloseTimestamp() == null); - } - LOGGER.trace("computed keepStageOpen = {}", keepStageOpen); - if (!keepStageOpen) { - onStageClose(ctx, result); - } - - if (LOGGER.isTraceEnabled()) { - logCtx(ctx, "After completing work items: " + actions, result); - } - LOGGER.trace("--- completeWorkItems EXIT"); - } - - private void logCtx(EngineInvocationContext ctx, String message, OperationResult result) - throws SchemaException, ObjectNotFoundException { - String rootOid = ctx.aCase.getParentRef() != null ? ctx.aCase.getParentRef().getOid() : ctx.aCase.getOid(); - CaseType rootCase = repositoryService.getObject(CaseType.class, rootOid, null, result).asObjectable(); - LOGGER.trace("###### [ {} ] ######", message); - LOGGER.trace("Root case:\n{}", modelHelper.dumpCase(rootCase)); - for (CaseType subcase : miscHelper.getSubcases(rootCase, result)) { - LOGGER.trace("Subcase:\n{}", modelHelper.dumpCase(subcase)); - } - LOGGER.trace("###### [ END OF {} ] ######", message); - } - - public void onStageClose(EngineInvocationContext ctx, OperationResult result) - throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { - LOGGER.trace("+++ onStageClose ENTER: ctx={}", ctx); - boolean stopTheProcess = closeTheStage(ctx, result); - if (stopTheProcess) { - recordProcessOutcome(ctx, SchemaConstants.MODEL_APPROVAL_OUTCOME_REJECT, result); - stopProcessInstance(ctx, result); - } else { - runTheProcess(ctx, result); // temporary - } - LOGGER.trace("--- onStageClose EXIT: ctx={}", ctx); - } - - private void recordProcessOutcome(EngineInvocationContext ctx, String processOutcome, OperationResult result) - throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { - List> modifications = prismContext.deltaFor(CaseType.class) - .item(CaseType.F_OUTCOME) - .replace(processOutcome) - .asItemDeltas(); - modifyCase(ctx, modifications, result); - } - - private void modifyCase(EngineInvocationContext ctx, List> modifications, OperationResult result) - throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException { - repositoryService.modifyObject(CaseType.class, ctx.getCaseOid(), modifications, result); - ItemDeltaCollectionsUtil.applyTo(modifications, ctx.aCase.asPrismContainerValue()); - } - - - private boolean closeTheStage(EngineInvocationContext ctx, OperationResult result) { - LOGGER.trace("+++ closeTheStage ENTER: ctx={}", ctx); - ApprovalStageDefinitionType stageDef = WfContextUtil.getCurrentStageDefinition(ctx.aCase); -// List stageEvents = WfContextUtil.getEventsForCurrentStage(ctx.wfContext, StageCompletionEventType.class); - boolean approved; - StageComputeHelper.ComputationResult preStageComputationResult = ctx.getPreStageComputationResult(); - if (preStageComputationResult != null) { - ApprovalLevelOutcomeType outcome = preStageComputationResult.getPredeterminedOutcome(); - switch (outcome) { - case APPROVE: - case SKIP: - approved = true; - break; - case REJECT: - approved = false; - break; - default: - throw new IllegalStateException("Unknown outcome: " + outcome); // TODO less draconian handling - } - // TODO stage closure event - } else { - LOGGER.trace("****************************************** Summarizing decisions in stage {} (stage evaluation strategy = {}): ", - stageDef.getName(), stageDef.getEvaluationStrategy()); - - // TODO let's take the work items themselves - List itemEvents = WfContextUtil.getEventsForCurrentStage(ctx.aCase, WorkItemCompletionEventType.class); - - boolean allApproved = true; - for (WorkItemCompletionEventType event : itemEvents) { - LOGGER.trace(" - {}", event); - allApproved &= ApprovalUtils.isApproved(event.getOutput()); - } - approved = allApproved; - if (stageDef.getEvaluationStrategy() == LevelEvaluationStrategyType.FIRST_DECIDES) { - Set outcomes = itemEvents.stream() - .map(e -> e.getOutput().getOutcome()) - .collect(Collectors.toSet()); - if (outcomes.size() > 1) { - LOGGER.warn("Ambiguous outcome with firstDecides strategy in {}: {} response(s), providing outcomes of {}", - WfContextUtil.getBriefDiagInfo(ctx.aCase), itemEvents.size(), outcomes); - itemEvents.sort(Comparator.nullsLast(Comparator.comparing(event -> XmlTypeConverter.toMillis(event.getTimestamp())))); - WorkItemCompletionEventType first = itemEvents.get(0); - approved = ApprovalUtils.isApproved(first.getOutput()); - LOGGER.warn("Possible race condition, so taking the first one: {} ({})", approved, first); + } catch (PreconditionViolationException e) { + boolean repeat = attempt < MAX_ATTEMPTS; + String action = repeat ? "retried" : "aborted"; + LOGGER.info("Approval commit conflict detected; operation will be {} (this was attempt {} of {})", + action, attempt, MAX_ATTEMPTS); + if (repeat) { + attempt++; + } else { + throw new SystemException("Couldn't execute " + request.getClass() + " in " + MAX_ATTEMPTS + " attempts", e); } } } - - triggerHelper.removeAllStageTriggersForWorkItem(ctx.aCase, result); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Closing the stage for approval process instance {} (case oid {}), stage {}: result of this stage: {}", - ctx.getProcessInstanceName(), - ctx.getCaseOid(), WfContextUtil.getStageDiagName(stageDef), approved); - } - - LOGGER.trace("--- closeTheStage EXIT: ctx={}, approved={}", ctx, approved); - return !approved; - } - - public MidPointPrincipal getMidPointPrincipal() { - MidPointPrincipal user; - try { - user = SecurityUtil.getPrincipal(); - } catch (SecurityViolationException e) { - throw new SystemException("Couldn't get midPoint principal: " + e.getMessage(), e); - } - return user; } - private EngineInvocationContext createInvocationContext(WorkItemId workItemId, OperationResult result) + private EngineInvocationContext createInvocationContext(String caseOid, Task opTask, OperationResult result) throws SchemaException, ObjectNotFoundException { - PrismObject caseObject = repositoryService.getObject(CaseType.class, workItemId.caseOid, null, result); - // temporary (we should get the task from the upper layers) - Task opTask = taskManager.createTaskInstance(); - MidPointPrincipal user = getMidPointPrincipal(); - if (user != null) { - opTask.setOwner(user.getUser().asPrismObject()); - } - return new EngineInvocationContext(caseObject.asObjectable(), opTask); + PrismObject caseObject = repositoryService.getObject(CaseType.class, caseOid, null, result); + return new EngineInvocationContext(caseObject.asObjectable(), opTask, this, getMidPointPrincipal()); } - public void closeOtherWorkItems(EngineInvocationContext ctx, WorkItemEventCauseInformationType causeInformation, XMLGregorianCalendar now, - OperationResult result) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException { - WorkItemEventCauseTypeType causeType = causeInformation != null ? causeInformation.getType() : null; - LOGGER.trace("+++ closeOtherWorkItems ENTER: ctx={}, cause type={}", ctx, causeType); - for (CaseWorkItemType workItem : ctx.aCase.getWorkItem()) { - if (workItem.getCloseTimestamp() == null) { - closeWorkItem(ctx, workItem, now, result); - onWorkItemClosure(ctx, workItem, false, causeInformation, result); - } - } - LOGGER.trace("--- closeOtherWorkItems EXIT: ctx={}", ctx); - } - - private void onWorkItemClosure(EngineInvocationContext ctx, CaseWorkItemType workItem, boolean realClosure, - WorkItemEventCauseInformationType causeInformation, OperationResult result) { - // this might be cancellation because of: - // (1) user completion of this task - // (2) timed completion of this task - // (3) user completion of another task - // (4) timed completion of another task - // (5) process stop/deletion - // - // Actually, when the source is (4) timed completion of another task, it is quite probable that this task - // would be closed for the same reason. For a user it would be misleading if we would simply view this task - // as 'cancelled', while, in fact, it is e.g. approved/rejected because of a timed action. - - LOGGER.trace("+++ onWorkItemClosure ENTER: workItem={}, ctx={}, realClosure={}", workItem, ctx, realClosure); - WorkItemOperationKindType operationKind = realClosure ? WorkItemOperationKindType.COMPLETE : WorkItemOperationKindType.CANCEL; - + private MidPointPrincipal getMidPointPrincipal() { MidPointPrincipal user; try { user = SecurityUtil.getPrincipal(); } catch (SecurityViolationException e) { - throw new SystemException("Couldn't determine current user: " + e.getMessage(), e); - } - - ObjectReferenceType userRef = user != null ? user.toObjectReference() : workItem.getPerformerRef(); // partial fallback - - // We don't pass userRef (initiator) to the audit method. It does need the whole object (not only the reference), - // so it fetches it directly from the security enforcer (logged-in user). This could change in the future. - AuditEventRecord auditEventRecord = primaryChangeProcessor.prepareWorkItemDeletedAuditRecord(workItem, causeInformation, ctx.aCase, result); - auditService.audit(auditEventRecord, ctx.opTask); - try { - List assigneesAndDeputies = miscHelper.getAssigneesAndDeputies(workItem, ctx.opTask, result); - WorkItemAllocationChangeOperationInfo operationInfo = - new WorkItemAllocationChangeOperationInfo(operationKind, assigneesAndDeputies, null); - WorkItemOperationSourceInfo sourceInfo = new WorkItemOperationSourceInfo(userRef, causeInformation, null); - if (workItem.getAssigneeRef().isEmpty()) { - notificationHelper.notifyWorkItemDeleted(null, workItem, operationInfo, sourceInfo, ctx.aCase, ctx.opTask, result); - } else { - for (ObjectReferenceType assigneeOrDeputy : assigneesAndDeputies) { - notificationHelper.notifyWorkItemDeleted(assigneeOrDeputy, workItem, operationInfo, sourceInfo, ctx.aCase, ctx.opTask, result); - } - } - notificationHelper.notifyWorkItemAllocationChangeCurrentActors(workItem, operationInfo, sourceInfo, null, ctx.aCase, ctx.opTask, result); - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't audit work item complete event", e); - } - - WorkItemId workItemId = WorkItemId.create(ctx.getCaseOid(), workItem.getId()); - - AbstractWorkItemOutputType output = workItem.getOutput(); - if (realClosure || output != null) { - WorkItemCompletionEventType event = new WorkItemCompletionEventType(prismContext); - fillInWorkItemEvent(event, user, workItemId, workItem, prismContext); - event.setCause(causeInformation); - event.setOutput(output); - ObjectDeltaType additionalDelta = output instanceof WorkItemResultType && ((WorkItemResultType) output).getAdditionalDeltas() != null ? - ((WorkItemResultType) output).getAdditionalDeltas().getFocusPrimaryDelta() : null; - recordEventInCase(event, additionalDelta, ctx.getCaseOid(), result); - } - - triggerHelper.removeTriggersForWorkItem(ctx.aCase, workItemId, result); - - LOGGER.trace("--- onWorkItemClosure EXIT: workItem={}, ctx={}, realClosure={}", workItem, ctx, realClosure); - } - - public void deleteCase(String caseOid, OperationResult parentResult) { - throw new UnsupportedOperationException("Not implemented yet"); - } - - public void closeCase(String caseOid, Task task, OperationResult result) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - // TODO close case + work items + ... - throw new UnsupportedOperationException("Not implemented yet"); - } - - public void closeCaseInternal(CaseType aCase, Task task, OperationResult result) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - LOGGER.debug("Marking case {} as closed", aCase); - repositoryService.modifyObject(CaseType.class, aCase.getOid(), - prepareCaseClosureModifications(clock.currentTimeXMLGregorianCalendar()), result); - } - - /** - * We need to check - * 1) if there are any executable cases that depend on this one - * 2) if we can close the parent (root) - */ - public void checkDependentCases(String rootOid, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException, PreconditionViolationException { - CaseType aCase = repositoryService.getObject(CaseType.class, rootOid, null, result).asObjectable(); - if (CaseTypeUtil.isClosed(aCase)) { - return; - } - List subcases = miscHelper.getSubcases(rootOid, result); - List openOids = subcases.stream() - .filter(c -> !CaseTypeUtil.isClosed(c)) - .map(ObjectType::getOid) - .collect(Collectors.toList()); - LOGGER.debug("open cases OIDs: {}", openOids); - if (openOids.isEmpty()) { - closeCaseInternal(aCase, task, result); - } else { - ObjectQuery query = prismContext.queryFor(TaskType.class) - .item(TaskType.F_OBJECT_REF).ref(openOids.toArray(new String[0])) - .and().item(TaskType.F_EXECUTION_STATUS).eq(TaskExecutionStatusType.WAITING) - .build(); - SearchResultList> waitingTasks = repositoryService - .searchObjects(TaskType.class, query, null, result); - LOGGER.debug("Waiting tasks: {}", waitingTasks); - for (PrismObject waitingTask : waitingTasks) { - String waitingCaseOid = waitingTask.asObjectable().getObjectRef().getOid(); - assert waitingCaseOid != null; - List waitingCaseList = subcases.stream().filter(c -> waitingCaseOid.equals(c.getOid())) - .collect(Collectors.toList()); - assert waitingCaseList.size() == 1; - Set prerequisiteOids = waitingCaseList.get(0).getPrerequisiteRef().stream() - .map(ObjectReferenceType::getOid) - .collect(Collectors.toSet()); - Collection openPrerequisites = CollectionUtils.intersection(prerequisiteOids, openOids); - LOGGER.trace("prerequisite OIDs = {}; intersection with open OIDs = {}", prerequisiteOids, openPrerequisites); - if (openPrerequisites.isEmpty()) { - LOGGER.trace("All prerequisites are fulfilled, going to release the task {}", waitingTask); - taskManager.unpauseTask(taskManager.createTaskInstance(waitingTask, result), result); - } else { - LOGGER.trace("...task is not released and continues waiting for those cases"); - } - } - } - } - - public SearchResultList getWorkItemsForCase(String caseOid, - Collection> options, OperationResult result) throws SchemaException { - return repositoryService.searchContainers(CaseWorkItemType.class, - prismContext.queryFor(CaseWorkItemType.class).ownerId(caseOid).build(), - options, result); - - } - - public WfContextType getWorkflowContext(CaseWorkItemType caseWorkItem, OperationResult result) - throws SchemaException, ObjectNotFoundException { - CaseType wfCase = CaseWorkItemUtil.getCaseRequired(caseWorkItem); - if (wfCase.getTaskRef() == null) { - throw new IllegalStateException("No taskRef for case " + wfCase); - } - PrismObject taskObject = repositoryService.getObject(TaskType.class, wfCase.getTaskRef().getOid(), null, result); - return taskObject.asObjectable().getWorkflowContext(); - } - - public Integer countWorkItems(ObjectQuery query, OperationResult result) { - return repositoryService.countContainers(CaseWorkItemType.class, query, null, result); - } - - public SearchResultList searchWorkItems(ObjectQuery query, Collection> options, - OperationResult result) throws SchemaException { - return repositoryService.searchContainers(CaseWorkItemType.class, query, options, result); - } - - public void claim(WorkItemId workItemId, MidPointPrincipal principal, Task task, OperationResult result) { - throw new UnsupportedOperationException("not implemented yet"); - } - - public void unclaim(WorkItemId workItemId, MidPointPrincipal principal, - Task task, OperationResult result) { - throw new UnsupportedOperationException("not implemented yet"); - } - - public void createWorkItems(EngineInvocationContext ctx, List workItems, OperationResult result) - throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException { - LOGGER.trace("+++ createWorkItems ENTER: ctx={}, workItems={}", ctx, workItems); - List> modifications = prismContext.deltaFor(CaseType.class) - .item(CaseType.F_WORK_ITEM).addRealValues(workItems) - .asItemDeltas(); - repositoryService.modifyObject(CaseType.class, ctx.getCaseOid(), modifications, result); - refreshContext(ctx, result); - replaceWorkItemsWithRepoVersions(ctx.aCase, workItems); - onWorkItemsCreation(ctx, workItems, result); - LOGGER.trace("--- createWorkItems EXIT: ctx={}", ctx, workItems); - } - - private void replaceWorkItemsWithRepoVersions(CaseType wfCase, List workItems) { - List newItems = new ArrayList<>(); - item: for (CaseWorkItemType workItem : workItems) { - for (CaseWorkItemType i : wfCase.getWorkItem()) { - if (i.equals(workItem)) { - newItems.add(i); - continue item; - } - } - throw new IllegalStateException("Work item " + workItem + " was not found among case work items in " + wfCase); - } - workItems.clear(); - workItems.addAll(newItems); - } - - private void onWorkItemsCreation(EngineInvocationContext ctx, List workItems, OperationResult result) - throws SchemaException { - LOGGER.trace("+++ onWorkItemsCreation ENTER: ctx={}, workItems={}", ctx, workItems); - for (CaseWorkItemType workItem : workItems) { - AuditEventRecord auditEventRecord = primaryChangeProcessor.prepareWorkItemCreatedAuditRecord(workItem, ctx.aCase, result); - auditService.audit(auditEventRecord, ctx.opTask); - } - for (CaseWorkItemType workItem : workItems) { - try { - List assigneesAndDeputies = miscHelper.getAssigneesAndDeputies(workItem, ctx.opTask, result); - for (ObjectReferenceType assigneesOrDeputy : assigneesAndDeputies) { - notificationHelper.notifyWorkItemCreated(assigneesOrDeputy, workItem, ctx.aCase, ctx.opTask, result); // we assume originalAssigneeRef == assigneeRef in this case - } - WorkItemAllocationChangeOperationInfo operationInfo = - new WorkItemAllocationChangeOperationInfo(null, Collections.emptyList(), assigneesAndDeputies); - notificationHelper.notifyWorkItemAllocationChangeNewActors(workItem, operationInfo, null, ctx.aCase, ctx.opTask, result); - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't send notification about work item create event", e); - } - } - LOGGER.trace("--- onWorkItemsCreation EXIT: ctx={}, workItems={}", ctx, workItems); - } - - public WorkItemId createWorkItemId(EngineInvocationContext ctx, CaseWorkItemType workItem) { - return WorkItemId.create(ctx.getCaseOid(), workItem.getId()); - } - - public void executeDelegation(WorkItemId workItemId, List delegates, WorkItemDelegationMethodType method, - WorkItemEscalationLevelType targetEscalationInfo, Duration newDuration, WorkItemEventCauseInformationType causeInformation, - OperationResult result, MidPointPrincipal principal, ObjectReferenceType initiator, Task opTask, - CaseWorkItemType workItem) throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { - - EngineInvocationContext ctx = createInvocationContext(workItemId, result); - - List assigneesBefore = CloneUtil.cloneCollectionMembers(workItem.getAssigneeRef()); - List assigneesAndDeputiesBefore = miscHelper.getAssigneesAndDeputies(workItem, opTask, result); - - WorkItemOperationKindType operationKind = targetEscalationInfo != null ? ESCALATE : DELEGATE; - - WorkItemAllocationChangeOperationInfo operationInfoBefore = - new WorkItemAllocationChangeOperationInfo(operationKind, assigneesAndDeputiesBefore, null); - WorkItemOperationSourceInfo sourceInfo = new WorkItemOperationSourceInfo(initiator, causeInformation, null); - notificationHelper.notifyWorkItemAllocationChangeCurrentActors(workItem, operationInfoBefore, sourceInfo, null, ctx.aCase, - ctx.opTask, result); - - if (method == null) { - method = WorkItemDelegationMethodType.REPLACE_ASSIGNEES; - } - - List newAssignees = new ArrayList<>(); - List delegatedTo = new ArrayList<>(); - WfContextUtil.computeAssignees(newAssignees, delegatedTo, delegates, method, workItem.getAssigneeRef()); - - List> workItemDeltas = prismContext.deltaFor(CaseType.class) - .item(CaseType.F_WORK_ITEM, workItemId.id, CaseWorkItemType.F_ASSIGNEE_REF).replaceRealValues(newAssignees) - .asItemDeltas(); - if (newDuration != null) { - XMLGregorianCalendar newDeadline; - if (workItem.getDeadline() != null) { - newDeadline = (XMLGregorianCalendar) workItem.getDeadline().clone(); - } else { - newDeadline = XmlTypeConverter.createXMLGregorianCalendar(new Date()); - } - newDeadline.add(newDuration); - workItemDeltas.add( - prismContext.deltaFor(CaseType.class) - .item(CaseType.F_WORK_ITEM, workItemId.id, CaseWorkItemType.F_DEADLINE).replace(newDeadline) - .asItemDelta()); - workItem.setDeadline(newDeadline); - } - - int escalationLevel = WfContextUtil.getEscalationLevelNumber(workItem); - WorkItemEscalationLevelType newEscalationInfo; - if (targetEscalationInfo != null) { - newEscalationInfo = targetEscalationInfo.clone(); - newEscalationInfo.setNumber(++escalationLevel); - } else { - newEscalationInfo = null; - } - - WorkItemDelegationEventType event = WfContextUtil.createDelegationEvent(newEscalationInfo, assigneesBefore, delegatedTo, method, causeInformation, prismContext); - if (newEscalationInfo != null) { - workItemDeltas.add( - prismContext.deltaFor(CaseType.class) - .item(CaseType.F_WORK_ITEM, workItemId.id, CaseWorkItemType.F_ESCALATION_LEVEL).replace(newEscalationInfo) - .asItemDelta()); - workItem.setEscalationLevel(newEscalationInfo); - } - - repositoryService.modifyObject(CaseType.class, ctx.getCaseOid(), workItemDeltas, result); - - fillInWorkItemEvent(event, principal, workItemId, workItem, prismContext); - recordEventInCase(event, null, ctx.getCaseOid(), result); - - ApprovalStageDefinitionType level = WfContextUtil.getCurrentStageDefinition(ctx.aCase); - triggerHelper.createTriggersForTimedActions(workItemId, escalationLevel, - XmlTypeConverter.toDate(workItem.getCreateTimestamp()), - XmlTypeConverter.toDate(workItem.getDeadline()), ctx.aCase, level.getTimedActions(), result); - - CaseWorkItemType workItemAfter = getWorkItem(workItemId, result); - CaseType aCaseAfter = repositoryService.getObject(CaseType.class, ctx.getCaseOid(), null, result).asObjectable(); - List assigneesAndDeputiesAfter = miscHelper.getAssigneesAndDeputies(workItemAfter, opTask, result); - WorkItemAllocationChangeOperationInfo operationInfoAfter = - new WorkItemAllocationChangeOperationInfo(operationKind, assigneesAndDeputiesBefore, assigneesAndDeputiesAfter); - notificationHelper.notifyWorkItemAllocationChangeNewActors(workItemAfter, operationInfoAfter, sourceInfo, aCaseAfter, ctx.opTask, result); - } - - private static void fillInWorkItemEvent(WorkItemEventType event, MidPointPrincipal currentUser, WorkItemId workItemId, - CaseWorkItemType workItem, PrismContext prismContext) { - if (currentUser != null) { - event.setInitiatorRef(ObjectTypeUtil.createObjectRef(currentUser.getUser(), prismContext)); - event.setAttorneyRef(ObjectTypeUtil.createObjectRef(currentUser.getAttorney(), prismContext)); - } - event.setTimestamp(XmlTypeConverter.createXMLGregorianCalendar(new Date())); - event.setExternalWorkItemId(workItemId.asString()); - event.setOriginalAssigneeRef(workItem.getOriginalAssigneeRef()); - event.setStageNumber(workItem.getStageNumber()); - event.setEscalationLevel(workItem.getEscalationLevel()); - } - - public void advanceProcessInstance(EngineInvocationContext ctx, OperationResult result) - throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException { - int stageCount = getStageCount(ctx); - int currentStage = getCurrentStage(ctx); - if (currentStage == stageCount) { - ctx.setDone(true); - return; - } - - int stageToBe = currentStage + 1; - List> stageModifications = prismContext.deltaFor(CaseType.class) - .item(CaseType.F_STAGE_NUMBER).replace(stageToBe) - .asItemDeltas(); - modifyCase(ctx, stageModifications, result); - - ApprovalStageDefinitionType stageDef = WfContextUtil.getCurrentStageDefinition(ctx.aCase); - - StageComputeHelper.ComputationResult computationResult = - stageComputeHelper.computeStageApprovers(stageDef, - () -> stageComputeHelper.getDefaultVariables(ctx.aCase, ctx.getWfContext(), ctx.opTask.getChannel(), result), ctx.opTask, result); - - ctx.setPreStageComputationResult(computationResult); - ApprovalLevelOutcomeType predeterminedOutcome = computationResult.getPredeterminedOutcome(); - Set approverRefs = computationResult.getApproverRefs(); - - if (LOGGER.isDebugEnabled()) { - if (computationResult.noApproversFound()) { - LOGGER.debug("No approvers at the stage '{}' for process {} (case oid {}) - outcome-if-no-approvers is {}", stageDef.getName(), - ctx.getProcessInstanceName(), ctx.aCase.getOid(), stageDef.getOutcomeIfNoApprovers()); - } - LOGGER.debug("Approval process instance {} (case oid {}), stage {}: predetermined outcome: {}, approvers: {}", - ctx.getProcessInstanceName(), ctx.aCase.getOid(), - WfContextUtil.getStageDiagName(stageDef), predeterminedOutcome, approverRefs); - } - - if (predeterminedOutcome != null) { - onStageClose(ctx, result); - return; - } - - List workItems = new ArrayList<>(); - XMLGregorianCalendar createTimestamp = clock.currentTimeXMLGregorianCalendar(); - XMLGregorianCalendar deadline; - if (stageDef.getDuration() != null) { - deadline = (XMLGregorianCalendar) createTimestamp.clone(); - deadline.add(stageDef.getDuration()); - } else { - deadline = null; - } - for (ObjectReferenceType approverRef : approverRefs) { - CaseWorkItemType workItem = new CaseWorkItemType(prismContext) - .name(ctx.getProcessInstanceName()) - .stageNumber(stageToBe) - .createTimestamp(createTimestamp) - .deadline(deadline); - if (approverRef.getType() == null) { - approverRef.setType(UserType.COMPLEX_TYPE); - } - if (QNameUtil.match(UserType.COMPLEX_TYPE, approverRef.getType())) { - workItem.setOriginalAssigneeRef(approverRef.clone()); - workItem.getAssigneeRef().add(approverRef.clone()); - } else if (QNameUtil.match(RoleType.COMPLEX_TYPE, approverRef.getType()) || - QNameUtil.match(OrgType.COMPLEX_TYPE, approverRef.getType()) || - QNameUtil.match(ServiceType.COMPLEX_TYPE, approverRef.getType())) { - workItem.getCandidateRef().add(approverRef.clone()); - // todo what about originalAssigneeRef? - } else { - throw new IllegalStateException("Unsupported type of the approver: " + approverRef.getType() + " in " + approverRef); - } - if (stageDef.getAdditionalInformation() != null) { - try { - ExpressionVariables variables = stageComputeHelper.getDefaultVariables(ctx.aCase, ctx.getWfContext(), ctx.getChannel(), result); - List additionalInformation = expressionEvaluationHelper.evaluateExpression(stageDef.getAdditionalInformation(), variables, - "additional information expression", InformationType.class, InformationType.COMPLEX_TYPE, - true, this::createInformationType, ctx.opTask, result); - workItem.getAdditionalInformation().addAll(additionalInformation); - } catch (Throwable t) { - throw new SystemException("Couldn't evaluate additional information expression in " + ctx, t); - } - } - workItems.add(workItem); - } - createWorkItems(ctx, workItems, result); - for (CaseWorkItemType workItem : workItems) { - triggerHelper.createTriggersForTimedActions(createWorkItemId(ctx, workItem), 0, - XmlTypeConverter.toDate(workItem.getCreateTimestamp()), - XmlTypeConverter.toDate(workItem.getDeadline()), ctx.aCase, stageDef.getTimedActions(), result); - } - } - - private Integer getCurrentStage(EngineInvocationContext ctx) { - int rv = defaultIfNull(ctx.aCase.getStageNumber(), 0); - checkCurrentStage(ctx, rv); - return rv; - } - - private void checkCurrentStage(EngineInvocationContext ctx, int rv) { - if (rv < 0 || rv > getStageCount(ctx)) { - LOGGER.error("Current stage is below 0 or beyond the number of stages: {}\n{}", rv, ctx.debugDump()); - throw new IllegalStateException("Current stage is below 0 or beyond the number of stages: " + rv); - } - } - - private int getStageCount(EngineInvocationContext ctx) { - Integer stageCount = WfContextUtil.getStageCount(ctx.getWfContext()); - if (stageCount == null) { - LOGGER.error("Couldn't determine stage count from the workflow context\n{}", ctx.debugDump()); - throw new IllegalStateException("Couldn't determine stage count from the workflow context"); + throw new SystemException("Couldn't get midPoint principal: " + e.getMessage(), e); } - return stageCount; - } - - private InformationType createInformationType(Object o) { - if (o == null || o instanceof InformationType) { - return (InformationType) o; - } else if (o instanceof String) { - return MidpointParsingMigrator.stringToInformationType((String) o); - } else { - throw new IllegalArgumentException("Object cannot be converted into InformationType: " + o); + if (user == null) { + throw new SystemException("No principal"); } + return user; } - // private void completeStage(EngineInvocationContext ctx, ApprovalLevelOutcomeType outcome, - // AutomatedCompletionReasonType automatedCompletionReason, OperationResult result) { - // recordAutoCompletionDecision(ctx.wfTask.getOid(), predeterminedOutcome, automatedCompletionReason, stageToBe, result); - // } - - private void recordAutoCompletionDecision(String taskOid, ApprovalLevelOutcomeType outcome, - AutomatedCompletionReasonType reason, int stageNumber, OperationResult opResult) { - StageCompletionEventType event = new StageCompletionEventType(prismContext); - event.setTimestamp(clock.currentTimeXMLGregorianCalendar()); - event.setStageNumber(stageNumber); - event.setAutomatedDecisionReason(reason); - event.setOutcome(ApprovalUtils.toUri(outcome)); - recordEventInCase(event, null, taskOid, opResult); - } - - @NotNull - public CaseWorkItemType getWorkItem(WorkItemId id, OperationResult result) - throws SchemaException, ObjectNotFoundException { - PrismObject caseObject = repositoryService.getObject(CaseType.class, id.caseOid, null, result); - //noinspection unchecked - PrismContainerValue pcv = (PrismContainerValue) - caseObject.find(ItemPath.create(CaseType.F_WORK_ITEM, id.id)); - if (pcv == null) { - throw new ObjectNotFoundException("No work item " + id.id + " in " + caseObject); - } - return pcv.asContainerable(); - } - - // additional delta is a bit hack ... TODO refactor (but without splitting the modify operation!) - private void recordEventInCase(CaseEventType event, ObjectDeltaType additionalDelta, String caseOid, OperationResult result) { - try { - S_ItemEntry deltaBuilder = prismContext.deltaFor(CaseType.class) - .item(F_EVENT).add(event); - - if (additionalDelta != null) { - PrismObject aCase = repositoryService.getObject(CaseType.class, caseOid, null, result); - WfPrimaryChangeProcessorStateType state = WfContextUtil - .getPrimaryChangeProcessorState(aCase.asObjectable().getWorkflowContext()); - ObjectTreeDeltasType updatedDelta = ObjectTreeDeltas.mergeDeltas(state.getDeltasToProcess(), - additionalDelta, prismContext); - ItemPath deltasToProcessPath = ItemPath.create(F_WORKFLOW_CONTEXT, F_PROCESSOR_SPECIFIC_STATE, - WfPrimaryChangeProcessorStateType.F_DELTAS_TO_PROCESS); // assuming it already exists! - ItemDefinition deltasToProcessDefinition = prismContext.getSchemaRegistry() - .findContainerDefinitionByCompileTimeClass(WfPrimaryChangeProcessorStateType.class) - .findItemDefinition(WfPrimaryChangeProcessorStateType.F_DELTAS_TO_PROCESS); - deltaBuilder = deltaBuilder.item(deltasToProcessPath, deltasToProcessDefinition) - .replace(updatedDelta); - } - repositoryService.modifyObject(CaseType.class, caseOid, deltaBuilder.asItemDeltas(), result); - } catch (ObjectNotFoundException | SchemaException | ObjectAlreadyExistsException e) { - throw new SystemException("Couldn't record decision to the case " + caseOid + ": " + e.getMessage(), e); + private void executeActionChain(Action action, OperationResult result) + throws SchemaException, SecurityViolationException, ObjectNotFoundException, CommunicationException, + ConfigurationException, ExpressionEvaluationException { + while (action != null) { + action = action.execute(result); } } - } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/Action.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/Action.java new file mode 100644 index 00000000000..6c7a0ffa9b8 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/Action.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.wf.impl.engine.WorkflowEngine; +import org.jetbrains.annotations.NotNull; + +/** + * + */ +public abstract class Action { + + @NotNull public final WorkflowEngine engine; + @NotNull public final EngineInvocationContext ctx; + + Action(@NotNull EngineInvocationContext ctx) { + this.ctx = ctx; + this.engine = ctx.getEngine(); + } + + public abstract Action execute(OperationResult result) + throws SchemaException, SecurityViolationException, ObjectNotFoundException, CommunicationException, + ConfigurationException, ExpressionEvaluationException; + + void traceEnter(Trace logger) { + logger.trace("+++ ENTER: ctx={}", ctx); + } + + void traceExit(Trace logger, Action next) { + logger.trace("+++ EXIT: next={}, ctx={}", next, ctx); + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ActionFactory.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ActionFactory.java new file mode 100644 index 00000000000..7899d0b2311 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ActionFactory.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.wf.api.request.*; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +@Component +public class ActionFactory { + + private Map, Class> requestToActionMap = new HashMap<>(); + + { + requestToActionMap.put(CompleteWorkItemsRequest.class, CompleteWorkItemsAction.class); + requestToActionMap.put(DelegateWorkItemsRequest.class, DelegateWorkItemsAction.class); + requestToActionMap.put(ClaimWorkItemsRequest.class, ClaimWorkItemsAction.class); + requestToActionMap.put(ReleaseWorkItemsRequest.class, ReleaseWorkItemsAction.class); + requestToActionMap.put(CancelCaseRequest.class, CancelCaseAction.class); + requestToActionMap.put(OpenCaseRequest.class, OpenCaseAction.class); + } + + public Action create(Request request, EngineInvocationContext ctx) { + Class actionClass = requestToActionMap.get(request.getClass()); + if (actionClass != null) { + try { + return actionClass + .getConstructor(EngineInvocationContext.class, request.getClass()) + .newInstance(ctx, request); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new SystemException("Couldn't invoke constructor on action class " + actionClass.getName() + ": " + e.getMessage(), e); + } + } else { + throw new IllegalArgumentException("No action for request: " + request); + } + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CancelCaseAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CancelCaseAction.java new file mode 100644 index 00000000000..c968cf38fb9 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CancelCaseAction.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.api.request.CancelCaseRequest; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; + +/** + * + */ +public class CancelCaseAction extends RequestedAction { + + private static final Trace LOGGER = TraceManager.getTrace(CancelCaseAction.class); + + public CancelCaseAction(EngineInvocationContext ctx, CancelCaseRequest request) { + super(ctx, request); + } + + @Override + public Action execute(OperationResult result) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + traceEnter(LOGGER); + + engine.securityEnforcer.authorize(ModelAuthorizationAction.STOP_APPROVAL_PROCESS_INSTANCE.getUrl(), null, + AuthorizationParameters.Builder.buildObject(ctx.getCurrentCase().asPrismObject()), null, ctx.getTask(), result); + + // TODO consider putting some events and notifications here + + CloseCaseAction next = new CloseCaseAction(ctx, null); + traceExit(LOGGER, next); + return next; + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ClaimWorkItemsAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ClaimWorkItemsAction.java new file mode 100644 index 00000000000..b4fc329627b --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ClaimWorkItemsAction.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.result.OperationResultStatus; +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.request.ClaimWorkItemsRequest; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType; +import org.jetbrains.annotations.NotNull; + +/** + * + */ +public class ClaimWorkItemsAction extends RequestedAction { + + private static final Trace LOGGER = TraceManager.getTrace(ClaimWorkItemsAction.class); + + public ClaimWorkItemsAction(@NotNull EngineInvocationContext ctx, @NotNull ClaimWorkItemsRequest request) { + super(ctx, request); + } + + @Override + public Action execute(OperationResult result) throws SecurityViolationException { + traceEnter(LOGGER); + + for (ClaimWorkItemsRequest.SingleClaim claim : request.getClaims()) { + CaseWorkItemType workItem = ctx.findWorkItemById(claim.getWorkItemId()); + if (workItem.getCloseTimestamp() != null) { + result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Work item has been already closed"); // todo better result handling + } else if (!workItem.getAssigneeRef().isEmpty()) { + String desc; + if (workItem.getAssigneeRef().size() == 1 && ctx.getPrincipal().getOid().equals(workItem.getAssigneeRef().get(0).getOid())) { + desc = "the current"; + } else { + desc = "another"; + } + throw new SystemException("The work item is already assigned to "+desc+" user"); + } else if (!engine.authorizationHelper.isAuthorizedToClaim(workItem)) { + throw new SecurityViolationException("You are not authorized to claim the selected work item."); + } else { + workItem.getAssigneeRef().add(ctx.getPrincipal().toObjectReference().clone()); + } + } + + traceExit(LOGGER, null); + return null; + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CloseCaseAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CloseCaseAction.java new file mode 100644 index 00000000000..94586d16ef4 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CloseCaseAction.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * + */ +public class CloseCaseAction extends InternalAction { + + private static final Trace LOGGER = TraceManager.getTrace(CloseCaseAction.class); + private final String outcome; + + CloseCaseAction(EngineInvocationContext ctx, String outcome) { + super(ctx); + this.outcome = outcome; + } + + @Override + public Action execute(OperationResult result) { + traceEnter(LOGGER); + + CaseType currentCase = ctx.getCurrentCase(); + + XMLGregorianCalendar now = engine.clock.currentTimeXMLGregorianCalendar(); + for (CaseWorkItemType wi : currentCase.getWorkItem()) { + if (wi.getCloseTimestamp() == null) { + wi.setCloseTimestamp(now); + } + } + + String state = currentCase.getState(); + if (state == null || SchemaConstants.CASE_STATE_CREATED.equals(state) || SchemaConstants.CASE_STATE_OPEN.equals(state)) { + currentCase.setOutcome(outcome); + currentCase.setState(SchemaConstants.CASE_STATE_CLOSING); + ctx.setWasClosed(true); + // audit and notification is done after the onProcessEnd is finished + } else { + LOGGER.debug("Case {} was already closed; its state is {}", currentCase, state); + result.recordWarning("Case was already closed"); + } + + traceExit(LOGGER, null); + return null; + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CloseStageAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CloseStageAction.java new file mode 100644 index 00000000000..8f9e737fb4a --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CloseStageAction.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.WfContextUtil; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.wf.impl.engine.WorkflowEngine; +import com.evolveum.midpoint.wf.impl.processes.common.StageComputeHelper.ComputationResult; +import com.evolveum.midpoint.wf.util.ApprovalUtils; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import java.util.*; + +/** + * + */ +public class CloseStageAction extends InternalAction { + + private static final Trace LOGGER = TraceManager.getTrace(CloseStageAction.class); + + private final ComputationResult preStageComputationResult; + + CloseStageAction(EngineInvocationContext ctx, ComputationResult preStageComputationResult) { + super(ctx); + this.preStageComputationResult = preStageComputationResult; + } + + @Override + public Action execute(OperationResult result) { + traceEnter(LOGGER); + + int currentStage = ctx.getCurrentStage(); + ApprovalStageDefinitionType stageDef = ctx.getCurrentStageDefinition(); + + boolean approved; + if (preStageComputationResult != null) { + ApprovalLevelOutcomeType outcome = preStageComputationResult.getPredeterminedOutcome(); + switch (outcome) { + case APPROVE: + case SKIP: + approved = true; + break; + case REJECT: + approved = false; + break; + default: + throw new IllegalStateException("Unknown outcome: " + outcome); // TODO less draconian handling + } + recordAutoCompletionDecision(preStageComputationResult, currentStage); + } else { + LOGGER.trace("****************************************** Summarizing decisions in stage {} (stage evaluation strategy = {}): ", + stageDef.getName(), stageDef.getEvaluationStrategy()); + + List workItems = ctx.getWorkItemsForStage(currentStage); + + boolean allApproved = true; + List answeredWorkItems = new ArrayList<>(); + Set outcomes = new HashSet<>(); + for (CaseWorkItemType workItem : workItems) { + LOGGER.trace(" - {}", workItem); + allApproved &= ApprovalUtils.isApproved(workItem.getOutput()); + if (workItem.getCloseTimestamp() != null && workItem.getPerformerRef() != null) { + answeredWorkItems.add(workItem); + outcomes.add(workItem.getOutput() != null ? workItem.getOutput().getOutcome() : null); + } + } + if (stageDef.getEvaluationStrategy() == LevelEvaluationStrategyType.FIRST_DECIDES) { + if (outcomes.size() > 1) { + LOGGER.warn("Ambiguous outcome with firstDecides strategy in {}: {} response(s), providing outcomes of {}", + WfContextUtil.getBriefDiagInfo(ctx.getCurrentCase()), answeredWorkItems.size(), outcomes); + answeredWorkItems.sort(Comparator.nullsLast(Comparator.comparing(item -> XmlTypeConverter.toMillis(item.getCloseTimestamp())))); + CaseWorkItemType first = answeredWorkItems.get(0); + approved = ApprovalUtils.isApproved(first.getOutput()); + LOGGER.warn("Possible race condition, so taking the first one: {} ({})", approved, first); + } else { + approved = ApprovalUtils.isApproved(outcomes.iterator().next()) && !outcomes.isEmpty(); + } + } else { + approved = allApproved && !answeredWorkItems.isEmpty(); + } + } + + engine.triggerHelper.removeAllStageTriggersForWorkItem(ctx.getCurrentCase()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Closing the stage for approval process instance {} (case oid {}), stage {}: result of this stage: {}", + ctx.getProcessInstanceName(), + ctx.getCaseOid(), WfContextUtil.getStageDiagName(stageDef), approved); + } + + Action next; + if (approved) { + if (currentStage < ctx.getNumberOfStages()) { + next = new OpenStageAction(ctx); + } else { + next = new CloseCaseAction(ctx, SchemaConstants.MODEL_APPROVAL_OUTCOME_APPROVE); + } + } else { + next = new CloseCaseAction(ctx, SchemaConstants.MODEL_APPROVAL_OUTCOME_REJECT); + } + + traceExit(LOGGER, next); + return next; + } + + private void recordAutoCompletionDecision(ComputationResult computationResult, int stageNumber) { + StageCompletionEventType event = new StageCompletionEventType(engine.prismContext); + event.setTimestamp(engine.clock.currentTimeXMLGregorianCalendar()); + event.setStageNumber(stageNumber); + event.setAutomatedDecisionReason(computationResult.getAutomatedCompletionReason()); + event.setOutcome(ApprovalUtils.toUri(computationResult.getPredeterminedOutcome())); + ctx.addEvent(event); + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CompleteWorkItemsAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CompleteWorkItemsAction.java new file mode 100644 index 00000000000..58118b28532 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CompleteWorkItemsAction.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.schema.DeltaConvertor; +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.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.api.request.CompleteWorkItemsRequest; +import com.evolveum.midpoint.wf.impl.access.AuthorizationHelper; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.wf.util.ApprovalUtils; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; +import org.jetbrains.annotations.NotNull; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * + */ +public class CompleteWorkItemsAction extends RequestedAction { + + private static final Trace LOGGER = TraceManager.getTrace(CompleteWorkItemsAction.class); + + public CompleteWorkItemsAction(EngineInvocationContext ctx, @NotNull CompleteWorkItemsRequest request) { + super(ctx, request); + } + + @Override + public Action execute(OperationResult result) + throws SchemaException, SecurityViolationException, ObjectNotFoundException, CommunicationException, + ConfigurationException, ExpressionEvaluationException { + traceEnter(LOGGER); + LOGGER.trace("Completions: {}", request.getCompletions()); + + ApprovalStageDefinitionType stageDef = ctx.getCurrentStageDefinition(); + LevelEvaluationStrategyType levelEvaluationStrategyType = stageDef.getEvaluationStrategy(); + + boolean closeOtherWorkItems = false; + + XMLGregorianCalendar now = engine.clock.currentTimeXMLGregorianCalendar(); + for (CompleteWorkItemsRequest.SingleCompletion completion : request.getCompletions()) { + CaseWorkItemType workItem = ctx.findWorkItemById(completion.getWorkItemId()); + + if (!engine.authorizationHelper.isAuthorized(workItem, AuthorizationHelper.RequestedOperation.COMPLETE, + ctx.getTask(), result)) { + throw new SecurityViolationException("You are not authorized to complete the work item."); + } + + if (workItem.getCloseTimestamp() != null) { + LOGGER.trace("Work item {} was already completed on {}", workItem.getId(), workItem.getCloseTimestamp()); + result.recordWarning("Work item " + workItem.getId() + " was already completed on " + + workItem.getCloseTimestamp()); + continue; + } + + String outcome = completion.getOutcome(); + + LOGGER.trace("+++ recordCompletionOfWorkItem ENTER: workItem={}, outcome={}", workItem, outcome); + LOGGER.trace("======================================== Recording individual decision of {}", ctx.getPrincipal()); + + @NotNull WorkItemResultType itemResult = new WorkItemResultType(engine.prismContext); + itemResult.setOutcome(outcome); + itemResult.setComment(completion.getComment()); + boolean isApproved = ApprovalUtils.isApproved(itemResult); + if (isApproved && completion.getAdditionalDelta() != null) { + ObjectDeltaType additionalDeltaBean = DeltaConvertor.toObjectDeltaType(completion.getAdditionalDelta()); + ObjectTreeDeltasType treeDeltas = new ObjectTreeDeltasType(); + treeDeltas.setFocusPrimaryDelta(additionalDeltaBean); + itemResult.setAdditionalDeltas(treeDeltas); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Recording decision for approval process instance {} (case oid {}), stage {}: decision: {}", + ctx.getProcessInstanceName(), ctx.getCaseOid(), + WfContextUtil.getStageDiagName(stageDef), itemResult.getOutcome()); + } + + ObjectReferenceType performerRef = ObjectTypeUtil.createObjectRef(ctx.getPrincipal().getUser(), engine.prismContext); + workItem.setOutput(itemResult); + workItem.setPerformerRef(performerRef); + workItem.setCloseTimestamp(now); + + if (levelEvaluationStrategyType == LevelEvaluationStrategyType.FIRST_DECIDES) { + LOGGER.trace("Finishing the stage, because the stage evaluation strategy is 'firstDecides'."); + closeOtherWorkItems = true; + } else if ((levelEvaluationStrategyType == null || levelEvaluationStrategyType == LevelEvaluationStrategyType.ALL_MUST_AGREE) && !isApproved) { + LOGGER.trace("Finishing the stage, because the stage eval strategy is 'allMustApprove' and the decision was 'reject'."); + closeOtherWorkItems = true; + } + + engine.workItemHelper.recordWorkItemClosure(ctx, workItem, true, request.getCauseInformation(), result); + } + + Action next; + if (closeOtherWorkItems) { + doCloseOtherWorkItems(ctx, request.getCauseInformation(), now, result); + next = new CloseStageAction(ctx, null); + } else if (!ctx.isAnyCurrentStageWorkItemOpen()) { + next = new CloseStageAction(ctx, null); + } else { + next = null; + } + + traceExit(LOGGER, next); + return next; + } + + private void doCloseOtherWorkItems(EngineInvocationContext ctx, WorkItemEventCauseInformationType causeInformation, + XMLGregorianCalendar now, OperationResult result) throws SchemaException { + WorkItemEventCauseTypeType causeType = causeInformation != null ? causeInformation.getType() : null; + LOGGER.trace("+++ closeOtherWorkItems ENTER: ctx={}, cause type={}", ctx, causeType); + for (CaseWorkItemType workItem : ctx.getCurrentCase().getWorkItem()) { + if (workItem.getCloseTimestamp() == null) { + workItem.setCloseTimestamp(now); + engine.workItemHelper.recordWorkItemClosure(ctx, workItem, false, causeInformation, result); + } + } + LOGGER.trace("--- closeOtherWorkItems EXIT: ctx={}", ctx); + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/DelegateWorkItemsAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/DelegateWorkItemsAction.java new file mode 100644 index 00000000000..879e3ee75de --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/DelegateWorkItemsAction.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.prism.util.CloneUtil; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +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.schema.util.WorkItemId; +import com.evolveum.midpoint.util.exception.*; +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.request.DelegateWorkItemsRequest; +import com.evolveum.midpoint.wf.impl.access.AuthorizationHelper; +import com.evolveum.midpoint.wf.impl.engine.helpers.DelayedNotification; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.wf.impl.engine.helpers.WorkItemHelper; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import javax.xml.datatype.XMLGregorianCalendar; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +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; + +/** + * + */ +public class DelegateWorkItemsAction extends RequestedAction { + + private static final Trace LOGGER = TraceManager.getTrace(DelegateWorkItemsAction.class); + + public DelegateWorkItemsAction(EngineInvocationContext ctx, DelegateWorkItemsRequest request) { + super(ctx, request); + } + + @Override + public Action execute(OperationResult result) + throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, + SecurityViolationException, ExpressionEvaluationException { + traceEnter(LOGGER); + for (DelegateWorkItemsRequest.SingleDelegation delegation : request.getDelegations()) { + executeDelegation(delegation, result); + } + traceExit(LOGGER, null); + return null; + } + + // TODO check work item state; + // check if there are any approvers etc + + private void executeDelegation(DelegateWorkItemsRequest.SingleDelegation delegation, + OperationResult result) + throws SchemaException, SecurityViolationException, ObjectNotFoundException, CommunicationException, + ConfigurationException, ExpressionEvaluationException { + CaseWorkItemType workItem = ctx.findWorkItemById(delegation.getWorkItemId()); + + if (!engine.authorizationHelper.isAuthorized(workItem, AuthorizationHelper.RequestedOperation.DELEGATE, ctx.getTask(), result)) { + throw new SecurityViolationException("You are not authorized to delegate this work item."); + } + + if (workItem.getCloseTimestamp() != null) { + LOGGER.debug("Work item {} in {} was already closed, ignoring the delegation request", workItem, ctx.getCurrentCase()); + result.recordWarning("Work item was already closed"); + return; + } + + List assigneesBefore = CloneUtil.cloneCollectionMembers(workItem.getAssigneeRef()); + List assigneesAndDeputiesBefore = engine.miscHelper.getAssigneesAndDeputies(workItem, + ctx.getTask(), result); + + WorkItemOperationKindType operationKind = delegation.getTargetEscalationInfo() != null ? ESCALATE : DELEGATE; + + WorkItemAllocationChangeOperationInfo operationInfoBefore = + new WorkItemAllocationChangeOperationInfo(operationKind, assigneesAndDeputiesBefore, null); + + WorkItemEventCauseInformationType causeInformation = request.getCauseInformation(); + ObjectReferenceType initiator = + causeInformation == null || causeInformation.getType() == WorkItemEventCauseTypeType.USER_ACTION ? + ObjectTypeUtil.createObjectRef(ctx.getPrincipal().getUser(), engine.prismContext) : null; + + WorkItemOperationSourceInfo sourceInfo = new WorkItemOperationSourceInfo(initiator, causeInformation, null); + ctx.prepareNotification(new DelayedNotification.AllocationChangeCurrent(ctx.getCurrentCase(), workItem, operationInfoBefore, sourceInfo, null)); + + List newAssignees = new ArrayList<>(); + List delegatedTo = new ArrayList<>(); + WfContextUtil.computeAssignees(newAssignees, delegatedTo, delegation.getDelegates(), delegation.getMethod(), workItem.getAssigneeRef()); + + workItem.getAssigneeRef().clear(); + workItem.getAssigneeRef().addAll(CloneUtil.cloneCollectionMembers(newAssignees)); + if (delegation.getNewDuration() != null) { + XMLGregorianCalendar newDeadline; + if (workItem.getDeadline() != null) { + newDeadline = (XMLGregorianCalendar) workItem.getDeadline().clone(); + } else { + newDeadline = XmlTypeConverter.createXMLGregorianCalendar(new Date()); + } + newDeadline.add(delegation.getNewDuration()); + workItem.setDeadline(newDeadline); + } + + int escalationLevel = WfContextUtil.getEscalationLevelNumber(workItem); + WorkItemEscalationLevelType newEscalationInfo; + if (delegation.getTargetEscalationInfo() != null) { + newEscalationInfo = delegation.getTargetEscalationInfo().clone(); + newEscalationInfo.setNumber(++escalationLevel); + } else { + newEscalationInfo = null; + } + + WorkItemDelegationEventType event = WfContextUtil.createDelegationEvent(newEscalationInfo, assigneesBefore, delegatedTo, + delegation.getMethod(), causeInformation, engine.prismContext); + if (newEscalationInfo != null) { + workItem.setEscalationLevel(newEscalationInfo); + } + + WorkItemId workItemId = ctx.createWorkItemId(workItem); + + WorkItemHelper.fillInWorkItemEvent(event, ctx.getPrincipal(), workItemId, workItem, engine.prismContext); + ctx.addEvent(event); + + ApprovalStageDefinitionType level = ctx.getCurrentStageDefinition(); + engine.triggerHelper.createTriggersForTimedActions(ctx.getCurrentCase(), workItem.getId(), escalationLevel, + XmlTypeConverter.toDate(workItem.getCreateTimestamp()), + XmlTypeConverter.toDate(workItem.getDeadline()), level.getTimedActions(), result); + + List assigneesAndDeputiesAfter = engine.miscHelper.getAssigneesAndDeputies(workItem, ctx.getTask(), result); + WorkItemAllocationChangeOperationInfo operationInfoAfter = + new WorkItemAllocationChangeOperationInfo(operationKind, assigneesAndDeputiesBefore, assigneesAndDeputiesAfter); + ctx.prepareNotification(new DelayedNotification.AllocationChangeNew(ctx.getCurrentCase(), workItem, operationInfoAfter, sourceInfo)); + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/InternalAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/InternalAction.java new file mode 100644 index 00000000000..b465e0092c6 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/InternalAction.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import org.jetbrains.annotations.NotNull; + +/** + * This is an action that is invoked internally i.e. without a request. + * (Currently it's used as a marker class.) + */ +public abstract class InternalAction extends Action { + + InternalAction(@NotNull EngineInvocationContext ctx) { + super(ctx); + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/OpenCaseAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/OpenCaseAction.java new file mode 100644 index 00000000000..957c9e83546 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/OpenCaseAction.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.api.request.OpenCaseRequest; +import com.evolveum.midpoint.wf.impl.engine.helpers.DelayedNotification; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; + +/** + * + */ +public class OpenCaseAction extends RequestedAction { + + private static final Trace LOGGER = TraceManager.getTrace(OpenCaseAction.class); + + public OpenCaseAction(EngineInvocationContext ctx, OpenCaseRequest request) { + super(ctx, request); + } + + @Override + public Action execute(OperationResult result) { + traceEnter(LOGGER); + + Action next; + String currentState = ctx.getCurrentCase().getState(); + if (currentState != null && !currentState.equals(SchemaConstants.CASE_STATE_CREATED)) { // todo URI comparison + LOGGER.debug("Case was already opened; its state is {}", currentState); + next = null; + } else { + engine.auditHelper.prepareProcessStartRecord(ctx, result); + ctx.prepareNotification(new DelayedNotification.ProcessStart(ctx.getCurrentCase())); + ctx.getCurrentCase().setState(SchemaConstants.CASE_STATE_OPEN); + + if (ctx.getNumberOfStages() > 0) { + next = new OpenStageAction(ctx); + } else { + next = new CloseCaseAction(ctx, SchemaConstants.MODEL_APPROVAL_OUTCOME_APPROVE); + } + } + + traceExit(LOGGER, next); + return next; + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/OpenStageAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/OpenStageAction.java new file mode 100644 index 00000000000..26e298ede4b --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/OpenStageAction.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.MidpointParsingMigrator; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.WfContextUtil; +import com.evolveum.midpoint.util.QNameUtil; +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.api.WorkItemAllocationChangeOperationInfo; +import com.evolveum.midpoint.wf.impl.engine.helpers.DelayedNotification; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.wf.impl.processes.common.StageComputeHelper; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.jetbrains.annotations.NotNull; + +import javax.xml.datatype.XMLGregorianCalendar; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +/** + * + */ +class OpenStageAction extends InternalAction { + + private static final Trace LOGGER = TraceManager.getTrace(OpenStageAction.class); + + OpenStageAction(EngineInvocationContext ctx) { + super(ctx); + } + + @Override + public Action execute(OperationResult result) { + traceEnter(LOGGER); + + Action next; + + int numberOfStages = ctx.getNumberOfStages(); + int currentStage = ctx.getCurrentStage(); + if (currentStage == numberOfStages) { + next = new CloseCaseAction(ctx, SchemaConstants.MODEL_APPROVAL_OUTCOME_APPROVE); + } else { + int stageToBe = currentStage + 1; + ctx.getCurrentCase().setStageNumber(stageToBe); + ApprovalStageDefinitionType stageDef = ctx.getCurrentStageDefinition(); + + StageComputeHelper.ComputationResult preStageComputationResult = + engine.stageComputeHelper.computeStageApprovers(stageDef, + () -> engine.stageComputeHelper + .getDefaultVariables(ctx.getCurrentCase(), ctx.getWfContext(), ctx.getTask().getChannel(), result), + ctx.getTask(), result); + + ApprovalLevelOutcomeType predeterminedOutcome = preStageComputationResult.getPredeterminedOutcome(); + Set approverRefs = preStageComputationResult.getApproverRefs(); + logPreStageComputationResult(preStageComputationResult, stageDef, predeterminedOutcome, approverRefs); + + if (predeterminedOutcome != null) { + next = new CloseStageAction(ctx, preStageComputationResult); + } else { + assert !approverRefs.isEmpty(); + XMLGregorianCalendar createTimestamp = engine.clock.currentTimeXMLGregorianCalendar(); + XMLGregorianCalendar deadline; + if (stageDef.getDuration() != null) { + deadline = (XMLGregorianCalendar) createTimestamp.clone(); + deadline.add(stageDef.getDuration()); + } else { + deadline = null; + } + AtomicLong idCounter = new AtomicLong(defaultIfNull(ctx.getCurrentCase().asPrismObject().getHighestId(), 0L) + 1); + for (ObjectReferenceType approverRef : approverRefs) { + CaseWorkItemType workItem = createWorkItem(stageDef, createTimestamp, deadline, idCounter, approverRef, result); + prepareAuditAndNotifications(workItem, result); + createTriggers(workItem, stageDef, result); + ctx.getCurrentCase().getWorkItem().add(workItem); + } + next = null; // at least one work item was created, so we must wait for its completion + } + } + traceExit(LOGGER, next); + return next; + } + + private void createTriggers(CaseWorkItemType workItem, ApprovalStageDefinitionType stageDef, OperationResult result) { + engine.triggerHelper.createTriggersForTimedActions(ctx.getCurrentCase(), workItem.getId(), 0, + XmlTypeConverter.toDate(workItem.getCreateTimestamp()), + XmlTypeConverter.toDate(workItem.getDeadline()), + stageDef.getTimedActions(), + result); + } + + private void prepareAuditAndNotifications(CaseWorkItemType workItem, OperationResult result) { + AuditEventRecord auditEventRecord = engine.primaryChangeProcessor.prepareWorkItemCreatedAuditRecord(workItem, + ctx.getCurrentCase(), result); + ctx.addAuditRecord(auditEventRecord); + try { + List assigneesAndDeputies = engine.miscHelper.getAssigneesAndDeputies(workItem, ctx.getTask(), result); + for (ObjectReferenceType assigneesOrDeputy : assigneesAndDeputies) { + // we assume originalAssigneeRef == assigneeRef in this case + ctx.prepareNotification(new DelayedNotification.ItemCreation(ctx.getCurrentCase(), workItem, null, null, assigneesOrDeputy)); + } + WorkItemAllocationChangeOperationInfo operationInfo = + new WorkItemAllocationChangeOperationInfo(null, Collections.emptyList(), assigneesAndDeputies); + ctx.prepareNotification(new DelayedNotification.AllocationChangeNew(ctx.getCurrentCase(), workItem, operationInfo, null)); + } catch (SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't send notification about work item create event", e); + } + } + + @NotNull + private CaseWorkItemType createWorkItem(ApprovalStageDefinitionType stageDef, XMLGregorianCalendar createTimestamp, + XMLGregorianCalendar deadline, AtomicLong idCounter, ObjectReferenceType approverRef, OperationResult result) { + CaseWorkItemType workItem = new CaseWorkItemType(engine.prismContext) + .name(ctx.getProcessInstanceName()) + .id(idCounter.getAndIncrement()) + .stageNumber(stageDef.getNumber()) + .createTimestamp(createTimestamp) + .deadline(deadline); + if (approverRef.getType() == null) { + approverRef.setType(UserType.COMPLEX_TYPE); + } + if (QNameUtil.match(UserType.COMPLEX_TYPE, approverRef.getType())) { + workItem.setOriginalAssigneeRef(approverRef.clone()); + workItem.getAssigneeRef().add(approverRef.clone()); + } else if (QNameUtil.match(RoleType.COMPLEX_TYPE, approverRef.getType()) || + QNameUtil.match(OrgType.COMPLEX_TYPE, approverRef.getType()) || + QNameUtil.match(ServiceType.COMPLEX_TYPE, approverRef.getType())) { + workItem.getCandidateRef().add(approverRef.clone()); + // todo what about originalAssigneeRef? + } else { + throw new IllegalStateException( + "Unsupported type of the approver: " + approverRef.getType() + " in " + approverRef); + } + if (stageDef.getAdditionalInformation() != null) { + try { + ExpressionVariables variables = engine.stageComputeHelper + .getDefaultVariables(ctx.getCurrentCase(), ctx.getWfContext(), ctx.getChannel(), result); + List additionalInformation = engine.expressionEvaluationHelper + .evaluateExpression(stageDef.getAdditionalInformation(), variables, + "additional information expression", InformationType.class, + InformationType.COMPLEX_TYPE, + true, this::createInformationType, ctx.getTask(), result); + workItem.getAdditionalInformation().addAll(additionalInformation); + } catch (Throwable t) { + throw new SystemException("Couldn't evaluate additional information expression in " + ctx, t); + } + } + return workItem; + } + + private InformationType createInformationType(Object o) { + if (o == null || o instanceof InformationType) { + return (InformationType) o; + } else if (o instanceof String) { + return MidpointParsingMigrator.stringToInformationType((String) o); + } else { + throw new IllegalArgumentException("Object cannot be converted into InformationType: " + o); + } + } + + private void logPreStageComputationResult(StageComputeHelper.ComputationResult preStageComputationResult, + ApprovalStageDefinitionType stageDef, + ApprovalLevelOutcomeType predeterminedOutcome, + Set approverRefs) { + if (LOGGER.isDebugEnabled()) { + if (preStageComputationResult.noApproversFound()) { + LOGGER.debug("No approvers at the stage '{}' for process {} (case oid {}) - outcome-if-no-approvers is {}", + stageDef.getName(), + ctx.getProcessInstanceName(), ctx.getCurrentCase().getOid(), stageDef.getOutcomeIfNoApprovers()); + } + LOGGER.debug("Approval process instance {} (case oid {}), stage {}: predetermined outcome: {}, approvers: {}", + ctx.getProcessInstanceName(), ctx.getCurrentCase().getOid(), + WfContextUtil.getStageDiagName(stageDef), predeterminedOutcome, approverRefs); + } + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ReleaseWorkItemsAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ReleaseWorkItemsAction.java new file mode 100644 index 00000000000..46ecfd44ea0 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ReleaseWorkItemsAction.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.result.OperationResultStatus; +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.request.ReleaseWorkItemsRequest; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType; +import org.jetbrains.annotations.NotNull; + +/** + * + */ +public class ReleaseWorkItemsAction extends RequestedAction { + + private static final Trace LOGGER = TraceManager.getTrace(ReleaseWorkItemsAction.class); + + public ReleaseWorkItemsAction(@NotNull EngineInvocationContext ctx, @NotNull ReleaseWorkItemsRequest request) { + super(ctx, request); + } + + @Override + public Action execute(OperationResult result) { + traceEnter(LOGGER); + + for (ReleaseWorkItemsRequest.SingleRelease release : request.getReleases()) { + CaseWorkItemType workItem = ctx.findWorkItemById(release.getWorkItemId()); + if (workItem.getCloseTimestamp() != null) { + LOGGER.debug("Work item {} in {} cannot be released because it's already closed", workItem, ctx.getCurrentCase()); + result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "There are no candidates this work item can be offered to"); + } else if (workItem.getAssigneeRef().isEmpty()) { + throw new SystemException("The work item is not assigned to a user"); + } else if (workItem.getAssigneeRef().size() > 1) { + throw new SystemException("The work item is assigned to more than one user, so it cannot be released"); + } else if (!ctx.getPrincipal().getOid().equals(workItem.getAssigneeRef().get(0).getOid())) { + throw new SystemException("The work item is not assigned to the current user"); + } else if (workItem.getCandidateRef().isEmpty()) { + result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "There are no candidates this work item can be offered to"); + } else { + workItem.getAssigneeRef().clear(); + } + } + + traceExit(LOGGER, null); + return null; + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/RequestedAction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/RequestedAction.java new file mode 100644 index 00000000000..68351cb2280 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/RequestedAction.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010-2019 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.engine.actions; + +import com.evolveum.midpoint.wf.api.request.Request; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import org.jetbrains.annotations.NotNull; + +/** + * Action that is invoked on the basis of an external request. + */ +public abstract class RequestedAction extends Action { + + @NotNull public final R request; + + RequestedAction(@NotNull EngineInvocationContext ctx, @NotNull R request) { + super(ctx); + this.request = request; + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/AuditHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/AuditHelper.java similarity index 87% rename from model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/AuditHelper.java rename to model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/AuditHelper.java index f8aebc09a71..c0708ce75d6 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/AuditHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/AuditHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.evolveum.midpoint.wf.impl.engine; +package com.evolveum.midpoint.wf.impl.engine.helpers; import com.evolveum.midpoint.audit.api.AuditEventRecord; import com.evolveum.midpoint.audit.api.AuditEventStage; @@ -30,7 +30,6 @@ import com.evolveum.midpoint.schema.util.WorkItemId; import com.evolveum.midpoint.security.api.MidPointPrincipal; import com.evolveum.midpoint.security.api.SecurityContextManager; -import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; @@ -38,7 +37,8 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.wf.api.WorkflowConstants; -import com.evolveum.midpoint.wf.impl.processors.ChangeProcessor; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor; import com.evolveum.midpoint.wf.impl.util.MiscHelper; import com.evolveum.midpoint.wf.util.ApprovalUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -64,26 +64,30 @@ public class AuditHelper { @Autowired private SecurityContextManager securityContextManager; @Autowired private PrismContext prismContext; @Autowired private MiscHelper miscHelper; + @Autowired private PrimaryChangeProcessor primaryChangeProcessor; // todo @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; //region Recoding of audit events - public void auditProcessStart(CaseType aCase, WfContextType wfContext, - ChangeProcessor changeProcessor, Task opTask, OperationResult result) { - auditProcessStartEnd(aCase, AuditEventStage.REQUEST, wfContext, changeProcessor, opTask, result); + public void prepareProcessStartRecord(EngineInvocationContext ctx, OperationResult result) { + prepareProcessStartEndRecord(ctx, AuditEventStage.REQUEST, result); } - public void auditProcessEnd(CaseType aCase, WfContextType wfContext, ChangeProcessor changeProcessor, Task opTask, - OperationResult result) { - auditProcessStartEnd(aCase, AuditEventStage.EXECUTION, wfContext, changeProcessor, opTask, result); + public void prepareProcessEndRecord(EngineInvocationContext ctx, OperationResult result) { + prepareProcessStartEndRecord(ctx, AuditEventStage.EXECUTION, result); + } + + private void prepareProcessStartEndRecord(EngineInvocationContext ctx, AuditEventStage stage, OperationResult result) { + AuditEventRecord auditEventRecord = primaryChangeProcessor.prepareProcessInstanceAuditRecord(ctx.getCurrentCase(), stage, ctx.getWfContext(), result); + ctx.addAuditRecord(auditEventRecord); } - private void auditProcessStartEnd(CaseType aCase, AuditEventStage stage, WfContextType wfContext, - ChangeProcessor changeProcessor, Task opTask, OperationResult result) { - AuditEventRecord auditEventRecord = changeProcessor.prepareProcessInstanceAuditRecord(aCase, stage, wfContext, result); - auditService.audit(auditEventRecord, opTask); + public void auditPreparedRecords(EngineInvocationContext ctx) { + for (AuditEventRecord record : ctx.pendingAuditRecords) { + auditService.audit(record, ctx.getTask()); + } } //endregion @@ -152,28 +156,30 @@ private List resolveIfNeeded(List refs private AuditEventRecord prepareWorkItemAuditRecordCommon(CaseWorkItemType workItem, CaseType aCase, AuditEventStage stage, OperationResult result) { + WfContextType wfc = aCase.getWorkflowContext(); + AuditEventRecord record = new AuditEventRecord(); record.setEventType(AuditEventType.WORK_ITEM); record.setEventStage(stage); - ObjectReferenceType objectRef = resolveIfNeeded(WfContextUtil.getObjectRef(workItem), result); + ObjectReferenceType objectRef = resolveIfNeeded(aCase.getObjectRef(), result); record.setTarget(objectRef.asReferenceValue()); record.setOutcome(OperationResultStatus.SUCCESS); record.setParameter(miscHelper.getCompleteStageInfo(aCase)); record.addReferenceValueIgnoreNull(WorkflowConstants.AUDIT_OBJECT, objectRef); - record.addReferenceValueIgnoreNull(WorkflowConstants.AUDIT_TARGET, resolveIfNeeded(WfContextUtil.getTargetRef(workItem), result)); + record.addReferenceValueIgnoreNull(WorkflowConstants.AUDIT_TARGET, resolveIfNeeded(aCase.getTargetRef(), result)); record.addReferenceValueIgnoreNull(WorkflowConstants.AUDIT_ORIGINAL_ASSIGNEE, resolveIfNeeded(workItem.getOriginalAssigneeRef(), result)); record.addReferenceValues(WorkflowConstants.AUDIT_CURRENT_ASSIGNEE, resolveIfNeeded(workItem.getAssigneeRef(), result)); record.addPropertyValueIgnoreNull(WorkflowConstants.AUDIT_STAGE_NUMBER, workItem.getStageNumber()); - record.addPropertyValueIgnoreNull(WorkflowConstants.AUDIT_STAGE_COUNT, WfContextUtil.getStageCount(workItem)); - record.addPropertyValueIgnoreNull(WorkflowConstants.AUDIT_STAGE_NAME, WfContextUtil.getStageName(workItem)); - record.addPropertyValueIgnoreNull(WorkflowConstants.AUDIT_STAGE_DISPLAY_NAME, WfContextUtil.getStageDisplayName(workItem)); + record.addPropertyValueIgnoreNull(WorkflowConstants.AUDIT_STAGE_COUNT, WfContextUtil.getStageCount(wfc)); + record.addPropertyValueIgnoreNull(WorkflowConstants.AUDIT_STAGE_NAME, WfContextUtil.getStageName(aCase)); + record.addPropertyValueIgnoreNull(WorkflowConstants.AUDIT_STAGE_DISPLAY_NAME, WfContextUtil.getStageDisplayName(aCase)); record.addPropertyValueIgnoreNull(WorkflowConstants.AUDIT_ESCALATION_LEVEL_NUMBER, WfContextUtil.getEscalationLevelNumber(workItem)); record.addPropertyValueIgnoreNull(WorkflowConstants.AUDIT_ESCALATION_LEVEL_NAME, WfContextUtil.getEscalationLevelName(workItem)); record.addPropertyValueIgnoreNull(WorkflowConstants.AUDIT_ESCALATION_LEVEL_DISPLAY_NAME, WfContextUtil.getEscalationLevelDisplayName(workItem)); - record.addPropertyValue(WorkflowConstants.AUDIT_WORK_ITEM_ID, WorkItemId.of(workItem).asString()); + record.addPropertyValue(WorkflowConstants.AUDIT_WORK_ITEM_ID, WorkItemId.create(aCase.getOid(), workItem.getId()).asString()); //record.addPropertyValue(WorkflowConstants.AUDIT_PROCESS_INSTANCE_ID, WfContextUtil.getProcessInstanceId(workItem)); return record; } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/DelayedNotification.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/DelayedNotification.java new file mode 100644 index 00000000000..f36fb5eae06 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/DelayedNotification.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2010-2019 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.engine.helpers; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.wf.api.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import javax.xml.datatype.Duration; + +/** + * + */ + +public abstract class DelayedNotification { + + public final CaseType aCase; + + DelayedNotification(CaseType aCase) { + this.aCase = aCase.clone(); + } + + public static class ProcessStart extends DelayedNotification { + public ProcessStart(CaseType aCase) { + super(aCase); + } + + @Override + public void send(WorkflowListener listener, OperationResult result) { + listener.onProcessInstanceStart(aCase, result); + } + } + + public static class ProcessEnd extends DelayedNotification { + public ProcessEnd(CaseType aCase) { + super(aCase); + } + + @Override + public void send(WorkflowListener listener, OperationResult result) { + listener.onProcessInstanceEnd(aCase, result); + } + } + + public abstract static class WorkItem extends DelayedNotification { + final CaseWorkItemType workItem; + final OI operationInfo; + final WorkItemOperationSourceInfo sourceInfo; + + WorkItem(CaseType aCase, CaseWorkItemType workItem, OI operationInfo, WorkItemOperationSourceInfo sourceInfo) { + super(aCase); + this.workItem = workItem.clone(); + this.operationInfo = operationInfo; + this.sourceInfo = sourceInfo; + } + } + + public static class ItemCreation extends WorkItem { + public ItemCreation(CaseType aCase, CaseWorkItemType workItem, WorkItemOperationInfo operationInfo, + WorkItemOperationSourceInfo sourceInfo, ObjectReferenceType assignee) { + super(aCase, workItem, operationInfo, sourceInfo); + this.assignee = assignee; + } + + public final ObjectReferenceType assignee; + + @Override + public void send(WorkflowListener listener, OperationResult result) { + listener.onWorkItemCreation(assignee, workItem, aCase, result); + } + } + + public static class ItemDeletion extends WorkItem { + public ItemDeletion(CaseType aCase, CaseWorkItemType workItem, WorkItemOperationInfo operationInfo, + WorkItemOperationSourceInfo sourceInfo, ObjectReferenceType assignee) { + super(aCase, workItem, operationInfo, sourceInfo); + this.assignee = assignee; + } + + public final ObjectReferenceType assignee; + + @Override + public void send(WorkflowListener listener, OperationResult result) { + listener.onWorkItemDeletion(assignee, workItem, operationInfo, sourceInfo, aCase, result); + } + } + + public static class ItemCustom extends WorkItem { + public ItemCustom(CaseType aCase, CaseWorkItemType workItem, WorkItemOperationInfo operationInfo, + WorkItemOperationSourceInfo sourceInfo, ObjectReferenceType assignee, + WorkItemNotificationActionType notificationAction, + WorkItemEventCauseInformationType cause) { + super(aCase, workItem, operationInfo, sourceInfo); + this.assignee = assignee; + this.notificationAction = notificationAction; + this.cause = cause; + } + + public final ObjectReferenceType assignee; + public final WorkItemNotificationActionType notificationAction; + public final WorkItemEventCauseInformationType cause; + + @Override + public void send(WorkflowListener listener, OperationResult result) { + listener.onWorkItemCustomEvent(assignee, workItem, notificationAction, cause, aCase, result); + } + } + + public static class AllocationChangeCurrent extends WorkItem { + public AllocationChangeCurrent(CaseType aCase, CaseWorkItemType workItem, + WorkItemAllocationChangeOperationInfo operationInfo, WorkItemOperationSourceInfo sourceInfo, Duration timeBefore) { + super(aCase, workItem, operationInfo, sourceInfo); + this.timeBefore = timeBefore; + } + + public final Duration timeBefore; + + @Override + public void send(WorkflowListener listener, OperationResult result) { + listener.onWorkItemAllocationChangeCurrentActors(workItem, operationInfo, sourceInfo, timeBefore, aCase, result); + } + } + + public static class AllocationChangeNew extends WorkItem { + public AllocationChangeNew(CaseType aCase, CaseWorkItemType workItem, + WorkItemAllocationChangeOperationInfo operationInfo, WorkItemOperationSourceInfo sourceInfo) { + super(aCase, workItem, operationInfo, sourceInfo); + } + + @Override + public void send(WorkflowListener listener, OperationResult result) { + listener.onWorkItemAllocationChangeNewActors(workItem, operationInfo, sourceInfo, aCase, result); + } + } + + public abstract void send(WorkflowListener listener, OperationResult result); +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/NotificationHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/NotificationHelper.java new file mode 100644 index 00000000000..c66efb34561 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/NotificationHelper.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2010-2019 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.engine.helpers; + +import com.evolveum.midpoint.schema.result.OperationResult; +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.WorkflowListener; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.stereotype.Component; + +import javax.xml.datatype.Duration; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Helps with notification activities. + */ +@Component +public class NotificationHelper { + + private static final Trace LOGGER = TraceManager.getTrace(NotificationHelper.class); + + private Set workflowListeners = ConcurrentHashMap.newKeySet(); + + public void sendPreparedNotifications(EngineInvocationContext ctx, OperationResult result) { + for (DelayedNotification notification : ctx.pendingNotifications) { + for (WorkflowListener listener : workflowListeners) { + notification.send(listener, result); + } + } + } + + // The following two methods are of "immediate notification" kind. They are an exception; usually we + // prepare notifications first and send them only after the case modification succeeds. + + public void notifyWorkItemAllocationChangeCurrentActors(CaseWorkItemType workItem, + @NotNull WorkItemAllocationChangeOperationInfo operationInfo, + WorkItemOperationSourceInfo sourceInfo, Duration timeBefore, + CaseType aCase, OperationResult result) { + for (WorkflowListener workflowListener : workflowListeners) { + workflowListener.onWorkItemAllocationChangeCurrentActors(workItem, operationInfo, sourceInfo, timeBefore, aCase, result); + } + } + + public void notifyWorkItemCustom(@Nullable ObjectReferenceType assignee, CaseWorkItemType workItem, + WorkItemEventCauseInformationType cause, CaseType aCase, + @NotNull WorkItemNotificationActionType notificationAction, + OperationResult result) { + for (WorkflowListener workflowListener : workflowListeners) { + workflowListener.onWorkItemCustomEvent(assignee, workItem, notificationAction, cause, aCase, result); + } + } + + public void registerWorkItemListener(WorkflowListener workflowListener) { + LOGGER.trace("Registering work item listener {}", workflowListener); + workflowListeners.add(workflowListener); + } + +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/TriggerHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/TriggerHelper.java new file mode 100644 index 00000000000..e2683cc3b05 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/TriggerHelper.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010-2019 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.engine.helpers; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.util.PrismUtil; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.WfContextUtil; +import com.evolveum.midpoint.util.exception.SchemaException; +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.impl.processes.common.WfTimedActionTriggerHandler; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TriggerType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemTimedActionsType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +/** + * + */ +@Component +public class TriggerHelper { + + private static final Trace LOGGER = TraceManager.getTrace(TriggerHelper.class); + + @Autowired private PrismContext prismContext; + + public void createTriggersForTimedActions(CaseType currentCase, long workItemId, int escalationLevel, + Date workItemCreateTime, + Date workItemDeadline, List timedActionsList, OperationResult result) { + LOGGER.trace("Creating triggers for timed actions for work item {}, escalation level {}, create time {}, deadline {}, {} timed action(s)", + workItemId, escalationLevel, workItemCreateTime, workItemDeadline, timedActionsList.size()); + try { + List triggers = WfContextUtil.createTriggers(escalationLevel, workItemCreateTime, workItemDeadline, + timedActionsList, prismContext, LOGGER, workItemId, WfTimedActionTriggerHandler.HANDLER_URI); + LOGGER.trace("Adding {} triggers to {}:\n{}", triggers.size(), currentCase, + PrismUtil.serializeQuietlyLazily(prismContext, triggers)); + currentCase.getTrigger().addAll(triggers); + } catch (SchemaException | RuntimeException e) { + throw new SystemException("Couldn't add trigger(s) to " + currentCase + ": " + e.getMessage(), e); + } + } + + void removeTriggersForWorkItem(CaseType aCase, long workItemId, OperationResult result) { + for (Iterator iterator = aCase.getTrigger().iterator(); iterator.hasNext(); ) { + TriggerType triggerType = iterator.next(); + if (WfTimedActionTriggerHandler.HANDLER_URI.equals(triggerType.getHandlerUri())) { + //noinspection unchecked + PrismProperty workItemIdProperty = triggerType.getExtension().asPrismContainerValue() + .findProperty(SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ID); + if (workItemIdProperty != null) { + Long realValue = workItemIdProperty.getRealValue(); + if (realValue != null && realValue == workItemId) { + iterator.remove(); + } + } + } + } + } + + // not necessary any more, as work item triggers are deleted when the work item (task) is deleted + // (and there are currently no triggers other than work-item-related) + public void removeAllStageTriggersForWorkItem(CaseType aCase) { + aCase.getTrigger().removeIf(triggerType -> WfTimedActionTriggerHandler.HANDLER_URI.equals(triggerType.getHandlerUri())); + } + +// private void removeSelectedTriggers(CaseType aCase, List> toDelete, OperationResult result) { +// LOGGER.trace("About to delete {} triggers from {}: {}", toDelete.size(), aCase, toDelete); +// if (!toDelete.isEmpty()) { +// try { +// List> itemDeltas = prismContext.deltaFor(TaskType.class) +// .item(TaskType.F_TRIGGER).delete(toDelete) +// .asItemDeltas(); +// repositoryService.modifyObject(CaseType.class, aCase.getOid(), itemDeltas, result); +// } catch (SchemaException|ObjectNotFoundException|ObjectAlreadyExistsException|RuntimeException e) { +// LoggingUtils.logUnexpectedException(LOGGER, "Couldn't remove triggers from {}", e, aCase); +// } +// } +// } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/WorkItemHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/WorkItemHelper.java new file mode 100644 index 00000000000..2df11644750 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/WorkItemHelper.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2010-2019 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.engine.helpers; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.schema.util.WorkItemId; +import com.evolveum.midpoint.security.api.MidPointPrincipal; +import com.evolveum.midpoint.security.api.SecurityUtil; +import com.evolveum.midpoint.util.exception.*; +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.api.WorkItemAllocationChangeOperationInfo; +import com.evolveum.midpoint.wf.api.WorkItemOperationSourceInfo; +import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; +import com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor; +import com.evolveum.midpoint.wf.impl.util.MiscHelper; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.List; + +/** + * + */ +@Component +public class WorkItemHelper { + + private static final Trace LOGGER = TraceManager.getTrace(WorkItemHelper.class); + + @Autowired private PrismContext prismContext; + @Autowired private PrimaryChangeProcessor primaryChangeProcessor; + @Autowired private MiscHelper miscHelper; + @Autowired private TriggerHelper triggerHelper; + + public static void fillInWorkItemEvent(WorkItemEventType event, MidPointPrincipal currentUser, WorkItemId workItemId, + CaseWorkItemType workItem, PrismContext prismContext) { + if (currentUser != null) { + event.setInitiatorRef(ObjectTypeUtil.createObjectRef(currentUser.getUser(), prismContext)); + event.setAttorneyRef(ObjectTypeUtil.createObjectRef(currentUser.getAttorney(), prismContext)); + } + event.setTimestamp(XmlTypeConverter.createXMLGregorianCalendar(new Date())); + event.setExternalWorkItemId(workItemId.asString()); + event.setOriginalAssigneeRef(workItem.getOriginalAssigneeRef()); + event.setStageNumber(workItem.getStageNumber()); + event.setEscalationLevel(workItem.getEscalationLevel()); + } + + public void recordWorkItemClosure(EngineInvocationContext ctx, CaseWorkItemType workItem, boolean realClosure, + WorkItemEventCauseInformationType causeInformation, OperationResult result) throws SchemaException { + // this might be cancellation because of: + // (1) user completion of this task + // (2) timed completion of this task + // (3) user completion of another task + // (4) timed completion of another task + // (5) process stop/deletion + // + // Actually, when the source is (4) timed completion of another task, it is quite probable that this task + // would be closed for the same reason. For a user it would be misleading if we would simply view this task + // as 'cancelled', while, in fact, it is e.g. approved/rejected because of a timed action. + + LOGGER.trace("+++ recordWorkItemClosure ENTER: workItem={}, ctx={}, realClosure={}", workItem, ctx, realClosure); + WorkItemOperationKindType operationKind = realClosure ? WorkItemOperationKindType.COMPLETE : WorkItemOperationKindType.CANCEL; + + MidPointPrincipal user; + try { + user = SecurityUtil.getPrincipal(); + } catch (SecurityViolationException e) { + throw new SystemException("Couldn't determine current user: " + e.getMessage(), e); + } + + ObjectReferenceType userRef = user != null ? user.toObjectReference() : workItem.getPerformerRef(); // partial fallback + + // We don't pass userRef (initiator) to the audit method. It does need the whole object (not only the reference), + // so it fetches it directly from the security enforcer (logged-in user). This could change in the future. + AuditEventRecord auditEventRecord = primaryChangeProcessor.prepareWorkItemDeletedAuditRecord(workItem, causeInformation, + ctx.getCurrentCase(), result); + ctx.addAuditRecord(auditEventRecord); + try { + List assigneesAndDeputies = miscHelper.getAssigneesAndDeputies(workItem, ctx.getTask(), result); + WorkItemAllocationChangeOperationInfo operationInfo = + new WorkItemAllocationChangeOperationInfo(operationKind, assigneesAndDeputies, null); + WorkItemOperationSourceInfo sourceInfo = new WorkItemOperationSourceInfo(userRef, causeInformation, null); + if (workItem.getAssigneeRef().isEmpty()) { + ctx.prepareNotification(new DelayedNotification.ItemDeletion(ctx.getCurrentCase(), workItem, operationInfo, sourceInfo, null)); + } else { + for (ObjectReferenceType assigneeOrDeputy : assigneesAndDeputies) { + ctx.prepareNotification(new DelayedNotification.ItemDeletion(ctx.getCurrentCase(), workItem, operationInfo, sourceInfo, assigneeOrDeputy)); + } + } + ctx.prepareNotification(new DelayedNotification.AllocationChangeCurrent(ctx.getCurrentCase(), workItem, operationInfo, sourceInfo, null)); + } catch (SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't audit work item complete event", e); + } + + WorkItemId workItemId = WorkItemId.create(ctx.getCaseOid(), workItem.getId()); + + AbstractWorkItemOutputType output = workItem.getOutput(); + if (realClosure || output != null) { + WorkItemCompletionEventType event = new WorkItemCompletionEventType(prismContext); + fillInWorkItemEvent(event, user, workItemId, workItem, prismContext); + event.setCause(causeInformation); + event.setOutput(output); + ctx.addEvent(event); + ObjectDeltaType additionalDelta = output instanceof WorkItemResultType && ((WorkItemResultType) output).getAdditionalDeltas() != null ? + ((WorkItemResultType) output).getAdditionalDeltas().getFocusPrimaryDelta() : null; + ctx.updateDelta(additionalDelta); + } + + triggerHelper.removeTriggersForWorkItem(ctx.getCurrentCase(), workItem.getId(), result); + + LOGGER.trace("--- recordWorkItemClosure EXIT: workItem={}, ctx={}, realClosure={}", workItem, ctx, realClosure); + } + +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/CaseOperationExecutionTaskHandler.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/CaseOperationExecutionTaskHandler.java similarity index 95% rename from model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/CaseOperationExecutionTaskHandler.java rename to model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/CaseOperationExecutionTaskHandler.java index b3af6f55cac..dae0f0eb25b 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/tasks/CaseOperationExecutionTaskHandler.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/CaseOperationExecutionTaskHandler.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package com.evolveum.midpoint.wf.impl.tasks; +package com.evolveum.midpoint.wf.impl.execution; import com.evolveum.midpoint.model.api.context.ModelProjectionContext; import com.evolveum.midpoint.model.impl.lens.Clockwork; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.repo.api.PreconditionViolationException; @@ -35,14 +34,14 @@ 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.engine.WorkflowEngine; -import com.evolveum.midpoint.wf.impl.util.MiscHelper; import com.evolveum.midpoint.wf.impl.processors.primary.ApprovalMetadataHelper; import com.evolveum.midpoint.wf.impl.processors.primary.PcpGeneralHelper; +import com.evolveum.midpoint.wf.impl.util.MiscHelper; import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskPartitionDefinitionType; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @@ -63,14 +62,15 @@ public class CaseOperationExecutionTaskHandler implements TaskHandler { public static final String HANDLER_URI = "http://midpoint.evolveum.com/xml/ns/public/workflow/operation-execution/handler-3"; @Autowired private TaskManager taskManager; - @Autowired private PrismContext prismContext; @Autowired private ApprovalMetadataHelper metadataHelper; @Autowired private Clockwork clockwork; @Autowired private MiscHelper miscHelper; @Autowired private PcpGeneralHelper pcpGeneralHelper; @Autowired private ApprovalMetadataHelper approvalMetadataHelper; - @Autowired private WorkflowEngine workflowEngine; - @Autowired private RepositoryService repositoryService; + @Autowired private ExecutionHelper executionHelper; + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; @Override public TaskRunResult run(RunningTask task, TaskPartitionDefinitionType partitionDefinition) { @@ -119,8 +119,8 @@ private void executeLocalChanges(CaseType subcase, RunningTask task, OperationRe } mergeDeltasToModelContext(modelContext, singletonList(deltas)); executeModelContext(modelContext, subcase, task, result); - workflowEngine.closeCaseInternal(subcase, task, result); - workflowEngine.checkDependentCases(subcase.getParentRef().getOid(), task, result); + executionHelper.closeCaseInRepository(subcase, result); + executionHelper.checkDependentCases(subcase.getParentRef().getOid(), result); } private void executeAllChanges(CaseType rootCase, RunningTask task, OperationResult result) @@ -129,7 +129,7 @@ private void executeAllChanges(CaseType rootCase, RunningTask task, OperationRes SecurityViolationException { LensContext modelContext = collectApprovedDeltasToModelContext(rootCase, task, result); executeModelContext(modelContext, rootCase, task, result); - workflowEngine.closeCaseInternal(rootCase, task, result); + executionHelper.closeCaseInRepository(rootCase, result); } private LensContext collectApprovedDeltasToModelContext(CaseType rootCase, RunningTask task, OperationResult result) diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/ExecutionHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/ExecutionHelper.java new file mode 100644 index 00000000000..daeaac10d9b --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/ExecutionHelper.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2010-2019 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.execution; + +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.SearchResultList; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.CaseTypeUtil; +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.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.impl.access.AuthorizationHelper; +import com.evolveum.midpoint.wf.impl.engine.helpers.AuditHelper; +import com.evolveum.midpoint.wf.impl.engine.helpers.NotificationHelper; +import com.evolveum.midpoint.wf.impl.engine.helpers.TriggerHelper; +import com.evolveum.midpoint.wf.impl.engine.helpers.WorkItemHelper; +import com.evolveum.midpoint.wf.impl.processes.common.ExpressionEvaluationHelper; +import com.evolveum.midpoint.wf.impl.processes.common.StageComputeHelper; +import com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor; +import com.evolveum.midpoint.wf.impl.util.MiscHelper; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + */ +@Component +public class ExecutionHelper { + + private static final Trace LOGGER = TraceManager.getTrace(ExecutionHelper.class); + + @Autowired public Clock clock; + @Autowired + @Qualifier("cacheRepositoryService") + public RepositoryService repositoryService; + @Autowired public PrismContext prismContext; + @Autowired private TaskManager taskManager; + @Autowired public AuditHelper auditHelper; + @Autowired public NotificationHelper notificationHelper; + @Autowired public StageComputeHelper stageComputeHelper; + @Autowired public PrimaryChangeProcessor primaryChangeProcessor; // todo + @Autowired public MiscHelper miscHelper; + @Autowired public TriggerHelper triggerHelper; + @Autowired public ExpressionEvaluationHelper expressionEvaluationHelper; + @Autowired public WorkItemHelper workItemHelper; + @Autowired public AuthorizationHelper authorizationHelper; + + public void closeCaseInRepository(CaseType aCase, OperationResult result) + throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { + List> modifications = prismContext.deltaFor(CaseType.class) + .item(CaseType.F_STATE).replace(SchemaConstants.CASE_STATE_CLOSED) + .item(CaseType.F_CLOSE_TIMESTAMP).replace(clock.currentTimeXMLGregorianCalendar()) + .asItemDeltas(); + repositoryService.modifyObject(CaseType.class, aCase.getOid(), modifications, result); + LOGGER.debug("Marked case {} as closed", aCase); + } + + public void setCaseStateInRepository(CaseType aCase, String newState, OperationResult result) + throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { + List> modifications = prismContext.deltaFor(CaseType.class) + .item(CaseType.F_STATE).replace(newState) + .asItemDeltas(); + repositoryService.modifyObject(CaseType.class, aCase.getOid(), modifications, result); + LOGGER.debug("Marked case {} as {}", aCase, newState); + } + + /** + * We need to check + * 1) if there are any executable cases that depend on this one + * 2) if we can close the parent (root) + */ + public void checkDependentCases(String rootOid, OperationResult result) + throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException, PreconditionViolationException { + CaseType aCase = repositoryService.getObject(CaseType.class, rootOid, null, result).asObjectable(); + if (CaseTypeUtil.isClosed(aCase)) { + return; + } + List subcases = miscHelper.getSubcases(rootOid, result); + LOGGER.debug("Subcases:"); + for (CaseType subcase : subcases) { + LOGGER.debug(" - {}: state={}, closeTS={}", subcase, subcase.getState(), subcase.getCloseTimestamp()); + } + List openOids = subcases.stream() + .filter(c -> !CaseTypeUtil.isClosed(c)) + .map(ObjectType::getOid) + .collect(Collectors.toList()); + LOGGER.debug("open cases OIDs: {}", openOids); + if (openOids.isEmpty()) { + closeCaseInRepository(aCase, result); + } else { + ObjectQuery query = prismContext.queryFor(TaskType.class) + .item(TaskType.F_OBJECT_REF).ref(openOids.toArray(new String[0])) + .and().item(TaskType.F_EXECUTION_STATUS).eq(TaskExecutionStatusType.WAITING) + .build(); + SearchResultList> waitingTasks = repositoryService + .searchObjects(TaskType.class, query, null, result); + LOGGER.debug("Waiting tasks: {}", waitingTasks); + for (PrismObject waitingTask : waitingTasks) { + String waitingCaseOid = waitingTask.asObjectable().getObjectRef().getOid(); + assert waitingCaseOid != null; + List waitingCaseList = subcases.stream().filter(c -> waitingCaseOid.equals(c.getOid())) + .collect(Collectors.toList()); + assert waitingCaseList.size() == 1; + Set prerequisiteOids = waitingCaseList.get(0).getPrerequisiteRef().stream() + .map(ObjectReferenceType::getOid) + .collect(Collectors.toSet()); + Collection openPrerequisites = CollectionUtils.intersection(prerequisiteOids, openOids); + LOGGER.trace("prerequisite OIDs = {}; intersection with open OIDs = {}", prerequisiteOids, openPrerequisites); + if (openPrerequisites.isEmpty()) { + LOGGER.trace("All prerequisites are fulfilled, going to release the task {}", waitingTask); + taskManager.unpauseTask(taskManager.createTaskInstance(waitingTask, result), result); + } else { + LOGGER.trace("...task is not released and continues waiting for those cases"); + } + } + } + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java index 64b1d5e0352..dee07164748 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java @@ -49,6 +49,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.WfConfigurationType; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @@ -76,7 +77,9 @@ public class WfHook implements ChangeHook { @Autowired private HookRegistry hookRegistry; @Autowired private WorkflowManager workflowManager; @Autowired private ClockworkMedic medic; - @Autowired private RepositoryService repositoryService; + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; private static final String DOT_CLASS = WfHook.class.getName() + "."; private static final String OPERATION_INVOKE = DOT_CLASS + "invoke"; diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/StageComputeHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/StageComputeHelper.java index 543e0c49479..3fc55db30fe 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/StageComputeHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/StageComputeHelper.java @@ -25,7 +25,6 @@ import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; import com.evolveum.midpoint.schema.DeltaConvertor; import com.evolveum.midpoint.schema.constants.ExpressionConstants; -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.task.api.Task; @@ -39,6 +38,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.xml.namespace.QName; @@ -49,10 +49,11 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static com.evolveum.midpoint.schema.constants.SchemaConstants.*; import static com.evolveum.midpoint.xml.ns._public.common.common_3.ApprovalLevelOutcomeType.APPROVE; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.ApprovalLevelOutcomeType.REJECT; import static com.evolveum.midpoint.xml.ns._public.common.common_3.AutomatedCompletionReasonType.AUTO_COMPLETION_CONDITION; import static com.evolveum.midpoint.xml.ns._public.common.common_3.AutomatedCompletionReasonType.NO_ASSIGNEES_FOUND; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; /** * Helps with computing things needed for stage approval (e.g. approvers, auto-approval result, ...) @@ -65,7 +66,9 @@ public class StageComputeHelper { @Autowired private ExpressionEvaluationHelper evaluationHelper; @Autowired private PrismContext prismContext; @Autowired private MiscHelper miscHelper; - @Autowired private RepositoryService repositoryService; + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; public ExpressionVariables getDefaultVariables(CaseType aCase, WfContextType wfContext, String requestChannel, OperationResult result) @@ -202,10 +205,8 @@ public ComputationResult computeStageApprovers(ApprovalStageDefinitionType stage if (rv.approverRefs.isEmpty()) { rv.noApproversFound = true; - if (stageDef.getOutcomeIfNoApprovers() != null) { // should be always the case (default is REJECT) - rv.predeterminedOutcome = stageDef.getOutcomeIfNoApprovers(); - rv.automatedCompletionReason = NO_ASSIGNEES_FOUND; - } + rv.predeterminedOutcome = defaultIfNull(stageDef.getOutcomeIfNoApprovers(), REJECT); + rv.automatedCompletionReason = NO_ASSIGNEES_FOUND; } } return rv; diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/WfTimedActionTriggerHandler.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/WfTimedActionTriggerHandler.java index c3333076d70..31f22fce387 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/WfTimedActionTriggerHandler.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/WfTimedActionTriggerHandler.java @@ -25,25 +25,23 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.schema.util.WfContextUtil; -import com.evolveum.midpoint.schema.util.WorkItemTypeUtil; +import com.evolveum.midpoint.schema.util.*; import com.evolveum.midpoint.task.api.RunningTask; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.util.Holder; import com.evolveum.midpoint.util.exception.*; 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.api.WorkItemAllocationChangeOperationInfo; -import com.evolveum.midpoint.schema.util.WorkItemId; import com.evolveum.midpoint.wf.api.WorkItemOperationSourceInfo; import com.evolveum.midpoint.wf.api.WorkflowConstants; -import com.evolveum.midpoint.wf.api.CompleteAction; +import com.evolveum.midpoint.wf.api.request.CompleteWorkItemsRequest; import com.evolveum.midpoint.wf.impl.access.WorkItemManager; +import com.evolveum.midpoint.wf.impl.engine.helpers.NotificationHelper; import com.evolveum.midpoint.wf.impl.engine.WorkflowEngine; import com.evolveum.midpoint.wf.impl.util.MiscHelper; -import com.evolveum.midpoint.wf.impl.engine.NotificationHelper; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.BooleanUtils; import org.jetbrains.annotations.NotNull; @@ -103,42 +101,48 @@ public Collection handle(PrismObject obje * completed and the other ones cancelled because the stage is being closed. (Before midPoint 4.0 there has to be * a special code to treat this.) */ - List completeActions = new ArrayList<>(); + List completeActions = new ArrayList<>(); + Holder causeHolder = new Holder<>(); for (TriggerType trigger : triggers) { boolean ok = true; OperationResult opResult = triggersResult .createSubresult(WfTimedActionTriggerHandler.class.getName() + ".handleTrigger"); - String workItemId = ObjectTypeUtil + Long workItemId = ObjectTypeUtil .getExtensionItemRealValue(trigger.getExtension(), SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ID); if (workItemId == null) { LOGGER.warn("Trigger without workItemId; ignoring it: " + trigger); opResult.recordStatus(OperationResultStatus.NOT_APPLICABLE, "No work item ID"); } else { try { - CaseWorkItemType workItem = workflowEngine.getWorkItem(WorkItemId.create(workItemId), opResult); - Duration timeBeforeAction = ObjectTypeUtil - .getExtensionItemRealValue(trigger.getExtension(), - SchemaConstants.MODEL_EXTENSION_TIME_BEFORE_ACTION); - if (timeBeforeAction != null) { - AbstractWorkItemActionType action = ObjectTypeUtil - .getExtensionItemRealValue(trigger.getExtension(), - SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ACTION); - if (action == null) { - LOGGER.warn("Notification trigger without workItemAction; ignoring it: {}", trigger); - continue; - } - executeNotifications(timeBeforeAction, action, workItem, aCase, opTask, opResult); + CaseWorkItemType workItem = CaseWorkItemUtil.getWorkItem(aCase, workItemId); + if (workItem == null) { + LOGGER.warn("Work item {} couldn't be found; ignoring the trigger: {}", workItemId, trigger); + opResult.recordStatus(OperationResultStatus.NOT_APPLICABLE, "No work item with given ID"); } else { - WorkItemActionsType actions = ObjectTypeUtil + Duration timeBeforeAction = ObjectTypeUtil .getExtensionItemRealValue(trigger.getExtension(), - SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ACTIONS); - if (actions == null) { - LOGGER.warn("Trigger without workItemActions; ignoring it: " + trigger); - continue; + SchemaConstants.MODEL_EXTENSION_TIME_BEFORE_ACTION); + if (timeBeforeAction != null) { + AbstractWorkItemActionType action = ObjectTypeUtil + .getExtensionItemRealValue(trigger.getExtension(), + SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ACTION); + if (action == null) { + LOGGER.warn("Notification trigger without workItemAction; ignoring it: {}", trigger); + continue; + } + executeNotifications(timeBeforeAction, action, workItem, aCase, opTask, opResult); + } else { + WorkItemActionsType actions = ObjectTypeUtil + .getExtensionItemRealValue(trigger.getExtension(), + SchemaConstants.MODEL_EXTENSION_WORK_ITEM_ACTIONS); + if (actions == null) { + LOGGER.warn("Trigger without workItemActions; ignoring it: " + trigger); + continue; + } + executeActions(actions, workItem, aCase, completeActions, causeHolder, opTask, opResult); } - executeActions(actions, workItem, aCase, completeActions, opTask, opResult); + opResult.computeStatusIfUnknown(); } - opResult.computeStatusIfUnknown(); } catch (RuntimeException | ObjectNotFoundException | SchemaException | SecurityViolationException | ExpressionEvaluationException | CommunicationException | ConfigurationException | ObjectAlreadyExistsException e) { String message = "Exception while handling work item trigger for ID " + workItemId + ": " + e.getMessage(); @@ -155,7 +159,9 @@ public Collection handle(PrismObject obje OperationResult result = triggersResult .createSubresult(WfTimedActionTriggerHandler.class.getName() + ".handleCompletions"); try { - workItemManager.completeWorkItems(completeActions, opTask, result); + CompleteWorkItemsRequest request = new CompleteWorkItemsRequest(aCase.getOid(), causeHolder.getValue()); + request.getCompletions().addAll(completeActions); + workItemManager.completeWorkItems(request, opTask, result); result.recordSuccessIfUnknown(); } catch (Throwable t) { LoggingUtils.logUnexpectedException(LOGGER, "Couldn't handler work item completion", t); @@ -180,11 +186,13 @@ private void executeNotifications(Duration timeBeforeAction, AbstractWorkItemAct new WorkItemAllocationChangeOperationInfo(operationKind, assigneesAndDeputies, null); WorkItemOperationSourceInfo sourceInfo = new WorkItemOperationSourceInfo(null, cause, action); notificationHelper.notifyWorkItemAllocationChangeCurrentActors(workItem, operationInfo, sourceInfo, timeBeforeAction, - aCase, opTask, result); + aCase, result); } private void executeActions(WorkItemActionsType actions, CaseWorkItemType workItem, CaseType aCase, - List completeActions, Task opTask, OperationResult result) + List completeActions, + Holder causeHolder, + Task opTask, OperationResult result) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, ObjectAlreadyExistsException { for (WorkItemNotificationActionType notificationAction : actions.getNotify()) { @@ -198,9 +206,9 @@ private void executeActions(WorkItemActionsType actions, CaseWorkItemType workIt } CompleteWorkItemActionType complete = actions.getComplete(); if (complete != null) { - completeActions.add(new CompleteAction(WorkItemId.of(workItem), workItem, - defaultIfNull(complete.getOutcome(), SchemaConstants.MODEL_APPROVAL_OUTCOME_REJECT), - null, null, WfContextUtil.createCause(complete))); + completeActions.add(new CompleteWorkItemsRequest.SingleCompletion(workItem.getId(), + defaultIfNull(complete.getOutcome(), SchemaConstants.MODEL_APPROVAL_OUTCOME_REJECT), null, null)); + causeHolder.setValue(WfContextUtil.createCause(complete)); } } @@ -253,10 +261,10 @@ private void executeNotificationAction(CaseWorkItemType workItem, @NotNull WorkI if (BooleanUtils.isNotFalse(notificationAction.isPerAssignee())) { List assigneesAndDeputies = miscHelper.getAssigneesAndDeputies(workItem, opTask, result); for (ObjectReferenceType assigneeOrDeputy : assigneesAndDeputies) { - notificationHelper.notifyWorkItemCustom(assigneeOrDeputy, workItem, cause, aCase, opTask, notificationAction, result); + notificationHelper.notifyWorkItemCustom(assigneeOrDeputy, workItem, cause, aCase, notificationAction, result); } } else { - notificationHelper.notifyWorkItemCustom(null, workItem, cause, aCase, opTask, notificationAction, result); + notificationHelper.notifyWorkItemCustom(null, workItem, cause, aCase, notificationAction, result); } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ModelHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ModelHelper.java index c15b6a60983..deed5599653 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ModelHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ModelHelper.java @@ -29,11 +29,11 @@ import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; import com.evolveum.midpoint.wf.impl.engine.WorkflowEngine; import com.evolveum.midpoint.wf.impl.util.MiscHelper; import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemObjectsType; import org.jetbrains.annotations.NotNull; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; @@ -79,13 +79,12 @@ public class ModelHelper { */ public StartInstruction createInstructionForRoot(ChangeProcessor changeProcessor, @NotNull ModelInvocationContext ctx, ModelContext contextForRootCase, OperationResult result) throws SchemaException { - StartInstruction instruction = StartInstruction.create(changeProcessor); + StartInstruction instruction = StartInstruction.create(changeProcessor, SystemObjectsType.ARCHETYPE_OPERATION_REQUEST.value()); instruction.setModelContext(contextForRootCase); LocalizableMessage rootCaseName = determineRootCaseName(ctx); String rootCaseNameInDefaultLocale = localizationService.translate(rootCaseName, Locale.getDefault()); - instruction.setLocalizableName(rootCaseName); - instruction.setName(rootCaseNameInDefaultLocale); + instruction.setName(rootCaseNameInDefaultLocale, rootCaseName); instruction.setObjectRef(ctx); instruction.setRequesterRef(ctx.getRequestor(result)); return instruction; @@ -129,10 +128,9 @@ private LocalizableMessage determineRootCaseName(ModelInvocationContext ctx) * @param result * @return reference to a newly created job * @throws SchemaException - * @throws ObjectNotFoundException */ - public CaseType addRoot(StartInstruction rootInstruction, Task task, - OperationResult result) throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { + public CaseType addRoot(StartInstruction rootInstruction, Task task, OperationResult result) + throws SchemaException, ObjectAlreadyExistsException { CaseType rootCase = addCase(rootInstruction, task, result); result.setCaseOid(rootCase.getOid()); //wfTaskUtil.setRootTaskOidImmediate(task, rootCase.getOid(), result); @@ -172,20 +170,14 @@ public String dumpCase(CaseType aCase) { /** * TODO - * @param parentCase the task that will be the parent of the task of newly created wf-task; it may be null * @param instruction the wf task creation instruction - * @param task + * */ public CaseType addCase(StartInstruction instruction, Task task, OperationResult result) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { + throws SchemaException, ObjectAlreadyExistsException { LOGGER.trace("Processing start instruction:\n{}", instruction.debugDumpLazily()); CaseType aCase = instruction.getCase(); repositoryService.addObject(aCase.asPrismObject(), null, result); - - if (instruction.startsWorkflowProcess()) { - EngineInvocationContext ctx = new EngineInvocationContext(aCase, task); - workflowEngine.startProcessInstance(ctx, result); - } return aCase; } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/StartInstruction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/StartInstruction.java index 5a157057bad..2ebe0a6b515 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/StartInstruction.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/StartInstruction.java @@ -22,16 +22,19 @@ import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ObjectDelta; +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.LocalizationUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.SingleLocalizableMessage; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringTranslationType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; import org.jetbrains.annotations.NotNull; @@ -48,29 +51,32 @@ */ public class StartInstruction implements DebugDumpable { + @SuppressWarnings("unused") private static final Trace LOGGER = TraceManager.getTrace(StartInstruction.class); protected final CaseType aCase; private final ChangeProcessor changeProcessor; //region Constructors - protected StartInstruction(@NotNull ChangeProcessor changeProcessor) { + protected StartInstruction(@NotNull ChangeProcessor changeProcessor, @NotNull String archetypeOid) { this.changeProcessor = changeProcessor; PrismContext prismContext = changeProcessor.getPrismContext(); aCase = new CaseType(prismContext); + ObjectReferenceType approvalArchetypeRef = ObjectTypeUtil.createObjectRef(archetypeOid, ObjectTypes.ARCHETYPE); + aCase.getArchetypeRef().add(approvalArchetypeRef.clone()); + aCase.beginAssignment().targetRef(approvalArchetypeRef).end(); aCase.setWorkflowContext(new WfContextType(prismContext)); aCase.setMetadata(new MetadataType(prismContext)); aCase.getMetadata().setCreateTimestamp(createXMLGregorianCalendar(new Date())); } - @SuppressWarnings("unchecked") - public static StartInstruction create(ChangeProcessor changeProcessor) { - return new StartInstruction(changeProcessor); + public static StartInstruction create(ChangeProcessor changeProcessor, @NotNull String archetypeOid) { + return new StartInstruction(changeProcessor, archetypeOid); } //endregion // region Getters and setters - public ChangeProcessor getChangeProcessor() { + protected ChangeProcessor getChangeProcessor() { return changeProcessor; } @@ -80,14 +86,24 @@ public ChangeProcessor getChangeProcessor() { // aCase.getWorkflowContext().setProcessInstanceName(name); // } - public void setLocalizableName(LocalizableMessage name) { - aCase.setLocalizableName(LocalizationUtil.createLocalizableMessageType(name)); - } - public void setName(String name) { aCase.setName(PolyStringType.fromOrig(name)); } + public void setName(String name, LocalizableMessage localizable) { + PolyStringType polyName = PolyStringType.fromOrig(name); + if (localizable != null) { + if (!(localizable instanceof SingleLocalizableMessage)) { + throw new UnsupportedOperationException( + "Localizable messages other than SingleLocalizableMessage cannot be used for approval case names: " + + localizable); + } else { + polyName.setTranslation(PolyStringTranslationType.fromLocalizableMessage((SingleLocalizableMessage) localizable)); + } + } + aCase.setName(polyName); + } + public boolean startsWorkflowProcess() { return getWfContext().getProcessSpecificState() != null; } @@ -171,6 +187,12 @@ public void setParent(CaseType parent) { //region "Output" methods public CaseType getCase() { + if (startsWorkflowProcess()) { + // These cases will be open explicitly using the workflow engine + aCase.setState(SchemaConstants.CASE_STATE_CREATED); + } else { + aCase.setState(SchemaConstants.CASE_STATE_OPEN); + } return aCase; } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GeneralChangeProcessor.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GeneralChangeProcessor.java index 99abc3359ba..ff4bec52a00 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GeneralChangeProcessor.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GeneralChangeProcessor.java @@ -141,7 +141,7 @@ private HookOperationMode applyScenario(GeneralChangeProcessorScenarioType scena return HookOperationMode.BACKGROUND; - } catch (SchemaException|ObjectNotFoundException|CommunicationException|ConfigurationException|ObjectAlreadyExistsException|ExpressionEvaluationException|RuntimeException|Error e) { + } catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | ObjectAlreadyExistsException | ExpressionEvaluationException | RuntimeException | Error e) { LoggingUtils.logUnexpectedException(LOGGER, "Workflow process(es) could not be started", e); result.recordFatalError("Workflow process(es) could not be started: " + e, e); return HookOperationMode.ERROR; @@ -154,7 +154,7 @@ private HookOperationMode applyScenario(GeneralChangeProcessorScenarioType scena @Override public void onProcessEnd(EngineInvocationContext ctx, OperationResult result) throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - Task task = ctx.opTask; + Task task = ctx.getTask(); // we simply put model context back into parent task // (or if it is null, we set the task to skip model context processing) diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/BaseGcpScenarioBean.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/BaseGcpScenarioBean.java index 7c122eaa4a8..f18d1cd7183 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/BaseGcpScenarioBean.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/scenarios/BaseGcpScenarioBean.java @@ -24,7 +24,7 @@ import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.wf.impl.engine.AuditHelper; +import com.evolveum.midpoint.wf.impl.engine.helpers.AuditHelper; import com.evolveum.midpoint.wf.impl.processors.general.GcpExternalizationHelper; import com.evolveum.midpoint.wf.impl.processors.general.GeneralChangeProcessor; import com.evolveum.midpoint.wf.impl.processors.StartInstruction; @@ -90,7 +90,7 @@ public AuditEventRecord prepareWorkItemDeletedAuditRecord(CaseWorkItemType workI @Override public StartInstruction prepareJobCreationInstruction(GeneralChangeProcessorScenarioType scenarioType, LensContext context, CaseType rootCase, Task taskFromModel, OperationResult result) throws SchemaException { - StartInstruction instruction = StartInstruction.create(generalChangeProcessor); + StartInstruction instruction = StartInstruction.create(generalChangeProcessor, SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value()); // todo reconsider the archetype instruction.setRequesterRef(taskFromModel.getOwner()); instruction.setName("Workflow-monitoring task"); return instruction; diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/ApprovalMetadataHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/ApprovalMetadataHelper.java index 4496bcab86a..f6543d4e870 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/ApprovalMetadataHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/ApprovalMetadataHelper.java @@ -59,7 +59,6 @@ public class ApprovalMetadataHelper { @Autowired private SystemObjectCache systemObjectCache; @Autowired private PrismContext prismContext; @Autowired private MiscHelper miscHelper; - @Autowired private RepositoryService repositoryService; @Autowired private WorkflowManager workflowManager; public void addAssignmentApprovalMetadata(ObjectDelta objectDelta, CaseType aCase, diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpGeneralHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpGeneralHelper.java index a7cb056ff0c..1e651b8e45a 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpGeneralHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpGeneralHelper.java @@ -34,6 +34,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTreeDeltasType; import com.evolveum.midpoint.xml.ns._public.common.common_3.WfPrimaryChangeProcessorStateType; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import java.util.List; @@ -52,7 +53,9 @@ public class PcpGeneralHelper { private static final Trace LOGGER = TraceManager.getTrace(PcpGeneralHelper.class); @Autowired private PrismContext prismContext; - @Autowired private RepositoryService repositoryService; + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; ObjectTreeDeltas retrieveDeltasToProcess(CaseType aCase) throws SchemaException { PrismProperty deltaTypePrismProperty = aCase.asPrismObject() diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpStartInstruction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpStartInstruction.java index a4207f6313b..65cfaf73ebf 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpStartInstruction.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpStartInstruction.java @@ -49,8 +49,8 @@ public class PcpStartInstruction extends StartInstruction { private boolean executeApprovedChangeImmediately; // should the child job execute approved change immediately (i.e. executeModelOperationHandler must be set as well!) - protected PcpStartInstruction(ChangeProcessor changeProcessor) { - super(changeProcessor); + protected PcpStartInstruction(@NotNull ChangeProcessor changeProcessor, @NotNull String archetypeOid) { + super(changeProcessor, archetypeOid); WfPrimaryChangeProcessorStateType state = new WfPrimaryChangeProcessorStateType(getPrismContext()); state.setProcessor(changeProcessor.getClass().getName()); getWfContext().setProcessorSpecificState(state); @@ -63,13 +63,14 @@ public static PcpStartInstruction createItemApprovalInstruction( ItemApprovalProcessStateType processState = new ItemApprovalProcessStateType(prismContext) .approvalSchema(approvalSchemaType) .policyRules(attachedPolicyRules); - PcpStartInstruction instruction = new PcpStartInstruction(changeProcessor); + PcpStartInstruction instruction = new PcpStartInstruction(changeProcessor, + SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value()); instruction.setProcessState(processState); return instruction; } - public static PcpStartInstruction createEmpty(ChangeProcessor changeProcessor) { - return new PcpStartInstruction(changeProcessor); + public static PcpStartInstruction createEmpty(ChangeProcessor changeProcessor, @NotNull String archetypeOid) { + return new PcpStartInstruction(changeProcessor, archetypeOid); } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java index 0f9f02137f7..b544e258705 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java @@ -46,24 +46,29 @@ 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.engine.AuditHelper; +import com.evolveum.midpoint.wf.api.request.OpenCaseRequest; import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; import com.evolveum.midpoint.wf.impl.engine.WorkflowEngine; +import com.evolveum.midpoint.wf.impl.engine.helpers.AuditHelper; +import com.evolveum.midpoint.wf.impl.execution.CaseOperationExecutionTaskHandler; +import com.evolveum.midpoint.wf.impl.execution.ExecutionHelper; import com.evolveum.midpoint.wf.impl.processes.common.StageComputeHelper; import com.evolveum.midpoint.wf.impl.processors.*; import com.evolveum.midpoint.wf.impl.processors.primary.aspect.PrimaryChangeAspect; -import com.evolveum.midpoint.wf.impl.tasks.CaseOperationExecutionTaskHandler; -import com.evolveum.midpoint.wf.impl.processors.StartInstruction; import com.evolveum.midpoint.wf.impl.util.MiscHelper; import com.evolveum.midpoint.wf.util.ApprovalUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.collections4.CollectionUtils; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; import java.util.stream.Collectors; import static com.evolveum.midpoint.audit.api.AuditEventStage.REQUEST; @@ -83,9 +88,12 @@ public class PrimaryChangeProcessor extends BaseChangeProcessor { @Autowired private StageComputeHelper stageComputeHelper; @Autowired private PcpGeneralHelper generalHelper; @Autowired private MiscHelper miscHelper; - @Autowired private WorkflowEngine workflowEngine; + @Autowired private ExecutionHelper executionHelper; @Autowired private TaskManager taskManager; - @Autowired private RepositoryService repositoryService; + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; + @Autowired private WorkflowEngine workflowEngine; private List allChangeAspects = new ArrayList<>(); @@ -256,19 +264,24 @@ private HookOperationMode executeStartInstructions(List ins List allSubcases = new ArrayList<>(instructions.size() + 1); CollectionUtils.addIgnoreNull(allSubcases, case0); + List casesToStart = new ArrayList<>(); + // create the regular (approval) child cases for (PcpStartInstruction instruction : instructions) { instruction.setParent(rootCase); - CaseType wfCase = modelHelper.addCase(instruction, ctx.task, result); - allSubcases.add(wfCase); + CaseType subCase = modelHelper.addCase(instruction, ctx.task, result); + allSubcases.add(subCase); if (instruction.isObjectCreationInstruction()) { if (objectCreationCase == null) { - objectCreationCase = wfCase; + objectCreationCase = subCase; } else { throw new IllegalStateException("More than one case that creates the object: " + - objectCreationCase + " and " + wfCase); + objectCreationCase + " and " + subCase); } } + if (instruction.startsWorkflowProcess()) { + casesToStart.add(subCase.getOid()); + } } // create dependencies @@ -283,19 +296,24 @@ private HookOperationMode executeStartInstructions(List ins generalHelper.addPrerequisites(subcase, prerequisites, result); } + modelHelper.logJobsBeforeStart(rootCase, ctx.task, result); + if (case0 != null) { if (ModelExecuteOptions.isExecuteImmediatelyAfterApproval(ctx.modelContext.getOptions())) { submitExecutionTask(case0, false, result); } else { - workflowEngine.closeCaseInternal(case0, ctx.task, result); + executionHelper.closeCaseInRepository(case0, result); } } - modelHelper.logJobsBeforeStart(rootCase, ctx.task, result); + LOGGER.trace("Starting the cases: {}", casesToStart); + for (String caseToStart : casesToStart) { + workflowEngine.executeRequest(new OpenCaseRequest(caseToStart), ctx.task, result); + } + return HookOperationMode.BACKGROUND; - } catch (SchemaException | ObjectNotFoundException | ObjectAlreadyExistsException | CommunicationException | - ConfigurationException | ExpressionEvaluationException | RuntimeException e) { + } catch (SchemaException | ObjectNotFoundException | ObjectAlreadyExistsException | CommunicationException | ConfigurationException | ExpressionEvaluationException | RuntimeException | SecurityViolationException e) { LoggingUtils.logUnexpectedException(LOGGER, "Workflow process(es) could not be started", e); result.recordFatalError("Workflow process(es) could not be started: " + e, e); return HookOperationMode.ERROR; @@ -305,7 +323,7 @@ private HookOperationMode executeStartInstructions(List ins } private CaseType addRoot(ModelInvocationContext ctx, OperationResult result) - throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { + throws SchemaException, ObjectAlreadyExistsException { LensContext contextForRoot = contextCopyWithNoDelta(ctx.modelContext); StartInstruction instructionForRoot = modelHelper.createInstructionForRoot(this, ctx, contextForRoot, result); @@ -315,7 +333,7 @@ private CaseType addRoot(ModelInvocationContext ctx, OperationResult result) private PcpStartInstruction createInstruction0(ModelInvocationContext ctx, ObjectTreeDeltas changesWithoutApproval, CaseType rootCase) throws SchemaException { if (changesWithoutApproval != null && !changesWithoutApproval.isEmpty()) { - PcpStartInstruction instruction0 = PcpStartInstruction.createEmpty(this); + PcpStartInstruction instruction0 = PcpStartInstruction.createEmpty(this, SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value()); instruction0.setName("Changes that do not require approval"); instruction0.setObjectRef(ctx); instruction0.setDeltasToProcess(changesWithoutApproval); @@ -372,15 +390,22 @@ private LensContext contextCopyWithNoDelta(ModelContext context) { //endregion //region Processing process finish event + + /** + * This method is called OUTSIDE the workflow engine computation - i.e. changes are already committed into repository. + */ @Override public void onProcessEnd(EngineInvocationContext ctx, OperationResult result) throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException, PreconditionViolationException { - ObjectTreeDeltas deltas = prepareDeltaOut(ctx.aCase); - generalHelper.storeResultingDeltas(ctx.aCase, deltas, result); + CaseType currentCase = ctx.getCurrentCase(); + + ObjectTreeDeltas deltas = prepareDeltaOut(currentCase); + generalHelper.storeResultingDeltas(currentCase, deltas, result); + // note: resulting deltas are not stored in currentCase object (these are in repo only) // here we should execute the deltas, if appropriate! - CaseType rootCase = generalHelper.getRootCase(ctx.aCase, result); + CaseType rootCase = generalHelper.getRootCase(currentCase, result); LensContextType modelContext = rootCase.getModelContext(); if (modelContext == null) { throw new IllegalStateException("No model context in root case " + rootCase); @@ -389,42 +414,48 @@ public void onProcessEnd(EngineInvocationContext ctx, OperationResult result) Boolean.TRUE.equals(modelContext.getOptions().isExecuteImmediatelyAfterApproval()); if (immediately) { if (deltas != null) { - LOGGER.debug("Case {} is approved with immediate execution -- let's start the process", ctx.aCase); + LOGGER.debug("Case {} is approved with immediate execution -- let's start the process", currentCase); boolean waiting; - if (!ctx.aCase.getPrerequisiteRef().isEmpty()) { + if (!currentCase.getPrerequisiteRef().isEmpty()) { ObjectQuery query = prismContext.queryFor(CaseType.class) - .id(ctx.aCase.getPrerequisiteRef().stream().map(ObjectReferenceType::getOid).toArray(String[]::new)) + .id(currentCase.getPrerequisiteRef().stream().map(ObjectReferenceType::getOid).toArray(String[]::new)) .and().not().item(CaseType.F_STATE).eq(SchemaConstants.CASE_STATE_CLOSED) .build(); SearchResultList> openPrerequisites = repositoryService .searchObjects(CaseType.class, query, null, result); waiting = !openPrerequisites.isEmpty(); if (waiting) { - LOGGER.debug("Case {} cannot be executed now because of the following open prerequisites: {}", openPrerequisites); + LOGGER.debug("Case {} cannot be executed now because of the following open prerequisites: {} -- the execution task will be created in WAITING state", + currentCase, openPrerequisites); } } else { waiting = false; } - submitExecutionTask(ctx.aCase, waiting, result); + submitExecutionTask(currentCase, waiting, result); } else { - LOGGER.debug("Case {} is rejected (with immediate execution) -- nothing to do here", ctx.aCase, rootCase); - workflowEngine.closeCaseInternal(ctx.aCase, ctx.opTask, result); - workflowEngine.checkDependentCases(ctx.aCase.getParentRef().getOid(), ctx.opTask, result); + LOGGER.debug("Case {} is rejected (with immediate execution) -- nothing to do here", currentCase); + executionHelper.closeCaseInRepository(currentCase, result); + executionHelper.checkDependentCases(currentCase.getParentRef().getOid(), result); } } else { - LOGGER.debug("Case {} is completed; but execution is delayed so let's check other subcases of {}", ctx.aCase, rootCase); - workflowEngine.closeCaseInternal(ctx.aCase, ctx.opTask, result); + LOGGER.debug("Case {} is completed; but execution is delayed so let's check other subcases of {}", + currentCase, rootCase); + executionHelper.closeCaseInRepository(currentCase, result); List subcases = miscHelper.getSubcases(rootCase, result); if (subcases.stream().allMatch(CaseTypeUtil::isClosed)) { LOGGER.debug("All subcases of {} are closed, so let's execute the deltas", rootCase); submitExecutionTask(rootCase, false, result); } else { LOGGER.debug("Some subcases of {} are not closed yet. Delta execution is therefore postponed.", rootCase); + for (CaseType subcase : subcases) { + LOGGER.debug(" - {}: state={} (isClosed={})", subcase, subcase.getState(), CaseTypeUtil.isClosed(subcase)); + } } } } - private void submitExecutionTask(CaseType aCase, boolean waiting, OperationResult result) throws SchemaException, ObjectNotFoundException { + private void submitExecutionTask(CaseType aCase, boolean waiting, OperationResult result) + throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { Task task = taskManager.createTaskInstance("execute"); task.setName("Execution of " + aCase.getName().getOrig()); task.setOwner(getExecutionTaskOwner(result)); @@ -434,6 +465,8 @@ private void submitExecutionTask(CaseType aCase, boolean waiting, OperationResul task.setInitialExecutionStatus(TaskExecutionStatus.WAITING); } taskManager.switchToBackground(task, result); + + executionHelper.setCaseStateInRepository(aCase, SchemaConstants.CASE_STATE_EXECUTING, result); } private PrismObject getExecutionTaskOwner(OperationResult result) throws SchemaException, ObjectNotFoundException { diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspectHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspectHelper.java index 086d8d8b6fc..1525f454575 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspectHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspectHelper.java @@ -26,14 +26,6 @@ import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.api.context.ModelContext; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; @@ -44,7 +36,6 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.GenericPcpAspectConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PcpAspectConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PrimaryChangeProcessorConfigurationType; import org.apache.velocity.util.StringUtils; diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java index 690b341b3fe..914b20a4e12 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/AssignmentPolicyAspectPart.java @@ -337,8 +337,7 @@ private PcpStartInstruction prepareAssignmentRelatedStartInstruction( // .arg(processNameInDefaultLocale) // .build(), Locale.getDefault()); - instruction.setName(processNameInDefaultLocale); - instruction.setLocalizableName(processName); + instruction.setName(processNameInDefaultLocale, processName); return instruction; } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java index 3bf69f9730c..bcc8e64d229 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/policy/ObjectPolicyAspectPart.java @@ -198,8 +198,7 @@ private void prepareObjectRelatedTaskInstructions( // .arg(processNameInDefaultLocale) // .build(), Locale.getDefault()); - instruction.setName(processNameInDefaultLocale); - instruction.setLocalizableName(processName); + instruction.setName(processNameInDefaultLocale, processName); //instruction.setProcessInstanceName(processNameInDefaultLocale); instructions.add(instruction); diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java new file mode 100644 index 00000000000..7c8e8997844 --- /dev/null +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2010-2019 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; + +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.impl.AbstractModelImplementationIntegrationTest; +import com.evolveum.midpoint.model.impl.lens.Clockwork; +import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReference; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit; +import com.evolveum.midpoint.prism.util.PrismUtil; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.SearchResultList; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.CaseWorkItemUtil; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.security.api.SecurityUtil; +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.test.AbstractIntegrationTest; +import com.evolveum.midpoint.test.Checker; +import com.evolveum.midpoint.test.IntegrationTestTools; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.wf.api.WorkflowManager; +import com.evolveum.midpoint.wf.impl.access.WorkItemManager; +import com.evolveum.midpoint.wf.impl.engine.WorkflowEngine; +import com.evolveum.midpoint.wf.impl.policy.ExpectedWorkItem; +import com.evolveum.midpoint.wf.impl.processors.general.GeneralChangeProcessor; +import com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor; +import com.evolveum.midpoint.wf.impl.util.MiscHelper; +import com.evolveum.midpoint.wf.util.QueryUtils; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; + +import javax.xml.namespace.QName; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.testng.AssertJUnit.*; + +/** + * @author mederly + * + */ +@ContextConfiguration(locations = {"classpath:ctx-workflow-test-main.xml"}) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public abstract class AbstractWfTest extends AbstractModelImplementationIntegrationTest { + + public static final File ROLE_SUPERUSER_FILE = new File(COMMON_DIR, "role-superuser.xml"); + public static final File USER_ADMINISTRATOR_FILE = new File(COMMON_DIR, "user-administrator.xml"); + + protected static final File USER_JACK_FILE = new File(COMMON_DIR, "user-jack.xml"); + + protected static final File ROLE_APPROVER_FILE = new File(COMMON_DIR, "041-role-approver.xml"); + protected static final File ARCHETYPE_OPERATION_REQUEST_FILE = new File(COMMON_DIR, "024-archetype-operation-request.xml"); + protected static final File ARCHETYPE_APPROVAL_CASE_FILE = new File(COMMON_DIR, "025-archetype-approval-case.xml"); + + protected static final String USER_ADMINISTRATOR_OID = SystemObjectsType.USER_ADMINISTRATOR.value(); + + protected String userJackOid; + + @Autowired protected Clockwork clockwork; + @Autowired protected TaskManager taskManager; + @Autowired protected WorkflowManager workflowManager; + @Autowired protected WorkflowEngine workflowEngine; + @Autowired protected WorkItemManager workItemManager; + @Autowired protected PrimaryChangeProcessor primaryChangeProcessor; + @Autowired protected GeneralChangeProcessor generalChangeProcessor; + @Autowired protected SystemObjectCache systemObjectCache; + @Autowired protected RelationRegistry relationRegistry; + @Autowired protected WfTestHelper testHelper; + @Autowired protected MiscHelper miscHelper; + + protected PrismObject userAdministrator; + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + modelService.postInit(initResult); + + PrismObject sysconfig = prismContext.parseObject(getSystemConfigurationFile()); + updateSystemConfiguration(sysconfig.asObjectable()); + repoAddObject(sysconfig, initResult); + + repoAddObjectFromFile(ROLE_SUPERUSER_FILE, initResult); + userAdministrator = repoAddObjectFromFile(USER_ADMINISTRATOR_FILE, initResult); + login(userAdministrator); + + repoAddObjectFromFile(ROLE_APPROVER_FILE, initResult).getOid(); + repoAddObjectFromFile(ARCHETYPE_OPERATION_REQUEST_FILE, initResult).getOid(); + repoAddObjectFromFile(ARCHETYPE_APPROVAL_CASE_FILE, initResult).getOid(); + + userJackOid = repoAddObjectFromFile(USER_JACK_FILE, initResult).getOid(); + } + + @Override + protected PrismObject getDefaultActor() { + return userAdministrator; + } + + protected void updateSystemConfiguration(SystemConfigurationType systemConfiguration) throws SchemaException, IOException { + // nothing to do by default + } + + protected abstract File getSystemConfigurationFile(); + + protected Map createResultMap(String oid, WorkflowResult result) { + Map retval = new HashMap<>(); + retval.put(oid, result); + return retval; + } + + protected Map createResultMap(String oid, WorkflowResult approved, String oid2, + WorkflowResult approved2) { + Map retval = new HashMap<>(); + retval.put(oid, approved); + retval.put(oid2, approved2); + return retval; + } + + protected Map createResultMap(String oid, WorkflowResult approved, String oid2, + WorkflowResult approved2, String oid3, WorkflowResult approved3) { + Map retval = new HashMap<>(); + retval.put(oid, approved); + retval.put(oid2, approved2); + retval.put(oid3, approved3); + return retval; + } + + protected void checkAuditRecords(Map expectedResults) { + checkWorkItemAuditRecords(expectedResults); + checkWfProcessAuditRecords(expectedResults); + } + + protected void checkWorkItemAuditRecords(Map expectedResults) { + WfTestUtil.checkWorkItemAuditRecords(expectedResults, dummyAuditService); + } + + protected void checkWfProcessAuditRecords(Map expectedResults) { + WfTestUtil.checkWfProcessAuditRecords(expectedResults, dummyAuditService); + } + + protected void removeAllAssignments(String oid, OperationResult result) throws Exception { + PrismObject user = repositoryService.getObject(UserType.class, oid, null, result); + for (AssignmentType at : user.asObjectable().getAssignment()) { + ObjectDelta delta = prismContext.deltaFactory().object() + .createModificationDeleteContainer(UserType.class, oid, UserType.F_ASSIGNMENT, + at.asPrismContainerValue().clone()); + repositoryService.modifyObject(UserType.class, oid, delta.getModifications(), result); + display("Removed assignment " + at + " from " + user); + } + } + + protected CaseWorkItemType getWorkItem(Task task, OperationResult result) + throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { + //Collection> options = GetOperationOptions.resolveItemsNamed(CaseWorkItemType.F_TASK_REF); + SearchResultList itemsAll = modelService.searchContainers(CaseWorkItemType.class, getOpenItemsQuery(), null, task, result); + if (itemsAll.size() != 1) { + System.out.println("Unexpected # of work items: " + itemsAll.size()); + for (CaseWorkItemType workItem : itemsAll) { + System.out.println(PrismUtil.serializeQuietly(prismContext, workItem)); + } + } + assertEquals("Wrong # of total work items", 1, itemsAll.size()); + return itemsAll.get(0); + } + + protected SearchResultList getWorkItems(Task task, OperationResult result) throws Exception { + return modelService.searchContainers(CaseWorkItemType.class, getOpenItemsQuery(), null, task, result); + } + + protected void displayWorkItems(String title, List workItems) { + workItems.forEach(wi -> display(title, wi)); + } + + protected ObjectReferenceType ort(String oid) { + return ObjectTypeUtil.createObjectRef(oid, ObjectTypes.USER); + } + + protected PrismReferenceValue prv(String oid) { + return ObjectTypeUtil.createObjectRef(oid, ObjectTypes.USER).asReferenceValue(); + } + + protected PrismReference ref(List orts) { + PrismReference rv = prismContext.itemFactory().createReference(new QName("dummy")); + orts.forEach(ort -> { + try { + rv.add(ort.asReferenceValue().clone()); + } catch (SchemaException e) { + throw new IllegalStateException(e); + } + }); + return rv; + } + + protected PrismReference ref(ObjectReferenceType ort) { + return ref(Collections.singletonList(ort)); + } + + protected void assertObjectInTaskTree(Task rootTask, String oid, boolean checkObjectOnSubtasks, OperationResult result) + throws SchemaException { + assertObjectInTask(rootTask, oid); + if (checkObjectOnSubtasks) { + for (Task task : rootTask.listSubtasks(result)) { + assertObjectInTask(task, oid); + } + } + } + + protected void assertObjectInTask(Task task, String oid) { + assertEquals("Missing or wrong object OID in task " + task, oid, task.getObjectOid()); + } + + protected void waitForTaskClose(final Task task, final int timeout) throws Exception { + final OperationResult waitResult = new OperationResult(AbstractIntegrationTest.class + ".waitForTaskClose"); + Checker checker = new Checker() { + @Override + public boolean check() throws CommonException { + task.refresh(waitResult); + OperationResult result = task.getResult(); + if (verbose) + display("Check result", result); + return task.getExecutionStatus() == TaskExecutionStatus.CLOSED; + } + + @Override + public void timeout() { + try { + task.refresh(waitResult); + } catch (Throwable e) { + display("Exception during task refresh", e); + } + OperationResult result = task.getResult(); + display("Result of timed-out task", result); + assert false : "Timeout (" + timeout + ") while waiting for " + task + " to finish. Last result " + result; + } + }; + IntegrationTestTools.waitFor("Waiting for " + task + " finish", checker, timeout, 1000); + } + + protected String getTargetOid(CaseWorkItemType caseWorkItem) { + ObjectReferenceType targetRef = CaseWorkItemUtil.getCaseRequired(caseWorkItem).getTargetRef(); + assertNotNull("targetRef not found", targetRef); + String roleOid = targetRef.getOid(); + assertNotNull("requested role OID not found", roleOid); + return roleOid; + } + + protected void checkTargetOid(CaseWorkItemType caseWorkItem, String expectedOid) + throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, + SecurityViolationException { + String realOid = getTargetOid(caseWorkItem); + assertEquals("Unexpected target OID", expectedOid, realOid); + } + + + protected void assertDeltasEqual(String message, ObjectDelta expectedDelta, ObjectDelta realDelta) { +// removeOldValues(expectedDelta); +// removeOldValues(realDelta); + if (!expectedDelta.equivalent(realDelta)) { + fail(message + "\nExpected:\n" + expectedDelta.debugDump() + "\nReal:\n" + realDelta.debugDump()); + } + } + +// private void removeOldValues(ObjectDelta delta) { +// if (delta.isModify()) { +// delta.getModifications().forEach(mod -> mod.setEstimatedOldValues(null)); +// } +// } + + protected void assertNoObject(ObjectType object) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + assertNull("Object was created but it shouldn't be", + searchObjectByName(object.getClass(), object.getName().getOrig())); + } + + protected void assertNoObject(PrismObject object) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + assertNoObject(object.asObjectable()); + } + + protected void assertObject(T object) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + PrismObject objectFromRepo = searchObjectByName((Class) object.getClass(), object.getName().getOrig()); + assertNotNull("Object " + object + " was not created", objectFromRepo); + objectFromRepo.removeItem(ObjectType.F_METADATA, Item.class); + objectFromRepo.removeItem(ObjectType.F_OPERATION_EXECUTION, Item.class); + if (!object.equals(objectFromRepo.asObjectable())) { + System.out.println("Expected:\n" + prismContext.xmlSerializer().serialize(object.asPrismObject())); + System.out.println("Actual:\n" + prismContext.xmlSerializer().serialize(objectFromRepo)); + } + assertEquals("Object is different from the one that was expected", object, objectFromRepo.asObjectable()); + } + + protected void checkVisibleWorkItem(ExpectedWorkItem expectedWorkItem, int count, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException, CommunicationException { + S_AtomicFilterExit q = QueryUtils + .filterForAssignees(prismContext.queryFor(CaseWorkItemType.class), SecurityUtil.getPrincipal(), + OtherPrivilegesLimitationType.F_APPROVAL_WORK_ITEMS, relationRegistry); + q = q.and().item(CaseWorkItemType.F_CLOSE_TIMESTAMP).isNull(); + List currentWorkItems = modelService.searchContainers(CaseWorkItemType.class, q.build(), null, task, result); + long found = currentWorkItems.stream().filter(wi -> expectedWorkItem == null || expectedWorkItem.matches(wi)).count(); + assertEquals("Wrong # of matching work items", count, found); + } + + protected ObjectQuery getOpenItemsQuery() { + return prismContext.queryFor(CaseWorkItemType.class) + .item(CaseWorkItemType.F_CLOSE_TIMESTAMP).isNull() + .build(); + } +} diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/WfTestHelper.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/WfTestHelper.java index b449197b270..4d9ff5a9048 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/WfTestHelper.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/WfTestHelper.java @@ -31,6 +31,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import java.util.List; @@ -48,7 +49,9 @@ public class WfTestHelper { private boolean verbose = false; - @Autowired private RepositoryService repositoryService; + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; public static CaseType findAndRemoveCase0(List subcases) { CaseType case0 = null; diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/legacy/AbstractWfTestLegacy.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/legacy/AbstractWfTestLegacy.java index 680ff36e85b..cb7818dd3dc 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/legacy/AbstractWfTestLegacy.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/legacy/AbstractWfTestLegacy.java @@ -307,7 +307,7 @@ void executeTest(String testName, String focusOid, TestDetails testDetails) thro //String taskId = processInstance.getWorkItems().get(0).getWorkItemId(); //WorkItemDetailed workItemDetailed = wfDataAccessor.getWorkItemDetailsById(taskId, result); - SearchResultList workItems = workflowEngine.getWorkItemsForCase(subcase.getOid(), null, result); + List workItems = getWorkItemsForCase(subcase.getOid(), null, result); CaseWorkItemType workItem = MiscUtil.extractSingleton(workItems); assertNotNull("work item not found", workItem); diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/legacy/TestUserChangeApprovalLegacy.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/legacy/TestUserChangeApprovalLegacy.java index c82170970d5..ae3fcdc6a75 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/legacy/TestUserChangeApprovalLegacy.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/legacy/TestUserChangeApprovalLegacy.java @@ -159,6 +159,7 @@ protected void assertWfContextAfterClockworkRun(CaseType rootCase, List> options1 = schemaHelper.getOperationOptionsBuilder() diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java index 0b672f9abd3..f626d498b8d 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/AbstractWfTestPolicy.java @@ -20,40 +20,33 @@ import com.evolveum.midpoint.model.api.context.ModelState; import com.evolveum.midpoint.model.api.hooks.HookOperationMode; import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.impl.AbstractModelImplementationIntegrationTest; import com.evolveum.midpoint.model.impl.lens.Clockwork; import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.delta.DeltaFactory; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit; -import com.evolveum.midpoint.prism.util.PrismUtil; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.*; -import com.evolveum.midpoint.security.api.SecurityUtil; +import com.evolveum.midpoint.schema.util.CaseTypeUtil; +import com.evolveum.midpoint.schema.util.CaseWorkItemUtil; +import com.evolveum.midpoint.schema.util.WfContextUtil; +import com.evolveum.midpoint.schema.util.WorkItemId; 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.test.AbstractIntegrationTest; -import com.evolveum.midpoint.test.Checker; -import com.evolveum.midpoint.test.IntegrationTestTools; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.wf.api.WorkflowManager; +import com.evolveum.midpoint.wf.impl.AbstractWfTest; import com.evolveum.midpoint.wf.impl.WfTestHelper; import com.evolveum.midpoint.wf.impl.WfTestUtil; +import com.evolveum.midpoint.wf.impl.access.WorkItemManager; import com.evolveum.midpoint.wf.impl.engine.WorkflowEngine; -import com.evolveum.midpoint.wf.impl.WorkflowResult; -import com.evolveum.midpoint.wf.impl.util.MiscHelper; import com.evolveum.midpoint.wf.impl.processors.general.GeneralChangeProcessor; import com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor; -import com.evolveum.midpoint.wf.util.QueryUtils; +import com.evolveum.midpoint.wf.impl.util.MiscHelper; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -61,18 +54,17 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; -import javax.xml.namespace.QName; import java.io.File; -import java.io.IOException; import java.util.*; import static com.evolveum.midpoint.prism.PrismConstants.T_PARENT; import static com.evolveum.midpoint.schema.GetOperationOptions.createRetrieve; import static com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType.*; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType.F_ASSIGNEE_REF; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType.F_ORIGINAL_ASSIGNEE_REF; import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType.F_WORKFLOW_CONTEXT; import static com.evolveum.midpoint.xml.ns._public.common.common_3.WfContextType.F_PROCESSOR_SPECIFIC_STATE; import static com.evolveum.midpoint.xml.ns._public.common.common_3.WfPrimaryChangeProcessorStateType.F_DELTAS_TO_PROCESS; -import static com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType.*; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.testng.AssertJUnit.*; @@ -83,14 +75,11 @@ */ @ContextConfiguration(locations = {"classpath:ctx-workflow-test-main.xml"}) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class AbstractWfTestPolicy extends AbstractModelImplementationIntegrationTest { +public class AbstractWfTestPolicy extends AbstractWfTest { protected static final File TEST_RESOURCE_DIR = new File("src/test/resources/policy"); private static final File SYSTEM_CONFIGURATION_FILE = new File(TEST_RESOURCE_DIR, "system-configuration.xml"); - public static final File ROLE_SUPERUSER_FILE = new File(TEST_RESOURCE_DIR, "role-superuser.xml"); - public static final File USER_ADMINISTRATOR_FILE = new File(TEST_RESOURCE_DIR, "user-administrator.xml"); - protected static final File USER_JACK_FILE = new File(TEST_RESOURCE_DIR, "user-jack.xml"); protected static final File USER_JACK_DEPUTY_FILE = new File(TEST_RESOURCE_DIR, "user-jack-deputy.xml"); // delegation is created only when needed protected static final File USER_BOB_FILE = new File(TEST_RESOURCE_DIR, "user-bob.xml"); protected static final File USER_CHUCK_FILE = new File(TEST_RESOURCE_DIR, "user-chuck.xml"); @@ -105,7 +94,6 @@ public class AbstractWfTestPolicy extends AbstractModelImplementationIntegration protected static final File USER_SECURITY_APPROVER_DEPUTY_FILE = new File(TEST_RESOURCE_DIR, "user-security-approver-deputy.xml"); protected static final File USER_SECURITY_APPROVER_DEPUTY_LIMITED_FILE = new File(TEST_RESOURCE_DIR, "user-security-approver-deputy-limited.xml"); - protected static final File ROLE_APPROVER_FILE = new File(TEST_RESOURCE_DIR, "041-role-approver.xml"); protected static final File METAROLE_DEFAULT_FILE = new File(TEST_RESOURCE_DIR, "metarole-default.xml"); protected static final File METAROLE_SECURITY_FILE = new File(TEST_RESOURCE_DIR, "metarole-security.xml"); // following 2 are not used by default (assigned when necessary) @@ -134,7 +122,6 @@ public class AbstractWfTestPolicy extends AbstractModelImplementationIntegration protected static final String USER_ADMINISTRATOR_OID = SystemObjectsType.USER_ADMINISTRATOR.value(); - protected String userJackOid; protected String userJackDeputyOid; protected String userBobOid; protected String userChuckOid; @@ -149,7 +136,6 @@ public class AbstractWfTestPolicy extends AbstractModelImplementationIntegration protected String userSecurityApproverDeputyOid; protected String userSecurityApproverDeputyLimitedOid; - protected String roleApproverOid; protected String metaroleDefaultOid; protected String metaroleSecurityOid; protected String metarolePruneTest2xRolesOid; @@ -179,6 +165,7 @@ public class AbstractWfTestPolicy extends AbstractModelImplementationIntegration @Autowired protected TaskManager taskManager; @Autowired protected WorkflowManager workflowManager; @Autowired protected WorkflowEngine workflowEngine; + @Autowired protected WorkItemManager workItemManager; @Autowired protected PrimaryChangeProcessor primaryChangeProcessor; @Autowired protected GeneralChangeProcessor generalChangeProcessor; @Autowired protected SystemObjectCache systemObjectCache; @@ -186,28 +173,15 @@ public class AbstractWfTestPolicy extends AbstractModelImplementationIntegration @Autowired protected WfTestHelper testHelper; @Autowired protected MiscHelper miscHelper; - protected PrismObject userAdministrator; - @Override public void initSystem(Task initTask, OperationResult initResult) throws Exception { super.initSystem(initTask, initResult); - modelService.postInit(initResult); - - PrismObject sysconfig = prismContext.parseObject(getSystemConfigurationFile()); - updateSystemConfiguration(sysconfig.asObjectable()); - repoAddObject(sysconfig, initResult); - - repoAddObjectFromFile(ROLE_SUPERUSER_FILE, initResult); - userAdministrator = repoAddObjectFromFile(USER_ADMINISTRATOR_FILE, initResult); - login(userAdministrator); - roleApproverOid = repoAddObjectFromFile(ROLE_APPROVER_FILE, initResult).getOid(); metaroleDefaultOid = repoAddObjectFromFile(METAROLE_DEFAULT_FILE, initResult).getOid(); metaroleSecurityOid = repoAddObjectFromFile(METAROLE_SECURITY_FILE, initResult).getOid(); metarolePruneTest2xRolesOid = repoAddObjectFromFile(METAROLE_PRUNE_TEST2X_ROLES_FILE, initResult).getOid(); metaroleApproveUnassign = repoAddObjectFromFile(METAROLE_APPROVE_UNASSIGN_FILE, initResult).getOid(); - userJackOid = repoAddObjectFromFile(USER_JACK_FILE, initResult).getOid(); userJackDeputyOid = repoAddObjectFromFile(USER_JACK_DEPUTY_FILE, initResult).getOid(); userBobOid = repoAddObjectFromFile(USER_BOB_FILE, initResult).getOid(); userChuckOid = repoAddObjectFromFile(USER_CHUCK_FILE, initResult).getOid(); @@ -242,15 +216,6 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti userTemplateAssigningRole1aOidAfter = repoAddObjectFromFile(USER_TEMPLATE_ASSIGNING_ROLE_1A_AFTER, initResult).getOid(); } - @Override - protected PrismObject getDefaultActor() { - return userAdministrator; - } - - protected void updateSystemConfiguration(SystemConfigurationType systemConfiguration) throws SchemaException, IOException { - // nothing to do by default - } - protected File getSystemConfigurationFile() { return SYSTEM_CONFIGURATION_FILE; } @@ -264,53 +229,6 @@ protected void importLead1Deputies(Task task, OperationResult result) throws Exc userLead1Deputy2Oid = addAndRecomputeUser(USER_LEAD1_DEPUTY_2_FILE, task, result); } - protected Map createResultMap(String oid, WorkflowResult result) { - Map retval = new HashMap<>(); - retval.put(oid, result); - return retval; - } - - protected Map createResultMap(String oid, WorkflowResult approved, String oid2, - WorkflowResult approved2) { - Map retval = new HashMap<>(); - retval.put(oid, approved); - retval.put(oid2, approved2); - return retval; - } - - protected Map createResultMap(String oid, WorkflowResult approved, String oid2, - WorkflowResult approved2, String oid3, WorkflowResult approved3) { - Map retval = new HashMap<>(); - retval.put(oid, approved); - retval.put(oid2, approved2); - retval.put(oid3, approved3); - return retval; - } - - protected void checkAuditRecords(Map expectedResults) { - checkWorkItemAuditRecords(expectedResults); - checkWfProcessAuditRecords(expectedResults); - } - - protected void checkWorkItemAuditRecords(Map expectedResults) { - WfTestUtil.checkWorkItemAuditRecords(expectedResults, dummyAuditService); - } - - protected void checkWfProcessAuditRecords(Map expectedResults) { - WfTestUtil.checkWfProcessAuditRecords(expectedResults, dummyAuditService); - } - - protected void removeAllAssignments(String oid, OperationResult result) throws Exception { - PrismObject user = repositoryService.getObject(UserType.class, oid, null, result); - for (AssignmentType at : user.asObjectable().getAssignment()) { - ObjectDelta delta = prismContext.deltaFactory().object() - .createModificationDeleteContainer(UserType.class, oid, UserType.F_ASSIGNMENT, - at.asPrismContainerValue().clone()); - repositoryService.modifyObject(UserType.class, oid, delta.getModifications(), result); - display("Removed assignment " + at + " from " + user); - } - } - public void createObject(final String TEST_NAME, ObjectType object, boolean immediate, boolean approve, String assigneeOid) throws Exception { ObjectDelta addObjectDelta = DeltaFactory.Object.createAddDelta((PrismObject) object.asPrismObject()); @@ -485,52 +403,6 @@ protected Boolean decideOnApproval(CaseWorkItemType caseWorkItem) throws Excepti }, 1); } - protected CaseWorkItemType getWorkItem(Task task, OperationResult result) - throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { - //Collection> options = GetOperationOptions.resolveItemsNamed(CaseWorkItemType.F_TASK_REF); - SearchResultList itemsAll = modelService.searchContainers(CaseWorkItemType.class, getOpenItemsQuery(), null, task, result); - if (itemsAll.size() != 1) { - System.out.println("Unexpected # of work items: " + itemsAll.size()); - for (CaseWorkItemType workItem : itemsAll) { - System.out.println(PrismUtil.serializeQuietly(prismContext, workItem)); - } - } - assertEquals("Wrong # of total work items", 1, itemsAll.size()); - return itemsAll.get(0); - } - - protected SearchResultList getWorkItems(Task task, OperationResult result) throws Exception { - return modelService.searchContainers(CaseWorkItemType.class, getOpenItemsQuery(), null, task, result); - } - - protected void displayWorkItems(String title, List workItems) { - workItems.forEach(wi -> display(title, wi)); - } - - protected ObjectReferenceType ort(String oid) { - return ObjectTypeUtil.createObjectRef(oid, ObjectTypes.USER); - } - - protected PrismReferenceValue prv(String oid) { - return ObjectTypeUtil.createObjectRef(oid, ObjectTypes.USER).asReferenceValue(); - } - - protected PrismReference ref(List orts) { - PrismReference rv = prismContext.itemFactory().createReference(new QName("dummy")); - orts.forEach(ort -> { - try { - rv.add(ort.asReferenceValue().clone()); - } catch (SchemaException e) { - throw new IllegalStateException(e); - } - }); - return rv; - } - - protected PrismReference ref(ObjectReferenceType ort) { - return ref(Collections.singletonList(ort)); - } - protected abstract class TestDetails { protected LensContext createModelContext(OperationResult result) throws Exception { return null; @@ -628,7 +500,7 @@ protected void executeTest(String testName, TestDetails te // now check the workflow state String caseOid = subcase.getOid(); - SearchResultList caseWorkItems = workflowEngine.getWorkItemsForCase(caseOid, null, result); + List caseWorkItems = getWorkItemsForCase(caseOid, null, result); assertFalse("work item not found", caseWorkItems.isEmpty()); for (CaseWorkItemType caseWorkItem : caseWorkItems) { @@ -695,47 +567,6 @@ protected void executeTest(String testName, TestDetails te display("Output context", modelContext); } - protected void assertObjectInTaskTree(Task rootTask, String oid, boolean checkObjectOnSubtasks, OperationResult result) - throws SchemaException { - assertObjectInTask(rootTask, oid); - if (checkObjectOnSubtasks) { - for (Task task : rootTask.listSubtasks(result)) { - assertObjectInTask(task, oid); - } - } - } - - protected void assertObjectInTask(Task task, String oid) { - assertEquals("Missing or wrong object OID in task " + task, oid, task.getObjectOid()); - } - - protected void waitForTaskClose(final Task task, final int timeout) throws Exception { - final OperationResult waitResult = new OperationResult(AbstractIntegrationTest.class + ".waitForTaskClose"); - Checker checker = new Checker() { - @Override - public boolean check() throws CommonException { - task.refresh(waitResult); - OperationResult result = task.getResult(); - if (verbose) - display("Check result", result); - return task.getExecutionStatus() == TaskExecutionStatus.CLOSED; - } - - @Override - public void timeout() { - try { - task.refresh(waitResult); - } catch (Throwable e) { - display("Exception during task refresh", e); - } - OperationResult result = task.getResult(); - display("Result of timed-out task", result); - assert false : "Timeout (" + timeout + ") while waiting for " + task + " to finish. Last result " + result; - } - }; - IntegrationTestTools.waitFor("Waiting for " + task + " finish", checker, timeout, 1000); - } - protected void assertWfContextAfterClockworkRun(CaseType rootCase, List subcases, List workItems, OperationResult result, String objectOid, @@ -944,51 +775,4 @@ public List getApprovalSequence() { }, expectedSubTaskCount); } - protected void assertDeltasEqual(String message, ObjectDelta expectedDelta, ObjectDelta realDelta) { -// removeOldValues(expectedDelta); -// removeOldValues(realDelta); - if (!expectedDelta.equivalent(realDelta)) { - fail(message + "\nExpected:\n" + expectedDelta.debugDump() + "\nReal:\n" + realDelta.debugDump()); - } - } - -// private void removeOldValues(ObjectDelta delta) { -// if (delta.isModify()) { -// delta.getModifications().forEach(mod -> mod.setEstimatedOldValues(null)); -// } -// } - - protected void assertNoObject(ObjectType object) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - assertNull("Object was created but it shouldn't be", - searchObjectByName(object.getClass(), object.getName().getOrig())); - } - - protected void assertNoObject(PrismObject object) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - assertNoObject(object.asObjectable()); - } - - protected void assertObject(T object) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - PrismObject objectFromRepo = searchObjectByName((Class) object.getClass(), object.getName().getOrig()); - assertNotNull("Object " + object + " was not created", objectFromRepo); - objectFromRepo.removeItem(ObjectType.F_METADATA, Item.class); - objectFromRepo.removeItem(ObjectType.F_OPERATION_EXECUTION, Item.class); - assertEquals("Object is different from the one that was expected", object, objectFromRepo.asObjectable()); - } - - protected void checkVisibleWorkItem(ExpectedWorkItem expectedWorkItem, int count, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException, CommunicationException { - S_AtomicFilterExit q = QueryUtils - .filterForAssignees(prismContext.queryFor(CaseWorkItemType.class), SecurityUtil.getPrincipal(), - OtherPrivilegesLimitationType.F_APPROVAL_WORK_ITEMS, relationRegistry); - q = q.and().item(CaseWorkItemType.F_CLOSE_TIMESTAMP).isNull(); - List currentWorkItems = modelService.searchContainers(CaseWorkItemType.class, q.build(), null, task, result); - long found = currentWorkItems.stream().filter(wi -> expectedWorkItem == null || expectedWorkItem.matches(wi)).count(); - assertEquals("Wrong # of matching work items", count, found); - } - - protected ObjectQuery getOpenItemsQuery() { - return prismContext.queryFor(CaseWorkItemType.class) - .item(CaseWorkItemType.F_CLOSE_TIMESTAMP).isNull() - .build(); - } } diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/global/TestLifecycleGlobal.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/global/TestLifecycleGlobal.java index 2ed78fe7133..fba228d59d1 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/global/TestLifecycleGlobal.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/lifecycle/global/TestLifecycleGlobal.java @@ -31,6 +31,7 @@ import com.evolveum.midpoint.test.util.TestUtil; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.xml.DomAwareEqualsStrategy; import com.evolveum.midpoint.wf.impl.policy.ApprovalInstruction; import com.evolveum.midpoint.wf.impl.policy.ExpectedTask; import com.evolveum.midpoint.wf.impl.policy.ExpectedWorkItem; diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestDelegation.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestDelegation.java index 99938bade7f..1e3d1b20a94 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestDelegation.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/policy/other/TestDelegation.java @@ -138,7 +138,7 @@ public void test120DelegateToUser2() throws Exception { PrismAsserts.assertReferenceValues(ref(workItem.getAssigneeRef()), userLead1Oid, userLead2Oid); assertRefEquals("Wrong originalAssigneeRef", ort(userLead1Oid), workItem.getOriginalAssigneeRef()); - CaseWorkItemType workItem1 = workflowEngine.getWorkItem(WorkItemId.of(workItem), result); + CaseWorkItemType workItem1 = workItemManager.getWorkItem(WorkItemId.of(workItem), result); System.out.println("Work item: " + workItem1); List assigneeOids = getAssigneeOids(workItem1); assertEquals("Wrong midpoint-assignee values", new HashSet<>(Arrays.asList(userLead1Oid, userLead2Oid)), @@ -179,7 +179,7 @@ public void test130DelegateToUser3ByReplace() throws Exception { PrismAsserts.assertReferenceValues(ref(workItem.getAssigneeRef()), userLead3Oid); assertRefEquals("Wrong originalAssigneeRef", ort(userLead1Oid), workItem.getOriginalAssigneeRef()); - CaseWorkItemType fullWorkItem = workflowEngine.getWorkItem(WorkItemId.of(workItem), result); + CaseWorkItemType fullWorkItem = workItemManager.getWorkItem(WorkItemId.of(workItem), result); System.out.println("Full work item: " + fullWorkItem); List assigneeOids = getAssigneeOids(fullWorkItem); assertEquals("Wrong assignees", Collections.singleton(userLead3Oid), new HashSet<>(assigneeOids)); @@ -213,7 +213,7 @@ public void test140DelegateToNoneByReplace() throws Exception { assertEquals("Wrong assigneeRef count", 0, workItem.getAssigneeRef().size()); assertRefEquals("Wrong originalAssigneeRef", ort(userLead1Oid), workItem.getOriginalAssigneeRef()); - CaseWorkItemType fullWorkItem = workflowEngine.getWorkItem(WorkItemId.of(workItem), result); + CaseWorkItemType fullWorkItem = workItemManager.getWorkItem(WorkItemId.of(workItem), result); System.out.println("Full work item: " + fullWorkItem); assertEquals("Wrong # of assignees", 0, getAssigneeOids(fullWorkItem).size()); diff --git a/model/workflow-impl/src/test/resources/common/024-archetype-operation-request.xml b/model/workflow-impl/src/test/resources/common/024-archetype-operation-request.xml new file mode 100644 index 00000000000..6c2fc750949 --- /dev/null +++ b/model/workflow-impl/src/test/resources/common/024-archetype-operation-request.xml @@ -0,0 +1,49 @@ + + + Operation Request + + Archetype for cases that describe operation requests, e.g. role assignment requests. + + + + + Operation Requests + + fa fa-play-circle + + + + + + + http://midpoint.evolveum.com/xml/ns/public/gui/component-3#caseTabOverviewRequest + + true + + + + + + + CaseType + + + diff --git a/model/workflow-impl/src/test/resources/common/025-archetype-approval-case.xml b/model/workflow-impl/src/test/resources/common/025-archetype-approval-case.xml new file mode 100644 index 00000000000..c9a9fba23f7 --- /dev/null +++ b/model/workflow-impl/src/test/resources/common/025-archetype-approval-case.xml @@ -0,0 +1,49 @@ + + + Approval Case + + Archetype for approval cases, e.g. role assignment approval. + + + + + Approval Cases + + fe fe-approver + + + + + + + http://midpoint.evolveum.com/xml/ns/public/gui/component-3#caseTabOverviewApproval + + true + + + + + + + CaseType + + + diff --git a/model/workflow-impl/src/test/resources/policy/041-role-approver.xml b/model/workflow-impl/src/test/resources/common/041-role-approver.xml similarity index 100% rename from model/workflow-impl/src/test/resources/policy/041-role-approver.xml rename to model/workflow-impl/src/test/resources/common/041-role-approver.xml diff --git a/model/workflow-impl/src/test/resources/common/user-administrator.xml b/model/workflow-impl/src/test/resources/common/user-administrator.xml index a18a41ee672..efb8629d442 100644 --- a/model/workflow-impl/src/test/resources/common/user-administrator.xml +++ b/model/workflow-impl/src/test/resources/common/user-administrator.xml @@ -40,17 +40,6 @@ - - - - - - - - - - - diff --git a/model/workflow-impl/src/test/resources/policy/user-administrator.xml b/model/workflow-impl/src/test/resources/policy/user-administrator.xml deleted file mode 100644 index efb8629d442..00000000000 --- a/model/workflow-impl/src/test/resources/policy/user-administrator.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - administrator - - - - midPoint Administrator - midPoint - Administrator - administrator@evolveum.com - - - - - - http://www.w3.org/2001/04/xmlenc#aes128-cbc - - - 4HXeUejV93Vd3JuIZz7sbs5bVko= - - - Q27VymuHR348Vb9Ln5p06RT667FqZPSijEMxVDWw7D8= - - - - - - diff --git a/model/workflow-impl/src/test/resources/policy/user-jack.xml b/model/workflow-impl/src/test/resources/policy/user-jack.xml deleted file mode 100644 index b44ae24aa65..00000000000 --- a/model/workflow-impl/src/test/resources/policy/user-jack.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - jack - - Black Pearl - pistol - mouth - - Jack Sparrow - Jack - Sparrow - Jackie - Cpt. - PhD. - jack.sparrow@evolveum.com - 555-1234 - emp1234 - CAPTAIN - Caribbean - - - - - deadmentellnotales - - - - - - enabled - - diff --git a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ManualConnectorInstance.java b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ManualConnectorInstance.java index 434aa6eaec3..1ff8f1dbc79 100644 --- a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ManualConnectorInstance.java +++ b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ManualConnectorInstance.java @@ -35,6 +35,7 @@ import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.DeltaConvertor; import com.evolveum.midpoint.schema.constants.ConnectorTestOperation; +import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.internals.InternalMonitor; import com.evolveum.midpoint.schema.internals.InternalsConfig; @@ -42,6 +43,7 @@ import com.evolveum.midpoint.schema.processor.ResourceAttribute; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.OidUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.task.api.Task; @@ -50,8 +52,6 @@ import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; @@ -250,6 +250,11 @@ private PrismObject addCase(String description, String resourceOid, St caseType.setObjectChange(objectDelta); } + ObjectReferenceType archetypeRef = ObjectTypeUtil + .createObjectRef(SystemObjectsType.ARCHETYPE_MANUAL_CASE.value(), ObjectTypes.ARCHETYPE); + caseType.getArchetypeRef().add(archetypeRef.clone()); + caseType.beginAssignment().targetRef(archetypeRef).end(); + for (ObjectReferenceType operator : operators) { CaseWorkItemType workItem = new CaseWorkItemType(getPrismContext()) .originalAssigneeRef(operator.clone()) diff --git a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java index d3ff37be5c9..ca6a580ca19 100644 --- a/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/RepositoryCache.java @@ -298,9 +298,15 @@ private void repoOpEnd(Long startTime) { monitor.recordRepoOperation(System.currentTimeMillis() - startTime); } } - + + /* + * Tasks are usually rapidly changing. + * + * Cases are perhaps not changing that rapidly but these are objects that are used for communication of various parties; + * so - to avoid having stale data - we skip caching them altogether. + */ private boolean alwaysNotCacheable(Class type) { - return type.equals(TaskType.class); + return type.equals(TaskType.class) || type.equals(CaseType.class); } @Override diff --git a/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/QueryInterpreter2Test.java b/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/QueryInterpreter2Test.java index d18e389071b..73a9217ff31 100644 --- a/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/QueryInterpreter2Test.java +++ b/repo/repo-sql-impl-test/src/test/java/com/evolveum/midpoint/repo/sql/QueryInterpreter2Test.java @@ -2528,7 +2528,7 @@ public void test0530queryObjectSubstringName() throws Exception { .item(F_NAME).containsPoly("a").matchingOrig() .build(); count = repositoryService.countObjects(ObjectType.class, objectQuery, null, result); - assertEquals(22, count); + assertEquals(23, count); } finally { close(session); diff --git a/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdLdapCookedTest.java b/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdLdapCookedTest.java index 0b6ea95a5ad..aab0e31f6cb 100644 --- a/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdLdapCookedTest.java +++ b/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdLdapCookedTest.java @@ -46,7 +46,7 @@ public void test050Capabilities() throws Exception { assertTrue("No native activation capability", ResourceTypeUtil.hasResourceNativeActivationCapability(resourceType)); assertTrue("No native activation status capability", ResourceTypeUtil.hasResourceNativeActivationStatusCapability(resourceType)); // assertTrue("No native lockout capability", ResourceTypeUtil.hasResourceNativeActivationLockoutCapability(resourceType)); - assertTrue("No native credentias capability", ResourceTypeUtil.isCredentialsCapabilityEnabled(resourceType)); + assertTrue("No native credentias capability", ResourceTypeUtil.isCredentialsCapabilityEnabled(resourceType, null)); } protected void assertAccountDisabled(PrismObject shadow) { diff --git a/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdLdapRawTest.java b/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdLdapRawTest.java index 2331e36d3b2..9c080ff3ca7 100644 --- a/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdLdapRawTest.java +++ b/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdLdapRawTest.java @@ -48,7 +48,7 @@ public void test050Capabilities() throws Exception { assertFalse("No native activation capability", ResourceTypeUtil.hasResourceNativeActivationCapability(resourceType)); assertFalse("No native activation status capability", ResourceTypeUtil.hasResourceNativeActivationStatusCapability(resourceType)); assertFalse("No native lockout capability", ResourceTypeUtil.hasResourceNativeActivationLockoutCapability(resourceType)); - assertTrue("No native credentias capability", ResourceTypeUtil.isCredentialsCapabilityEnabled(resourceType)); + assertTrue("No native credentias capability", ResourceTypeUtil.isCredentialsCapabilityEnabled(resourceType, null)); } diff --git a/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdTest.java b/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdTest.java index d4e8192177f..71ba012b81b 100644 --- a/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdTest.java +++ b/testing/conntest/src/test/java/com/evolveum/midpoint/testing/conntest/ad/AbstractAdTest.java @@ -294,7 +294,7 @@ public void test050Capabilities() throws Exception { assertTrue("No native activation capability", ResourceTypeUtil.hasResourceNativeActivationCapability(resourceType)); assertTrue("No native activation status capability", ResourceTypeUtil.hasResourceNativeActivationStatusCapability(resourceType)); assertTrue("No native lockout capability", ResourceTypeUtil.hasResourceNativeActivationLockoutCapability(resourceType)); - assertTrue("No native credentias capability", ResourceTypeUtil.isCredentialsCapabilityEnabled(resourceType)); + assertTrue("No native credentias capability", ResourceTypeUtil.isCredentialsCapabilityEnabled(resourceType, null)); }