Skip to content

Commit

Permalink
Assignment approval metadata.
Browse files Browse the repository at this point in the history
  • Loading branch information
mederly committed Oct 31, 2017
1 parent 02a6b7d commit f98d4b9
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 47 deletions.
Expand Up @@ -25,32 +25,14 @@
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;

import com.evolveum.midpoint.util.QNameUtil;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Element;

import com.evolveum.midpoint.prism.ComplexTypeDefinition;
import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerDefinition;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismReference;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.OriginType;
import com.evolveum.midpoint.prism.Visitable;
import com.evolveum.midpoint.prism.Visitor;
import com.evolveum.midpoint.prism.delta.ChangeType;
import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.DeltaSetTriple;
Expand Down Expand Up @@ -1172,6 +1154,10 @@ public static <T> void assertEqualsCollectionUnordered(String message, Collectio
"; was "+actualCollection;
}

public static <T> void assertEqualsCollectionUnordered(String message, Collection<T> expected, Collection<T> real) {
assertEquals(message, new HashSet<>(expected), new HashSet<>(real));
}

public static void assertOrigEqualsPolyStringCollectionUnordered(String message, Collection<PolyStringType> actualCollection, String... expectedValues) {
List<PolyStringType> expectedCollection = new ArrayList<>();
for (String expectedValue : expectedValues) {
Expand Down Expand Up @@ -1299,4 +1285,9 @@ private static void assertHasNoObject(PrismReferenceValue prv) {
assertNull("Resolved object present in " + prv + ": " + prv.getObject(), prv.getObject());
}

public static void assertReferenceOids(String message, Collection<String> expectedOids,
Collection<? extends Referencable> realReferences) {
Set<String> realOids = realReferences.stream().map(r -> r.getOid()).collect(Collectors.toSet());
assertEquals(message, new HashSet<>(expectedOids), realOids);
}
}
Expand Up @@ -46,7 +46,7 @@
* Handles a "ModelOperation task" - executes a given model operation in a context
* of the task (i.e., in most cases, asynchronously).
*
* The context of the model operation (i.e., model context) is stored in task extension property
* The context of the model operation (i.e., model context) is stored in task property
* called "modelContext". When this handler is executed, the context is retrieved, unwrapped from
* its XML representation, and the model operation is (re)started.
*
Expand Down
Expand Up @@ -158,6 +158,7 @@ private <F extends FocusType> void applyAssignmentMetadataDelta(LensContext<F> c
if (objectDelta.isAdd()) {
applyAssignmentMetadataObject(context, objectDelta.getObjectToAdd(), now, task, result);
} else {
// see also ApprovalMetadataHelper.addAssignmentApprovalMetadataOnObjectModify
Set<Long> processedIds = new HashSet<>();
List<ItemDelta<?,?>> assignmentMetadataDeltas = new ArrayList<>();
for (ItemDelta<?,?> itemDelta: objectDelta.getModifications()) {
Expand Down
@@ -0,0 +1,138 @@
/*
* Copyright (c) 2010-2017 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.evolveum.midpoint.wf.impl.processors.primary;

import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
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.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.wf.api.WorkflowManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;

import static com.evolveum.midpoint.prism.path.ItemPath.CompareResult.EQUIVALENT;
import static com.evolveum.midpoint.prism.path.ItemPath.CompareResult.SUPERPATH;
import static org.apache.commons.collections4.CollectionUtils.emptyIfNull;

/**
* @author mederly
*/
@Component
public class ApprovalMetadataHelper {

private static final Trace LOGGER = TraceManager.getTrace(ApprovalMetadataHelper.class);

@Autowired private PrismContext prismContext;
@Autowired private WorkflowManager workflowManager;

public void addAssignmentApprovalMetadata(ObjectDelta<?> objectDelta, Task task, OperationResult result) throws SchemaException {
if (objectDelta.isAdd()) {
addAssignmentApprovalMetadataOnObjectAdd(objectDelta.getObjectToAdd(), task, result);
} else if (objectDelta.isModify()) {
addAssignmentApprovalMetadataOnObjectModify(objectDelta, task, result);
}
}

private void addAssignmentApprovalMetadataOnObjectModify(ObjectDelta<?> objectDelta, Task task,
OperationResult result) throws SchemaException {
// see also OperationalDataManager.applyAssignmentMetadataDelta
Collection<ObjectReferenceType> approvedBy = workflowManager.getApprovedBy(task, result);
Collection<String> comments = workflowManager.getApproverComments(task, result);
Set<Long> processedIds = new HashSet<>();
List<ItemDelta<?,?>> assignmentMetadataDeltas = new ArrayList<>();
for (ItemDelta<?,?> itemDelta: objectDelta.getModifications()) {
ItemPath deltaPath = itemDelta.getPath();
ItemPath.CompareResult comparison = deltaPath.compareComplex(SchemaConstants.PATH_ASSIGNMENT);
if (comparison == EQUIVALENT) {
// whole assignment is being added/replaced (or deleted but we are not interested in that)
ContainerDelta<AssignmentType> assignmentDelta = (ContainerDelta<AssignmentType>)itemDelta;
for (PrismContainerValue<AssignmentType> assignmentContainerValue : emptyIfNull(assignmentDelta.getValuesToAdd())) {
addAssignmentCreationApprovalMetadata(assignmentContainerValue.asContainerable(), approvedBy, comments);
}
for (PrismContainerValue<AssignmentType> assignmentContainerValue: emptyIfNull(assignmentDelta.getValuesToReplace())) {
addAssignmentCreationApprovalMetadata(assignmentContainerValue.asContainerable(), approvedBy, comments);
}
} else if (comparison == SUPERPATH) {
ItemPathSegment secondSegment = deltaPath.rest().first();
if (!(secondSegment instanceof IdItemPathSegment)) {
throw new IllegalStateException("Assignment modification contains no assignment ID. Offending path = " + deltaPath);
}
Long id = ((IdItemPathSegment) secondSegment).getId();
if (id == null) {
throw new IllegalStateException("Assignment modification contains no assignment ID. Offending path = " + deltaPath);
}
if (processedIds.add(id)) {
assignmentMetadataDeltas.addAll(
createAssignmentModificationApprovalMetadata(objectDelta.getObjectTypeClass(), id, approvedBy, comments));
}
}
}
ItemDelta.mergeAll(objectDelta.getModifications(), assignmentMetadataDeltas);
}

private void addAssignmentApprovalMetadataOnObjectAdd(PrismObject<?> object, Task task,
OperationResult result) throws SchemaException {
Objectable objectable = object.asObjectable();
if (!(objectable instanceof FocusType)) {
return;
}
FocusType focus = (FocusType) objectable;

Collection<ObjectReferenceType> approvedBy = workflowManager.getApprovedBy(task, result);
Collection<String> comments = workflowManager.getApproverComments(task, result);

for (AssignmentType assignment : focus.getAssignment()) {
addAssignmentCreationApprovalMetadata(assignment, approvedBy, comments);
}
}

private void addAssignmentCreationApprovalMetadata(AssignmentType assignment, Collection<ObjectReferenceType> approvedBy,
Collection<String> comments) {
MetadataType metadata = assignment.getMetadata();
if (metadata == null) {
assignment.setMetadata(metadata = new MetadataType(prismContext));
}
metadata.getCreateApproverRef().clear();
metadata.getCreateApproverRef().addAll(approvedBy);
metadata.getCreateApprovalComment().clear();
metadata.getCreateApprovalComment().addAll(comments);
}

private Collection<ItemDelta<?, ?>> createAssignmentModificationApprovalMetadata(Class<? extends Objectable> objectTypeClass,
long assignmentId, Collection<ObjectReferenceType> approvedBy, Collection<String> comments) throws SchemaException {
return DeltaBuilder.deltaFor(objectTypeClass, prismContext)
.item(FocusType.F_ASSIGNMENT, assignmentId, AssignmentType.F_METADATA, MetadataType.F_MODIFY_APPROVER_REF).replaceRealValues(approvedBy)
.item(FocusType.F_ASSIGNMENT, assignmentId, AssignmentType.F_METADATA, MetadataType.F_MODIFY_APPROVAL_COMMENT).replaceRealValues(comments)
.asItemDeltas();
}
}
Expand Up @@ -57,11 +57,9 @@ public class WfPrepareChildOperationTaskHandler implements TaskHandler {
private static final Trace LOGGER = TraceManager.getTrace(WfPrepareChildOperationTaskHandler.class);

//region Spring dependencies and initialization
@Autowired
private TaskManager taskManager;

@Autowired
private WfTaskController wfTaskController;
@Autowired private TaskManager taskManager;
@Autowired private WfTaskController wfTaskController;
@Autowired private ApprovalMetadataHelper metadataHelper;

@PostConstruct
public void init() {
Expand Down Expand Up @@ -102,7 +100,9 @@ public TaskRunResult run(Task task) {
wfTask.deleteModelOperationContext();
} else {
// place deltaOut into model context
modelContext.getFocusContext().setPrimaryDelta(deltasOut.getFocusChange());
ObjectDelta focusChange = deltasOut.getFocusChange();
metadataHelper.addAssignmentApprovalMetadata(focusChange, task, result);
modelContext.getFocusContext().setPrimaryDelta(focusChange);
Set<Map.Entry<ResourceShadowDiscriminator, ObjectDelta<ShadowType>>> entries = deltasOut.getProjectionChangeMapEntries();
for (Map.Entry<ResourceShadowDiscriminator, ObjectDelta<ShadowType>> entry : entries) {
// TODO what if projection context does not exist?
Expand Down
Expand Up @@ -56,11 +56,9 @@ public class WfPrepareRootOperationTaskHandler implements TaskHandler {
private static final Trace LOGGER = TraceManager.getTrace(WfPrepareRootOperationTaskHandler.class);

//region Spring dependencies and initialization
@Autowired
private TaskManager taskManager;

@Autowired
private WfTaskController wfTaskController;
@Autowired private TaskManager taskManager;
@Autowired private WfTaskController wfTaskController;
@Autowired private ApprovalMetadataHelper metadataHelper;

@PostConstruct
public void init() {
Expand Down Expand Up @@ -103,7 +101,11 @@ public TaskRunResult run(Task task) {
LOGGER.trace("Child job {} returned {} deltas", child, deltas != null ? deltas.getDeltaList().size() : 0);
}
if (deltas != null) {
if (deltas.getFocusChange() != null && deltas.getFocusChange().isAdd()) {
ObjectDelta focusChange = deltas.getFocusChange();
if (focusChange != null) {
metadataHelper.addAssignmentApprovalMetadata(focusChange, child.getTask(), result);
}
if (focusChange != null && focusChange.isAdd()) {
deltasToMerge.add(0, deltas); // "add" must go first
} else {
deltasToMerge.add(deltas);
Expand All @@ -116,9 +118,7 @@ public TaskRunResult run(Task task) {
LensFocusContext focusContext = rootContext.getFocusContext();
ObjectDelta focusDelta = deltaToMerge.getFocusChange();
if (focusDelta != null) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Adding delta to root model context; delta = {}", focusDelta.debugDump(0));
}
LOGGER.trace("Adding delta to root model context; delta = {}", focusDelta.debugDumpLazily());
if (focusContext.getPrimaryDelta() != null && !focusContext.getPrimaryDelta().isEmpty()) {
focusContext.addPrimaryDelta(focusDelta);
} else {
Expand Down
Expand Up @@ -22,6 +22,7 @@
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.util.PrismAsserts;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
Expand Down Expand Up @@ -51,8 +52,7 @@
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 com.evolveum.midpoint.xml.ns._public.common.common_3.PartialProcessingTypeType.PROCESS;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static java.util.Collections.*;
import static org.testng.AssertJUnit.*;

/**
Expand Down Expand Up @@ -468,12 +468,12 @@ protected void assertDeltaExecuted(int number, boolean yes, Task rootTask, Opera

@Override
protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception {
return true;
return null;
}

@Override
public List<ApprovalInstruction> getApprovalSequence() {
return null;
return singletonList(new ApprovalInstruction(null, true, USER_ADMINISTRATOR_OID, "comment1"));
}

@Override
Expand All @@ -489,6 +489,17 @@ protected void afterFirstClockworkRun(Task rootTask, List<Task> subtasks, List<W
PrismObject<UserType> jackAfter = getUser(userJackOid);
display("jack after", jackAfter);
assertAssignedRoles(jackAfter, roleRole29Oid);
assertAssignmentMetadata(jackAfter, roleRole29Oid, emptySet(), emptySet(), singleton(USER_ADMINISTRATOR_OID), singleton("comment1"));
}

private void assertAssignmentMetadata(PrismObject<? extends FocusType> object, String targetOid, Set<String> createApproverOids,
Set<String> createApprovalComments, Set<String> modifyApproverOids, Set<String> modifyApprovalComments) {
AssignmentType assignment = findAssignmentByTargetRequired(object, targetOid);
MetadataType metadata = assignment.getMetadata();
PrismAsserts.assertReferenceOids("Wrong create approvers", createApproverOids, metadata.getCreateApproverRef());
PrismAsserts.assertEqualsCollectionUnordered("Wrong create comments", createApprovalComments, metadata.getCreateApprovalComment());
PrismAsserts.assertReferenceOids("Wrong modify approvers", modifyApproverOids, metadata.getModifyApproverRef());
PrismAsserts.assertEqualsCollectionUnordered("Wrong modify comments", modifyApprovalComments, metadata.getModifyApprovalComment());
}

@Test
Expand Down Expand Up @@ -578,12 +589,12 @@ protected void assertDeltaExecuted(int number, boolean yes, Task rootTask, Opera

@Override
protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception {
return true;
return null;
}

@Override
public List<ApprovalInstruction> getApprovalSequence() {
return null;
return singletonList(new ApprovalInstruction(null, true, USER_ADMINISTRATOR_OID, "comment2"));
}

@Override
Expand All @@ -599,6 +610,7 @@ protected void afterFirstClockworkRun(Task rootTask, List<Task> subtasks, List<W
PrismObject<UserType> jackAfter = getUser(userJackOid);
display("jack after", jackAfter);
assertAssignedRoles(jackAfter, roleRole29Oid);
assertAssignmentMetadata(jackAfter, roleRole29Oid, emptySet(), emptySet(), singleton(USER_ADMINISTRATOR_OID), singleton("comment2"));
}

@Test
Expand Down Expand Up @@ -709,7 +721,12 @@ protected void assertDeltaExecuted(int number, boolean yes, Task rootTask, Opera

@Override
protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception {
return true;
return null;
}

@Override
public List<ApprovalInstruction> getApprovalSequence() {
return singletonList(new ApprovalInstruction(null, true, USER_ADMINISTRATOR_OID, "comment3"));
}

@Override
Expand All @@ -725,6 +742,7 @@ protected void afterFirstClockworkRun(Task rootTask, List<Task> subtasks, List<W
PrismObject<UserType> jack = getUser(userJackOid);
display("jack", jack);
assertAssignedRoles(jack, roleRole28Oid);
assertAssignmentMetadata(jack, roleRole28Oid, singleton(USER_ADMINISTRATOR_OID), singleton("comment3"), emptySet(), emptySet());
}

@Test
Expand Down Expand Up @@ -815,12 +833,12 @@ protected void assertDeltaExecuted(int number, boolean yes, Task rootTask, Opera

@Override
protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception {
return true;
return null;
}

@Override
public List<ApprovalInstruction> getApprovalSequence() {
return null;
return singletonList(new ApprovalInstruction(null, true, USER_ADMINISTRATOR_OID, "comment4"));
}

@Override
Expand All @@ -836,6 +854,7 @@ protected void afterFirstClockworkRun(Task rootTask, List<Task> subtasks, List<W
PrismObject<UserType> jackAfter = getUser(userJackOid);
display("jack after", jackAfter);
assertAssignedRoles(jackAfter, roleRole28Oid);
assertAssignmentMetadata(jackAfter, roleRole28Oid, singleton(USER_ADMINISTRATOR_OID), singleton("comment3"), singleton(USER_ADMINISTRATOR_OID), singleton("comment4"));
}

@Test
Expand Down

0 comments on commit f98d4b9

Please sign in to comment.