Skip to content

Commit

Permalink
Re-use sequence values during approvals
Browse files Browse the repository at this point in the history
When a sequence value(s) are used during operation being approved,
they are now stored along with the approval request. After the request
is approved, the same values are applied. Therefore, e.g. for user
creation, the properties of user actually created are the same as
they were in the creation approval request.

This resolves MID-7575.

(cherry picked from commit 921c190)
  • Loading branch information
mederly committed Mar 7, 2022
1 parent a28a88a commit f65832b
Show file tree
Hide file tree
Showing 12 changed files with 298 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,71 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="sequences" type="c:LensContextSequencesType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Information about current values of sequences that are used in this clockwork execution.
When a value is here it means that it is allocated in the respective sequence object!
</xsd:documentation>
<xsd:appinfo>
<a:since>4.5</a:since>
<a:since>4.0.5</a:since>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:long"/>
</xsd:complexType>

<xsd:complexType name="LensContextSequencesType">
<xsd:annotation>
<xsd:documentation>
Values of sequences used in the computation.
</xsd:documentation>
<xsd:appinfo>
<a:container/>
<a:since>4.5</a:since>
<a:since>4.0.5</a:since>
</xsd:appinfo>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="sequenceValue" type="c:LensContextSequenceValueType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:long"/>
</xsd:complexType>

<xsd:complexType name="LensContextSequenceValueType">
<xsd:annotation>
<xsd:documentation>
Value of a sequence used in the computation.
</xsd:documentation>
<xsd:appinfo>
<a:container/>
<a:since>4.5</a:since>
<a:since>4.0.5</a:since>
</xsd:appinfo>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="sequenceRef" type="c:ObjectReferenceType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Sequence whose value is recorded here.
</xsd:documentation>
<xsd:appinfo>
<a:objectReferenceTargetType>c:SequenceType</a:objectReferenceTargetType>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:element name="value" type="xsd:long" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
The value used in the computation.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:long"/>
</xsd:complexType>

<xsd:complexType name="LensElementContextType">
<xsd:annotation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ public class LensContext<F extends ObjectType> implements ModelContext<F>, Clone
*/
private transient Collection<ProgressListener> progressListeners;

/**
* Current values of sequences used during the clockwork.
*/
private final Map<String, Long> sequences = new HashMap<>();

/**
Expand Down Expand Up @@ -1030,6 +1033,7 @@ protected void copyValues(LensContext<F> clone) {
for (LensProjectionContext thisProjectionContext : this.projectionContexts) {
clone.projectionContexts.add(thisProjectionContext.clone(this));
}
clone.sequences.putAll(this.sequences);
}

private Map<String, ResourceType> cloneResourceCache() {
Expand Down Expand Up @@ -1141,7 +1145,10 @@ public String debugDump(int indent, boolean showTriples) {
historicResourceObjects.toString(), indent + 1); // temporary
// impl
}

if (!sequences.isEmpty()) {
sb.append("\n");
DebugUtil.debugDumpWithLabel(sb, "Sequence values", sequences, indent + 1);
}
return sb.toString();
}

Expand Down Expand Up @@ -1345,6 +1352,16 @@ public LensContextType toLensContextType(ExportType exportType) throws SchemaExc
.add(simplifyExecutedDelta(executedDelta).toLensObjectDeltaOperationType());
}
}
if (!getSequences().isEmpty()) {
LensContextSequencesType sBean = new LensContextSequencesType();
for (Entry<String, Long> entry : getSequences().entrySet()) {
sBean.getSequenceValue().add(
new LensContextSequenceValueType()
.sequenceRef(entry.getKey(), SequenceType.COMPLEX_TYPE)
.value(entry.getValue()));
}
lensContextType.setSequences(sBean);
}

return lensContextType;
}
Expand Down Expand Up @@ -1422,6 +1439,15 @@ public static <T extends ObjectType> LensContext<T> fromLensContextBean(LensCont
lensContext.rottenExecutedDeltas.add(objectDeltaOperation);
}

if (bean.getSequences() != null) {
for (LensContextSequenceValueType seqValueBean : bean.getSequences().getSequenceValue()) {
String oid = seqValueBean.getSequenceRef() != null ? seqValueBean.getSequenceRef().getOid() : null;
if (oid != null) {
lensContext.setSequenceCounter(oid, seqValueBean.getValue());
}
}
}

if (result.isUnknown()) {
result.computeStatus();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,7 @@ public static <F extends ObjectType> void reclaimSequences(LensContext<F> contex
// ... but otherwise ignore it and go on
}
}
context.getSequences().clear();
}

public static <AH extends AssignmentHolderType> void applyObjectPolicyConstraints(LensFocusContext<AH> focusContext, ArchetypePolicyType archetypePolicy, PrismContext prismContext) throws SchemaException, ConfigurationException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,14 @@ public static boolean isApprovedFromUri(String uri) {
return isApproved(fromUri(uri));
}

@Deprecated
public static AbstractWorkItemOutputType createApproveOutput(PrismContext prismContext) {
return new AbstractWorkItemOutputType(prismContext)
.outcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_APPROVE);
}

public static AbstractWorkItemOutputType createApproveOutput() {
return new AbstractWorkItemOutputType()
.outcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_APPROVE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,6 @@ protected void assertWfContextAfterClockworkRun(
CaseType rootCase, List<CaseType> subcases, List<CaseWorkItemType> workItems,
String objectOid, List<ExpectedTask> expectedTasks, List<ExpectedWorkItem> expectedWorkItems) {

// TODO: dead code, remove 2021
// final Collection<SelectorOptions<GetOperationOptions>> options =
// SelectorOptions.createCollection(prismContext.path(F_APPROVAL_CONTEXT, F_WORK_ITEM), createRetrieve());

display("rootCase", rootCase);
assertEquals("Wrong # of wf subcases (" + expectedTasks + ")", expectedTasks.size(), subcases.size());
int i = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright (C) 2010-2022 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.wf.impl.other;

import static com.evolveum.midpoint.test.util.MidPointTestConstants.TEST_RESOURCES_DIR;

import java.io.File;

import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.testng.annotations.Test;

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.schema.util.cases.ApprovalUtils;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.test.TestResource;
import com.evolveum.midpoint.wf.impl.AbstractWfTestPolicy;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

/**
* Test for using sequences in objects being approved.
*
* See MID-7575.
*/
@ContextConfiguration(locations = { "classpath:ctx-workflow-test-main.xml" })
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class TestSequence extends AbstractWfTestPolicy {

private static final File TEST_DIR = new File(TEST_RESOURCES_DIR, "sequence");

private static final File SYSTEM_CONFIGURATION_FILE = new File(TEST_DIR, "system-configuration.xml");

private static final TestResource<SequenceType> SEQUENCE_USER_NAME =
new TestResource<>(TEST_DIR, "sequence-user-name.xml", "84f9d763-bfd9-4edb-9f5b-a49158580e16");

private static final TestResource<ObjectTemplateType> TEMPLATE_USER =
new TestResource<>(TEST_DIR, "template-user.xml", "5e156e0f-5844-44d3-a7f7-78df11e3c98a");

private String rootCaseOid;

@Override
public void initSystem(Task initTask, OperationResult initResult) throws Exception {
super.initSystem(initTask, initResult);
addObject(SEQUENCE_USER_NAME, initTask, initResult);
addObject(TEMPLATE_USER, initTask, initResult);
}

@Override
protected File getSystemConfigurationFile() {
return SYSTEM_CONFIGURATION_FILE;
}

/**
* Request a user be created. It should be named 100000.
*/
@Test
public void test100RequestUserCreation() throws Exception {
given();
Task task = getTestTask();
OperationResult result = task.getResult();
login(USER_ADMINISTRATOR_USERNAME);

when("user creation is requested");
UserType user = new UserType()
.fullName("Joe Black");

addObject(user.asPrismObject(), task, result);

then("an approval case should be created");

// @formatter:off
CaseType rootCase = assertCase(result, "after")
.display()
.displayXml()
.subcases()
.single()
.display()
.end()
.end()
.getObjectable();
// @formatter:on

rootCaseOid = rootCase.getOid();

and("the user to be created should have a name of 100000 (start of sequence)");
UserType userToCreate = (UserType) ObjectTypeUtil.getObjectFromReference(rootCase.getObjectRef());
assertUser(userToCreate, "user to create")
.display()
.assertName("100000")
.assertFullName("Joe Black");

assertNoObjectByName(UserType.class, "100000", task, result);
}

/**
* Approve user creation. The created user should have the same name as the one that was being approved (100000).
*/
@Test
public void test110ApproveUserCreation() throws Exception {
given();
Task task = getTestTask();
OperationResult result = task.getResult();

when("creation is approved");
CaseWorkItemType workItem = getWorkItem(task, result);
caseService.completeWorkItem(
WorkItemId.of(workItem),
ApprovalUtils.createApproveOutput(),
task,
result);

then("user is created with the same name");
waitForCaseClose(getCase(rootCaseOid), 20000);

assertUserAfterByUsername("100000")
.assertFullName("Joe Black");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!--
~ Copyright (C) 2010-2022 Evolveum and contributors
~
~ This work is dual-licensed under the Apache License 2.0
~ and European Union Public License. See LICENSE file for details.
-->

<sequence xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
oid="84f9d763-bfd9-4edb-9f5b-a49158580e16">
<name>sequence-user-name</name>
<counter>100000</counter>
<maxUnusedValues>10</maxUnusedValues>
</sequence>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ Copyright (C) 2010-2022 Evolveum and contributors
~
~ This work is dual-licensed under the Apache License 2.0
~ and European Union Public License. See LICENSE file for details.
-->

<systemConfiguration oid="00000000-0000-0000-0000-000000000001"
xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3">
<name>SystemConfiguration</name>
<defaultObjectPolicyConfiguration>
<objectTemplateRef oid="5e156e0f-5844-44d3-a7f7-78df11e3c98a" />
<type>UserType</type>
</defaultObjectPolicyConfiguration>
<workflowConfiguration>
<useLegacyApproversSpecification>never</useLegacyApproversSpecification>
<useDefaultApprovalPolicyRules>never</useDefaultApprovalPolicyRules>
</workflowConfiguration>
<globalPolicyRule>
<name>User creation approval</name>
<policyConstraints>
<modification>
<operation>add</operation>
</modification>
</policyConstraints>
<policyActions>
<approval>
<approverRef oid="00000000-0000-0000-0000-000000000002" type="UserType"/>
</approval>
</policyActions>
<focusSelector>
<type>UserType</type>
</focusSelector>
</globalPolicyRule>
</systemConfiguration>
23 changes: 23 additions & 0 deletions model/workflow-impl/src/test/resources/sequence/template-user.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!--
~ Copyright (C) 2010-2022 Evolveum and contributors
~
~ This work is dual-licensed under the Apache License 2.0
~ and European Union Public License. See LICENSE file for details.
-->

<objectTemplate
xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3"
oid="5e156e0f-5844-44d3-a7f7-78df11e3c98a">
<name>template-user</name>
<item>
<ref>name</ref>
<mapping>
<strength>weak</strength>
<expression>
<sequentialValue>
<sequenceRef oid="84f9d763-bfd9-4edb-9f5b-a49158580e16"/>
</sequentialValue>
</expression>
</mapping>
</item>
</objectTemplate>
1 change: 1 addition & 0 deletions model/workflow-impl/testng-integration.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<class name="com.evolveum.midpoint.wf.impl.other.TestParallelApprovals"/>
<class name="com.evolveum.midpoint.wf.impl.other.TestPreview"/>
<class name="com.evolveum.midpoint.wf.impl.other.TestApprovalTaskOwner"/>
<class name="com.evolveum.midpoint.wf.impl.other.TestSequence"/>
</classes>
</test>
</suite>
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ public SubcasesAsserter<RA> subcases() {
} catch (SchemaException e) {
throw new AssertionError(e);
}
SubcasesAsserter<RA> asserter = new SubcasesAsserter<>(this, asObjectableList(subcases), getDetails());
SubcasesAsserter<RA> asserter =
new SubcasesAsserter<>(this, asObjectableList(subcases), getDetails());
copySetupTo(asserter);
return asserter;
}
Expand Down

0 comments on commit f65832b

Please sign in to comment.