diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java index 80eb9c9614b..f7472904002 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java @@ -1624,16 +1624,27 @@ public ObjectDelta subtract(@NotNull Collection paths) { return rv; } - public static class FactorOutResult { + public static class FactorOutResultMulti { public final ObjectDelta remainder; public final List> offsprings = new ArrayList<>(); - public FactorOutResult(ObjectDelta remainder) { + public FactorOutResultMulti(ObjectDelta remainder) { this.remainder = remainder; } } - public FactorOutResult factorOut(Collection paths, boolean cloneDelta) { + public static class FactorOutResultSingle { + public final ObjectDelta remainder; + public final ObjectDelta offspring; + + public FactorOutResultSingle(ObjectDelta remainder, ObjectDelta offspring) { + this.remainder = remainder; + this.offspring = offspring; + } + } + + @NotNull + public FactorOutResultSingle factorOut(Collection paths, boolean cloneDelta) { if (isAdd()) { return factorOutForAddDelta(paths, cloneDelta); } else if (isDelete()) { @@ -1643,7 +1654,8 @@ public FactorOutResult factorOut(Collection paths, boolean cloneDel } } - public FactorOutResult factorOutValues(ItemPath path, boolean cloneDelta) throws SchemaException { + @NotNull + public FactorOutResultMulti factorOutValues(ItemPath path, boolean cloneDelta) throws SchemaException { if (isAdd()) { return factorOutValuesForAddDelta(path, cloneDelta); } else if (isDelete()) { @@ -1662,9 +1674,9 @@ public FactorOutResult factorOutValues(ItemPath path, boolean cloneDelta) thr * involving splitting value-to-be-added into remainder and offspring delta. It's probably doable, * but some conditions would have to be met, e.g. inducement to be added must have an ID. */ - private FactorOutResult factorOutForModifyDelta(Collection paths, boolean cloneDelta) { + private FactorOutResultSingle factorOutForModifyDelta(Collection paths, boolean cloneDelta) { ObjectDelta remainder = cloneIfRequested(cloneDelta); - FactorOutResult rv = new FactorOutResult<>(remainder); + ObjectDelta offspring = null; List> modificationsFound = new ArrayList<>(); for (Iterator> iterator = remainder.modifications.iterator(); iterator.hasNext(); ) { ItemDelta modification = iterator.next(); @@ -1674,14 +1686,13 @@ private FactorOutResult factorOutForModifyDelta(Collection paths, b } } if (!modificationsFound.isEmpty()) { - ObjectDelta offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); + offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); modificationsFound.forEach(offspring::addModification); - rv.offsprings.add(offspring); } - return rv; + return new FactorOutResultSingle<>(remainder, offspring); } - private FactorOutResult factorOutForAddDelta(Collection paths, boolean cloneDelta) { + private FactorOutResultSingle factorOutForAddDelta(Collection paths, boolean cloneDelta) { List> itemsFound = new ArrayList<>(); for (ItemPath path : paths) { Item item = objectToAdd.findItem(path); @@ -1690,17 +1701,15 @@ private FactorOutResult factorOutForAddDelta(Collection paths, bool } } if (itemsFound.isEmpty()) { - return new FactorOutResult<>(this); + return new FactorOutResultSingle<>(this, null); } ObjectDelta remainder = cloneIfRequested(cloneDelta); - FactorOutResult rv = new FactorOutResult<>(remainder); ObjectDelta offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); for (Item item : itemsFound) { remainder.getObjectToAdd().remove(item); offspring.addModification(ItemDelta.createAddDeltaFor(item)); } - rv.offsprings.add(offspring); - return rv; + return new FactorOutResultSingle<>(remainder, offspring); } private ObjectDelta cloneIfRequested(boolean cloneDelta) { @@ -1716,9 +1725,9 @@ private ObjectDelta cloneIfRequested(boolean cloneDelta) { * involving splitting value-to-be-added into remainder and offspring delta. It's probably doable, * but some conditions would have to be met, e.g. inducement to be added must have an ID. */ - private FactorOutResult factorOutValuesForModifyDelta(ItemPath path, boolean cloneDelta) throws SchemaException { + private FactorOutResultMulti factorOutValuesForModifyDelta(ItemPath path, boolean cloneDelta) throws SchemaException { ObjectDelta remainder = cloneIfRequested(cloneDelta); - FactorOutResult rv = new FactorOutResult<>(remainder); + FactorOutResultMulti rv = new FactorOutResultMulti<>(remainder); MultiValuedMap> modificationsForId = new ArrayListValuedHashMap<>(); PrismObjectDefinition objDef = objectTypeClass != null ? prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(objectTypeClass) : null; @@ -1766,7 +1775,7 @@ private FactorOutResult factorOutValuesForModifyDelta(ItemPath path, boolean return rv; } - private ItemDelta createNewDelta(FactorOutResult rv, ItemDelta modification) + private ItemDelta createNewDelta(FactorOutResultMulti rv, ItemDelta modification) throws SchemaException { ObjectDelta offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); ItemDelta delta = modification.getDefinition().instantiate().createDelta(modification.getPath()); @@ -1775,14 +1784,14 @@ private ItemDelta createNewDelta(FactorOutResult rv, ItemDelta modifica return delta; } - private FactorOutResult factorOutValuesForAddDelta(ItemPath path, boolean cloneDelta) { + private FactorOutResultMulti factorOutValuesForAddDelta(ItemPath path, boolean cloneDelta) { Item item = objectToAdd.findItem(path); if (item == null || item.isEmpty()) { - return new FactorOutResult<>(this); + return new FactorOutResultMulti<>(this); } ObjectDelta remainder = cloneIfRequested(cloneDelta); remainder.getObjectToAdd().remove(item); - FactorOutResult rv = new FactorOutResult<>(remainder); + FactorOutResultMulti rv = new FactorOutResultMulti<>(remainder); for (PrismValue value : item.getValues()) { ObjectDelta offspring = new ObjectDelta<>(objectTypeClass, ChangeType.MODIFY, prismContext); offspring.addModification(ItemDelta.createAddDeltaFor(item, value)); diff --git a/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestSchemaDelta.java b/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestSchemaDelta.java index 5c0e79f5b8a..616a3798295 100644 --- a/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestSchemaDelta.java +++ b/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestSchemaDelta.java @@ -62,7 +62,7 @@ public void testAssignmentSameNullIdApplyToObject() throws Exception { PrismObject user = PrismTestUtil.parseObject(USER_JACK_FILE); //Delta - PrismContainerValue assignmentValue = new PrismContainerValue(getPrismContext()); + PrismContainerValue assignmentValue = new PrismContainerValue<>(getPrismContext()); // The value id is null assignmentValue.setPropertyRealValue(AssignmentType.F_DESCRIPTION, "jamalalicha patlama paprtala", getPrismContext()); @@ -91,7 +91,7 @@ public void testAddInducementConstructionSameNullIdApplyToObject() throws Except PrismObject role = PrismTestUtil.parseObject(ROLE_CONSTRUCTION_FILE); //Delta - PrismContainerValue inducementValue = new PrismContainerValue(getPrismContext()); + PrismContainerValue inducementValue = new PrismContainerValue<>(getPrismContext()); // The value id is null inducementValue.setPropertyRealValue(AssignmentType.F_DESCRIPTION, "jamalalicha patlama paprtala", getPrismContext()); @@ -328,7 +328,7 @@ public void testSubtractAssignmentFromAddDelta() throws Exception { // WHEN PrismContainerDefinition assignmentDef = PrismTestUtil.getSchemaRegistry() .findContainerDefinitionByCompileTimeClass(AssignmentType.class).clone(); - ((PrismContainerDefinitionImpl) assignmentDef).setMaxOccurs(1); + assignmentDef.setMaxOccurs(1); PrismContainer assignmentContainer = assignmentDef.instantiate(); PrismContainerValue assignmentValue = @@ -360,14 +360,14 @@ public void testSubtractAssignmentFromModifyDelta() throws Exception { user.asObjectable().getAssignment().get(0).setId(9999L); AssignmentType assignment9999 = new AssignmentType(); assignment9999.setId(9999L); - ObjectDelta delta = (ObjectDelta) DeltaBuilder.deltaFor(UserType.class, getPrismContext()) + ObjectDelta delta = DeltaBuilder.deltaFor(UserType.class, getPrismContext()) .item(UserType.F_ASSIGNMENT).delete(assignment9999) - .asObjectDelta(user.getOid()); + .asObjectDeltaCast(user.getOid()); // WHEN PrismContainerDefinition assignmentDef = PrismTestUtil.getSchemaRegistry() .findContainerDefinitionByCompileTimeClass(AssignmentType.class).clone(); - ((PrismContainerDefinitionImpl) assignmentDef).setMaxOccurs(1); + assignmentDef.setMaxOccurs(1); PrismContainer assignmentContainer = assignmentDef.instantiate(); PrismContainerValue assignmentValue = @@ -401,18 +401,18 @@ public void testFactorAddDeltaForItem() throws Exception { ObjectDelta addDelta = ObjectDelta.createAddDelta(user); // WHEN - ObjectDelta.FactorOutResult out = addDelta.factorOut(singleton(new ItemPath(UserType.F_ASSIGNMENT)), true); + ObjectDelta.FactorOutResultSingle out = addDelta.factorOut(singleton(new ItemPath(UserType.F_ASSIGNMENT)), true); // THEN System.out.println("Delta before factorOut:\n" + addDelta.debugDump() + "\n"); System.out.println("Delta after factorOut:\n" + out.remainder.debugDump() + "\n"); - System.out.println("Offspring deltas:\n" + DebugUtil.debugDump(out.offsprings) + "\n"); + System.out.println("Offspring delta:\n" + DebugUtil.debugDump(out.offspring) + "\n"); assertTrue("Remaining delta is not an ADD delta", out.remainder.isAdd()); assertEquals("Wrong # of remaining assignments", 0, out.remainder.getObjectToAdd().asObjectable().getAssignment().size()); - assertEquals("Wrong # of offspring deltas", 1, out.offsprings.size()); - assertEquals("Wrong # of modifications in offspring", 1, out.offsprings.get(0).getModifications().size()); - assertEquals("Wrong # of assignments to add", 3, out.offsprings.get(0).getModifications().iterator().next().getValuesToAdd().size()); + assertNotNull("Missing offspring delta", out.offspring); + assertEquals("Wrong # of modifications in offspring", 1, out.offspring.getModifications().size()); + assertEquals("Wrong # of assignments to add", 3, out.offspring.getModifications().iterator().next().getValuesToAdd().size()); } // subtract of single-valued PCV from multivalued one @@ -426,17 +426,17 @@ public void testFactorAddDeltaForItems() throws Exception { ObjectDelta addDelta = ObjectDelta.createAddDelta(user); // WHEN - ObjectDelta.FactorOutResult out = addDelta.factorOut(asList(new ItemPath(UserType.F_GIVEN_NAME), new ItemPath(UserType.F_FAMILY_NAME)), true); + ObjectDelta.FactorOutResultSingle out = addDelta.factorOut(asList(new ItemPath(UserType.F_GIVEN_NAME), new ItemPath(UserType.F_FAMILY_NAME)), true); // THEN System.out.println("Delta before factorOut:\n" + addDelta.debugDump() + "\n"); System.out.println("Delta after factorOut:\n" + out.remainder.debugDump() + "\n"); - System.out.println("Offspring deltas:\n" + DebugUtil.debugDump(out.offsprings) + "\n"); + System.out.println("Offspring delta:\n" + DebugUtil.debugDump(out.offspring) + "\n"); assertTrue("Remaining delta is not an ADD delta", out.remainder.isAdd()); assertEquals("Wrong # of remaining assignments", 3, out.remainder.getObjectToAdd().asObjectable().getAssignment().size()); - assertEquals("Wrong # of offspring deltas", 1, out.offsprings.size()); - assertEquals("Wrong # of modifications in offspring", 2, out.offsprings.get(0).getModifications().size()); + assertNotNull("Missing offspring delta", out.offspring); + assertEquals("Wrong # of modifications in offspring", 2, out.offspring.getModifications().size()); } @Test @@ -449,7 +449,7 @@ public void testFactorAddDeltaForItemValues() throws Exception { ObjectDelta addDelta = ObjectDelta.createAddDelta(user); // WHEN - ObjectDelta.FactorOutResult out = addDelta.factorOutValues(new ItemPath(UserType.F_ASSIGNMENT), true); + ObjectDelta.FactorOutResultMulti out = addDelta.factorOutValues(new ItemPath(UserType.F_ASSIGNMENT), true); // THEN System.out.println("Delta before factorOut:\n" + addDelta.debugDump() + "\n"); @@ -480,17 +480,17 @@ public void testFactorModifyDeltaForItem() throws Exception { .asObjectDeltaCast("oid1"); // WHEN - ObjectDelta.FactorOutResult out = delta.factorOut(singleton(new ItemPath(UserType.F_ASSIGNMENT)), true); + ObjectDelta.FactorOutResultSingle out = delta.factorOut(singleton(new ItemPath(UserType.F_ASSIGNMENT)), true); // THEN System.out.println("Delta before operation:\n" + delta.debugDump() + "\n"); System.out.println("Delta after factorOut:\n" + out.remainder.debugDump() + "\n"); - System.out.println("Offspring deltas:\n" + DebugUtil.debugDump(out.offsprings) + "\n"); + System.out.println("Offspring delta:\n" + DebugUtil.debugDump(out.offspring) + "\n"); assertTrue("Remaining delta is not a MODIFY delta", out.remainder.isModify()); assertEquals("Wrong # of remaining modifications", 1, out.remainder.getModifications().size()); - assertEquals("Wrong # of offspring deltas", 1, out.offsprings.size()); - assertEquals("Wrong # of modifications in offspring 0", 2, out.offsprings.get(0).getModifications().size()); + assertNotNull("Missing offspring delta", out.offspring); + assertEquals("Wrong # of modifications in offspring 0", 2, out.offspring.getModifications().size()); } @Test @@ -510,7 +510,7 @@ public void testFactorModifyDeltaForItemValues() throws Exception { .asObjectDeltaCast("oid1"); // WHEN - ObjectDelta.FactorOutResult out = delta.factorOutValues(new ItemPath(UserType.F_ASSIGNMENT), true); + ObjectDelta.FactorOutResultMulti out = delta.factorOutValues(new ItemPath(UserType.F_ASSIGNMENT), true); // THEN System.out.println("Delta before operation:\n" + delta.debugDump() + "\n"); diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java index ec18385a160..e9e9915e070 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java @@ -33,12 +33,13 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; public interface EvaluatedAssignment extends DebugDumpable { AssignmentType getAssignmentType(); + Long getAssignmentId(); + Collection getAuthorizations(); Collection getAdminGuiConfigurations(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java index c86cea8a7a2..0cec40b34da 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java @@ -105,6 +105,12 @@ public AssignmentType getAssignmentType() { return asContainerable(assignmentIdi.getSingleValue(evaluatedOld)); } + @Override + public Long getAssignmentId() { + Item, PrismContainerDefinition> any = assignmentIdi.getAnyItem(); + return any != null && !any.getValues().isEmpty() ? any.getValue(0).getId() : null; + } + @Override public AssignmentType getAssignmentType(boolean old) { return asContainerable(assignmentIdi.getSingleValue(old)); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java index 8947e15abd3..d84141c0cc7 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java @@ -72,12 +72,12 @@ public class PolicyRuleProcessor { @Autowired private MappingEvaluator mappingEvaluator; @Autowired private PolicyStateRecorder policyStateRecorder; - @Autowired private AssignmentConstraintEvaluator assignmentConstraintEvaluator; + @Autowired private AssignmentModificationConstraintEvaluator assignmentConstraintEvaluator; @Autowired private HasAssignmentConstraintEvaluator hasAssignmentConstraintEvaluator; @Autowired private ExclusionConstraintEvaluator exclusionConstraintEvaluator; @Autowired private MultiplicityConstraintEvaluator multiplicityConstraintEvaluator; @Autowired private PolicySituationConstraintEvaluator policySituationConstraintEvaluator; - @Autowired private ModificationConstraintEvaluator modificationConstraintEvaluator; + @Autowired private ObjectModificationConstraintEvaluator modificationConstraintEvaluator; @Autowired private StateConstraintEvaluator stateConstraintEvaluator; @Autowired private CompositeConstraintEvaluator compositeConstraintEvaluator; @Autowired private TransitionConstraintEvaluator transitionConstraintEvaluator; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/AssignmentConstraintEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/AssignmentModificationConstraintEvaluator.java similarity index 54% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/AssignmentConstraintEvaluator.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/AssignmentModificationConstraintEvaluator.java index ca7131eea3a..2186dc31053 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/AssignmentConstraintEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/AssignmentModificationConstraintEvaluator.java @@ -18,9 +18,10 @@ import com.evolveum.midpoint.model.api.context.EvaluatedModificationTrigger; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; -import com.evolveum.midpoint.model.api.context.ModelState; import com.evolveum.midpoint.model.impl.lens.projector.policy.AssignmentPolicyRuleEvaluationContext; import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleEvaluationContext; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; @@ -30,28 +31,30 @@ import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentModificationPolicyConstraintType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyConstraintKindType; import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; -import org.springframework.beans.factory.annotation.Autowired; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; import org.springframework.stereotype.Component; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; -import java.util.Collections; +import java.util.Collection; import java.util.List; +import static java.util.Collections.singletonList; +import static org.apache.commons.collections4.CollectionUtils.emptyIfNull; + /** * @author semancik * @author mederly */ @Component -public class AssignmentConstraintEvaluator implements PolicyConstraintEvaluator { +public class AssignmentModificationConstraintEvaluator extends ModificationConstraintEvaluator { private static final String CONSTRAINT_KEY_PREFIX = "assignmentModification."; - @Autowired private ConstraintEvaluatorHelper evaluatorHelper; - @Override public EvaluatedPolicyRuleTrigger evaluate(JAXBElement constraintElement, PolicyRuleEvaluationContext rctx, OperationResult result) @@ -61,36 +64,27 @@ public EvaluatedPolicyRuleTrigger evaluate(JAXBElement ctx = (AssignmentPolicyRuleEvaluationContext) rctx; - if (ctx.isDirect && (ctx.inPlus || ctx.inMinus) && matchesOperation(constraint, ctx.inPlus, ctx.inMinus)) { - List relationsToCheck = constraint.getRelation().isEmpty() ? - Collections.singletonList(null) : constraint.getRelation(); - for (QName constraintRelation : relationsToCheck) { - if (MiscSchemaUtil.compareRelation(constraintRelation, ctx.evaluatedAssignment.getRelation())) { - return new EvaluatedModificationTrigger( - PolicyConstraintKindType.ASSIGNMENT_MODIFICATION, - constraint, createMessage(constraintElement, ctx, result)); - } - } + if (!ctx.isDirect || + !operationMatches(constraint, ctx.inPlus, ctx.inZero, ctx.inMinus) || + !relationMatches(constraint, ctx) || + !pathsMatch(constraint, ctx)) { + return null; } - return null; + // TODO check modifications + return new EvaluatedModificationTrigger( + PolicyConstraintKindType.ASSIGNMENT_MODIFICATION, + constraint, createMessage(constraintElement, ctx, result)); } private LocalizableMessage createMessage(JAXBElement constraint, AssignmentPolicyRuleEvaluationContext ctx, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { - ModelState state = ctx.lensContext.getState(); - String stateKey; - if (state == ModelState.INITIAL || state == ModelState.PRIMARY) { - stateKey = "toBe"; - } else { - stateKey = "was"; - // TODO derive more precise information from executed deltas, if needed - } + String stateKey = createStateKey(ctx); if (ctx.inPlus) { stateKey += "Added"; } else if (ctx.inMinus) { stateKey += "Deleted"; - } else{ + } else { stateKey += "Modified"; } LocalizableMessage builtInMessage = new LocalizableMessageBuilder() @@ -101,11 +95,62 @@ private LocalizableMessage createMessage(JAXBElement boolean relationMatches(AssignmentModificationPolicyConstraintType constraint, + AssignmentPolicyRuleEvaluationContext ctx) { + List relationsToCheck = constraint.getRelation().isEmpty() ? + singletonList(null) : constraint.getRelation(); + for (QName constraintRelation : relationsToCheck) { + if (MiscSchemaUtil.compareRelation(constraintRelation, ctx.evaluatedAssignment.getRelation())) { + return true; + } + } + return false; + } + + private boolean operationMatches(AssignmentModificationPolicyConstraintType constraint, boolean inPlus, boolean inZero, boolean inMinus) { List operations = constraint.getOperation(); return operations.isEmpty() || inPlus && operations.contains(ChangeTypeType.ADD) || + inZero && operations.contains(ChangeTypeType.MODIFY) || inMinus && operations.contains(ChangeTypeType.DELETE); } + // TODO discriminate between primary and secondary changes (perhaps make it configurable) + // Primary changes are "approvable", secondary ones are not. + private boolean pathsMatch(AssignmentModificationPolicyConstraintType constraint, + AssignmentPolicyRuleEvaluationContext ctx) throws SchemaException { + + // hope this is correctly filled in + if (constraint.getItem().isEmpty()) { + if (ctx.inPlus || ctx.inMinus) { + return true; + } else { + Collection> subItemDeltas = ctx.evaluatedAssignment.getAssignmentIdi().getSubItemDeltas(); + return subItemDeltas != null && !subItemDeltas.isEmpty(); + } + } + for (ItemPathType path : constraint.getItem()) { + ItemPath itemPath = path.getItemPath(); + if (ctx.inPlus && !pathMatches(ctx.evaluatedAssignment.getAssignmentType(false), itemPath) || + ctx.inMinus && !pathMatches(ctx.evaluatedAssignment.getAssignmentType(true), itemPath) || + ctx.inZero && !pathMatches(ctx.evaluatedAssignment.getAssignmentIdi().getSubItemDeltas(), itemPath)) { + return false; + } + } + return true; + } + + private boolean pathMatches(AssignmentType assignment, ItemPath path) throws SchemaException { + return assignment.asPrismContainerValue().containsItem(path, false); + } + + private boolean pathMatches(Collection> deltas, ItemPath path) { + for (ItemDelta delta : emptyIfNull(deltas)) { + // TODO what about changes like extension/cities[2]/name (in delta) vs. extension/cities/name (in spec) + if (path.isSubPathOrEquivalent(delta.getPath().tail(2))) { + return true; + } + } + return false; + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ModificationConstraintEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ModificationConstraintEvaluator.java index d80276b207d..3126ddbe1e6 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ModificationConstraintEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ModificationConstraintEvaluator.java @@ -16,67 +16,29 @@ package com.evolveum.midpoint.model.impl.lens.projector.policy.evaluators; -import com.evolveum.midpoint.model.api.context.EvaluatedModificationTrigger; -import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; import com.evolveum.midpoint.model.api.context.ModelState; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.projector.policy.ObjectPolicyRuleEvaluationContext; import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleEvaluationContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.util.LocalizableMessage; -import com.evolveum.midpoint.util.LocalizableMessageBuilder; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -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.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; -import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ModificationPolicyConstraintType; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.xml.bind.JAXBElement; -import java.util.List; - /** * @author semancik * @author mederly */ @Component -public class ModificationConstraintEvaluator implements PolicyConstraintEvaluator { +public abstract class ModificationConstraintEvaluator implements PolicyConstraintEvaluator { private static final Trace LOGGER = TraceManager.getTrace(ModificationConstraintEvaluator.class); - private static final String CONSTRAINT_KEY_PREFIX = "objectModification."; - - @Autowired private ConstraintEvaluatorHelper evaluatorHelper; - - @Override - public EvaluatedPolicyRuleTrigger evaluate(JAXBElement constraint, - PolicyRuleEvaluationContext rctx, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { - - if (!(rctx instanceof ObjectPolicyRuleEvaluationContext)) { - return null; - } - ObjectPolicyRuleEvaluationContext ctx = (ObjectPolicyRuleEvaluationContext) rctx; - - if (modificationConstraintMatches(constraint.getValue(), ctx)) { - LocalizableMessage message = createMessage(constraint, rctx, result); - return new EvaluatedModificationTrigger(PolicyConstraintKindType.OBJECT_MODIFICATION, constraint.getValue(), message); - } else { - return null; - } - } + @Autowired protected ConstraintEvaluatorHelper evaluatorHelper; - private LocalizableMessage createMessage(JAXBElement constraint, - PolicyRuleEvaluationContext rctx, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { + @NotNull + protected String createStateKey(PolicyRuleEvaluationContext rctx) { ModelState state = rctx.lensContext.getState(); String stateKey; if (state == ModelState.INITIAL || state == ModelState.PRIMARY) { @@ -85,61 +47,6 @@ private LocalizableMessage createMessage(JAXBElement boolean modificationConstraintMatches(ModificationPolicyConstraintType constraint, - ObjectPolicyRuleEvaluationContext ctx) throws SchemaException { - if (!operationMatches(ctx.focusContext, constraint.getOperation())) { - LOGGER.trace("Rule {} operation not applicable", ctx.policyRule.getName()); - return false; - } - if (constraint.getItem().isEmpty()) { - return ctx.focusContext.hasAnyDelta(); - } - ObjectDelta summaryDelta = ObjectDelta.union(ctx.focusContext.getPrimaryDelta(), ctx.focusContext.getSecondaryDelta()); - if (summaryDelta == null) { - return false; - } - for (ItemPathType path : constraint.getItem()) { - if (!pathMatches(summaryDelta, ctx.focusContext.getObjectOld(), path.getItemPath())) { - return false; - } - } - return true; - } - - private boolean pathMatches(ObjectDelta delta, PrismObject objectOld, ItemPath path) - throws SchemaException { - if (delta.isAdd()) { - return delta.getObjectToAdd().containsItem(path, false); - } else if (delta.isDelete()) { - return objectOld != null && objectOld.containsItem(path, false); - } else { - return delta.findItemDelta(path) != null; - } - } - - private boolean operationMatches(LensFocusContext focusContext, List operations) { - if (operations.isEmpty()) { - return true; - } - for (ChangeTypeType operation: operations) { - if (focusContext.operationMatches(operation)) { - return true; - } - } - return false; + return stateKey; } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ObjectModificationConstraintEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ObjectModificationConstraintEvaluator.java new file mode 100644 index 00000000000..59d70f3a617 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ObjectModificationConstraintEvaluator.java @@ -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.model.impl.lens.projector.policy.evaluators; + +import com.evolveum.midpoint.model.api.context.EvaluatedModificationTrigger; +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.model.impl.lens.projector.policy.ObjectPolicyRuleEvaluationContext; +import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleEvaluationContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.LocalizableMessageBuilder; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +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.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ModificationPolicyConstraintType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyConstraintKindType; +import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import org.springframework.stereotype.Component; + +import javax.xml.bind.JAXBElement; +import java.util.List; + +/** + * @author semancik + * @author mederly + */ +@Component +public class ObjectModificationConstraintEvaluator extends ModificationConstraintEvaluator { + + private static final Trace LOGGER = TraceManager.getTrace(ObjectModificationConstraintEvaluator.class); + + private static final String CONSTRAINT_KEY_PREFIX = "objectModification."; + + @Override + public EvaluatedPolicyRuleTrigger evaluate(JAXBElement constraint, + PolicyRuleEvaluationContext rctx, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { + + if (!(rctx instanceof ObjectPolicyRuleEvaluationContext)) { + return null; + } + ObjectPolicyRuleEvaluationContext ctx = (ObjectPolicyRuleEvaluationContext) rctx; + + if (modificationConstraintMatches(constraint.getValue(), ctx)) { + LocalizableMessage message = createMessage(constraint, rctx, result); + return new EvaluatedModificationTrigger(PolicyConstraintKindType.OBJECT_MODIFICATION, constraint.getValue(), message); + } else { + return null; + } + } + + private LocalizableMessage createMessage(JAXBElement constraint, + PolicyRuleEvaluationContext rctx, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { + String stateKey = createStateKey(rctx); + if (rctx.focusContext.isAdd()) { + stateKey += "Added"; + } else if (rctx.focusContext.isDelete()) { + stateKey += "Deleted"; + } else{ + stateKey += "Modified"; + } + LocalizableMessage builtInMessage = new LocalizableMessageBuilder() + .key(SchemaConstants.DEFAULT_POLICY_CONSTRAINT_KEY_PREFIX + CONSTRAINT_KEY_PREFIX + stateKey) + .args(evaluatorHelper.createObjectSpecification(rctx.focusContext.getObjectAny())) + .build(); + return evaluatorHelper.createLocalizableMessage(constraint.getValue(), rctx, builtInMessage, result); + } + + // TODO discriminate between primary and secondary changes (perhaps make it configurable) + // Primary changes are "approvable", secondary ones are not. + private boolean modificationConstraintMatches(ModificationPolicyConstraintType constraint, + ObjectPolicyRuleEvaluationContext ctx) throws SchemaException { + if (!operationMatches(ctx.focusContext, constraint.getOperation())) { + LOGGER.trace("Rule {} operation not applicable", ctx.policyRule.getName()); + return false; + } + if (constraint.getItem().isEmpty()) { + return ctx.focusContext.hasAnyDelta(); + } + ObjectDelta summaryDelta = ObjectDelta.union(ctx.focusContext.getPrimaryDelta(), ctx.focusContext.getSecondaryDelta()); + if (summaryDelta == null) { + return false; + } + for (ItemPathType path : constraint.getItem()) { + if (!pathMatches(summaryDelta, ctx.focusContext.getObjectOld(), path.getItemPath())) { + return false; + } + } + return true; + } + + private boolean pathMatches(ObjectDelta delta, PrismObject objectOld, ItemPath path) + throws SchemaException { + if (delta.isAdd()) { + return delta.getObjectToAdd().containsItem(path, false); + } else if (delta.isDelete()) { + return objectOld != null && objectOld.containsItem(path, false); + } else { + return delta.findItemDelta(path) != null; + } + } + + private boolean operationMatches(LensFocusContext focusContext, List operations) { + if (operations.isEmpty()) { + return true; + } + for (ChangeTypeType operation: operations) { + if (focusContext.operationMatches(operation)) { + return true; + } + } + return false; + } +} diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules2.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules2.java index 26865e22941..e586c5df7be 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules2.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules2.java @@ -75,7 +75,7 @@ public class TestPolicyRules2 extends AbstractLensTest { protected static final File ROLE_UNRESOLVABLE_REFERENCES_FILE = new File(TEST_DIR, "role-unresolvable-references.xml"); protected static final File ROLE_AMBIGUOUS_REFERENCE_FILE = new File(TEST_DIR, "role-ambiguous-reference.xml"); - private static final int STUDENT_TARGET_RULES = 5; // one is global + private static final int STUDENT_TARGET_RULES = 6; // one is global private static final int STUDENT_FOCUS_RULES = 21; private static final String ACTIVITY_DESCRIPTION = "PROJECTOR (test)"; @@ -217,6 +217,7 @@ public void test110JoeAttemptAssignRoleStudent() throws Exception { /** * Jacks's cost center is set to be 1900. So 1900/new constraint should trigger. But not 1900/current nor 1900/old. + * Also validTo constraint should trigger. */ @SuppressWarnings("unchecked") @Test @@ -230,8 +231,10 @@ public void test120JackAttemptToMoveTo1900AndAssignRoleStudent() throws Exceptio LensContext context = createUserLensContext(); fillContextWithUser(context, USER_JACK_OID, result); + AssignmentType assignment = ObjectTypeUtil.createAssignmentTo(roleStudentOid, ObjectTypes.ROLE, prismContext); + assignment.beginActivation().validTo("2099-01-01T00:00:00"); context.getFocusContext().addPrimaryDelta((ObjectDelta) DeltaBuilder.deltaFor(UserType.class, prismContext) - .item(UserType.F_ASSIGNMENT).add(ObjectTypeUtil.createAssignmentTo(roleStudentOid, ObjectTypes.ROLE, prismContext)) + .item(UserType.F_ASSIGNMENT).add(assignment) .item(UserType.F_COST_CENTER).replace("1900") .asObjectDelta(USER_JACK_OID)); display("Input context", context); @@ -251,8 +254,8 @@ public void test120JackAttemptToMoveTo1900AndAssignRoleStudent() throws Exceptio //dumpPolicySituations(context); assertEvaluatedTargetPolicyRules(context, STUDENT_TARGET_RULES); - assertTargetTriggers(context, null, 2); - assertTargetTriggers(context, PolicyConstraintKindType.ASSIGNMENT_MODIFICATION, 1); + assertTargetTriggers(context, null, 3); + assertTargetTriggers(context, PolicyConstraintKindType.ASSIGNMENT_MODIFICATION, 2); assertTargetTriggers(context, PolicyConstraintKindType.OBJECT_STATE, 1); assertEvaluatedFocusPolicyRules(context, STUDENT_FOCUS_RULES); @@ -281,8 +284,10 @@ public void test130JackMoveTo1900AndAssignRoleStudent() throws Exception { LensContext context = createUserLensContext(); fillContextWithUser(context, USER_JACK_OID, result); + AssignmentType assignment = ObjectTypeUtil.createAssignmentTo(roleStudentOid, ObjectTypes.ROLE, prismContext); + assignment.beginActivation().validTo("2099-01-01T00:00:00"); context.getFocusContext().addPrimaryDelta((ObjectDelta) DeltaBuilder.deltaFor(UserType.class, prismContext) - .item(UserType.F_ASSIGNMENT).add(ObjectTypeUtil.createAssignmentTo(roleStudentOid, ObjectTypes.ROLE, prismContext)) + .item(UserType.F_ASSIGNMENT).add(assignment) .item(UserType.F_COST_CENTER).replace("1900") .asObjectDelta(USER_JACK_OID)); display("Input context", context); @@ -303,8 +308,8 @@ public void test130JackMoveTo1900AndAssignRoleStudent() throws Exception { dumpPolicySituations(context); assertEvaluatedTargetPolicyRules(context, STUDENT_TARGET_RULES); - assertTargetTriggers(context, null, 2); - assertTargetTriggers(context, PolicyConstraintKindType.ASSIGNMENT_MODIFICATION, 1); + assertTargetTriggers(context, null, 3); + assertTargetTriggers(context, PolicyConstraintKindType.ASSIGNMENT_MODIFICATION, 2); assertTargetTriggers(context, PolicyConstraintKindType.OBJECT_STATE, 1); assertEvaluatedFocusPolicyRules(context, STUDENT_FOCUS_RULES); @@ -323,6 +328,55 @@ public void test130JackMoveTo1900AndAssignRoleStudent() throws Exception { // SchemaConstants.MODEL_POLICY_SITUATION_HAS_NO_ASSIGNMENT); } + @SuppressWarnings("unchecked") + @Test + public void test135JackChangeValidTo() throws Exception { + final String TEST_NAME = "test135JackChangeValidTo"; + TestUtil.displayTestTitle(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestPolicyRules2.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + Long assignmentId = getUser(USER_JACK_OID).asObjectable().getAssignment().get(0).getId(); + + LensContext context = createUserLensContext(); + fillContextWithUser(context, USER_JACK_OID, result); + context.getFocusContext().addPrimaryDelta((ObjectDelta) DeltaBuilder.deltaFor(UserType.class, prismContext) + .item(UserType.F_ASSIGNMENT, assignmentId, AssignmentType.F_ACTIVATION, ActivationType.F_VALID_TO).replace() + .asObjectDelta(USER_JACK_OID)); + display("Input context", context); + + assertFocusModificationSanity(context); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + + // cannot run the clockwork as in the secondary state the deltas are no longer considered (!) + projector.project(context, ACTIVITY_DESCRIPTION, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + dumpPolicyRules(context); + dumpPolicySituations(context); + + assertEvaluatedTargetPolicyRules(context, STUDENT_TARGET_RULES); + assertTargetTriggers(context, null, 3); + assertTargetTriggers(context, PolicyConstraintKindType.ASSIGNMENT_MODIFICATION, 2); + assertTargetTriggers(context, PolicyConstraintKindType.OBJECT_STATE, 1); + + assertEvaluatedFocusPolicyRules(context, STUDENT_FOCUS_RULES); + assertFocusTriggers(context, null, 9); + assertFocusTriggers(context, PolicyConstraintKindType.OBJECT_STATE, 1); + assertFocusTriggers(context, PolicyConstraintKindType.HAS_ASSIGNMENT, 4); + assertFocusTriggers(context, PolicyConstraintKindType.HAS_NO_ASSIGNMENT, 1); + assertFocusTriggers(context, PolicyConstraintKindType.TRANSITION, 3); + } + + @Test public void test140JackNoChange() throws Exception { final String TEST_NAME = "test140JackNoChange"; @@ -602,7 +656,7 @@ public void test180StudentRecompute() throws Exception { assertEvaluatedTargetPolicyRules(context, 0); assertTargetTriggers(context, null, 0); - assertEvaluatedFocusPolicyRules(context, 4); + assertEvaluatedFocusPolicyRules(context, 5); assertFocusTriggers(context, null, 2); assertFocusTriggers(context, PolicyConstraintKindType.OBJECT_STATE, 2); } diff --git a/model/model-impl/src/test/resources/lens/policy/role-student.xml b/model/model-impl/src/test/resources/lens/policy/role-student.xml index be3aed6d005..e6533ea095d 100644 --- a/model/model-impl/src/test/resources/lens/policy/role-student.xml +++ b/model/model-impl/src/test/resources/lens/policy/role-student.xml @@ -33,6 +33,19 @@ + + + any-change-to-assignment-of-student-with-validTo + + + activation/validTo + + + + + + + diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpChildWfTaskCreationInstruction.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpChildWfTaskCreationInstruction.java index 0dba9606f42..fc83599c8ef 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpChildWfTaskCreationInstruction.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PcpChildWfTaskCreationInstruction.java @@ -90,7 +90,7 @@ public void prepareCommonAttributes(PrimaryChangeAspect aspect, ModelContext wfContext.getEvent().add(event); } - public void setDeltasToProcess(ObjectDelta delta) { + public void setDeltasToProcess(ObjectDelta delta) { setDeltasToProcesses(new ObjectTreeDeltas<>(delta, getChangeProcessor().getPrismContext())); } 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 332cebe377f..38600048925 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 @@ -48,7 +48,6 @@ import com.evolveum.midpoint.wf.impl.processors.primary.PcpChildWfTaskCreationInstruction; import com.evolveum.midpoint.wf.impl.util.MiscDataUtil; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.Validate; import org.apache.velocity.util.StringUtils; import org.jetbrains.annotations.NotNull; @@ -60,6 +59,8 @@ import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef; import static com.evolveum.midpoint.wf.impl.util.MiscDataUtil.getFocusObjectName; import static com.evolveum.midpoint.wf.impl.util.MiscDataUtil.getFocusObjectOid; +import static java.util.Collections.singleton; +import static org.apache.commons.collections4.CollectionUtils.addIgnoreNull; /** * Part of PolicyRuleBasedAspect related to assignments. @@ -88,23 +89,24 @@ void extractAssignmentBasedInstructions(ObjectTreeDeltas objectTreeDeltas, Pr return; } - for (EvaluatedAssignment newAssignment : evaluatedAssignmentTriple.getPlusSet()) { - CollectionUtils.addIgnoreNull(instructions, - createInstructionFromAssignment(newAssignment, PlusMinusZero.PLUS, objectTreeDeltas, requester, ctx, result)); + for (EvaluatedAssignment assignmentAdded : evaluatedAssignmentTriple.getPlusSet()) { + addIgnoreNull(instructions, + createInstructionFromAssignment(assignmentAdded, PlusMinusZero.PLUS, objectTreeDeltas, requester, ctx, result)); } - for (EvaluatedAssignment newAssignment : evaluatedAssignmentTriple.getMinusSet()) { - CollectionUtils.addIgnoreNull(instructions, - createInstructionFromAssignment(newAssignment, PlusMinusZero.MINUS, objectTreeDeltas, requester, ctx, result)); + for (EvaluatedAssignment assignmentRemoved : evaluatedAssignmentTriple.getMinusSet()) { + addIgnoreNull(instructions, + createInstructionFromAssignment(assignmentRemoved, PlusMinusZero.MINUS, objectTreeDeltas, requester, ctx, result)); + } + for (EvaluatedAssignment assignmentModified : evaluatedAssignmentTriple.getZeroSet()) { + addIgnoreNull(instructions, + createInstructionFromAssignment(assignmentModified, PlusMinusZero.ZERO, objectTreeDeltas, requester, ctx, result)); } - // Note: to implement assignment modifications we would need to fix subtractFromModification method below } private PcpChildWfTaskCreationInstruction createInstructionFromAssignment( EvaluatedAssignment evaluatedAssignment, PlusMinusZero assignmentMode, @NotNull ObjectTreeDeltas objectTreeDeltas, PrismObject requester, ModelInvocationContext ctx, OperationResult result) throws SchemaException { - assert assignmentMode == PlusMinusZero.PLUS || assignmentMode == PlusMinusZero.MINUS; - // We collect all target rules; hoping that only relevant ones are triggered. // For example, if we have assignment policy rule on induced role, it will get here. // But projector will take care not to trigger it unless the rule is capable (e.g. configured) @@ -131,17 +133,51 @@ private PcpChildWfTaskCreationInstruction createIns } // Cut assignment from delta, prepare task instruction + ObjectDelta deltaToApprove; + if (assignmentMode != PlusMinusZero.ZERO) { + deltaToApprove = factorOutAssignmentValue(evaluatedAssignment, assignmentMode, objectTreeDeltas, ctx); + } else { + deltaToApprove = factorOutAssignmentModifications(evaluatedAssignment, objectTreeDeltas); + } + if (deltaToApprove == null) { + return null; + } + + ObjectDelta focusDelta = objectTreeDeltas.getFocusChange(); + if (focusDelta.isAdd()) { + miscDataUtil.generateFocusOidIfNeeded(ctx.modelContext, focusDelta); + } + return prepareAssignmentRelatedTaskInstruction(approvalSchemaResult, evaluatedAssignment, deltaToApprove, + assignmentMode, ctx.modelContext, requester, result); + } + + private ObjectDelta factorOutAssignmentModifications(EvaluatedAssignment evaluatedAssignment, + ObjectTreeDeltas objectTreeDeltas) { + Long id = evaluatedAssignment.getAssignmentId(); + if (id == null) { + // Should never occur: assignments to be modified must have IDs. + throw new IllegalStateException("None or unnumbered assignment in " + evaluatedAssignment); + } + ItemPath assignmentValuePath = new ItemPath(FocusType.F_ASSIGNMENT, id); + + ObjectDelta focusDelta = objectTreeDeltas.getFocusChange(); + assert focusDelta != null; + ObjectDelta.FactorOutResultSingle factorOutResult = focusDelta.factorOut(singleton(assignmentValuePath), false); + if (factorOutResult.offspring == null) { + LOGGER.trace("No modifications for an assignment, skipping approval action(s). Assignment = {}", evaluatedAssignment); + return null; + } + return factorOutResult.offspring; + } + + private ObjectDelta factorOutAssignmentValue(EvaluatedAssignment evaluatedAssignment, PlusMinusZero assignmentMode, + @NotNull ObjectTreeDeltas objectTreeDeltas, ModelInvocationContext ctx) throws SchemaException { + assert assignmentMode == PlusMinusZero.PLUS || assignmentMode == PlusMinusZero.MINUS; @SuppressWarnings("unchecked") PrismContainerValue assignmentValue = evaluatedAssignment.getAssignmentType().asPrismContainerValue(); - boolean assignmentRemoved; - switch (assignmentMode) { - case PLUS: assignmentRemoved = false; break; - case MINUS: assignmentRemoved = true; break; - default: throw new UnsupportedOperationException("Processing assignment zero set is not yet supported."); - } - boolean removed = objectTreeDeltas.subtractFromFocusDelta(new ItemPath(FocusType.F_ASSIGNMENT), assignmentValue, assignmentRemoved, - false); - if (!removed) { + boolean assignmentRemoved = assignmentMode == PlusMinusZero.MINUS; + boolean reallyRemoved = objectTreeDeltas.subtractFromFocusDelta(new ItemPath(FocusType.F_ASSIGNMENT), assignmentValue, assignmentRemoved, false); + if (!reallyRemoved) { ObjectDelta secondaryDelta = ctx.modelContext.getFocusContext().getSecondaryDelta(); if (secondaryDelta != null && secondaryDelta.subtract(new ItemPath(FocusType.F_ASSIGNMENT), assignmentValue, assignmentRemoved, true)) { LOGGER.trace("Assignment to be added/deleted was not found in primary delta. It is present in secondary delta, so there's nothing to be approved."); @@ -152,20 +188,19 @@ private PcpChildWfTaskCreationInstruction createIns + "\nPrimary delta:\n" + objectTreeDeltas.debugDump(); throw new IllegalStateException(message); } - ObjectDelta focusDelta = objectTreeDeltas.getFocusChange(); - if (focusDelta.isAdd()) { - miscDataUtil.generateFocusOidIfNeeded(ctx.modelContext, focusDelta); - } - return prepareAssignmentRelatedTaskInstruction(approvalSchemaResult, evaluatedAssignment, assignmentRemoved, ctx.modelContext, requester, result); + String objectOid = getFocusObjectOid(ctx.modelContext); + return assignmentToDelta(ctx.modelContext.getFocusClass(), + evaluatedAssignment.getAssignmentType(), assignmentRemoved, objectOid); } private void logApprovalActions(EvaluatedAssignment newAssignment, List triggeredApprovalActionRules, PlusMinusZero plusMinusZero) { if (LOGGER.isDebugEnabled() && !triggeredApprovalActionRules.isEmpty()) { LOGGER.trace("-------------------------------------------------------------"); + String verb = plusMinusZero == PlusMinusZero.PLUS ? "added" : + plusMinusZero == PlusMinusZero.MINUS ? "deleted" : "modified"; LOGGER.debug("Assignment to be {}: {}: {} this target policy rules, {} triggered approval actions:", - plusMinusZero == PlusMinusZero.PLUS ? "added" : "deleted", - newAssignment, newAssignment.getThisTargetPolicyRules().size(), triggeredApprovalActionRules.size()); + verb, newAssignment, newAssignment.getThisTargetPolicyRules().size(), triggeredApprovalActionRules.size()); for (EvaluatedPolicyRule t : triggeredApprovalActionRules) { LOGGER.debug(" - Approval actions: {}", t.getActions().getApproval()); for (EvaluatedPolicyRuleTrigger trigger : t.getTriggers()) { @@ -224,10 +259,10 @@ private ApprovalSchemaBuilder.Result createSchemaWithRules(List prepareAssignmentRelatedTaskInstruction( ApprovalSchemaBuilder.Result builderResult, - EvaluatedAssignment evaluatedAssignment, boolean assignmentRemoved, ModelContext modelContext, + EvaluatedAssignment evaluatedAssignment, ObjectDelta deltaToApprove, + PlusMinusZero assignmentMode, ModelContext modelContext, PrismObject requester, OperationResult result) throws SchemaException { - String objectOid = getFocusObjectOid(modelContext); String objectName = getFocusObjectName(modelContext); @SuppressWarnings("unchecked") @@ -235,10 +270,12 @@ private PcpChildWfTaskCreationInstruction prepareAs Validate.notNull(target, "assignment target is null"); String targetName = target.getName() != null ? target.getName().getOrig() : "(unnamed)"; - String operation = (assignmentRemoved - ? "unassigning " + targetName + " from " : - "assigning " + targetName + " to ") - + objectName; + String operation; + switch (assignmentMode) { + case PLUS: operation = "assigning " + targetName + " to " + objectName; break; + case MINUS: operation = "unassigning " + targetName + " from " + objectName; break; + default: operation = "modifying assignment of " + targetName + " on " + objectName; break; + } String approvalTaskName = "Approve " + operation; PcpChildWfTaskCreationInstruction instruction = @@ -247,9 +284,7 @@ private PcpChildWfTaskCreationInstruction prepareAs instruction.prepareCommonAttributes(main, modelContext, requester); - ObjectDelta delta = assignmentToDelta(modelContext.getFocusClass(), - evaluatedAssignment.getAssignmentType(), assignmentRemoved, objectOid); - instruction.setDeltasToProcess(delta); + instruction.setDeltasToProcess(deltaToApprove); instruction.setObjectRef(modelContext, result); instruction.setTargetRef(createObjectRef(target), result); 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 136866ce16b..84790184e8b 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 @@ -52,6 +52,7 @@ import static com.evolveum.midpoint.util.DebugUtil.debugDumpLazily; import static com.evolveum.midpoint.wf.impl.util.MiscDataUtil.getFocusObjectName; import static java.util.Collections.singletonList; +import static org.apache.commons.collections4.CollectionUtils.addIgnoreNull; /** * @author mederly @@ -145,11 +146,11 @@ private List> getDeltasToApprove(ObjectDel if (sourceSpec == null || sourceSpec.getItem().isEmpty() && sourceSpec.getItemValue() == null) { return addWholeDelta(focusDelta, rv); } else if (!sourceSpec.getItem().isEmpty()) { - ObjectDelta.FactorOutResult out = focusDelta.factorOut(ItemPathType.toItemPathList(sourceSpec.getItem()), false); - rv.addAll(out.offsprings); + ObjectDelta.FactorOutResultSingle out = focusDelta.factorOut(ItemPathType.toItemPathList(sourceSpec.getItem()), false); + addIgnoreNull(rv, out.offspring); } else { assert sourceSpec.getItemValue() != null; - ObjectDelta.FactorOutResult out = focusDelta.factorOutValues(sourceSpec.getItemValue().getItemPath(), false); + ObjectDelta.FactorOutResultMulti out = focusDelta.factorOutValues(sourceSpec.getItemValue().getItemPath(), false); rv.addAll(out.offsprings); } }