From c3d91cefcf6cb0e16f7678dfc5d2376a3f93529a Mon Sep 17 00:00:00 2001 From: skublik Date: Mon, 3 Jun 2019 13:18:48 +0200 Subject: [PATCH 01/20] moving the language icon for polystrings to the right edge MID-5387 --- .../gui/impl/prism/PrismReferenceHeaderPanel.html | 2 -- .../impl/prism/component/PolyStringEditorPanel.html | 10 +++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) 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..2f95250fa4c 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 @@ -22,7 +22,9 @@ @@ -31,8 +33,10 @@
-   - +
+ +
  +
From 4106faffb5204374025fd8f9f1f641f9fa2db70b Mon Sep 17 00:00:00 2001 From: skublik Date: Mon, 3 Jun 2019 15:13:37 +0200 Subject: [PATCH 02/20] adding Serializable for ItemPathPanelFactory MID-5394 --- .../midpoint/gui/impl/factory/ItemPathPanelFactory.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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() { From 0af46db0fc86942067cc8e7da8acbc89b4b1c21b Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Mon, 3 Jun 2019 20:41:49 +0200 Subject: [PATCH 03/20] Fix compilation issues in tests --- .../midpoint/testing/conntest/ad/AbstractAdLdapCookedTest.java | 2 +- .../midpoint/testing/conntest/ad/AbstractAdLdapRawTest.java | 2 +- .../evolveum/midpoint/testing/conntest/ad/AbstractAdTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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)); } From 0796c37d37ef124a3999186c80e3f188cdb47dc2 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Mon, 3 Jun 2019 20:47:24 +0200 Subject: [PATCH 04/20] Add appropriate archetype to approval cases --- .../midpoint/test/util/MidPointAsserts.java | 15 ++++++++- .../test/AbstractModelIntegrationTest.java | 31 +++++++++++++------ .../wf/impl/processors/StartInstruction.java | 4 +++ .../legacy/TestUserChangeApprovalLegacy.java | 1 + 4 files changed, 40 insertions(+), 11 deletions(-) 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/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..728037c777a 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(); 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..d047fed9d37 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,6 +22,7 @@ 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.result.OperationResult; import com.evolveum.midpoint.schema.util.LocalizationUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; @@ -58,6 +59,9 @@ protected StartInstruction(@NotNull ChangeProcessor changeProcessor) { this.changeProcessor = changeProcessor; PrismContext prismContext = changeProcessor.getPrismContext(); aCase = new CaseType(prismContext); + ObjectReferenceType approvalArchetypeRef = ObjectTypeUtil.createObjectRef(SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value(), 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())); 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..2eedef13513 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 Date: Mon, 3 Jun 2019 21:23:57 +0200 Subject: [PATCH 05/20] Stop maintaining case.localizableName We use new PolyString features instead. --- .../PolyStringTranslationArgumentType.java | 11 +++++++ .../types_3/PolyStringTranslationType.java | 33 +++++++++++++++++++ .../wf/impl/processors/ModelHelper.java | 3 +- .../wf/impl/processors/StartInstruction.java | 25 ++++++++++---- .../policy/AssignmentPolicyAspectPart.java | 3 +- .../policy/ObjectPolicyAspectPart.java | 3 +- 6 files changed, 65 insertions(+), 13 deletions(-) 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/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..9fb4ab5037b 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 @@ -84,8 +84,7 @@ public StartInstruction createInstructionForRoot(ChangeProcessor changeProcessor 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; 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 d047fed9d37..1b1f5573a0c 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 @@ -24,15 +24,16 @@ import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.schema.constants.ObjectTypes; 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; @@ -49,6 +50,7 @@ */ public class StartInstruction implements DebugDumpable { + @SuppressWarnings("unused") private static final Trace LOGGER = TraceManager.getTrace(StartInstruction.class); protected final CaseType aCase; @@ -67,14 +69,13 @@ protected StartInstruction(@NotNull ChangeProcessor changeProcessor) { aCase.getMetadata().setCreateTimestamp(createXMLGregorianCalendar(new Date())); } - @SuppressWarnings("unchecked") public static StartInstruction create(ChangeProcessor changeProcessor) { return new StartInstruction(changeProcessor); } //endregion // region Getters and setters - public ChangeProcessor getChangeProcessor() { + protected ChangeProcessor getChangeProcessor() { return changeProcessor; } @@ -84,14 +85,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; } 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); From d3d0285c0cc74682d02dc92f315cbe546ca918ad Mon Sep 17 00:00:00 2001 From: kate Date: Mon, 3 Jun 2019 22:50:42 +0200 Subject: [PATCH 06/20] case/work item panels work --- .../api/component/MainObjectListPanel.java | 6 + .../midpoint/gui/api/page/PageBase.java | 22 +- .../PrismObjectWrapperFactoryImpl.java | 3 + .../AbstractObjectMainPanel.java | 3 +- .../web/component/search/SearchFactory.java | 2 +- .../web/page/admin/PageAdminObjectList.java | 9 + .../page/admin/cases/CaseEventsTabPanel.java | 3 +- .../admin/cases/CaseWorkItemActionsPanel.html | 26 +++ .../admin/cases/CaseWorkItemActionsPanel.java | 204 ++++++++++++++++++ .../CaseWorkItemListWithDetailsPanel.html | 6 +- .../CaseWorkItemListWithDetailsPanel.java | 154 ++----------- .../web/page/admin/cases/PageCase.java | 120 +---------- .../page/admin/cases/PageCaseWorkItem.html | 1 + .../page/admin/cases/PageCaseWorkItem.java | 9 + .../web/page/admin/cases/PageCases.java | 185 +++++++++++----- .../localization/Midpoint.properties | 5 + .../resources/localization/schema.properties | 4 + .../common/common-case-management-3.xsd | 1 + 18 files changed, 431 insertions(+), 332 deletions(-) create mode 100644 gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.html create mode 100644 gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.java 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..c397d2c043c 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 @@ -1842,29 +1842,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/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/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/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/CaseWorkItemActionsPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.html new file mode 100644 index 00000000000..a5ac2d16b88 --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.html @@ -0,0 +1,26 @@ + + + + + +
+
+
+
+
+ + \ 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..9b2b6e077dd 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,10 +215,6 @@ public void onClick(AjaxRequestTarget target, IModel> rowModel){ return rowModel.getObject().getRealValue(); } @@ -257,86 +223,4 @@ 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); -// } - } - } 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..c2c8d1be908 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 @@ -57,133 +57,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) { 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/PageCases.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCases.java index a1cbd5acc00..e12d29aa7e1 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()"); @@ -112,6 +77,7 @@ protected void objectDetailsPerformed(AjaxRequestTarget target, CaseType caseIns PageParameters pageParameters = new PageParameters(); pageParameters.add(OnePageParameterEncoder.PARAMETER, caseInstance.getOid()); navigateToNext(PageCase.class, pageParameters); + navigateToNext(PageCase.class, pageParameters); } @Override @@ -256,21 +222,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) { 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/resources/localization/Midpoint.properties b/gui/admin-gui/src/main/resources/localization/Midpoint.properties index 579bf1daf1e..1ddbd88e2c7 100755 --- a/gui/admin-gui/src/main/resources/localization/Midpoint.properties +++ b/gui/admin-gui/src/main/resources/localization/Midpoint.properties @@ -2545,6 +2545,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 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..97a10ac3fbc 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 @@ -412,6 +412,7 @@ + From 0db51a109d428d61186f70439f5c3cb1a6b1b2bc Mon Sep 17 00:00:00 2001 From: kate Date: Mon, 3 Jun 2019 23:49:18 +0200 Subject: [PATCH 07/20] work items count fix (left menu) --- .../java/com/evolveum/midpoint/gui/api/page/PageBase.java | 7 ++++++- .../page/admin/cases/CaseWorkItemListWithDetailsPanel.java | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) 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 c397d2c043c..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); 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 9b2b6e077dd..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 @@ -220,7 +220,8 @@ private CaseWorkItemType unwrapRowModel(IModel Date: Tue, 4 Jun 2019 08:10:17 +0200 Subject: [PATCH 08/20] Remove case.localizableName from schema --- .../xml/ns/public/common/common-case-management-3.xsd | 11 ----------- 1 file changed, 11 deletions(-) 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..d4ddcfaf445 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 - - - From 06ff0fbbb6f2e4ff4172cf2eccfa10e54527e5e8 Mon Sep 17 00:00:00 2001 From: skublik Date: Tue, 4 Jun 2019 08:45:14 +0200 Subject: [PATCH 09/20] fixing width of clickable area MID-5387 --- .../prism/component/PolyStringEditorPanel.html | 18 ++++++++++++++---- .../prism/component/PolyStringEditorPanel.java | 4 ++-- 2 files changed, 16 insertions(+), 6 deletions(-) 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 2f95250fa4c..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,22 +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); } From 8ea58e92483ceafd587d96152ed28f1cdae51eba Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Tue, 4 Jun 2019 09:31:03 +0200 Subject: [PATCH 10/20] Add appropriate archetype to manual resource cases --- .../ucf/impl/builtin/ManualConnectorInstance.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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()) From 5f888ed8acd7f48781fc0dbbe9c0c1673bd26b45 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 4 Jun 2019 10:02:43 +0200 Subject: [PATCH 11/20] icon class fix --- .../resources/initial-objects/025-archetype-approval-case.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 13b139b2cba416067abcc4135b66ecbb49780e5e Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Tue, 4 Jun 2019 10:02:47 +0200 Subject: [PATCH 12/20] Fix root case archetype It should be OPERATION_REQUEST, not APPROVAL_CASE. --- .../midpoint/wf/impl/processors/ModelHelper.java | 3 ++- .../midpoint/wf/impl/processors/StartInstruction.java | 8 ++++---- .../general/scenarios/BaseGcpScenarioBean.java | 2 +- .../impl/processors/primary/PcpStartInstruction.java | 11 ++++++----- .../processors/primary/PrimaryChangeProcessor.java | 2 +- .../wf/impl/legacy/TestUserChangeApprovalLegacy.java | 3 ++- 6 files changed, 16 insertions(+), 13 deletions(-) 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 9fb4ab5037b..1ed540ca6e7 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 @@ -34,6 +34,7 @@ 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,7 +80,7 @@ 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); 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 1b1f5573a0c..af27ce16fe0 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 @@ -57,11 +57,11 @@ public class StartInstruction implements DebugDumpable { 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(SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value(), ObjectTypes.ARCHETYPE); + ObjectReferenceType approvalArchetypeRef = ObjectTypeUtil.createObjectRef(archetypeOid, ObjectTypes.ARCHETYPE); aCase.getArchetypeRef().add(approvalArchetypeRef.clone()); aCase.beginAssignment().targetRef(approvalArchetypeRef).end(); aCase.setWorkflowContext(new WfContextType(prismContext)); @@ -69,8 +69,8 @@ protected StartInstruction(@NotNull ChangeProcessor changeProcessor) { aCase.getMetadata().setCreateTimestamp(createXMLGregorianCalendar(new Date())); } - 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 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..b13d3f789e2 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 @@ -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/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..3f7a3694188 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 @@ -315,7 +315,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); 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 2eedef13513..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,7 +159,7 @@ protected void assertWfContextAfterClockworkRun(CaseType rootCase, List> options1 = schemaHelper.getOperationOptionsBuilder() From 3211cf5a25db7d6757bdc5aaacc2a526c6210f4e Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 4 Jun 2019 23:40:33 +0200 Subject: [PATCH 13/20] summary panel data, child cases tab panel --- .../midpoint/gui/api/ComponentConstants.java | 3 + ...witchableApprovalProcessPreviewsPanel.html | 32 ++-- ...witchableApprovalProcessPreviewsPanel.java | 76 ++++----- .../page/admin/cases/CaseSummaryPanel.java | 53 ++++++ .../page/admin/cases/ChildCasesTabPanel.html | 24 +++ .../page/admin/cases/ChildCasesTabPanel.java | 151 ++++++++++++++++++ .../cases/OperationRequestCaseTabPanel.java | 6 +- .../web/page/admin/cases/PageCase.java | 45 ++++-- .../page/admin/cases/PageCaseWorkItems.java | 89 +++-------- .../web/page/admin/cases/PageCases.java | 3 +- .../ApprovalStageExecutionInformationDto.java | 36 ++--- .../web/session/UserProfileStorage.java | 1 + .../localization/Midpoint.properties | 3 + .../midpoint/schema/util/WfContextUtil.java | 6 + 14 files changed, 373 insertions(+), 155 deletions(-) create mode 100644 gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.html create mode 100644 gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.java 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/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/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/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..954d28dd485 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 @@ -29,6 +29,8 @@ public class OperationRequestCaseTabPanel extends AbstractObjectTabPanel { private static final long serialVersionUID = 1L; + private static String ID_OPERATIONAL_REQUEST_CASE_PANEL = "operationRequestCasePanel"; + public OperationRequestCaseTabPanel(String id, Form> mainForm, LoadableModel> objectWrapperModel, PageBase pageBase) { super(id, mainForm, objectWrapperModel); } @@ -40,7 +42,9 @@ protected void onInitialize(){ } private void initLayout(){ - add(new WebMarkupContainer("aoperationRequestCasePanel")); + WebMarkupContainer operationalRequestCasePanel = new WebMarkupContainer(ID_OPERATIONAL_REQUEST_CASE_PANEL); + operationalRequestCasePanel.setOutputMarkupId(true); + add(operationalRequestCasePanel); } } 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 c2c8d1be908..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; @@ -94,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; @@ -108,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/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 e12d29aa7e1..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 @@ -77,7 +77,6 @@ protected void objectDetailsPerformed(AjaxRequestTarget target, CaseType caseIns PageParameters pageParameters = new PageParameters(); pageParameters.add(OnePageParameterEncoder.PARAMETER, caseInstance.getOid()); navigateToNext(PageCase.class, pageParameters); - navigateToNext(PageCase.class, pageParameters); } @Override @@ -308,7 +307,7 @@ public IModel getConfirmationMessageModel(){ private String getObjectRef(IModel> caseModel) { CaseType caseModelObject = caseModel.getObject().getValue(); - if (caseModelObject.getObjectRef() == null) { + if (caseModelObject == null || caseModelObject.getObjectRef() == null) { return ""; } return WebComponentUtil.getEffectiveName(caseModelObject.getObjectRef(), AbstractRoleType.F_DISPLAY_NAME, PageCases.this, 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/localization/Midpoint.properties b/gui/admin-gui/src/main/resources/localization/Midpoint.properties index 1ddbd88e2c7..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 @@ -3644,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/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..94ffabc2b26 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 @@ -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()); } From 763b580caf7c927f94b073ecb0edcfb3a89bcce6 Mon Sep 17 00:00:00 2001 From: skublik Date: Wed, 5 Jun 2019 13:01:45 +0200 Subject: [PATCH 14/20] fix for query field on audit log page MID-5395 --- .../web/page/admin/reports/component/AuditLogViewerPanel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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<>( From e133c4fcc7e985cbce87e72527f62529c21fbe3d Mon Sep 17 00:00:00 2001 From: kate Date: Wed, 5 Jun 2019 14:01:20 +0200 Subject: [PATCH 15/20] operational request tab for case page --- .../gui/api/util/WebComponentUtil.java | 38 +++++++++++++++++-- .../cases/OperationRequestCaseTabPanel.java | 32 ++++++++++++++-- .../admin/workflow/WorkItemDetailsPanel.java | 23 +---------- 3 files changed, 65 insertions(+), 28 deletions(-) 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/web/page/admin/cases/OperationRequestCaseTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/OperationRequestCaseTabPanel.java index 954d28dd485..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,7 +36,12 @@ 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); @@ -38,13 +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(){ - WebMarkupContainer operationalRequestCasePanel = new WebMarkupContainer(ID_OPERATIONAL_REQUEST_CASE_PANEL); - operationalRequestCasePanel.setOutputMarkupId(true); - add(operationalRequestCasePanel); +// 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/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); } }; } From 32b6d04c34e5fb12c8e6a1488888abe14fa9ce77 Mon Sep 17 00:00:00 2001 From: skublik Date: Wed, 5 Jun 2019 14:55:03 +0200 Subject: [PATCH 16/20] fix for task details MID-5390 --- .../midpoint/web/page/admin/server/PageTaskEdit.java | 5 ++--- .../evolveum/midpoint/web/page/admin/server/dto/TaskDto.java | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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; } From 259669fa662d6bb352431c25a32831a1a3855483 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Wed, 5 Jun 2019 16:58:25 +0200 Subject: [PATCH 17/20] Rework the workflow engine This is a major rework of the workflow engine (i.e. Activiti replacement). The primary goal was to avoid race conditions by executing all changes in a single repo MODIFY operation while checking the "CaseType version not changed" precondition. The secondary goal was to make the code more modular and therefore maintainable. See Action and Request class trees. --- .../web/page/admin/workflow/PageWorkItem.java | 4 +- .../page/admin/workflow/PageWorkItems.java | 8 +- .../com/evolveum/midpoint/prism/Item.java | 1 + .../midpoint/prism/util/CloneUtil.java | 2 + .../midpoint/prism/xml/XmlTypeConverter.java | 2 + .../midpoint/prism/impl/ItemImpl.java | 15 + .../schema/constants/SchemaConstants.java | 13 + .../schema/util/CaseWorkItemUtil.java | 9 + .../midpoint/schema/util/WfContextUtil.java | 8 +- .../xml/ns/public/model/extension-3.xsd | 2 +- .../caching/AbstractThreadLocalCache.java | 7 + .../midpoint/model/api/WorkflowService.java | 6 +- .../impl/controller/ModelController.java | 10 +- .../AbstractInternalModelIntegrationTest.java | 1 - ...actModelImplementationIntegrationTest.java | 13 + .../test/AbstractModelIntegrationTest.java | 6 +- ...istener.java => WorkflowListenerImpl.java} | 48 +- .../midpoint/wf/api/CompleteAction.java | 95 -- .../midpoint/wf/api/ProcessListener.java | 47 - ...temListener.java => WorkflowListener.java} | 149 +-- .../midpoint/wf/api/WorkflowManager.java | 14 +- .../wf/api/request/CancelCaseRequest.java | 30 + .../wf/api/request/ClaimWorkItemsRequest.java | 58 ++ .../api/request/CompleteWorkItemsRequest.java | 84 ++ .../api/request/DelegateWorkItemsRequest.java | 94 ++ .../wf/api/request/OpenCaseRequest.java | 30 + .../api/request/ReleaseWorkItemsRequest.java | 58 ++ .../midpoint/wf/api/request/Request.java | 46 + .../midpoint/wf/util/ApprovalUtils.java | 8 + model/workflow-impl/pom.xml | 6 - ...rovalSchemaExecutionInformationHelper.java | 5 +- .../midpoint/wf/impl/WorkflowManagerImpl.java | 34 +- ...sInstanceManager.java => CaseManager.java} | 52 +- .../wf/impl/access/WorkItemManager.java | 165 ++- .../impl/engine/EngineInvocationContext.java | 246 ++++- .../wf/impl/engine/NotificationHelper.java | 108 -- .../wf/impl/engine/TriggerHelper.java | 120 --- .../wf/impl/engine/WorkflowEngine.java | 952 ++---------------- .../wf/impl/engine/actions/Action.java | 50 + .../wf/impl/engine/actions/ActionFactory.java | 59 ++ .../impl/engine/actions/CancelCaseAction.java | 54 + .../engine/actions/ClaimWorkItemsAction.java | 67 ++ .../impl/engine/actions/CloseCaseAction.java | 71 ++ .../impl/engine/actions/CloseStageAction.java | 133 +++ .../actions/CompleteWorkItemsAction.java | 139 +++ .../actions/DelegateWorkItemsAction.java | 151 +++ .../impl/engine/actions/InternalAction.java | 31 + .../impl/engine/actions/OpenCaseAction.java | 62 ++ .../impl/engine/actions/OpenStageAction.java | 201 ++++ .../actions/ReleaseWorkItemsAction.java | 65 ++ .../impl/engine/actions/RequestedAction.java | 34 + .../engine/{ => helpers}/AuditHelper.java | 44 +- .../engine/helpers/DelayedNotification.java | 151 +++ .../engine/helpers/NotificationHelper.java | 79 ++ .../wf/impl/engine/helpers/TriggerHelper.java | 102 ++ .../impl/engine/helpers/WorkItemHelper.java | 136 +++ .../CaseOperationExecutionTaskHandler.java | 20 +- .../wf/impl/execution/ExecutionHelper.java | 136 +++ .../midpoint/wf/impl/hook/WfHook.java | 5 +- .../processes/common/StageComputeHelper.java | 15 +- .../common/WfTimedActionTriggerHandler.java | 80 +- .../wf/impl/processors/ModelHelper.java | 16 +- .../wf/impl/processors/StartInstruction.java | 7 + .../general/GeneralChangeProcessor.java | 4 +- .../scenarios/BaseGcpScenarioBean.java | 2 +- .../primary/ApprovalMetadataHelper.java | 1 - .../processors/primary/PcpGeneralHelper.java | 5 +- .../primary/PrimaryChangeProcessor.java | 86 +- .../aspect/PrimaryChangeAspectHelper.java | 9 - .../midpoint/wf/impl/AbstractWfTest.java | 335 ++++++ .../midpoint/wf/impl/WfTestHelper.java | 5 +- .../wf/impl/legacy/AbstractWfTestLegacy.java | 2 +- .../wf/impl/policy/AbstractWfTestPolicy.java | 244 +---- .../lifecycle/global/TestLifecycleGlobal.java | 1 + .../wf/impl/policy/other/TestDelegation.java | 6 +- .../024-archetype-operation-request.xml | 49 + .../common/025-archetype-approval-case.xml | 49 + .../{policy => common}/041-role-approver.xml | 0 .../resources/common/user-administrator.xml | 11 - .../test/resources/policy/role-superuser.xml | 24 - .../resources/policy/user-administrator.xml | 45 - .../src/test/resources/policy/user-jack.xml | 53 - .../midpoint/repo/cache/RepositoryCache.java | 10 +- 83 files changed, 3340 insertions(+), 2065 deletions(-) rename model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/{WorkflowListener.java => WorkflowListenerImpl.java} (85%) delete mode 100644 model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/CompleteAction.java delete mode 100644 model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/ProcessListener.java rename model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/{WorkItemListener.java => WorkflowListener.java} (68%) create mode 100644 model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/CancelCaseRequest.java create mode 100644 model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/ClaimWorkItemsRequest.java create mode 100644 model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/CompleteWorkItemsRequest.java create mode 100644 model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/DelegateWorkItemsRequest.java create mode 100644 model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/OpenCaseRequest.java create mode 100644 model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/ReleaseWorkItemsRequest.java create mode 100644 model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/request/Request.java rename model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/{ProcessInstanceManager.java => CaseManager.java} (63%) delete mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/NotificationHelper.java delete mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/TriggerHelper.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/Action.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ActionFactory.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CancelCaseAction.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ClaimWorkItemsAction.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CloseCaseAction.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CloseStageAction.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CompleteWorkItemsAction.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/DelegateWorkItemsAction.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/InternalAction.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/OpenCaseAction.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/OpenStageAction.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/ReleaseWorkItemsAction.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/RequestedAction.java rename model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/{ => helpers}/AuditHelper.java (87%) create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/DelayedNotification.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/NotificationHelper.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/TriggerHelper.java create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/helpers/WorkItemHelper.java rename model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/{tasks => execution}/CaseOperationExecutionTaskHandler.java (95%) create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/ExecutionHelper.java create mode 100644 model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java create mode 100644 model/workflow-impl/src/test/resources/common/024-archetype-operation-request.xml create mode 100644 model/workflow-impl/src/test/resources/common/025-archetype-approval-case.xml rename model/workflow-impl/src/test/resources/{policy => common}/041-role-approver.xml (100%) delete mode 100644 model/workflow-impl/src/test/resources/policy/role-superuser.xml delete mode 100644 model/workflow-impl/src/test/resources/policy/user-administrator.xml delete mode 100644 model/workflow-impl/src/test/resources/policy/user-jack.xml 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/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-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..619624b8e6f 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,22 @@ 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 automated actions, like execution + // of approved changes. + 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 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..2c2875b539f 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); } 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/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 728037c777a..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 @@ -3787,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..4100e8bf28d 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) 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..80cf7b97fee --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/actions/CloseCaseAction.java @@ -0,0 +1,71 @@ +/* + * 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.wf.impl.engine.WorkflowEngine; +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.setCloseTimestamp(now); + 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..7696de900e7 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/ExecutionHelper.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.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 { + LOGGER.debug("Marking case {} as closed", aCase); + 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); + } + + /** + * 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); + 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 1ed540ca6e7..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,7 +29,6 @@ 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; @@ -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 af27ce16fe0..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 @@ -23,6 +23,7 @@ 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.ObjectTypeUtil; import com.evolveum.midpoint.util.DebugDumpable; @@ -186,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 b13d3f789e2..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; 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/PrimaryChangeProcessor.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java index 3f7a3694188..da990a48de9 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); @@ -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,37 +414,42 @@ 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)); + } } } } 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/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/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/role-superuser.xml b/model/workflow-impl/src/test/resources/policy/role-superuser.xml deleted file mode 100644 index c0e5ee20fc4..00000000000 --- a/model/workflow-impl/src/test/resources/policy/role-superuser.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - Superuser - - http://midpoint.evolveum.com/xml/ns/public/security/authorization-3#all - - 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/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 From 06dc189705960e16df94c7c3c3045bcf71048d2e Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Wed, 5 Jun 2019 17:02:27 +0200 Subject: [PATCH 18/20] Adapt failing repo test --- .../com/evolveum/midpoint/repo/sql/QueryInterpreter2Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From e585f2e6a95852b4c9ecf488cfa0ba9d270c89f2 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Wed, 5 Jun 2019 17:06:40 +0200 Subject: [PATCH 19/20] Fix "reset parent value" when showing closed cases --- .../wf/impl/ApprovalSchemaExecutionInformationHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4100e8bf28d..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 @@ -172,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())); } From 0cdf45daacae9a3d4120715333dd9ecdc54a7394 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Wed, 5 Jun 2019 19:48:33 +0200 Subject: [PATCH 20/20] Add "executing" case state (also added some logging to diagnose strange "closed case is not recognized as closed later" issue) --- .../schema/constants/SchemaConstants.java | 9 +++++++-- .../wf/impl/engine/actions/CloseCaseAction.java | 2 -- .../wf/impl/execution/ExecutionHelper.java | 15 ++++++++++++++- .../primary/PrimaryChangeProcessor.java | 5 ++++- 4 files changed, 25 insertions(+), 6 deletions(-) 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 619624b8e6f..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 @@ -492,11 +492,16 @@ public abstract class SchemaConstants { 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 automated actions, like execution - // of approved changes. + // 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/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 index 80cf7b97fee..94586d16ef4 100644 --- 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 @@ -21,7 +21,6 @@ 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.xml.ns._public.common.common_3.CaseType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType; @@ -56,7 +55,6 @@ public Action execute(OperationResult result) { String state = currentCase.getState(); if (state == null || SchemaConstants.CASE_STATE_CREATED.equals(state) || SchemaConstants.CASE_STATE_OPEN.equals(state)) { currentCase.setOutcome(outcome); - currentCase.setCloseTimestamp(now); currentCase.setState(SchemaConstants.CASE_STATE_CLOSING); ctx.setWasClosed(true); // audit and notification is done after the onProcessEnd is finished 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 index 7696de900e7..daeaac10d9b 100644 --- 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 @@ -78,12 +78,21 @@ public class ExecutionHelper { public void closeCaseInRepository(CaseType aCase, OperationResult result) throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - LOGGER.debug("Marking case {} as closed", aCase); 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); } /** @@ -98,6 +107,10 @@ public void checkDependentCases(String rootOid, OperationResult result) 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) 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 da990a48de9..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 @@ -454,7 +454,8 @@ public void onProcessEnd(EngineInvocationContext ctx, OperationResult result) } } - 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)); @@ -464,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 {