diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java index 0352ea69f1c..dcd81809b0a 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java @@ -259,6 +259,7 @@ public String toString() { return toHumanReadableDescription(); } + @Override public String toHumanReadableDescription() { StringBuilder sb = new StringBuilder("RSD("); sb.append(kind==null?"null":kind.value()); diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java index 330eeb0313f..6e4db9dd7ae 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java @@ -61,6 +61,7 @@ import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; @@ -1283,17 +1284,10 @@ public boolean equals(Object obj) { return true; } - @Override - public String debugDump() { - return debugDump(0); - } - @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); - for (int i=0;i { +public interface PrismValueDeltaSetTripleProducer extends HumanReadableDescribable { QName getMappingQName(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AbstractConstruction.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AbstractConstruction.java index 0fc79414030..10f9b367e18 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AbstractConstruction.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AbstractConstruction.java @@ -19,6 +19,7 @@ import com.evolveum.midpoint.prism.OriginType; import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; import com.evolveum.midpoint.repo.common.expression.ObjectDeltaObject; import com.evolveum.midpoint.schema.util.ObjectResolver; import com.evolveum.midpoint.util.DebugDumpable; @@ -45,8 +46,9 @@ public abstract class AbstractConstruction focusOdo; private ObjectResolver objectResolver; private PrismContext prismContext; - private boolean isValid = true; + private boolean wasValid = true; + private PlusMinusZero relativityMode; public AbstractConstruction(T constructionType, ObjectType source) { this.constructionType = constructionType; @@ -130,6 +132,22 @@ public void setValid(boolean isValid) { this.isValid = isValid; } + public boolean getWasValid() { + return wasValid; + } + + public void setWasValid(boolean wasValid) { + this.wasValid = wasValid; + } + + public PlusMinusZero getRelativityMode() { + return relativityMode; + } + + public void setRelativityMode(PlusMinusZero relativityMode) { + this.relativityMode = relativityMode; + } + public AssignmentPathImpl getAssignmentPath() { return assignmentPath; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java index 5d54126f296..5115f578a9c 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java @@ -475,6 +475,7 @@ private void collectConstruction(AssignmentPathSegmentImpl segment, PlusMinusZer construction.setChannel(channel); construction.setOrderOneObject(segment.getOrderOneObject()); construction.setValid(isValid); + construction.setRelativityMode(mode); // Do not evaluate the construction here. We will do it in the second pass. Just prepare everything to be evaluated. if (mode == null) { @@ -505,6 +506,7 @@ private void collectPersonaConstruction(AssignmentPathSegmentImpl segment, PlusM construction.setOriginType(OriginType.ASSIGNMENTS); construction.setChannel(channel); construction.setValid(isValid); + construction.setRelativityMode(mode); ctx.evalAssignment.addPersonaConstruction(construction, mode); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java index aecdd1f1d2a..967815ef2c0 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java @@ -739,8 +739,9 @@ public String debugDump(int indent) { sb.append(" weak"); } sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, "isValid", isValid(), indent + 1); - sb.append("\n"); + DebugUtil.debugDumpWithLabelLn(sb, "isValid", isValid(), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "wasValid", getWasValid(), indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "relativityMode", getRelativityMode(), indent + 1); DebugUtil.debugDumpLabel(sb, "auxiliary object classes", indent + 1); if (auxiliaryObjectClassDefinitions == null) { sb.append(" (null)"); @@ -783,12 +784,29 @@ public String debugDump(int indent) { @Override public String toString() { - return "Construction(" + - (refinedObjectClassDefinition == null ? - getConstructionType() : refinedObjectClassDefinition.getShadowDiscriminator()) + - " in " + getSource() + - (isValid() ? "" : ", invalid") + - ")"; + StringBuilder sb = new StringBuilder("Construction("); + if (refinedObjectClassDefinition == null) { + sb.append(getConstructionType()); + } else { + sb.append(refinedObjectClassDefinition.getShadowDiscriminator()); + } + sb.append(" in ").append(getSource()); +// if (getRelativityMode() != null) { +// sb.append(", ").append(getRelativityMode()); +// } + if (isValid()) { + if (!getWasValid()) { + sb.append(", invalid->valid"); + } + } else { + if (getWasValid()) { + sb.append(", valid->invalid"); + } else { + sb.append(", invalid"); + } + } + sb.append(")"); + return sb.toString(); } } 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 cd99dbfa532..268aac89755 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 @@ -83,6 +83,7 @@ public class EvaluatedAssignmentImpl implements EvaluatedAs private PrismObject target; private boolean isValid; + private boolean wasValid; private boolean forceRecon; // used also to force recomputation of parentOrgRefs private boolean presentInCurrentObject; private boolean presentInOldObject; @@ -294,6 +295,14 @@ public void setValid(boolean isValid) { this.isValid = isValid; } + public boolean getWasValid() { + return wasValid; + } + + public void setWasValid(boolean wasValid) { + this.wasValid = wasValid; + } + public boolean isForceRecon() { return forceRecon; } @@ -315,6 +324,7 @@ public void evaluateConstructions(ObjectDeltaObject focusOdo, PrismObject construction :constructionTriple.getAllValues()) { construction.setFocusOdo(focusOdo); construction.setSystemConfiguration(systemConfiguration); + construction.setWasValid(wasValid); LOGGER.trace("Evaluating construction '{}' in {}", construction, construction.getSource()); construction.evaluate(task, result); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ItemValueWithOrigin.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ItemValueWithOrigin.java index d3985478f84..b8f8eb3538e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ItemValueWithOrigin.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ItemValueWithOrigin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * 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. @@ -74,6 +74,10 @@ public ObjectType getSource() { public boolean isValid() { return construction == null || construction.isValid(); } + + public boolean wasValid() { + return construction == null || construction.getWasValid(); + } public boolean equalsRealValue(V pvalue, ValueMatcher valueMatcher) throws SchemaException { if (itemValue == null) { @@ -127,11 +131,6 @@ private static Collection> { + + private static final Trace LOGGER = TraceManager.getTrace(IvwoConsolidator.class); + + private ItemPath itemPath; + private DeltaSetTriple ivwoTriple; + private D itemDefinition; + private ItemDelta aprioriItemDelta; + private PrismContainer itemContainer; + private ValueMatcher valueMatcher; + private Comparator comparator; + private boolean addUnchangedValues; + private boolean filterExistingValues; + private boolean isExclusiveStrong; + private String contextDescription; + private StrengthSelector strengthSelector = StrengthSelector.ALL; + + public ItemPath getItemPath() { + return itemPath; + } + + public void setItemPath(ItemPath itemPath) { + this.itemPath = itemPath; + } + + public DeltaSetTriple getIvwoTriple() { + return ivwoTriple; + } + + public void setIvwoTriple(DeltaSetTriple ivwoTriple) { + this.ivwoTriple = ivwoTriple; + } + + public D getItemDefinition() { + return itemDefinition; + } + + public void setItemDefinition(D itemDefinition) { + this.itemDefinition = itemDefinition; + } + + public ItemDelta getAprioriItemDelta() { + return aprioriItemDelta; + } + + public void setAprioriItemDelta(ItemDelta aprioriItemDelta) { + this.aprioriItemDelta = aprioriItemDelta; + } + + public PrismContainer getItemContainer() { + return itemContainer; + } + + public void setItemContainer(PrismContainer itemContainer) { + this.itemContainer = itemContainer; + } + + public ValueMatcher getValueMatcher() { + return valueMatcher; + } + + public void setValueMatcher(ValueMatcher valueMatcher) { + this.valueMatcher = valueMatcher; + } + + public Comparator getComparator() { + return comparator; + } + + public void setComparator(Comparator comparator) { + this.comparator = comparator; + } + + public boolean isAddUnchangedValues() { + return addUnchangedValues; + } + + public void setAddUnchangedValues(boolean addUnchangedValues) { + this.addUnchangedValues = addUnchangedValues; + } + + public boolean isFilterExistingValues() { + return filterExistingValues; + } + + public void setFilterExistingValues(boolean filterExistingValues) { + this.filterExistingValues = filterExistingValues; + } + + public boolean isExclusiveStrong() { + return isExclusiveStrong; + } + + public void setExclusiveStrong(boolean isExclusiveStrong) { + this.isExclusiveStrong = isExclusiveStrong; + } + + public String getContextDescription() { + return contextDescription; + } + + public void setContextDescription(String contextDescription) { + this.contextDescription = contextDescription; + } + + public StrengthSelector getStrengthSelector() { + return strengthSelector; + } + + public void setStrengthSelector(StrengthSelector strengthSelector) { + this.strengthSelector = strengthSelector; + } + + @NotNull + public ItemDelta consolidateToDelta() throws ExpressionEvaluationException, PolicyViolationException, SchemaException { + if (strengthSelector.isNone()) { + LOGGER.trace("Consolidation of {} skipped as strength selector is 'none'"); + return null; + } + + boolean isAssignment = new ItemPath(FocusType.F_ASSIGNMENT).equivalent(itemPath); + + ItemDelta itemDelta = itemDefinition.createEmptyDelta(itemPath); + + Item itemExisting = null; + if (itemContainer != null) { + itemExisting = itemContainer.findItem(itemPath); + } + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Consolidating {} IVwO triple:\n{}\nApriori Delta:\n{}\nExisting item:\n{}", + itemPath, ivwoTriple.debugDump(1), + DebugUtil.debugDump(aprioriItemDelta, 1), + DebugUtil.debugDump(itemExisting, 1)); + } + + Collection allValues = collectAllValues(); + + final MutableBoolean itemHasStrongMutable = new MutableBoolean(false); + SimpleVisitor visitor = pvwo -> { + if (pvwo.getMapping().getStrength() == MappingStrengthType.STRONG) { + itemHasStrongMutable.setValue(true); + } + }; + ivwoTriple.simpleAccept(visitor); + boolean ignoreNormalMappings = itemHasStrongMutable.booleanValue() && isExclusiveStrong; + + // We will process each value individually. I really mean each value. This whole method deals with + // a single item (e.g. attribute). But this loop iterates over every potential value of that item. + for (V value : allValues) { + + LOGGER.trace(" consolidating value: {}", value); + // Check what to do with the value using the usual "triple routine". It means that if a value is + // in zero set than we need no delta, plus set means add delta and minus set means delete delta. + // The first set that the value is present determines the result. + Collection> zeroIvwos = + collectIvwosFromSet(value, ivwoTriple.getZeroSet(), false); + Collection> plusIvwos = + collectIvwosFromSet(value, ivwoTriple.getPlusSet(), false); + Collection> minusIvwos = + collectIvwosFromSet(value, ivwoTriple.getMinusSet(), true); + + LOGGER.trace("PVWOs for value {}:\nzero = {}\nplus = {}\nminus = {}", value, zeroIvwos, plusIvwos, minusIvwos); + + PrismValueDeltaSetTripleProducer zeroStrongMapping = null; + if (!zeroIvwos.isEmpty()) { + for (ItemValueWithOrigin pvwo : zeroIvwos) { + PrismValueDeltaSetTripleProducer mapping = pvwo.getMapping(); + if (mapping.getStrength() == MappingStrengthType.STRONG) { + zeroStrongMapping = mapping; + } + } + } + + if (zeroStrongMapping != null && aprioriItemDelta != null && aprioriItemDelta.isValueToDelete(value, true)) { + throw new PolicyViolationException("Attempt to delete value " + value + " from item " + itemPath + + " but that value is mandated by a strong " + zeroStrongMapping.toHumanReadableDescription() + + " (for " + contextDescription + ")"); + } + if (!zeroIvwos.isEmpty() && !addUnchangedValues) { + // Value unchanged, nothing to do + LOGGER.trace("Value {} unchanged, doing nothing", value); + continue; + } + + PrismValueDeltaSetTripleProducer exclusiveMapping = null; + Collection> pvwosToAdd; + if (addUnchangedValues) { + pvwosToAdd = MiscUtil.union(zeroIvwos, plusIvwos); + } else { + pvwosToAdd = plusIvwos; + } + + if (!pvwosToAdd.isEmpty()) { + boolean hasOnlyWeakMappings = true; + boolean hasAtLeastOneStrongMapping = false; + // There may be several mappings that imply that value. So check them all for + // exclusions and strength + for (ItemValueWithOrigin pvwoToAdd : pvwosToAdd) { + PrismValueDeltaSetTripleProducer mapping = pvwoToAdd.getMapping(); + if (mapping.getStrength() != MappingStrengthType.WEAK) { + hasOnlyWeakMappings = false; + } + if (mapping.getStrength() == MappingStrengthType.STRONG) { + hasAtLeastOneStrongMapping = true; + } + if (mapping.isExclusive()) { + if (exclusiveMapping == null) { + exclusiveMapping = mapping; + } else { + String message = "Exclusion conflict in " + contextDescription + ", item " + itemPath + + ", conflicting constructions: " + exclusiveMapping + " and " + mapping; + LOGGER.error(message); + throw new ExpressionEvaluationException(message); + } + } + } + if (hasOnlyWeakMappings) { + // Postpone processing of weak values until we process all other values + LOGGER.trace("Value {} mapping is weak in item {}, postponing processing in {}", + value, itemPath, contextDescription); + continue; + } + if (!strengthSelector.isNormal() && !strengthSelector.isStrong()) { + LOGGER.trace("Value {} has only strong/normal mapping and we are processing only weak mappings now, in item {} in {}", + value, itemPath, contextDescription); + continue; + } + if (!hasAtLeastOneStrongMapping && ignoreNormalMappings) { + LOGGER.trace("Value {} mapping is normal in item {} and we have exclusiveStrong, skipping processing in {}", + value, itemPath, contextDescription); + continue; + } + if (hasAtLeastOneStrongMapping && aprioriItemDelta != null && aprioriItemDelta.isValueToDelete(value, true)) { + throw new PolicyViolationException("Attempt to delete value "+value+" from item "+itemPath + +" but that value is mandated by a strong mapping (in "+contextDescription+")"); + } + if (!hasAtLeastOneStrongMapping && (aprioriItemDelta != null && !aprioriItemDelta.isEmpty())) { + // There is already a delta, skip this + LOGGER.trace("Value {} mapping is not strong and the item {} already has a delta that is more concrete, " + + "skipping adding in {}", value, itemPath, contextDescription); + continue; + } + if (filterExistingValues && hasValue(itemExisting, value)) { + LOGGER.trace("Value {} NOT added to delta for item {} because the item already has that value in {}", + value, itemPath, contextDescription); + continue; + } + LOGGER.trace("Value {} added to delta as ADD for item {} in {}", value, itemPath, contextDescription); + itemDelta.addValueToAdd(LensUtil.cloneAndApplyMetadata(value, isAssignment, pvwosToAdd)); + continue; + } + + // We need to check for empty plus set. Values that are both in plus and minus are considered to be added. + // So check for that special case here to avoid removing them. + if (!minusIvwos.isEmpty() && plusIvwos.isEmpty()) { + boolean weakOnly = true; + boolean hasStrong = false; + boolean hasAuthoritative = false; + // There may be several mappings that imply that value. So check them all for + // exclusions and strength + for (ItemValueWithOrigin pvwo : minusIvwos) { + PrismValueDeltaSetTripleProducer mapping = pvwo.getMapping(); + if (mapping.getStrength() != MappingStrengthType.WEAK) { + weakOnly = false; + } + if (mapping.getStrength() == MappingStrengthType.STRONG) { + hasStrong = true; + } + if (mapping.isAuthoritative()) { + hasAuthoritative = true; + } + } + if (!hasAuthoritative) { + LOGGER.trace("Value {} has no authoritative mapping for item {}, skipping deletion in {}", + value, itemPath, contextDescription); + continue; + } + if (!hasStrong && (aprioriItemDelta != null && !aprioriItemDelta.isEmpty())) { + // There is already a delta, skip this + LOGGER.trace("Value {} mapping is not strong and the item {} already has a delta that is more concrete, skipping deletion in {}", + value, itemPath, contextDescription); + continue; + } + if (weakOnly && (itemExisting != null && !itemExisting.isEmpty())) { + // There is already a value, skip this + LOGGER.trace("Value {} mapping is weak and the item {} already has a value, skipping deletion in {}", + value, itemPath, contextDescription); + continue; + } + + if (weakOnly && !strengthSelector.isWeak() && (itemExisting == null || itemExisting.isEmpty())){ + // There is a weak mapping on a property, but we do not have full account available, so skipping deletion of the value is better way + LOGGER.trace("Value {} mapping is weak and the full account could not be fetched, skipping deletion in {}", + value, itemPath, contextDescription); + continue; + } + if (filterExistingValues && !hasValue(itemExisting, value)) { + LOGGER.trace("Value {} NOT add to delta as DELETE because item {} the item does not have that value in {} (matcher: {})", + value, itemPath, contextDescription, valueMatcher); + continue; + } + LOGGER.trace("Value {} added to delta as DELETE for item {} in {}", + value, itemPath, contextDescription); + itemDelta.addValueToDelete((V)value.clone()); + } + + if (!zeroIvwos.isEmpty()) { + boolean weakOnly = true; + boolean hasStrong = false; + boolean hasAuthoritative = false; + // There may be several mappings that imply that value. So check them all for + // exclusions and strength + for (ItemValueWithOrigin pvwo : zeroIvwos) { + PrismValueDeltaSetTripleProducer mapping = pvwo.getMapping(); + if (mapping.getStrength() != MappingStrengthType.WEAK) { + weakOnly = false; + } + if (mapping.getStrength() == MappingStrengthType.STRONG) { + hasStrong = true; + } + if (mapping.isAuthoritative()) { + hasAuthoritative = true; + } + } + + if (aprioriItemDelta != null && aprioriItemDelta.isReplace()) { + // Any strong mappings in the zero set needs to be re-applied as otherwise the replace will destroy it + if (hasStrong) { + LOGGER.trace("Value {} added to delta for item {} in {} because there is strong mapping in the zero set", + value, itemPath, contextDescription); + itemDelta.addValueToAdd(LensUtil.cloneAndApplyMetadata(value, isAssignment, zeroIvwos)); + continue; + } + } + } + } + + Item itemNew = null; + if (itemContainer != null) { + itemNew = itemContainer.findItem(itemPath); + } + if (!hasValue(itemNew, itemDelta)) { + // The application of computed delta results in no value, apply weak mappings + Collection> nonNegativePvwos = ivwoTriple.getNonNegativeValues(); + Collection> valuesToAdd = selectWeakValues(nonNegativePvwos, OriginType.ASSIGNMENTS); + if (valuesToAdd.isEmpty()) { + valuesToAdd = selectWeakValues(nonNegativePvwos, OriginType.OUTBOUND); + } + if (valuesToAdd.isEmpty()) { + valuesToAdd = selectWeakValues(nonNegativePvwos, null); + } + LOGGER.trace("No value for item {} in {}, weak mapping processing yielded values: {}", + itemPath, contextDescription, valuesToAdd); + for (ItemValueWithOrigin valueWithOrigin : valuesToAdd) { + itemDelta.addValueToAdd(LensUtil.cloneAndApplyMetadata(valueWithOrigin.getItemValue(), isAssignment, singleton(valueWithOrigin))); + } + } else { + LOGGER.trace("Existing values for item {} in {}, weak mapping processing skipped", itemPath, contextDescription); + } + + if (itemExisting != null) { + List existingValues = itemExisting.getValues(); + if (existingValues != null) { + itemDelta.setEstimatedOldValues(PrismValue.cloneCollection(existingValues)); + } + } + + return itemDelta; + } + + private Collection collectAllValues() throws SchemaException { + Collection allValues = new HashSet<>(); + collectAllValuesFromSet(allValues, ivwoTriple.getZeroSet()); + collectAllValuesFromSet(allValues, ivwoTriple.getPlusSet()); + collectAllValuesFromSet(allValues, ivwoTriple.getMinusSet()); + return allValues; + } + + private void collectAllValuesFromSet(Collection allValues, + Collection> collection) throws SchemaException { + if (collection == null) { + return; + } + for (ItemValueWithOrigin pvwo : collection) { + V pval = pvwo.getItemValue(); + if (valueMatcher == null) { + if (!PrismValue.containsRealValue(allValues, pval)) { + allValues.add(pval); + } + } else { + boolean found = false; + for (V valueFromAllvalues: allValues) { + if (valueMatcher.match(((PrismPropertyValue)valueFromAllvalues).getValue(), + ((PrismPropertyValue)pval).getValue())) { + found = true; + break; + } + } + if (!found) { + allValues.add(pval); + } + } + } + } + + private Collection> collectIvwosFromSet(V pvalue, Collection> deltaSet, boolean keepValidInvalid) throws SchemaException { + Collection> ivwos = new ArrayList<>(); + for (ItemValueWithOrigin setIvwo : deltaSet) { + if (!setIvwo.isValid()) { + if (!keepValidInvalid) { + continue; + } + if (!setIvwo.wasValid()) { + continue; + } + // valid -> invalid change. E.g. disabled assignment. We need to process that + } + if (setIvwo.equalsRealValue(pvalue, valueMatcher)) { + ivwos.add(setIvwo); + } + } + return ivwos; + } + + private Collection> selectWeakValues(Collection> pvwos, OriginType origin) { + Collection> values = new ArrayList<>(); + for (ItemValueWithOrigin pvwo: pvwos) { + if (pvwo.getMapping().getStrength() == MappingStrengthType.WEAK && strengthSelector.isWeak()) { + if (origin == null || origin == pvwo.getItemValue().getOriginType()) { + values.add(pvwo); + } + } + } + return values; + } + + private boolean hasValue(Item item, ItemDelta itemDelta) throws SchemaException { + if (item == null || item.isEmpty()) { + return itemDelta != null && itemDelta.addsAnyValue(); + } else { + if (itemDelta == null || itemDelta.isEmpty()) { + return true; + } else { + Item clonedItem = item.clone(); + itemDelta.applyToMatchingPath(clonedItem); + return !clonedItem.isEmpty(); + } + } + } + + private boolean hasValue(Item existingUserItem, V newValue) { + if (existingUserItem == null) { + return false; + } + if (valueMatcher != null && newValue instanceof PrismPropertyValue) { + return valueMatcher.hasRealValue((PrismProperty)existingUserItem, (PrismPropertyValue)newValue); + } else { + return existingUserItem.contains(newValue, true, comparator); + } + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java index a43259ecccc..a5b9aba13af 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java @@ -1040,6 +1040,9 @@ private void dumpEvaluatedAssignments(StringBuilder sb, String label, Collection sb.append("\n"); DebugUtil.indentDebugDump(sb, indent + 1); sb.append("-> ").append(assignment.getTarget()); + if (!assignment.isValid()) { + sb.append(" invalid "); + } dumpRulesIfNotEmpty(sb, "- focus rules", indent + 3, assignment.getFocusPolicyRules()); dumpRulesIfNotEmpty(sb, "- this target rules", indent + 3, assignment.getThisTargetPolicyRules()); dumpRulesIfNotEmpty(sb, "- other targets rules", indent + 3, assignment.getOtherTargetsPolicyRules()); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index 1853676fdab..3bc991e3660 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -184,266 +184,6 @@ public static LensProjectionContext createAccountContext( return new LensProjectionContext(context, rsd); } - - /** - * Consolidate the mappings of a single item to a delta. It takes the convenient structure of ItemValueWithOrigin triple. - * It produces the delta considering the mapping exclusion, authoritativeness and strength. - * - * filterExistingValues: if true, then values that already exist in the item are not added (and those that don't exist are not removed) - */ - @NotNull - public static > - ItemDelta consolidateTripleToDelta( - ItemPath itemPath, DeltaSetTriple triple, D itemDefinition, - ItemDelta aprioriItemDelta, PrismContainer itemContainer, ValueMatcher valueMatcher, Comparator comparator, - boolean addUnchangedValues, boolean filterExistingValues, boolean isExclusiveStrong, - String contextDescription, boolean applyWeak) throws ExpressionEvaluationException, PolicyViolationException, SchemaException { - - boolean isAssignment = new ItemPath(FocusType.F_ASSIGNMENT).equivalent(itemPath); - - ItemDelta itemDelta = itemDefinition.createEmptyDelta(itemPath); - - Item itemExisting = null; - if (itemContainer != null) { - itemExisting = itemContainer.findItem(itemPath); - } - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Consolidating {} triple:\n{}\nApriori Delta:\n{}\nExisting item:\n{}", - itemPath, triple.debugDump(1), - DebugUtil.debugDump(aprioriItemDelta, 1), - DebugUtil.debugDump(itemExisting, 1)); - } - - Collection allValues = collectAllValues(triple, valueMatcher); - - final MutableBoolean itemHasStrongMutable = new MutableBoolean(false); - SimpleVisitor visitor = pvwo -> { - if (pvwo.getMapping().getStrength() == MappingStrengthType.STRONG) { - itemHasStrongMutable.setValue(true); - } - }; - triple.simpleAccept(visitor); - boolean ignoreNormalMappings = itemHasStrongMutable.booleanValue() && isExclusiveStrong; - - // We will process each value individually. I really mean each value. This whole method deals with - // a single item (e.g. attribute). But this loop iterates over every potential value of that item. - for (V value : allValues) { - - LOGGER.trace(" consolidating value: {}", value); - // Check what to do with the value using the usual "triple routine". It means that if a value is - // in zero set than we need no delta, plus set means add delta and minus set means delete delta. - // The first set that the value is present determines the result. - Collection> zeroPvwos = - collectPvwosFromSet(value, triple.getZeroSet(), valueMatcher); - Collection> plusPvwos = - collectPvwosFromSet(value, triple.getPlusSet(), valueMatcher); - Collection> minusPvwos = - collectPvwosFromSet(value, triple.getMinusSet(), valueMatcher); - - LOGGER.trace("PVWOs for value {}:\nzero = {}\nplus = {}\nminus = {}", value, zeroPvwos, plusPvwos, minusPvwos); - - boolean zeroHasStrong = false; - if (!zeroPvwos.isEmpty()) { - for (ItemValueWithOrigin pvwo : zeroPvwos) { - PrismValueDeltaSetTripleProducer mapping = pvwo.getMapping(); - if (mapping.getStrength() == MappingStrengthType.STRONG) { - zeroHasStrong = true; - } - } - } - - if (zeroHasStrong && aprioriItemDelta != null && aprioriItemDelta.isValueToDelete(value, true)) { - throw new PolicyViolationException("Attempt to delete value "+value+" from item "+itemPath - +" but that value is mandated by a strong mapping (in "+contextDescription+")"); - } - if (!zeroPvwos.isEmpty() && !addUnchangedValues) { - // Value unchanged, nothing to do - LOGGER.trace("Value {} unchanged, doing nothing", value); - continue; - } - - PrismValueDeltaSetTripleProducer exclusiveMapping = null; - Collection> pvwosToAdd; - if (addUnchangedValues) { - pvwosToAdd = MiscUtil.union(zeroPvwos, plusPvwos); - } else { - pvwosToAdd = plusPvwos; - } - - if (!pvwosToAdd.isEmpty()) { - boolean weakOnly = true; - boolean hasStrong = false; - // There may be several mappings that imply that value. So check them all for - // exclusions and strength - for (ItemValueWithOrigin pvwoToAdd : pvwosToAdd) { - PrismValueDeltaSetTripleProducer mapping = pvwoToAdd.getMapping(); - if (mapping.getStrength() != MappingStrengthType.WEAK) { - weakOnly = false; - } - if (mapping.getStrength() == MappingStrengthType.STRONG) { - hasStrong = true; - } - if (mapping.isExclusive()) { - if (exclusiveMapping == null) { - exclusiveMapping = mapping; - } else { - String message = "Exclusion conflict in " + contextDescription + ", item " + itemPath + - ", conflicting constructions: " + exclusiveMapping + " and " + mapping; - LOGGER.error(message); - throw new ExpressionEvaluationException(message); - } - } - } - if (weakOnly) { - // Postpone processing of weak values until we process all other values - LOGGER.trace("Value {} mapping is weak in item {}, postponing processing in {}", - value, itemPath, contextDescription); - continue; - } - if (!hasStrong && ignoreNormalMappings) { - LOGGER.trace("Value {} mapping is normal in item {} and we have exclusiveStrong, skipping processing in {}", - value, itemPath, contextDescription); - continue; - } - if (hasStrong && aprioriItemDelta != null && aprioriItemDelta.isValueToDelete(value, true)) { - throw new PolicyViolationException("Attempt to delete value "+value+" from item "+itemPath - +" but that value is mandated by a strong mapping (in "+contextDescription+")"); - } - if (!hasStrong && (aprioriItemDelta != null && !aprioriItemDelta.isEmpty())) { - // There is already a delta, skip this - LOGGER.trace("Value {} mapping is not strong and the item {} already has a delta that is more concrete, " + - "skipping adding in {}", value, itemPath, contextDescription); - continue; - } - if (filterExistingValues && hasValue(itemExisting, value, valueMatcher, comparator)) { - LOGGER.trace("Value {} NOT added to delta for item {} because the item already has that value in {}", - value, itemPath, contextDescription); - continue; - } - LOGGER.trace("Value {} added to delta as ADD for item {} in {}", value, itemPath, contextDescription); - itemDelta.addValueToAdd(cloneAndApplyMetadata(value, isAssignment, pvwosToAdd)); - continue; - } - - // We need to check for empty plus set. Values that are both in plus and minus are considered to be added. - // So check for that special case here to avoid removing them. - if (!minusPvwos.isEmpty() && plusPvwos.isEmpty()) { - boolean weakOnly = true; - boolean hasStrong = false; - boolean hasAuthoritative = false; - // There may be several mappings that imply that value. So check them all for - // exclusions and strength - for (ItemValueWithOrigin pvwo : minusPvwos) { - PrismValueDeltaSetTripleProducer mapping = pvwo.getMapping(); - if (mapping.getStrength() != MappingStrengthType.WEAK) { - weakOnly = false; - } - if (mapping.getStrength() == MappingStrengthType.STRONG) { - hasStrong = true; - } - if (mapping.isAuthoritative()) { - hasAuthoritative = true; - } - } - if (!hasAuthoritative) { - LOGGER.trace("Value {} has no authoritative mapping for item {}, skipping deletion in {}", - value, itemPath, contextDescription); - continue; - } - if (!hasStrong && (aprioriItemDelta != null && !aprioriItemDelta.isEmpty())) { - // There is already a delta, skip this - LOGGER.trace("Value {} mapping is not strong and the item {} already has a delta that is more concrete, skipping deletion in {}", - value, itemPath, contextDescription); - continue; - } - if (weakOnly && (itemExisting != null && !itemExisting.isEmpty())) { - // There is already a value, skip this - LOGGER.trace("Value {} mapping is weak and the item {} already has a value, skipping deletion in {}", - value, itemPath, contextDescription); - continue; - } - - if (weakOnly && !applyWeak && (itemExisting == null || itemExisting.isEmpty())){ - // There is a weak mapping on a property, but we do not have full account available, so skipping deletion of the value is better way - LOGGER.trace("Value {} mapping is weak and the full account could not be fetched, skipping deletion in {}", - value, itemPath, contextDescription); - continue; - } - if (filterExistingValues && !hasValue(itemExisting, value, valueMatcher, comparator)) { - LOGGER.trace("Value {} NOT add to delta as DELETE because item {} the item does not have that value in {} (matcher: {})", - value, itemPath, contextDescription, valueMatcher); - continue; - } - LOGGER.trace("Value {} added to delta as DELETE for item {} in {}", - value, itemPath, contextDescription); - itemDelta.addValueToDelete((V)value.clone()); - } - - if (!zeroPvwos.isEmpty()) { - boolean weakOnly = true; - boolean hasStrong = false; - boolean hasAuthoritative = false; - // There may be several mappings that imply that value. So check them all for - // exclusions and strength - for (ItemValueWithOrigin pvwo : zeroPvwos) { - PrismValueDeltaSetTripleProducer mapping = pvwo.getMapping(); - if (mapping.getStrength() != MappingStrengthType.WEAK) { - weakOnly = false; - } - if (mapping.getStrength() == MappingStrengthType.STRONG) { - hasStrong = true; - } - if (mapping.isAuthoritative()) { - hasAuthoritative = true; - } - } - - if (aprioriItemDelta != null && aprioriItemDelta.isReplace()) { - // Any strong mappings in the zero set needs to be re-applied as otherwise the replace will destroy it - if (hasStrong) { - LOGGER.trace("Value {} added to delta for item {} in {} because there is strong mapping in the zero set", - value, itemPath, contextDescription); - itemDelta.addValueToAdd(cloneAndApplyMetadata(value, isAssignment, zeroPvwos)); - continue; - } - } - } - } - - Item itemNew = null; - if (itemContainer != null) { - itemNew = itemContainer.findItem(itemPath); - } - if (!hasValue(itemNew, itemDelta)) { - // The application of computed delta results in no value, apply weak mappings - Collection> nonNegativePvwos = triple.getNonNegativeValues(); - Collection> valuesToAdd = selectWeakValues(nonNegativePvwos, OriginType.ASSIGNMENTS, applyWeak); - if (valuesToAdd.isEmpty()) { - valuesToAdd = selectWeakValues(nonNegativePvwos, OriginType.OUTBOUND, applyWeak); - } - if (valuesToAdd.isEmpty()) { - valuesToAdd = selectWeakValues(nonNegativePvwos, null, applyWeak); - } - LOGGER.trace("No value for item {} in {}, weak mapping processing yielded values: {}", - itemPath, contextDescription, valuesToAdd); - for (ItemValueWithOrigin valueWithOrigin : valuesToAdd) { - itemDelta.addValueToAdd(cloneAndApplyMetadata(valueWithOrigin.getItemValue(), isAssignment, singleton(valueWithOrigin))); - } - } else { - LOGGER.trace("Existing values for item {} in {}, weak mapping processing skipped", itemPath, contextDescription); - } - - if (itemExisting != null) { - List existingValues = itemExisting.getValues(); - if (existingValues != null) { - itemDelta.setEstimatedOldValues(PrismValue.cloneCollection(existingValues)); - } - } - - return itemDelta; - } - public static V cloneAndApplyMetadata(V value, boolean isAssignment, Collection> origins) throws SchemaException { return cloneAndApplyMetadata(value, isAssignment, () -> getAutoCreationIdentifier(origins)); @@ -488,89 +228,7 @@ private static String getAutoCr return null; } - private static boolean hasValue(Item item, ItemDelta itemDelta) throws SchemaException { - if (item == null || item.isEmpty()) { - return itemDelta != null && itemDelta.addsAnyValue(); - } else { - if (itemDelta == null || itemDelta.isEmpty()) { - return true; - } else { - Item clonedItem = item.clone(); - itemDelta.applyToMatchingPath(clonedItem); - return !clonedItem.isEmpty(); - } - } - } - - private static Collection> selectWeakValues(Collection> pvwos, OriginType origin, boolean applyWeak) { - Collection> values = new ArrayList<>(); - for (ItemValueWithOrigin pvwo: pvwos) { - if (pvwo.getMapping().getStrength() == MappingStrengthType.WEAK && applyWeak) { - if (origin == null || origin == pvwo.getItemValue().getOriginType()) { - values.add(pvwo); - } - } - } - return values; - } - - private static boolean hasValue(Item existingUserItem, V newValue, ValueMatcher valueMatcher, Comparator comparator) { - if (existingUserItem == null) { - return false; - } - if (valueMatcher != null && newValue instanceof PrismPropertyValue) { - return valueMatcher.hasRealValue((PrismProperty)existingUserItem, (PrismPropertyValue)newValue); - } else { - return existingUserItem.contains(newValue, true, comparator); - } - } - - private static Collection collectAllValues - (DeltaSetTriple> triple, ValueMatcher valueMatcher) throws SchemaException { - Collection allValues = new HashSet<>(); - collectAllValuesFromSet(allValues, triple.getZeroSet(), valueMatcher); - collectAllValuesFromSet(allValues, triple.getPlusSet(), valueMatcher); - collectAllValuesFromSet(allValues, triple.getMinusSet(), valueMatcher); - return allValues; - } - - private static void collectAllValuesFromSet(Collection allValues, - Collection> collection, ValueMatcher valueMatcher) throws SchemaException { - if (collection == null) { - return; - } - for (ItemValueWithOrigin pvwo : collection) { - V pval = pvwo.getItemValue(); - if (valueMatcher == null) { - if (!PrismValue.containsRealValue(allValues, pval)) { - allValues.add(pval); - } - } else { - boolean found = false; - for (V valueFromAllvalues: allValues) { - if (valueMatcher.match(((PrismPropertyValue)valueFromAllvalues).getValue(), - ((PrismPropertyValue)pval).getValue())) { - found = true; - break; - } - } - if (!found) { - allValues.add(pval); - } - } - } - } - - private static Collection> collectPvwosFromSet(V pvalue, - Collection> deltaSet, ValueMatcher valueMatcher) throws SchemaException { - Collection> pvwos = new ArrayList<>(); - for (ItemValueWithOrigin setPvwo : deltaSet) { - if (setPvwo.equalsRealValue(pvalue, valueMatcher)) { - pvwos.add(setPvwo); - } - } - return pvwos; - } + public static PropertyDelta createActivationTimestampDelta(ActivationStatusType status, XMLGregorianCalendar now, PrismContainerDefinition activationDefinition, OriginType origin) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/StrengthSelector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/StrengthSelector.java new file mode 100644 index 00000000000..29e4f07a2ee --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/StrengthSelector.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 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; + +/** + * @author semancik + * + */ +public class StrengthSelector { + + public static final StrengthSelector ALL = new StrengthSelector(true, true, true); + public static final StrengthSelector ALL_EXCEPT_WEAK = new StrengthSelector(false, true, true); + public static final StrengthSelector WEAK_ONLY = new StrengthSelector(true, false, false); + + private final boolean weak; + private final boolean normal; + private final boolean strong; + + public StrengthSelector(boolean weak, boolean normal, boolean strong) { + super(); + this.weak = weak; + this.normal = normal; + this.strong = strong; + } + + public boolean isWeak() { + return weak; + } + + public boolean isNormal() { + return normal; + } + + public boolean isStrong() { + return strong; + } + + public StrengthSelector notWeak() { + return new StrengthSelector(false, normal, strong); + } + + public boolean isNone() { + return !weak && !normal && !strong; + } + + @Override + public String toString() { + return "StrengthSelector(weak=" + weak + ", normal=" + normal + ", strong=" + strong + ")"; + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java index 406b87a3b59..1ad6a31c4ca 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java @@ -22,9 +22,11 @@ import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; import com.evolveum.midpoint.model.impl.lens.Construction; import com.evolveum.midpoint.model.impl.lens.ItemValueWithOrigin; +import com.evolveum.midpoint.model.impl.lens.IvwoConsolidator; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.model.impl.lens.StrengthSelector; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContainerDefinition; import com.evolveum.midpoint.prism.PrismContainerValue; @@ -146,90 +148,26 @@ private boolean wasProjectionDeleted(LensProjectionContext accContext) { return false; } - + private ObjectDelta consolidateValuesToModifyDelta(LensContext context, - LensProjectionContext projCtx, boolean addUnchangedValues, Task task, OperationResult result) + LensProjectionContext projCtx, + boolean addUnchangedValues, + Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { - // "Squeeze" all the relevant mappings into a data structure that we can process conveniently. We want to have all the - // (meta)data about relevant for a specific attribute in one data structure, not spread over several account constructions. - Map,PrismPropertyDefinition>>> squeezedAttributes = - sqeeze(projCtx, construction -> (Collection)construction.getAttributeMappings()); - projCtx.setSqueezedAttributes(squeezedAttributes); - - Map,PrismContainerDefinition>>> squeezedAssociations = - sqeeze(projCtx, construction -> construction.getAssociationMappings()); - projCtx.setSqueezedAssociations(squeezedAssociations); - - // Association values in squeezed associations do not contain association name attribute. - // It is hacked-in later for use in this class, but not for other uses (e.g. in ReconciliationProcessor). - // So, we do it here - once and for all. - if (!squeezedAssociations.isEmpty()) { - fillInAssociationNames(squeezedAssociations); - } - - MappingExtractor,PrismPropertyDefinition,F> auxiliaryObjectClassExtractor = - construction -> { - PrismValueDeltaSetTripleProducer,PrismPropertyDefinition> prod = new PrismValueDeltaSetTripleProducer,PrismPropertyDefinition>() { - @Override - public QName getMappingQName() { - return ShadowType.F_AUXILIARY_OBJECT_CLASS; - } - @Override - public PrismValueDeltaSetTriple> getOutputTriple() { - PrismValueDeltaSetTriple> triple = new PrismValueDeltaSetTriple<>(); - if (construction.getAuxiliaryObjectClassDefinitions() != null) { - for (RefinedObjectClassDefinition auxiliaryObjectClassDefinition: construction.getAuxiliaryObjectClassDefinitions()) { - triple.addToZeroSet(new PrismPropertyValue(auxiliaryObjectClassDefinition.getTypeName())); - } - } - return triple; - } - @Override - public MappingStrengthType getStrength() { - return MappingStrengthType.STRONG; - } - @Override - public PrismValueDeltaSetTripleProducer,PrismPropertyDefinition> clone() { - return this; - } - @Override - public boolean isExclusive() { - return false; - } - @Override - public boolean isAuthoritative() { - return true; - } - @Override - public boolean isSourceless() { - return false; - } - @Override - public String getIdentifier() { - return null; - } - }; - Collection,PrismPropertyDefinition>> col = new ArrayList<>(1); - col.add(prod); - return col; - }; - - Map,PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses = - sqeeze(projCtx, auxiliaryObjectClassExtractor); - projCtx.setSqueezedAuxiliaryObjectClasses(squeezedAuxiliaryObjectClasses); - - ResourceShadowDiscriminator discr = projCtx.getResourceShadowDiscriminator(); + squeezeAll(context, projCtx); + ObjectDelta objectDelta = new ObjectDelta(ShadowType.class, ChangeType.MODIFY, prismContext); objectDelta.setOid(projCtx.getOid()); + ObjectDelta existingDelta = projCtx.getDelta(); // Do not automatically load the full projection now. Even if we have weak mapping. // That may be a waste of resources if the weak mapping results in no change anyway. // Let's be very very lazy about fetching the account from the resource. if (!projCtx.hasFullShadow() && - (hasActiveWeakMapping(squeezedAttributes, projCtx) || hasActiveWeakMapping(squeezedAssociations, projCtx) - || (hasActiveStrongMapping(squeezedAttributes, projCtx) || hasActiveStrongMapping(squeezedAssociations, projCtx)))) { + (hasActiveWeakMapping(projCtx.getSqueezedAttributes(), projCtx) || hasActiveWeakMapping(projCtx.getSqueezedAssociations(), projCtx) + || (hasActiveStrongMapping(projCtx.getSqueezedAttributes(), projCtx) || hasActiveStrongMapping(projCtx.getSqueezedAssociations(), projCtx)))) { // Full account was not yet loaded. This will cause problems as // the weak mapping may be applied even though it should not be // applied @@ -253,9 +191,25 @@ public String getIdentifier() { } } - boolean completeAccount = projCtx.hasFullShadow(); + RefinedObjectClassDefinition rOcDef = consolidateAuxiliaryObjectClasses(context, projCtx, addUnchangedValues, objectDelta, existingDelta); + - ObjectDelta existingDelta = projCtx.getDelta(); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Object class definition for {} consolidation:\n{}", projCtx.getResourceShadowDiscriminator(), rOcDef.debugDump()); + } + + consolidateAttributes(context, projCtx, addUnchangedValues, rOcDef, objectDelta, existingDelta, StrengthSelector.ALL); + + consolidateAssociations(context, projCtx, addUnchangedValues, rOcDef, objectDelta, existingDelta, StrengthSelector.ALL); + + return objectDelta; + } + + private RefinedObjectClassDefinition consolidateAuxiliaryObjectClasses(LensContext context, + LensProjectionContext projCtx, boolean addUnchangedValues, ObjectDelta objectDelta, ObjectDelta existingDelta) + throws SchemaException, ExpressionEvaluationException, PolicyViolationException { + ResourceShadowDiscriminator discr = projCtx.getResourceShadowDiscriminator(); + Map, PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses = projCtx.getSqueezedAuxiliaryObjectClasses(); // AUXILIARY OBJECT CLASSES ItemPath auxiliaryObjectClassItemPath = new ItemPath(ShadowType.F_AUXILIARY_OBJECT_CLASS); @@ -271,9 +225,9 @@ public String getIdentifier() { for (Entry, PrismPropertyDefinition>>> entry : squeezedAuxiliaryObjectClasses.entrySet()) { DeltaSetTriple, PrismPropertyDefinition>> ivwoTriple = entry.getValue(); - LOGGER.trace("CONSOLIDATE auxiliary object classes ({})", new Object[]{ discr }); + LOGGER.trace("CONSOLIDATE auxiliary object classes ({})", discr); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Auxiliary object class triple:\n{}",ivwoTriple.debugDump()); + LOGGER.trace("Auxiliary object class triple:\n{}", ivwoTriple.debugDump()); } for (ItemValueWithOrigin,PrismPropertyDefinition> ivwo: ivwoTriple.getAllValues()) { @@ -291,11 +245,21 @@ public String getIdentifier() { auxOcDefs.add(auxOcDef); } - ItemDelta, PrismPropertyDefinition> itemDelta = LensUtil.consolidateTripleToDelta( - auxiliaryObjectClassItemPath, ivwoTriple, auxiliaryObjectClassPropertyDef, - auxiliaryObjectClassAPrioriDelta, projCtx.getObjectNew(), null, null, addUnchangedValues, completeAccount, false, - discr.toHumanReadableDescription(), false); - PropertyDelta propDelta = (PropertyDelta)itemDelta; + IvwoConsolidator, PrismPropertyDefinition, ItemValueWithOrigin, PrismPropertyDefinition>> consolidator = new IvwoConsolidator<>(); + consolidator.setItemPath(auxiliaryObjectClassItemPath); + consolidator.setIvwoTriple(ivwoTriple); + consolidator.setItemDefinition(auxiliaryObjectClassPropertyDef); + consolidator.setAprioriItemDelta(auxiliaryObjectClassAPrioriDelta); + consolidator.setItemContainer(projCtx.getObjectNew()); + consolidator.setValueMatcher(null); + consolidator.setComparator(null); + consolidator.setAddUnchangedValues(addUnchangedValues); + consolidator.setFilterExistingValues(projCtx.hasFullShadow()); + consolidator.setExclusiveStrong(false); + consolidator.setContextDescription(discr.toHumanReadableDescription()); + consolidator.setStrengthSelector(StrengthSelector.ALL_EXCEPT_WEAK); + + PropertyDelta propDelta = (PropertyDelta) consolidator.consolidateToDelta(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Auxiliary object class delta:\n{}",propDelta.debugDump()); @@ -314,59 +278,33 @@ public String getIdentifier() { RefinedObjectClassDefinition rOcDef = new CompositeRefinedObjectClassDefinitionImpl( structuralObjectClassDefinition, auxOcDefs); + return rOcDef; - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Object class definition for {} consolidation:\n{}", discr, rOcDef.debugDump()); - } - - // ATTRIBUTES - // Iterate and process each attribute separately. Now that we have squeezed the data we can process each attribute just + } + + private void consolidateAttributes(LensContext context, LensProjectionContext projCtx, + boolean addUnchangedValues, RefinedObjectClassDefinition rOcDef, ObjectDelta objectDelta, + ObjectDelta existingDelta, StrengthSelector strengthSelector) + throws SchemaException, ExpressionEvaluationException, PolicyViolationException { + Map, PrismPropertyDefinition>>> squeezedAttributes = projCtx.getSqueezedAttributes(); + // Iterate and process each attribute separately. Now that we have squeezed the data we can process each attribute just // with the data in ItemValueWithOrigin triples. for (Map.Entry,PrismPropertyDefinition>>> entry : squeezedAttributes.entrySet()) { QName attributeName = entry.getKey(); DeltaSetTriple,PrismPropertyDefinition>> triple = entry.getValue(); - PropertyDelta propDelta = consolidateAttribute(rOcDef, discr, existingDelta, projCtx, - addUnchangedValues, completeAccount, attributeName, (DeltaSetTriple)triple); + PropertyDelta propDelta = consolidateAttribute(rOcDef, projCtx.getResourceShadowDiscriminator(), existingDelta, projCtx, + addUnchangedValues, attributeName, (DeltaSetTriple)triple, strengthSelector); if (propDelta != null) { objectDelta.addModification(propDelta); } } - - // ASSOCIATIONS - for (Entry,PrismContainerDefinition>>> entry : squeezedAssociations.entrySet()) { - QName associationName = entry.getKey(); - DeltaSetTriple,PrismContainerDefinition>> triple = entry.getValue(); - ContainerDelta containerDelta = consolidateAssociation(rOcDef, discr, existingDelta, projCtx, - addUnchangedValues, completeAccount, associationName, triple); - if (containerDelta != null) { - objectDelta.addModification(containerDelta); - } - } - - return objectDelta; - } - - private void fillInAssociationNames(Map,PrismContainerDefinition>>> squeezedAssociations) throws SchemaException { - PrismPropertyDefinition nameDefinition = prismContext.getSchemaRegistry() - .findContainerDefinitionByCompileTimeClass(ShadowAssociationType.class) - .findPropertyDefinition(ShadowAssociationType.F_NAME); - for (Entry,PrismContainerDefinition>>> entry : squeezedAssociations.entrySet()) { - DeltaSetTriple,PrismContainerDefinition>> deltaSetTriple = entry.getValue(); - for (ItemValueWithOrigin,PrismContainerDefinition> ivwo : deltaSetTriple.getAllValues()) { - PrismContainerValue value = ivwo.getItemValue(); - if (value != null && value.findProperty(ShadowAssociationType.F_NAME) == null) { // just for safety - PrismProperty nameProperty = value.createProperty(nameDefinition); - nameProperty.setRealValue(entry.getKey()); - } - } - } - LOGGER.trace("Names for squeezed associations filled-in."); } private PropertyDelta consolidateAttribute(RefinedObjectClassDefinition rOcDef, ResourceShadowDiscriminator discr, ObjectDelta existingDelta, LensProjectionContext projCtx, - boolean addUnchangedValues, boolean completeShadow, QName itemName, - DeltaSetTriple,PrismPropertyDefinition>> triple) throws SchemaException, ExpressionEvaluationException, PolicyViolationException { + boolean addUnchangedValues, QName itemName, + DeltaSetTriple,PrismPropertyDefinition>> triple, StrengthSelector strengthSelector) + throws SchemaException, ExpressionEvaluationException, PolicyViolationException { if (triple == null || triple.isEmpty()) { return null; @@ -383,14 +321,30 @@ private PropertyDelta consolidateAttribute(RefinedOb ValueMatcher valueMatcher = ValueMatcher.createMatcher(attributeDefinition, matchingRuleRegistry); - return (PropertyDelta) consolidateItem(rOcDef, discr, existingDelta, projCtx, addUnchangedValues, completeShadow, - attributeDefinition.isExlusiveStrong(), itemPath, attributeDefinition, triple, valueMatcher, null, "attribute "+itemName); + return (PropertyDelta) consolidateItem(rOcDef, discr, existingDelta, projCtx, addUnchangedValues, + attributeDefinition.isExlusiveStrong(), itemPath, attributeDefinition, triple, valueMatcher, null, strengthSelector, "attribute "+itemName); + } + + + private void consolidateAssociations(LensContext context, LensProjectionContext projCtx, + boolean addUnchangedValues, RefinedObjectClassDefinition rOcDef, ObjectDelta objectDelta, ObjectDelta existingDelta, StrengthSelector strengthSelector) + throws SchemaException, ExpressionEvaluationException, PolicyViolationException { + for (Entry,PrismContainerDefinition>>> entry : projCtx.getSqueezedAssociations().entrySet()) { + QName associationName = entry.getKey(); + DeltaSetTriple,PrismContainerDefinition>> triple = entry.getValue(); + ContainerDelta containerDelta = consolidateAssociation(rOcDef, projCtx.getResourceShadowDiscriminator(), + existingDelta, projCtx, addUnchangedValues, associationName, triple, strengthSelector); + if (containerDelta != null) { + objectDelta.addModification(containerDelta); + } + } } private ContainerDelta consolidateAssociation(RefinedObjectClassDefinition rOcDef, ResourceShadowDiscriminator discr, ObjectDelta existingDelta, LensProjectionContext projCtx, - boolean addUnchangedValues, boolean completeShadow, QName associationName, - DeltaSetTriple,PrismContainerDefinition>> triple) throws SchemaException, ExpressionEvaluationException, PolicyViolationException { + boolean addUnchangedValues, QName associationName, + DeltaSetTriple,PrismContainerDefinition>> triple, + StrengthSelector strengthSelector) throws SchemaException, ExpressionEvaluationException, PolicyViolationException { ItemPath itemPath = new ItemPath(ShadowType.F_ASSOCIATION); PrismContainerDefinition asspcContainerDef = getAssociationDefinition(); @@ -431,8 +385,8 @@ public int compare(PrismContainerValue o1, }; ContainerDelta delta = (ContainerDelta) consolidateItem(rOcDef, discr, existingDelta, - projCtx, addUnchangedValues, completeShadow, associationDef.isExclusiveStrong(), itemPath, - asspcContainerDef, triple, null, comparator, "association "+associationName); + projCtx, addUnchangedValues, associationDef.isExclusiveStrong(), itemPath, + asspcContainerDef, triple, null, comparator, strengthSelector, "association "+associationName); if (delta != null) { setAssociationName(delta.getValuesToAdd(), associationName); @@ -462,9 +416,9 @@ private PrismContainerDefinition getAssociationDefinition private ItemDelta consolidateItem(RefinedObjectClassDefinition rOcDef, ResourceShadowDiscriminator discr, ObjectDelta existingDelta, LensProjectionContext projCtx, - boolean addUnchangedValues, boolean completeShadow, boolean isExclusiveStrong, + boolean addUnchangedValues, boolean isExclusiveStrong, ItemPath itemPath, D itemDefinition, DeltaSetTriple> triple, - ValueMatcher valueMatcher, Comparator comparator, String itemDesc) + ValueMatcher valueMatcher, Comparator comparator, StrengthSelector strengthSelector, String itemDesc) throws SchemaException, ExpressionEvaluationException, PolicyViolationException { boolean forceAddUnchangedValues = false; @@ -479,13 +433,28 @@ private ItemDelta consolida } LOGGER.trace("CONSOLIDATE {}\n({}) completeShadow={}, addUnchangedValues={}, forceAddUnchangedValues={}", - itemDesc, discr, completeShadow, addUnchangedValues, forceAddUnchangedValues); - - // Use this common utility method to do the computation. It does most of the work. - ItemDelta itemDelta = LensUtil.consolidateTripleToDelta( - itemPath, (DeltaSetTriple)triple, itemDefinition, existingItemDelta, projCtx.getObjectNew(), - valueMatcher, comparator, addUnchangedValues || forceAddUnchangedValues, completeShadow, isExclusiveStrong, - discr.toHumanReadableDescription(), completeShadow); + itemDesc, discr, projCtx.hasFullShadow(), addUnchangedValues, forceAddUnchangedValues); + + // Use the consolidator to do the computation. It does most of the work. + IvwoConsolidator> consolidator = new IvwoConsolidator<>(); + consolidator.setItemPath(itemPath); + consolidator.setIvwoTriple(triple); + consolidator.setItemDefinition(itemDefinition); + consolidator.setAprioriItemDelta(existingItemDelta); + consolidator.setItemContainer(projCtx.getObjectNew()); + consolidator.setValueMatcher(valueMatcher); + consolidator.setComparator(comparator); + consolidator.setAddUnchangedValues(addUnchangedValues || forceAddUnchangedValues); + consolidator.setFilterExistingValues(projCtx.hasFullShadow()); + consolidator.setExclusiveStrong(isExclusiveStrong); + consolidator.setContextDescription(discr.toHumanReadableDescription()); + if (projCtx.hasFullShadow()) { + consolidator.setStrengthSelector(strengthSelector); + } else { + consolidator.setStrengthSelector(strengthSelector.notWeak()); + } + + ItemDelta itemDelta = consolidator.consolidateToDelta(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Consolidated delta (before sync filter) for {}:\n{}",discr,itemDelta==null?"null":itemDelta.debugDump()); @@ -525,6 +494,23 @@ private ItemDelta consolida return null; } + + private void fillInAssociationNames(Map,PrismContainerDefinition>>> squeezedAssociations) throws SchemaException { + PrismPropertyDefinition nameDefinition = prismContext.getSchemaRegistry() + .findContainerDefinitionByCompileTimeClass(ShadowAssociationType.class) + .findPropertyDefinition(ShadowAssociationType.F_NAME); + for (Entry,PrismContainerDefinition>>> entry : squeezedAssociations.entrySet()) { + DeltaSetTriple,PrismContainerDefinition>> deltaSetTriple = entry.getValue(); + for (ItemValueWithOrigin,PrismContainerDefinition> ivwo : deltaSetTriple.getAllValues()) { + PrismContainerValue value = ivwo.getItemValue(); + if (value != null && value.findProperty(ShadowAssociationType.F_NAME) == null) { // just for safety + PrismProperty nameProperty = value.createProperty(nameDefinition); + nameProperty.setRealValue(entry.getKey()); + } + } + } + LOGGER.trace("Names for squeezed associations filled-in."); + } private boolean hasActiveWeakMapping( Map>> squeezedAttributes, LensProjectionContext accCtx) throws SchemaException { @@ -747,6 +733,80 @@ private void cleanupValues(Collection> values, Propert } } } + + private void squeezeAll(LensContext context, LensProjectionContext projCtx) throws SchemaException { + // "Squeeze" all the relevant mappings into a data structure that we can process conveniently. We want to have all the + // (meta)data about relevant for a specific attribute in one data structure, not spread over several account constructions. + Map,PrismPropertyDefinition>>> squeezedAttributes = + sqeeze(projCtx, construction -> (Collection)construction.getAttributeMappings()); + projCtx.setSqueezedAttributes(squeezedAttributes); + + Map,PrismContainerDefinition>>> squeezedAssociations = + sqeeze(projCtx, construction -> construction.getAssociationMappings()); + projCtx.setSqueezedAssociations(squeezedAssociations); + + // Association values in squeezed associations do not contain association name attribute. + // It is hacked-in later for use in this class, but not for other uses (e.g. in ReconciliationProcessor). + // So, we do it here - once and for all. + if (!squeezedAssociations.isEmpty()) { + fillInAssociationNames(squeezedAssociations); + } + + MappingExtractor,PrismPropertyDefinition,F> auxiliaryObjectClassExtractor = + construction -> { + PrismValueDeltaSetTripleProducer,PrismPropertyDefinition> prod = new PrismValueDeltaSetTripleProducer,PrismPropertyDefinition>() { + @Override + public QName getMappingQName() { + return ShadowType.F_AUXILIARY_OBJECT_CLASS; + } + @Override + public PrismValueDeltaSetTriple> getOutputTriple() { + PrismValueDeltaSetTriple> triple = new PrismValueDeltaSetTriple<>(); + if (construction.getAuxiliaryObjectClassDefinitions() != null) { + for (RefinedObjectClassDefinition auxiliaryObjectClassDefinition: construction.getAuxiliaryObjectClassDefinitions()) { + triple.addToZeroSet(new PrismPropertyValue(auxiliaryObjectClassDefinition.getTypeName())); + } + } + return triple; + } + @Override + public MappingStrengthType getStrength() { + return MappingStrengthType.STRONG; + } + @Override + public PrismValueDeltaSetTripleProducer,PrismPropertyDefinition> clone() { + return this; + } + @Override + public boolean isExclusive() { + return false; + } + @Override + public boolean isAuthoritative() { + return true; + } + @Override + public boolean isSourceless() { + return false; + } + @Override + public String getIdentifier() { + return null; + } + @Override + public String toHumanReadableDescription() { + return "auxiliary object class construction " + construction; + } + }; + Collection,PrismPropertyDefinition>> col = new ArrayList<>(1); + col.add(prod); + return col; + }; + + Map,PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses = + sqeeze(projCtx, auxiliaryObjectClassExtractor); + projCtx.setSqueezedAuxiliaryObjectClasses(squeezedAuxiliaryObjectClasses); + } private Map>> sqeeze( LensProjectionContext projCtx, MappingExtractor extractor) { @@ -953,4 +1013,58 @@ private De return triple; } + void consolidateValuesPostRecon(LensContext context, LensProjectionContext projCtx, + Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, + ConfigurationException, SecurityViolationException, PolicyViolationException { + + //account was deleted, no changes are needed. + if (wasProjectionDeleted(projCtx)) { + dropAllProjectionDelta(projCtx); + return; + } + + SynchronizationPolicyDecision policyDecision = projCtx.getSynchronizationPolicyDecision(); + + if (policyDecision == SynchronizationPolicyDecision.DELETE) { + return; + } + + if (!projCtx.hasFullShadow()) { + return; + } + + boolean addUnchangedValues = false; + if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.ADD) { + addUnchangedValues = true; + } + + + ObjectDelta objectDelta = new ObjectDelta(ShadowType.class, ChangeType.MODIFY, prismContext); + objectDelta.setOid(projCtx.getOid()); + ObjectDelta existingDelta = projCtx.getDelta(); + + RefinedObjectClassDefinition rOcDef = projCtx.getCompositeObjectClassDefinition(); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Object class definition for {} post-recon consolidation:\n{}", projCtx.getResourceShadowDiscriminator(), rOcDef.debugDump()); + } + + consolidateAttributes(context, projCtx, addUnchangedValues, rOcDef, objectDelta, existingDelta, StrengthSelector.WEAK_ONLY); + consolidateAssociations(context, projCtx, addUnchangedValues, rOcDef, objectDelta, existingDelta, StrengthSelector.WEAK_ONLY); + + + if (objectDelta == null || objectDelta.isEmpty()) { + return; + } + ObjectDelta accountSecondaryDelta = projCtx.getSecondaryDelta(); + if (accountSecondaryDelta != null) { + accountSecondaryDelta.merge(objectDelta); + } else { + projCtx.setSecondaryDelta(objectDelta); + } + } + + + } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java index 4326d563da3..664cc49e089 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java @@ -657,4 +657,42 @@ private void addIterationTokenDeltas(LensProjectionContext accountContext) throw } + public void processPostRecon(LensContext context, + LensProjectionContext projectionContext, String activityDescription, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + return; + } + if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + // We can do this only for focus types. + return; + } + OperationResult processorResult = result.createMinorSubresult(ProjectionValuesProcessor.class.getName()+".processAccountsValuesPostRecon"); + processorResult.recordSuccessIfUnknown(); + processProjectionsPostRecon((LensContext) context, projectionContext, + activityDescription, task, processorResult); + + } + + private void processProjectionsPostRecon(LensContext context, + LensProjectionContext projContext, String activityDescription, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, + CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { + + SynchronizationPolicyDecision policyDecision = projContext.getSynchronizationPolicyDecision(); + if (policyDecision != null && policyDecision == SynchronizationPolicyDecision.UNLINK) { + // We will not update accounts that are being unlinked. + // we cannot skip deleted accounts here as the delete delta will be skipped as well + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping post-recon processing of value for {} because the decision is {}", projContext.getHumanReadableName(), policyDecision); + } + return; + } + + consolidationProcessor.consolidateValuesPostRecon(context, projContext, task, result); + + } + } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java index 56e7a6154c0..bb06efe0cc2 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java @@ -395,6 +395,15 @@ private void projectProjection(LensContext context, Le }, partialProcessingOptions::getProjectionReconciliation); + LensUtil.partialExecute("projectionValuesPostRecon", + () -> { + projectionValuesProcessor.processPostRecon(context, projectionContext, activityDescription, task, result); + if (consistencyChecks) context.checkConsistence(); + + projectionContext.recompute(); + if (consistencyChecks) context.checkConsistence(); + }, + partialProcessingOptions::getProjectionValues); LensUtil.partialExecute("projectionLifecycle", () -> { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java index f9f6fab4192..3292bbd421a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java @@ -244,7 +244,7 @@ private void reconcileAuxiliaryObjectClasses(LensProjectionContext projCtx) thro if (!isTolerantAuxiliaryObjectClasses(projCtx)) { for (PrismPropertyValue isPValue : arePValues) { - if (!isInPvwoValues(valueMatcher, isPValue.getValue(), shouldBePValues)) { + if (!isInPvwoValues(valueMatcher, isPValue.getValue(), shouldBePValues, true)) { auxObjectClassChanged = true; recordDelta(valueMatcher, projCtx, ItemPath.EMPTY_PATH, propDef, ModificationType.DELETE, isPValue.getValue(), null, "it is not given"); @@ -432,11 +432,17 @@ private void reconcileProjectionAttribute(QName attrName, // nor from secondary delta (because these got there from mappings). boolean hasStrongShouldBePValue = false; + boolean hasOtherNonWeakValues = false; for (ItemValueWithOrigin,PrismPropertyDefinition> shouldBePValue : shouldBePValues) { - if (shouldBePValue.getMapping() != null - && shouldBePValue.getMapping().getStrength() == MappingStrengthType.STRONG) { - hasStrongShouldBePValue = true; - break; + if (shouldBePValue.getMapping() != null) { + if (shouldBePValue.getMapping().getStrength() == MappingStrengthType.STRONG) { + hasStrongShouldBePValue = true; + hasOtherNonWeakValues = true; + break; + } + if (shouldBePValue.getMapping().getStrength() == null || shouldBePValue.getMapping().getStrength() == MappingStrengthType.NORMAL) { + hasOtherNonWeakValues = true; + } } } @@ -521,7 +527,7 @@ private void reconcileProjectionAttribute(QName attrName, } } - decideIfTolerate(projCtx, attributeDefinition, arePValues, shouldBePValues, valueMatcher); + decideIfTolerate(projCtx, attributeDefinition, arePValues, shouldBePValues, valueMatcher, hasOtherNonWeakValues); } @@ -785,7 +791,7 @@ private void decideIfTolerate(LensProjectionContext projCtx, RefinedAttributeDefinition attributeDefinition, Collection> arePValues, Collection,PrismPropertyDefinition>> shouldBePValues, - ValueMatcher valueMatcher) throws SchemaException { + ValueMatcher valueMatcher, boolean hasOtherNonWeakValues) throws SchemaException { for (PrismPropertyValue isPValue : arePValues) { if (matchPattern(attributeDefinition.getTolerantValuePattern(), isPValue, valueMatcher)){ @@ -799,7 +805,7 @@ private void decideIfTolerate(LensProjectionContext projCtx, } if (!attributeDefinition.isTolerant()) { - if (!isInPvwoValues(valueMatcher, isPValue.getValue(), shouldBePValues)) { + if (!isInPvwoValues(valueMatcher, isPValue.getValue(), shouldBePValues, hasOtherNonWeakValues)) { recordDeleteDelta(isPValue, attributeDefinition, valueMatcher, projCtx, "it is not given by any mapping and the attribute is not tolerant"); } } @@ -1054,7 +1060,8 @@ private boolean isInAssociationValues(ValueMatcher valueMatcher, ShadowAssociati } private boolean isInPvwoValues(ValueMatcher valueMatcher, T value, - Collection,PrismPropertyDefinition>> shouldBePvwos) { + Collection,PrismPropertyDefinition>> shouldBePvwos, + boolean hasOtherNonWeakValues) { if (shouldBePvwos == null || shouldBePvwos.isEmpty()) { return false; @@ -1064,6 +1071,9 @@ private boolean isInPvwoValues(ValueMatcher valueMatcher, T value, if (!shouldBePvwo.isValid()) { continue; } + if (hasOtherNonWeakValues && shouldBePvwo.getMapping() != null && shouldBePvwo.getMapping().getStrength() == MappingStrengthType.WEAK) { + continue; + } PrismPropertyValue shouldBePPValue = shouldBePvwo.getItemValue(); T shouldBeValue = shouldBePPValue.getValue(); if (matchValue(value, shouldBeValue, valueMatcher)) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentTripleEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentTripleEvaluator.java index 537604c946e..90c19e34f35 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentTripleEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentTripleEvaluator.java @@ -228,6 +228,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(evaluatedAssignment.isValid()); collectToMinus(evaluatedAssignmentTriple, evaluatedAssignment, forceRecon); } else { @@ -252,6 +253,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(evaluatedAssignment.isValid()); collectToZero(evaluatedAssignmentTriple, evaluatedAssignment, forceRecon); } else if (willHaveValue) { // add @@ -261,6 +263,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(false); collectToPlus(evaluatedAssignmentTriple, evaluatedAssignment, forceRecon); } else if (hadValue) { // delete @@ -270,6 +273,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(evaluatedAssignment.isValid()); collectToMinus(evaluatedAssignmentTriple, evaluatedAssignment, forceRecon); } else if (assignmentElement.isOld()) { // This is OK, safe to skip. This is just an relic of earlier processing. @@ -302,6 +306,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(evaluatedAssignment.isValid()); collectToZero(evaluatedAssignmentTriple, evaluatedAssignment, forceRecon); } else { if (LOGGER.isTraceEnabled()) { @@ -313,6 +318,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(false); collectToPlus(evaluatedAssignmentTriple, evaluatedAssignment, forceRecon); } @@ -327,6 +333,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(evaluatedAssignment.isValid()); collectToMinus(evaluatedAssignmentTriple, evaluatedAssignment, forceRecon); } else { @@ -353,6 +360,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(evaluatedAssignment.isValid()); collectToZero(evaluatedAssignmentTriple, evaluatedAssignment, true); } else if (isValid) { // Assignment became valid. We need to place it in plus set to initiate provisioning @@ -366,6 +374,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(false); collectToPlus(evaluatedAssignmentTriple, evaluatedAssignment, true); } else { // Assignment became invalid. We need to place is in minus set to initiate deprovisioning @@ -379,6 +388,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(true); collectToMinus(evaluatedAssignmentTriple, evaluatedAssignment, true); } } @@ -394,6 +404,7 @@ private void processAssignment(DeltaSetTriple> evalua } evaluatedAssignment.setPresentInCurrentObject(presentInCurrent); evaluatedAssignment.setPresentInOldObject(presentInOld); + evaluatedAssignment.setWasValid(evaluatedAssignment.isValid()); collectToZero(evaluatedAssignmentTriple, evaluatedAssignment, forceRecon); } } @@ -497,7 +508,7 @@ private EvaluatedAssignmentImpl evaluateAssignment(Item String assignmentPlacementDesc, Task task, OperationResult parentResult) throws SchemaException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { OperationResult result = parentResult.createMinorSubresult(AssignmentProcessor.class.getSimpleName()+".evaluateAssignment"); result.addParam("assignmentDescription", assignmentPlacementDesc); - try{ + try { // Evaluate assignment. This follows to the assignment targets, follows to the inducements, // evaluates all the expressions, etc. EvaluatedAssignmentImpl evaluatedAssignment = assignmentEvaluator.evaluate(assignmentIdi, mode, evaluateOld, source, assignmentPlacementDesc, task, result); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java index 72273eb0feb..0ac96a330b3 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java @@ -44,9 +44,11 @@ import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; import com.evolveum.midpoint.model.impl.ModelObjectResolver; import com.evolveum.midpoint.model.impl.lens.ItemValueWithOrigin; +import com.evolveum.midpoint.model.impl.lens.IvwoConsolidator; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.model.impl.lens.StrengthSelector; import com.evolveum.midpoint.model.impl.lens.projector.MappingEvaluator; import com.evolveum.midpoint.model.impl.trigger.RecomputeTriggerHandler; import com.evolveum.midpoint.prism.Item; @@ -312,12 +314,22 @@ Collection> computeIte boolean isNonTolerant = templateItemDefinition != null && Boolean.FALSE.equals(templateItemDefinition.isTolerant()); ItemDelta aprioriItemDelta = getAprioriItemDelta(targetObjectAPrioriDelta, itemPath); - boolean filterExistingValues = !isNonTolerant; // if non-tolerant, we want to gather ZERO & PLUS sets - ItemDefinition itemDefinition = focusDefinition.findItemDefinition(itemPath); - ItemDelta itemDelta = LensUtil.consolidateTripleToDelta(itemPath, (DeltaSetTriple)outputTriple, - itemDefinition, aprioriItemDelta, targetObject, null, null, - addUnchangedValues, filterExistingValues, false, contextDesc, true); - + + IvwoConsolidator consolidator = new IvwoConsolidator<>(); + consolidator.setItemPath(itemPath); + consolidator.setIvwoTriple(outputTriple); + consolidator.setItemDefinition(focusDefinition.findItemDefinition(itemPath)); + consolidator.setAprioriItemDelta(aprioriItemDelta); + consolidator.setItemContainer(targetObject); + consolidator.setValueMatcher(null); + consolidator.setComparator(null); + consolidator.setAddUnchangedValues(addUnchangedValues); + consolidator.setFilterExistingValues(!isNonTolerant); // if non-tolerant, we want to gather ZERO & PLUS sets + consolidator.setExclusiveStrong(false); + consolidator.setContextDescription(contextDesc); + consolidator.setStrengthSelector(StrengthSelector.ALL); + + ItemDelta itemDelta = consolidator.consolidateToDelta(); // Do a quick version of reconciliation. There is not much to reconcile as both the source and the target // is focus. But there are few cases to handle, such as strong mappings, and sourceless normal mappings. @@ -671,11 +683,24 @@ private ObjectDeltaObject getUpdatedFocusOdo(LensContex } Class focusClass = context.getFocusContext().getObjectTypeClass(); ItemDefinition itemDefinition = getObjectDefinition(focusClass).findItemDefinition(path); + // TODO not much sure about the parameters - ItemDelta itemDelta = LensUtil - .consolidateTripleToDelta(path, (DeltaSetTriple) triple, itemDefinition, - getAprioriItemDelta(focusOdo.getObjectDelta(), path), focusOdo.getNewObject(), null, null, - true, true, false, " updating chained source (" + path + ") in " + contextDesc, true); + IvwoConsolidator consolidator = new IvwoConsolidator<>(); + consolidator.setItemPath(path); + consolidator.setIvwoTriple(triple); + consolidator.setItemDefinition(itemDefinition); + consolidator.setAprioriItemDelta(getAprioriItemDelta(focusOdo.getObjectDelta(), path)); + consolidator.setItemContainer(focusOdo.getNewObject()); + consolidator.setValueMatcher(null); + consolidator.setComparator(null); + consolidator.setAddUnchangedValues(true); + consolidator.setFilterExistingValues(true); + consolidator.setExclusiveStrong(false); + consolidator.setContextDescription(" updating chained source (" + path + ") in " + contextDesc); + consolidator.setStrengthSelector(StrengthSelector.ALL); + + ItemDelta itemDelta = consolidator.consolidateToDelta(); + LOGGER.trace("Updating focus ODO with delta:\n{}", itemDelta.debugDumpLazily()); focusOdoCloned.update(itemDelta); } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java index 60ba25abe83..cf956f8ade0 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractConfiguredModelIntegrationTest.java @@ -303,6 +303,7 @@ public class AbstractConfiguredModelIntegrationTest extends AbstractModelIntegra protected static final String USER_JACK_GIVEN_NAME = "Jack"; protected static final String USER_JACK_FAMILY_NAME = "Sparrow"; protected static final String USER_JACK_ADDITIONAL_NAME = "Jackie"; + protected static final String USER_JACK_DESCRIPTION = "Where's the rum?"; protected static final String USER_JACK_EMPLOYEE_TYPE = "CAPTAIN"; protected static final String USER_JACK_EMPLOYEE_NUMBER = "emp1234"; protected static final String USER_JACK_LOCALITY = "Caribbean"; @@ -756,4 +757,9 @@ protected String getExpectedPasswordNotificationBodyPrefix(String dummyResourceN protected void displayPasswordNotifications() { displayNotifications(NOTIFIER_ACCOUNT_PASSWORD_NAME); } + + protected Object getQuote(String description, String fullName) { + return description + " -- " + fullName; + } + } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestEntitlements.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestEntitlements.java index 42a49c9f536..821c087416f 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestEntitlements.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestEntitlements.java @@ -1217,8 +1217,7 @@ public void test645JackRoleSwashbucklerIsValid() throws Exception { recomputeUser(USER_JACK_OID, task, result); // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); PrismObject user = getUser(USER_JACK_OID); display("User jack", user); @@ -1252,8 +1251,7 @@ public void test650JackRoleSwashbucklerBecomesInvalid() throws Exception { recomputeUser(USER_JACK_OID, task, result); // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); PrismObject user = getUser(USER_JACK_OID); display("User jack", user); @@ -1309,8 +1307,10 @@ public void test659UnassignRoleSwashbucklerFromJack() throws Exception { DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_DRINK_NAME, RESOURCE_DUMMY_DRINK); // Title is tolerant. Reconcile will not remove the value. + // It might seem that role unassign should remove the Swashbuckler value. But it should not + // because the role is not valid (expired) assertDummyAccountAttribute(null, ACCOUNT_JACK_DUMMY_USERNAME, - DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Bloody Pirate"); + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Bloody Pirate", "Swashbuckler"); } @Test diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java index bf3b52082c2..3b642173099 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java @@ -1133,7 +1133,7 @@ public void test131ModifyUserJackAssignAccount() throws Exception { TestUtil.assertSuccess("executeChanges result", result); XMLGregorianCalendar endTime = clock.currentTimeXMLGregorianCalendar(); assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 0); - assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 66); + assertCounterIncrement(InternalCounters.PRISM_OBJECT_CLONE_COUNT, 67); PrismObject userAfter = getUser(USER_JACK_OID); display("User after change execution", userAfter); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestTolerantAttributes.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestTolerantAttributes.java index bdcc17b244b..1f5bd97e8e4 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestTolerantAttributes.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestTolerantAttributes.java @@ -123,57 +123,59 @@ public void test100ModifyUserAddAccount() throws Exception { // Check account in dummy resource assertAccount(userJack, RESOURCE_DUMMY_BLACK_OID); - } + /** + * We are trying to add value to the resource (through a mapping). This value matches + * intolerant pattern. But as this value is explicitly added by a mapping from a primary + * delta then the value should be set to resource even in that case. + */ @Test - public void test101modifyAddAttributesIntolerantPattern() throws Exception{ - TestUtil.displayTestTitle(this, "test101modifyAddAttributesIntolerantPattern"); - - // GIVEN - Task task = taskManager.createTaskInstance(TestTolerantAttributes.class.getName() + ".test101modifyAddAttributesIntolerantPattern"); - OperationResult result = task.getResult(); - assumeAssignmentPolicy(AssignmentPolicyEnforcementType.POSITIVE); + public void test101ModifyAddAttributesIntolerantPattern() throws Exception { + final String TEST_NAME = "test101ModifyAddAttributesIntolerantPattern"; + displayTestTitle(TEST_NAME); - ObjectDelta userDelta = ObjectDelta.createEmptyModifyDelta(UserType.class, USER_JACK_OID, prismContext); - PropertyDelta propertyDelta = PropertyDelta.createModificationAddProperty(new ItemPath(UserType.F_DESCRIPTION), getUserDefinition().findPropertyDefinition(UserType.F_DESCRIPTION), "This value will be not added"); - userDelta.addModification(propertyDelta); - Collection> deltas = (Collection)MiscUtil.createCollection(userDelta); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + assumeAssignmentPolicy(AssignmentPolicyEnforcementType.POSITIVE); - modelService.executeChanges(deltas, ModelExecuteOptions.createReconcile(), task, result); + ObjectDelta userDelta = ObjectDelta.createEmptyModifyDelta(UserType.class, USER_JACK_OID, prismContext); + PropertyDelta propertyDelta = PropertyDelta.createModificationAddProperty( + new ItemPath(UserType.F_DESCRIPTION), getUserDefinition().findPropertyDefinition(UserType.F_DESCRIPTION), + "This value will be not added"); + userDelta.addModification(propertyDelta); + Collection> deltas = (Collection)MiscUtil.createCollection(userDelta); - result.computeStatus(); - TestUtil.assertSuccess(result); + // WHEN + displayWhen(TEST_NAME); + modelService.executeChanges(deltas, ModelExecuteOptions.createReconcile(), task, result); - // Check value in "quote attribute" - PrismObject userJack = modelService.getObject(UserType.class, USER_JACK_OID, null, task, result); - assertUserJack(userJack); - UserType userJackType = userJack.asObjectable(); - assertEquals("Unexpected number of accountRefs", 1, userJackType.getLinkRef().size()); - ObjectReferenceType accountRefType = userJackType.getLinkRef().get(0); - accountOid = accountRefType.getOid(); - assertFalse("No accountRef oid", StringUtils.isBlank(accountOid)); - PrismReferenceValue accountRefValue = accountRefType.asReferenceValue(); - assertEquals("OID mismatch in accountRefValue", accountOid, accountRefValue.getOid()); - assertNull("Unexpected object in accountRefValue", accountRefValue.getObject()); + // THEN + displayThen(TEST_NAME); + assertSuccess(result); - // Check shadow - PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountOid, null, result); - assertAccountShadowRepo(accountShadow, accountOid, "jack", getDummyResourceType(RESOURCE_DUMMY_BLACK_NAME)); + // Check value in "quote attribute" + PrismObject userJack = modelService.getObject(UserType.class, USER_JACK_OID, null, task, result); + assertUserJack(userJack); + accountOid = getSingleLinkOid(userJack); + assertFalse("No accountRef oid", StringUtils.isBlank(accountOid)); - // Check account - PrismObject accountModel = modelService.getObject(ShadowType.class, accountOid, null, task, result); - assertAccountShadowModel(accountModel, accountOid, "jack", getDummyResourceType(RESOURCE_DUMMY_BLACK_NAME)); + // Check shadow + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountOid, null, result); + assertAccountShadowRepo(accountShadow, accountOid, "jack", getDummyResourceType(RESOURCE_DUMMY_BLACK_NAME)); - // Check account in dummy resource - assertAccount(userJack, RESOURCE_DUMMY_BLACK_OID); + // Check account + PrismObject accountModel = modelService.getObject(ShadowType.class, accountOid, null, task, result); + assertAccountShadowModel(accountModel, accountOid, "jack", getDummyResourceType(RESOURCE_DUMMY_BLACK_NAME)); - // Check value of quote attribute - assertDummyAccountAttribute(RESOURCE_DUMMY_BLACK_NAME, "jack", "quote", null); + // Check account in dummy resource + assertAccount(userJack, RESOURCE_DUMMY_BLACK_OID); + // Check value of quote attribute + assertDummyAccountAttribute(RESOURCE_DUMMY_BLACK_NAME, "jack", "quote", "This value will be not added"); } - @Test public void test102modifyAddAttributeTolerantPattern() throws Exception{ TestUtil.displayTestTitle(this, "test102modifyAddAttributeTolerantPattern"); @@ -218,10 +220,8 @@ public void test102modifyAddAttributeTolerantPattern() throws Exception{ // Check value of quote attribute assertDummyAccountAttribute(RESOURCE_DUMMY_BLACK_NAME, "jack", "quote", "res-thiIsOk"); - } - @Test public void test103modifyReplaceAttributeIntolerant() throws Exception{ TestUtil.displayTestTitle(this, "test103modifyReplaceAttributeIntolerant"); @@ -266,7 +266,6 @@ public void test103modifyReplaceAttributeIntolerant() throws Exception{ // Check value of drink attribute assertDummyAccountAttribute(RESOURCE_DUMMY_BLACK_NAME, "jack", "gossip", null); - } @Test @@ -314,7 +313,6 @@ public void test104modifyReplaceAttributeTolerantPattern() throws Exception{ // Check value of drink attribute assertDummyAccountAttribute(RESOURCE_DUMMY_BLACK_NAME, "jack", "gossip", "thiIsOk"); - } @Test @@ -340,8 +338,7 @@ public void test105modifyAddNonTolerantAttribute() throws Exception{ } catch (PolicyViolationException ex){ //this is expected } - } - + } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestUserTemplate.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestUserTemplate.java index c8c952f2607..4ce6c599a84 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestUserTemplate.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestUserTemplate.java @@ -1051,13 +1051,12 @@ public void test162ModifyUserGivenNameAgainPhantomChange() throws Exception { dummyAuditService.assertRecords(2); dummyAuditService.assertSimpleRecordSanity(); dummyAuditService.assertAnyRequestDeltas(); - dummyAuditService.assertExecutionDeltas(2); + dummyAuditService.assertExecutionDeltas(1); dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); dummyAuditService.assertTarget(USER_JACK_OID); dummyAuditService.assertExecutionSuccess(); ObjectDeltaOperation objectDeltaOperation = dummyAuditService.getExecutionDelta(0, ChangeType.MODIFY, UserType.class); - assertEquals("unexpected number of modifications in audited delta", 8, objectDeltaOperation.getObjectDelta().getModifications().size()); // givenName + modifyTimestamp, modifyChannel, modifierRef + assertEquals("unexpected number of modifications in audited delta", 7, objectDeltaOperation.getObjectDelta().getModifications().size()); // givenName + modifyTimestamp, modifyChannel, modifierRef } @Test diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/mapping/TestMapping.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/mapping/TestMapping.java index 515ed13aeb8..f63637773cc 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/mapping/TestMapping.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/mapping/TestMapping.java @@ -41,7 +41,9 @@ import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.IdItemPathSegment; import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.path.NameItemPathSegment; import com.evolveum.midpoint.prism.util.PrismAsserts; import com.evolveum.midpoint.prism.util.PrismTestUtil; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; @@ -60,7 +62,11 @@ import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentPolicyEnforcementType; +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.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; @@ -91,9 +97,31 @@ public class TestMapping extends AbstractMappingTest { protected static final String RESOURCE_DUMMY_CUSTOM_FUNCTION_CRIMSON_NAME = "customFunction"; protected static final String RESOURCE_DUMMY_CUSTOM_FUNCTION_CRIMSON_NAMESPACE = MidPointConstants.NS_RI; + // COBALT: weak non-tolerant mappings + protected static final File RESOURCE_DUMMY_COBALT_FILE = new File(TEST_DIR, "resource-dummy-cobalt.xml"); + protected static final String RESOURCE_DUMMY_COBALT_OID = "7f8a927c-cac4-11e7-9733-9f90849f6d4a"; + protected static final String RESOURCE_DUMMY_COBALT_NAME = "cobalt"; + protected static final String RESOURCE_DUMMY_COBALT_NAMESPACE = MidPointConstants.NS_RI; + protected static final File ROLE_ANTINIHILIST_FILE = new File(TEST_DIR, "role-antinihilist.xml"); protected static final String ROLE_ANTINIHILIST_OID = "4c5c6c44-bd7d-11e7-99ef-9b82464da93d"; + protected static final File ROLE_BLUE_TITANIC_FILE = new File(TEST_DIR, "role-blue-titanic.xml"); + protected static final String ROLE_BLUE_TITANIC_OID = "97f8d44a-cab5-11e7-9d72-fbe451f26944"; + private static final String ROLE_TITANIC_SHIP_VALUE = "Titanic"; + + protected static final File ROLE_BLUE_POETRY_FILE = new File(TEST_DIR, "role-blue-poetry.xml"); + protected static final String ROLE_BLUE_POETRY_OID = "22d3d4f6-cabc-11e7-9441-4b5c10dd30e0"; + private static final String ROLE_POETRY_QUOTE_VALUE = "Oh freddled gruntbuggly"; + + protected static final File ROLE_COBALT_NEVERLAND_FILE = new File(TEST_DIR, "role-cobalt-neverland.xml"); + protected static final String ROLE_COBALT_NEVERLAND_OID = "04aca9d6-caca-11e7-9c6a-97b71af3e545"; + private static final String ROLE_COBALT_NEVERLAND_VALUE = "Neverland"; + + private static final String CAPTAIN_JACK_FULL_NAME = "Captain Jack Sparrow"; + + private static final String SHIP_BLACK_PEARL = "Black Pearl"; + protected static final String USER_GUYBRUSH_PASSWORD_1_CLEAR = "1wannaBEaP1rat3"; protected static final String USER_GUYBRUSH_PASSWORD_2_CLEAR = "1wannaBEtheP1rat3"; @@ -107,6 +135,7 @@ public class TestMapping extends AbstractMappingTest { protected static final String DRINK_GRAPPA = "grappa"; protected static final String DRINK_GIN = "gin"; protected static final String DRINK_MEZCAL = "mezcal"; + @Override @@ -119,8 +148,13 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti RESOURCE_DUMMY_LIGHT_CRIMSON_FILE, RESOURCE_DUMMY_LIGHT_CRIMSON_OID, initTask, initResult); initDummyResourcePirate(RESOURCE_DUMMY_CUSTOM_FUNCTION_CRIMSON_NAME, RESOURCE_DUMMY_CUSTOM_FUNCTION_CRIMSON_FILE, RESOURCE_DUMMY_CUSTOM_FUNCTION_CRIMSON_OID, initTask, initResult); + initDummyResourcePirate(RESOURCE_DUMMY_COBALT_NAME, + RESOURCE_DUMMY_COBALT_FILE, RESOURCE_DUMMY_COBALT_OID, initTask, initResult); repoAddObjectFromFile(ROLE_ANTINIHILIST_FILE, initResult); + repoAddObjectFromFile(ROLE_BLUE_TITANIC_FILE, initResult); + repoAddObjectFromFile(ROLE_BLUE_POETRY_FILE, initResult); + repoAddObjectFromFile(ROLE_COBALT_NEVERLAND_FILE, initResult); assumeAssignmentPolicy(AssignmentPolicyEnforcementType.FULL); } @@ -170,7 +204,7 @@ public void test100ModifyUserAssignAccountDummyBlue() throws Exception { assertNotNull("No drink UUID", drinkUuidBlue); display("Drink UUID", drinkUuidBlue.toString()); - assertAccountShip(userJack, ACCOUNT_JACK_DUMMY_FULLNAME, null, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME), task); + assertAccountShip(userJack, ACCOUNT_JACK_DUMMY_FULLNAME, null, RESOURCE_DUMMY_BLUE_NAME, task); assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, USER_JACK_USERNAME, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, "Where's the rum? -- Jack Sparrow"); @@ -186,346 +220,1184 @@ public void test100ModifyUserAssignAccountDummyBlue() throws Exception { } @Test - public void test101ModifyUserFullName() throws Exception { - final String TEST_NAME = "test101ModifyUserFullName"; + public void test101ModifyUserFullName() throws Exception { + final String TEST_NAME = "test101ModifyUserFullName"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + dummyAuditService.clear(); + + // WHEN + displayWhen(TEST_NAME); + modifyUserReplace(USER_JACK_OID, UserType.F_FULL_NAME, task, result, + PrismTestUtil.createPolyString(CAPTAIN_JACK_FULL_NAME)); + + // THEN + displayThen(TEST_NAME); + assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User after change execution", userJack); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userJack, USER_JACK_FULL_NAME, null, RESOURCE_DUMMY_BLUE_NAME, task); + + assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, USER_JACK_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, + getQuote(USER_JACK_DESCRIPTION, CAPTAIN_JACK_FULL_NAME)); + + // Check audit + display("Audit", dummyAuditService); + dummyAuditService.assertSimpleRecordSanity(); + dummyAuditService.assertRecords(2); + dummyAuditService.assertAnyRequestDeltas(); + dummyAuditService.assertExecutionDeltas(2); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); + dummyAuditService.assertExecutionSuccess(); + } + + @Test + public void test102ModifyUserFullNameRecon() throws Exception { + final String TEST_NAME = "test102ModifyUserFullNameRecon"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + dummyAuditService.clear(); + + // WHEN + displayWhen(TEST_NAME); + ObjectDelta objectDelta = createModifyUserReplaceDelta(USER_JACK_OID, UserType.F_FULL_NAME, + PrismTestUtil.createPolyString(CAPTAIN_JACK_FULL_NAME)); + Collection> deltas = MiscSchemaUtil.createCollection(objectDelta); + modelService.executeChanges(deltas, ModelExecuteOptions.createReconcile(), task, result); + + // THEN + displayThen(TEST_NAME); + assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User after change execution", userJack); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userJack, USER_JACK_FULL_NAME, null, RESOURCE_DUMMY_BLUE_NAME, task); + + // The quote attribute was empty before this operation. So the weak mapping kicks in + // and sets a new value. + assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, USER_JACK_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, + getQuote(USER_JACK_DESCRIPTION, CAPTAIN_JACK_FULL_NAME)); + + + // Check audit + display("Audit", dummyAuditService); + dummyAuditService.assertSimpleRecordSanity(); + dummyAuditService.assertRecords(2); + dummyAuditService.assertAnyRequestDeltas(); + dummyAuditService.assertExecutionDeltas(1); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); + dummyAuditService.assertExecutionSuccess(); + } + + @Test + public void test104ModifyUserOrganizationalUnit() throws Exception { + final String TEST_NAME = "test104ModifyUserOrganizationalUnit"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + dummyAuditService.clear(); + + // WHEN + modifyUserReplace(USER_JACK_OID, UserType.F_ORGANIZATIONAL_UNIT, task, result, + PrismTestUtil.createPolyString("Black Pearl")); + + // THEN + assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User after change execution", userJack); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userJack, USER_JACK_FULL_NAME, "Black Pearl", RESOURCE_DUMMY_BLUE_NAME, task); + + // Check audit + display("Audit", dummyAuditService); + dummyAuditService.assertSimpleRecordSanity(); + dummyAuditService.assertRecords(2); + dummyAuditService.assertAnyRequestDeltas(); + dummyAuditService.assertExecutionDeltas(2); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); + dummyAuditService.assertExecutionSuccess(); + } + + @Test + public void test105ModifyAccountShip() throws Exception { + final String TEST_NAME = "test105ModifyAccountShip"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + dummyAuditService.clear(); + + PrismObject userJack = getUser(USER_JACK_OID); + String accountOid = getSingleLinkOid(userJack); + + Collection> deltas = new ArrayList>(); + ObjectDelta accountDelta = ObjectDelta.createModificationReplaceProperty(ShadowType.class, + accountOid, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME).getAttributePath(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME), + prismContext, "Flying Dutchman"); + deltas.add(accountDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + assertSuccess(result); + + userJack = getUser(USER_JACK_OID); + display("User after change execution", userJack); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userJack, USER_JACK_FULL_NAME, "Flying Dutchman", RESOURCE_DUMMY_BLUE_NAME, task); + + // Check audit + display("Audit", dummyAuditService); + dummyAuditService.assertSimpleRecordSanity(); + dummyAuditService.assertRecords(2); + dummyAuditService.assertAnyRequestDeltas(); + dummyAuditService.assertExecutionDeltas(2); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); + dummyAuditService.assertExecutionSuccess(); + } + + /** + * There is a weak mapping for ship attribute. + * Therefore try to remove the value. The weak mapping should be applied. + */ + @Test + public void test106ModifyAccountShipReplaceEmpty() throws Exception { + final String TEST_NAME = "test106ModifyAccountShipReplaceEmpty"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + dummyAuditService.clear(); + + PrismObject userJack = getUser(USER_JACK_OID); + String accountOid = getSingleLinkOid(userJack); + + Collection> deltas = new ArrayList>(); + ObjectDelta accountDelta = ObjectDelta.createModificationReplaceProperty(ShadowType.class, + accountOid, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME).getAttributePath(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME), + prismContext); + deltas.add(accountDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + assertSuccess(result); + + userJack = getUser(USER_JACK_OID); + display("User after change execution", userJack); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userJack, USER_JACK_FULL_NAME, "Black Pearl", RESOURCE_DUMMY_BLUE_NAME, task); + + // Check audit + display("Audit", dummyAuditService); + dummyAuditService.assertSimpleRecordSanity(); + dummyAuditService.assertRecords(2); + dummyAuditService.assertAnyRequestDeltas(); + dummyAuditService.assertExecutionDeltas(2); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); + dummyAuditService.assertExecutionSuccess(); + } + + @Test + public void test107ModifyAccountShipAgain() throws Exception { + final String TEST_NAME = "test107ModifyAccountShipAgain"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + dummyAuditService.clear(); + + PrismObject userJack = getUser(USER_JACK_OID); + String accountOid = getSingleLinkOid(userJack); + + Collection> deltas = new ArrayList>(); + ObjectDelta accountDelta = ObjectDelta.createModificationReplaceProperty(ShadowType.class, + accountOid, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME).getAttributePath(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME), + prismContext, "HMS Dauntless"); + deltas.add(accountDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + assertSuccess(result); + + userJack = getUser(USER_JACK_OID); + display("User after change execution", userJack); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userJack, USER_JACK_FULL_NAME, "HMS Dauntless", RESOURCE_DUMMY_BLUE_NAME, task); + + // Check audit + display("Audit", dummyAuditService); + dummyAuditService.assertSimpleRecordSanity(); + dummyAuditService.assertRecords(2); + dummyAuditService.assertAnyRequestDeltas(); + dummyAuditService.assertExecutionDeltas(2); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); + dummyAuditService.assertExecutionSuccess(); + } + + /** + * There is a weak mapping for ship attribute. + * Therefore try to remove the value. The weak mapping should be applied. + */ + @Test + public void test108ModifyAccountShipDelete() throws Exception { + final String TEST_NAME = "test108ModifyAccountShipDelete"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + dummyAuditService.clear(); + + PrismObject userJack = getUser(USER_JACK_OID); + String accountOid = getSingleLinkOid(userJack); + + Collection> deltas = new ArrayList>(); + ObjectDelta accountDelta = ObjectDelta.createModificationDeleteProperty(ShadowType.class, + accountOid, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME).getAttributePath(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME), + prismContext, "HMS Dauntless"); + deltas.add(accountDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + assertSuccess(result); + + userJack = getUser(USER_JACK_OID); + display("User after change execution", userJack); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userJack, USER_JACK_FULL_NAME, SHIP_BLACK_PEARL, RESOURCE_DUMMY_BLUE_NAME, task); + + // Check audit + display("Audit", dummyAuditService); + dummyAuditService.assertSimpleRecordSanity(); + dummyAuditService.assertRecords(2); + dummyAuditService.assertAnyRequestDeltas(); + dummyAuditService.assertExecutionDeltas(2); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); + dummyAuditService.assertExecutionSuccess(); + } + + /** + * Assign Blue Titanic role. This role has strong mapping to blue resource + * ship attribute. The weak mapping on blue resource should NOT be applied. + * MID-4236 + */ + @Test + public void test110AssignBlueTitanic() throws Exception { + final String TEST_NAME = "test110AssignBlueTitanic"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + assignRole(USER_JACK_OID, ROLE_BLUE_TITANIC_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userAfter, USER_JACK_FULL_NAME, ROLE_TITANIC_SHIP_VALUE, RESOURCE_DUMMY_BLUE_NAME, task); + } + + @Test + public void test111Recompute() throws Exception { + final String TEST_NAME = "test111Recompute"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + recomputeUser(USER_JACK_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userAfter, USER_JACK_FULL_NAME, ROLE_TITANIC_SHIP_VALUE, RESOURCE_DUMMY_BLUE_NAME, task); + } + + /** + * Disable assignment of Blue Titanic role. + * The weak mapping should kick in and return black pearl back. + * MID-4236 + */ + @Test + public void test112DisableBlueTitanicAssignment() throws Exception { + final String TEST_NAME = "test112DisableBlueTitanicAssignment"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + AssignmentType titanicAssignment = getAssignment(userBefore, ROLE_BLUE_TITANIC_OID); + ItemPath assignmentStatusPath = new ItemPath( + new NameItemPathSegment(FocusType.F_ASSIGNMENT), + new IdItemPathSegment(titanicAssignment.getId()), + new NameItemPathSegment(AssignmentType.F_ACTIVATION), + new NameItemPathSegment(ActivationType.F_ADMINISTRATIVE_STATUS)); + + // WHEN + modifyUserReplace(USER_JACK_OID, assignmentStatusPath, task, result, ActivationStatusType.DISABLED); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userAfter, USER_JACK_FULL_NAME, SHIP_BLACK_PEARL, RESOURCE_DUMMY_BLUE_NAME, task); + } + + /** + * MID-4236 + */ + @Test + public void test113Recompute() throws Exception { + final String TEST_NAME = "test113Recompute"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + recomputeUser(USER_JACK_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userAfter, USER_JACK_FULL_NAME, SHIP_BLACK_PEARL, RESOURCE_DUMMY_BLUE_NAME, task); + } + + /** + * MID-4236 + */ + @Test + public void test114Reconcile() throws Exception { + final String TEST_NAME = "test114Reconcile"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + reconcileUser(USER_JACK_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userAfter, USER_JACK_FULL_NAME, SHIP_BLACK_PEARL, RESOURCE_DUMMY_BLUE_NAME, task); + } + + /** + * Re-enable assignment of Blue Titanic role. + * MID-4236 + */ + @Test + public void test115EnableBlueTitanicAssignment() throws Exception { + final String TEST_NAME = "test115EnableBlueTitanicAssignment"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + AssignmentType titanicAssignment = getAssignment(userBefore, ROLE_BLUE_TITANIC_OID); + ItemPath assignmentStatusPath = new ItemPath( + new NameItemPathSegment(FocusType.F_ASSIGNMENT), + new IdItemPathSegment(titanicAssignment.getId()), + new NameItemPathSegment(AssignmentType.F_ACTIVATION), + new NameItemPathSegment(ActivationType.F_ADMINISTRATIVE_STATUS)); + + // WHEN + modifyUserReplace(USER_JACK_OID, assignmentStatusPath, task, result, ActivationStatusType.ENABLED); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userAfter, USER_JACK_FULL_NAME, ROLE_TITANIC_SHIP_VALUE, RESOURCE_DUMMY_BLUE_NAME, task); + } + + @Test + public void test118UnassignBlueTitanic() throws Exception { + final String TEST_NAME = "test118UnassignBlueTitanic"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + AssignmentType titanicAssignment = getAssignment(userBefore, ROLE_BLUE_TITANIC_OID); + + // WHEN + unassign(UserType.class, USER_JACK_OID, titanicAssignment, null, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertAccountShip(userAfter, USER_JACK_FULL_NAME, SHIP_BLACK_PEARL, RESOURCE_DUMMY_BLUE_NAME, task); + } + + /** + * Assign Blue Poetry role. This role has strong mapping to blue resource + * quote attribute. The weak mapping on blue resource should NOT be applied. + * This is similar to Blue Titanic, but quote attribute is non-tolerant. + * MID-4236 + */ + @Test + public void test120AssignBluePoetry() throws Exception { + final String TEST_NAME = "test120AssignBluePoetry"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, + getQuote(USER_JACK_DESCRIPTION, CAPTAIN_JACK_FULL_NAME)); + + // WHEN + assignRole(USER_JACK_OID, ROLE_BLUE_POETRY_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, ROLE_POETRY_QUOTE_VALUE); + } + + @Test + public void test121Recompute() throws Exception { + final String TEST_NAME = "test121Recompute"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + recomputeUser(USER_JACK_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, ROLE_POETRY_QUOTE_VALUE); + } + + /** + * MID-4236 + */ + @Test + public void test122DisableBlueTitanicAssignment() throws Exception { + final String TEST_NAME = "test122DisableBlueTitanicAssignment"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + AssignmentType poetryAssignment = getAssignment(userBefore, ROLE_BLUE_POETRY_OID); + ItemPath assignmentStatusPath = new ItemPath( + new NameItemPathSegment(FocusType.F_ASSIGNMENT), + new IdItemPathSegment(poetryAssignment.getId()), + new NameItemPathSegment(AssignmentType.F_ACTIVATION), + new NameItemPathSegment(ActivationType.F_ADMINISTRATIVE_STATUS)); + + // WHEN + modifyUserReplace(USER_JACK_OID, assignmentStatusPath, task, result, ActivationStatusType.DISABLED); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, + getQuote(USER_JACK_DESCRIPTION, CAPTAIN_JACK_FULL_NAME)); + } + + /** + * MID-4236 + */ + @Test + public void test123Recompute() throws Exception { + final String TEST_NAME = "test123Recompute"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + recomputeUser(USER_JACK_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, + getQuote(USER_JACK_DESCRIPTION, CAPTAIN_JACK_FULL_NAME)); + } + + /** + * MID-4236 + */ + @Test + public void test124Reconcile() throws Exception { + final String TEST_NAME = "test124Reconcile"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + reconcileUser(USER_JACK_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, + getQuote(USER_JACK_DESCRIPTION, CAPTAIN_JACK_FULL_NAME)); + } + + /** + * Re-enable assignment of Blue Poetry role. + * MID-4236 + */ + @Test + public void test125EnableBluePoetryAssignment() throws Exception { + final String TEST_NAME = "test125EnableBluePoetryAssignment"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + AssignmentType poetryAssignment = getAssignment(userBefore, ROLE_BLUE_POETRY_OID); + ItemPath assignmentStatusPath = new ItemPath( + new NameItemPathSegment(FocusType.F_ASSIGNMENT), + new IdItemPathSegment(poetryAssignment.getId()), + new NameItemPathSegment(AssignmentType.F_ACTIVATION), + new NameItemPathSegment(ActivationType.F_ADMINISTRATIVE_STATUS)); + + // WHEN + modifyUserReplace(USER_JACK_OID, assignmentStatusPath, task, result, ActivationStatusType.ENABLED); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, ROLE_POETRY_QUOTE_VALUE); + } + + @Test + public void test128UnassignBluePoetry() throws Exception { + final String TEST_NAME = "test128UnassignBluePoetry"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + AssignmentType poetryAssignment = getAssignment(userBefore, ROLE_BLUE_POETRY_OID); + + // WHEN + unassign(UserType.class, USER_JACK_OID, poetryAssignment, null, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); + + assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, + getQuote(USER_JACK_DESCRIPTION, CAPTAIN_JACK_FULL_NAME)); + assertAccountShip(userAfter, USER_JACK_FULL_NAME, SHIP_BLACK_PEARL, RESOURCE_DUMMY_BLUE_NAME, task); + } + + @Test + public void test129ModifyUserUnassignAccountBlue() throws Exception { + final String TEST_NAME = "test129ModifyUserUnassignAccountBlue"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + dummyAuditService.clear(); + + Collection> deltas = new ArrayList>(); + ObjectDelta userDelta = createAccountAssignmentUserDelta(USER_JACK_OID, RESOURCE_DUMMY_BLUE_OID, null, false); + userDelta.addModificationReplaceProperty(UserType.F_FULL_NAME, PrismTestUtil.createPolyString(USER_JACK_FULL_NAME)); + userDelta.addModificationReplaceProperty(UserType.F_ORGANIZATIONAL_UNIT); + deltas.add(userDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + assertUserJack(userJack); + // Check accountRef + assertUserNoAccountRefs(userJack); + + // Check if dummy resource account is gone + assertNoDummyAccount("jack"); + + // Check audit + display("Audit", dummyAuditService); + dummyAuditService.assertSimpleRecordSanity(); + dummyAuditService.assertRecords(2); + dummyAuditService.assertAnyRequestDeltas(); + dummyAuditService.assertExecutionDeltas(3); + dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); + dummyAuditService.assertHasDelta(ChangeType.DELETE, ShadowType.class); + dummyAuditService.assertExecutionSuccess(); + } + + + @Test + public void test140AssignCobaltAccount() throws Exception { + final String TEST_NAME = "test140AssignCobaltAccount"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + assertUserJack(userBefore); + assertLinks(userBefore, 0); + assertNoDummyAccount(RESOURCE_DUMMY_BLUE_NAME, USER_JACK_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_COBALT_NAME, USER_JACK_USERNAME); + + // WHEN + assignAccount(USER_JACK_OID, RESOURCE_DUMMY_COBALT_OID, null, task, result); + + // THEN + assertSuccess(result); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); + assertLinks(userAfter, 1); + + assertDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_FULL_NAME, true); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, USER_JACK_LOCALITY); + } + + /** + * Destroy the value of account location attribute. Recompute should fix it. + * This is a "control group" for MID-4236 + */ + @Test + public void test141DestroyAndRecompute() throws Exception { + final String TEST_NAME = "test141DestroyAndRecompute"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + DummyAccount dummyAccountBefore = getDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + dummyAccountBefore.replaceAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, + "Wrongland"); + display("Account before", dummyAccountBefore); + + // WHEN + recomputeUser(USER_JACK_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); + + DummyAccount dummyAccountAfter = assertDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_FULL_NAME, true); + display("Account after", dummyAccountAfter); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, USER_JACK_LOCALITY); + } + + /** + * Destroy the value of account location attribute. Reconcile should fix it. + * This is a "control group" for MID-4236 + */ + @Test + public void test142DestroyAndReconcile() throws Exception { + final String TEST_NAME = "test142DestroyAndReconcile"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + DummyAccount dummyAccountBefore = getDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + dummyAccountBefore.replaceAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, + "Wrongland"); + display("Account before", dummyAccountBefore); + + // WHEN + reconcileUser(USER_JACK_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); + + DummyAccount dummyAccountAfter = assertDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_FULL_NAME, true); + display("Account after", dummyAccountAfter); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, USER_JACK_LOCALITY); + } + + /** + * Destroy the value of account location attribute. Recompute should fix it. + * This is a "control group" for MID-4236 + */ + @Test + public void test143ClearAndRecompute() throws Exception { + final String TEST_NAME = "test143ClearAndRecompute"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + DummyAccount dummyAccountBefore = getDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + dummyAccountBefore.replaceAttributeValues(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME + /* no value */); + display("Account before", dummyAccountBefore); + + // WHEN + recomputeUser(USER_JACK_OID, task, result); + + // THEN + assertSuccess(result); + + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); + + DummyAccount dummyAccountAfter = assertDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_FULL_NAME, true); + display("Account after", dummyAccountAfter); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, USER_JACK_LOCALITY); + } + + + /** + * Assign Cobalt role. This role has strong mapping to cobalt resource + * location attribute. The weak mapping on cobalt resource should NOT be applied. + * This is similar to Blue Titanic, but location attribute is non-tolerant and single-value. + * MID-4236 + */ + @Test + public void test150AssignCobaltNeverland() throws Exception { + final String TEST_NAME = "test150AssignCobaltNeverland"; displayTestTitle(TEST_NAME); // GIVEN Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); - dummyAuditService.clear(); // WHEN - displayWhen(TEST_NAME); - modifyUserReplace(USER_JACK_OID, UserType.F_FULL_NAME, task, result, - PrismTestUtil.createPolyString("Cpt. Jack Sparrow")); + assignRole(USER_JACK_OID, ROLE_COBALT_NEVERLAND_OID, task, result); // THEN - displayThen(TEST_NAME); assertSuccess(result); - PrismObject userJack = getUser(USER_JACK_OID); - display("User after change execution", userJack); - assertUserJack(userJack, "Cpt. Jack Sparrow", "Jack", "Sparrow"); - - assertAccountShip(userJack, "Jack Sparrow", null, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME), task); - - // 'quote' is non-tolerant attribute. As the source of the mapping is changed, the current - // value is no longer legal and the reconciliation has to remove it. - assertNoDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, USER_JACK_USERNAME, - DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); + assertLinks(userAfter, 1); - // Check audit - display("Audit", dummyAuditService); - dummyAuditService.assertSimpleRecordSanity(); - dummyAuditService.assertRecords(2); - dummyAuditService.assertAnyRequestDeltas(); - dummyAuditService.assertExecutionDeltas(2); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); - dummyAuditService.assertExecutionSuccess(); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, ROLE_COBALT_NEVERLAND_VALUE); } - + @Test - public void test102ModifyUserFullNameRecon() throws Exception { - final String TEST_NAME = "test102ModifyUserFullNameRecon"; + public void test151Recompute() throws Exception { + final String TEST_NAME = "test151Recompute"; displayTestTitle(TEST_NAME); // GIVEN Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); - dummyAuditService.clear(); // WHEN - displayWhen(TEST_NAME); - ObjectDelta objectDelta = createModifyUserReplaceDelta(USER_JACK_OID, UserType.F_FULL_NAME, - PrismTestUtil.createPolyString("Captain Jack Sparrow")); - Collection> deltas = MiscSchemaUtil.createCollection(objectDelta); - modelService.executeChanges(deltas, ModelExecuteOptions.createReconcile(), task, result); + recomputeUser(USER_JACK_OID, task, result); // THEN - displayThen(TEST_NAME); assertSuccess(result); - PrismObject userJack = getUser(USER_JACK_OID); - display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); - assertAccountShip(userJack, "Jack Sparrow", null, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME), task); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, ROLE_COBALT_NEVERLAND_VALUE); + } + + /** + * MID-4236 + */ + @Test + public void test152DisableCobalNeverlandAssignment() throws Exception { + final String TEST_NAME = "test152DisableCobalNeverlandAssignment"; + displayTestTitle(TEST_NAME); - // The quote attribute was empty before this operation. So the weak mapping kicks in - // and sets a new value. - assertDummyAccountAttribute(RESOURCE_DUMMY_BLUE_NAME, USER_JACK_USERNAME, - DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME, "Where's the rum? -- Captain Jack Sparrow"); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + AssignmentType roleAssignment = getAssignment(userBefore, ROLE_COBALT_NEVERLAND_OID); + ItemPath assignmentStatusPath = new ItemPath( + new NameItemPathSegment(FocusType.F_ASSIGNMENT), + new IdItemPathSegment(roleAssignment.getId()), + new NameItemPathSegment(AssignmentType.F_ACTIVATION), + new NameItemPathSegment(ActivationType.F_ADMINISTRATIVE_STATUS)); + // WHEN + modifyUserReplace(USER_JACK_OID, assignmentStatusPath, task, result, ActivationStatusType.DISABLED); - // Check audit - display("Audit", dummyAuditService); - dummyAuditService.assertSimpleRecordSanity(); - dummyAuditService.assertRecords(2); - dummyAuditService.assertAnyRequestDeltas(); - dummyAuditService.assertExecutionDeltas(2); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); - dummyAuditService.assertExecutionSuccess(); - } + // THEN + assertSuccess(result); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); + + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, USER_JACK_LOCALITY); + } + + /** + * MID-4236 + */ @Test - public void test104ModifyUserOrganizationalUnit() throws Exception { - final String TEST_NAME = "test104ModifyUserOrganizationalUnit"; + public void test153Recompute() throws Exception { + final String TEST_NAME = "test153Recompute"; displayTestTitle(TEST_NAME); // GIVEN Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); - dummyAuditService.clear(); // WHEN - modifyUserReplace(USER_JACK_OID, UserType.F_ORGANIZATIONAL_UNIT, task, result, - PrismTestUtil.createPolyString("Black Pearl")); + recomputeUser(USER_JACK_OID, task, result); // THEN assertSuccess(result); - PrismObject userJack = getUser(USER_JACK_OID); - display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); - - assertAccountShip(userJack, "Jack Sparrow", "Black Pearl", getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME), task); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); - // Check audit - display("Audit", dummyAuditService); - dummyAuditService.assertSimpleRecordSanity(); - dummyAuditService.assertRecords(2); - dummyAuditService.assertAnyRequestDeltas(); - dummyAuditService.assertExecutionDeltas(2); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); - dummyAuditService.assertExecutionSuccess(); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, USER_JACK_LOCALITY); } - + + /** + * MID-4236 + */ @Test - public void test105ModifyAccountShip() throws Exception { - final String TEST_NAME = "test105ModifyAccountShip"; + public void test154Reconcile() throws Exception { + final String TEST_NAME = "test154Reconcile"; displayTestTitle(TEST_NAME); // GIVEN Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); - dummyAuditService.clear(); - - PrismObject userJack = getUser(USER_JACK_OID); - String accountOid = getSingleLinkOid(userJack); - - Collection> deltas = new ArrayList>(); - ObjectDelta accountDelta = ObjectDelta.createModificationReplaceProperty(ShadowType.class, - accountOid, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME).getAttributePath(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME), - prismContext, "Flying Dutchman"); - deltas.add(accountDelta); // WHEN - modelService.executeChanges(deltas, null, task, result); + reconcileUser(USER_JACK_OID, task, result); // THEN assertSuccess(result); - userJack = getUser(USER_JACK_OID); - display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); - - assertAccountShip(userJack, "Jack Sparrow", "Flying Dutchman", getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME), task); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); - // Check audit - display("Audit", dummyAuditService); - dummyAuditService.assertSimpleRecordSanity(); - dummyAuditService.assertRecords(2); - dummyAuditService.assertAnyRequestDeltas(); - dummyAuditService.assertExecutionDeltas(2); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); - dummyAuditService.assertExecutionSuccess(); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, USER_JACK_LOCALITY); } - + /** - * There is a weak mapping for ship attribute. - * Therefore try to remove the value. The weak mapping should be applied. + * Destroy the value of account location attribute. Recompute should fix it. + * MID-4236 (this is where it is really reproduced) */ @Test - public void test106ModifyAccountShipReplaceEmpty() throws Exception { - final String TEST_NAME = "test106ModifyAccountShipReplaceEmpty"; + public void test155DestroyAndRecompute() throws Exception { + final String TEST_NAME = "test155DestroyAndRecompute"; displayTestTitle(TEST_NAME); // GIVEN Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); - dummyAuditService.clear(); - - PrismObject userJack = getUser(USER_JACK_OID); - String accountOid = getSingleLinkOid(userJack); - - Collection> deltas = new ArrayList>(); - ObjectDelta accountDelta = ObjectDelta.createModificationReplaceProperty(ShadowType.class, - accountOid, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME).getAttributePath(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME), - prismContext); - deltas.add(accountDelta); + + DummyAccount dummyAccountBefore = getDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + dummyAccountBefore.replaceAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, + "Wrongland"); + display("Account before", dummyAccountBefore); // WHEN - modelService.executeChanges(deltas, null, task, result); + recomputeUser(USER_JACK_OID, task, result); // THEN assertSuccess(result); - userJack = getUser(USER_JACK_OID); - display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); - - assertAccountShip(userJack, "Jack Sparrow", "Black Pearl", getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME), task); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); - // Check audit - display("Audit", dummyAuditService); - dummyAuditService.assertSimpleRecordSanity(); - dummyAuditService.assertRecords(2); - dummyAuditService.assertAnyRequestDeltas(); - dummyAuditService.assertExecutionDeltas(2); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); - dummyAuditService.assertExecutionSuccess(); + DummyAccount dummyAccountAfter = assertDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_FULL_NAME, true); + display("Account after", dummyAccountAfter); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, USER_JACK_LOCALITY); } - + + /** + * Destroy the value of account location attribute. Recompute should fix it. + * MID-4236 (this is where it is really reproduced) + */ @Test - public void test107ModifyAccountShipAgain() throws Exception { - final String TEST_NAME = "test107ModifyAccountShipAgain"; + public void test156ClearAndRecompute() throws Exception { + final String TEST_NAME = "test156ClearAndRecompute"; displayTestTitle(TEST_NAME); // GIVEN Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); - dummyAuditService.clear(); - - PrismObject userJack = getUser(USER_JACK_OID); - String accountOid = getSingleLinkOid(userJack); - - Collection> deltas = new ArrayList>(); - ObjectDelta accountDelta = ObjectDelta.createModificationReplaceProperty(ShadowType.class, - accountOid, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME).getAttributePath(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME), - prismContext, "HMS Dauntless"); - deltas.add(accountDelta); + + DummyAccount dummyAccountBefore = getDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + dummyAccountBefore.replaceAttributeValues(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME + /* no value */); + display("Account before", dummyAccountBefore); // WHEN - modelService.executeChanges(deltas, null, task, result); + recomputeUser(USER_JACK_OID, task, result); // THEN assertSuccess(result); - userJack = getUser(USER_JACK_OID); - display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); - - assertAccountShip(userJack, "Jack Sparrow", "HMS Dauntless", getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME), task); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); - // Check audit - display("Audit", dummyAuditService); - dummyAuditService.assertSimpleRecordSanity(); - dummyAuditService.assertRecords(2); - dummyAuditService.assertAnyRequestDeltas(); - dummyAuditService.assertExecutionDeltas(2); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); - dummyAuditService.assertExecutionSuccess(); + DummyAccount dummyAccountAfter = assertDummyAccount(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_FULL_NAME, true); + display("Account after", dummyAccountAfter); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, USER_JACK_LOCALITY); } - + /** - * There is a weak mapping for ship attribute. - * Therefore try to remove the value. The weak mapping should be applied. + * Re-enable assignment of Blue Poetry role. + * MID-4236 */ @Test - public void test108ModifyAccountShipDelete() throws Exception { - final String TEST_NAME = "test108ModifyAccountShipDelete"; + public void test157EnableCobaltNeverlandAssignment() throws Exception { + final String TEST_NAME = "test157EnableCobaltNeverlandAssignment"; displayTestTitle(TEST_NAME); // GIVEN Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); - dummyAuditService.clear(); - - PrismObject userJack = getUser(USER_JACK_OID); - String accountOid = getSingleLinkOid(userJack); - - Collection> deltas = new ArrayList>(); - ObjectDelta accountDelta = ObjectDelta.createModificationDeleteProperty(ShadowType.class, - accountOid, getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME).getAttributePath(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME), - prismContext, "HMS Dauntless"); - deltas.add(accountDelta); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + AssignmentType roleAssignment = getAssignment(userBefore, ROLE_COBALT_NEVERLAND_OID); + ItemPath assignmentStatusPath = new ItemPath( + new NameItemPathSegment(FocusType.F_ASSIGNMENT), + new IdItemPathSegment(roleAssignment.getId()), + new NameItemPathSegment(AssignmentType.F_ACTIVATION), + new NameItemPathSegment(ActivationType.F_ADMINISTRATIVE_STATUS)); // WHEN - modelService.executeChanges(deltas, null, task, result); + modifyUserReplace(USER_JACK_OID, assignmentStatusPath, task, result, ActivationStatusType.ENABLED); // THEN assertSuccess(result); - userJack = getUser(USER_JACK_OID); - display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); - - assertAccountShip(userJack, "Jack Sparrow", "Black Pearl", getDummyResourceController(RESOURCE_DUMMY_BLUE_NAME), task); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); - // Check audit - display("Audit", dummyAuditService); - dummyAuditService.assertSimpleRecordSanity(); - dummyAuditService.assertRecords(2); - dummyAuditService.assertAnyRequestDeltas(); - dummyAuditService.assertExecutionDeltas(2); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, ShadowType.class); - dummyAuditService.assertExecutionSuccess(); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, ROLE_COBALT_NEVERLAND_VALUE); } @Test - public void test109ModifyUserUnassignAccountBlue() throws Exception { - final String TEST_NAME = "test109ModifyUserUnassignAccountBlue"; + public void test158UnassignCobaltNeverland() throws Exception { + final String TEST_NAME = "test158UnassignCobaltNeverland"; displayTestTitle(TEST_NAME); // GIVEN Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); - dummyAuditService.clear(); - - Collection> deltas = new ArrayList>(); - ObjectDelta userDelta = createAccountAssignmentUserDelta(USER_JACK_OID, RESOURCE_DUMMY_BLUE_OID, null, false); - userDelta.addModificationReplaceProperty(UserType.F_FULL_NAME, PrismTestUtil.createPolyString("Jack Sparrow")); - userDelta.addModificationReplaceProperty(UserType.F_ORGANIZATIONAL_UNIT); - deltas.add(userDelta); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + AssignmentType roleAssignment = getAssignment(userBefore, ROLE_COBALT_NEVERLAND_OID); // WHEN - modelService.executeChanges(deltas, null, task, result); + unassign(UserType.class, USER_JACK_OID, roleAssignment, null, task, result); // THEN - assertSuccess(result); - - PrismObject userJack = getUser(USER_JACK_OID); - assertUserJack(userJack, "Jack Sparrow", "Jack", "Sparrow"); - // Check accountRef - assertUserNoAccountRefs(userJack); + assertSuccess(result); - // Check if dummy resource account is gone - assertNoDummyAccount("jack"); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); - // Check audit - display("Audit", dummyAuditService); - dummyAuditService.assertSimpleRecordSanity(); - dummyAuditService.assertRecords(2); - dummyAuditService.assertAnyRequestDeltas(); - dummyAuditService.assertExecutionDeltas(3); - dummyAuditService.assertHasDelta(ChangeType.MODIFY, UserType.class); - dummyAuditService.assertHasDelta(ChangeType.DELETE, ShadowType.class); - dummyAuditService.assertExecutionSuccess(); + assertDummyAccountAttribute(RESOURCE_DUMMY_COBALT_NAME, ACCOUNT_JACK_DUMMY_USERNAME, + DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME, USER_JACK_LOCALITY); } + @Test + public void test159UnassignCobaltAccount() throws Exception { + final String TEST_NAME = "test159UnassignCobaltAccount"; + displayTestTitle(TEST_NAME); + + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + + // WHEN + unassignAccount(USER_JACK_OID, RESOURCE_DUMMY_COBALT_OID, null, task, result); + + // THEN + assertSuccess(result); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertUserJack(userAfter); + assertLinks(userAfter, 0); + + assertNoDummyAccount(RESOURCE_DUMMY_BLUE_NAME, USER_JACK_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_COBALT_NAME, USER_JACK_USERNAME); + } /** * Red dummy has STRONG mappings. */ @Test - public void test120ModifyUserAssignAccountDummyRed() throws Exception { - final String TEST_NAME = "test120ModifyUserAssignAccountDummyRed"; + public void test160ModifyUserAssignAccountDummyRed() throws Exception { + final String TEST_NAME = "test160ModifyUserAssignAccountDummyRed"; displayTestTitle(TEST_NAME); // GIVEN @@ -558,7 +1430,7 @@ public void test120ModifyUserAssignAccountDummyRed() throws Exception { assertAccountShadowModel(accountModel, accountOid, "jack", getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); // Check account in dummy resource - assertDummyAccount(RESOURCE_DUMMY_RED_NAME, "jack", "Jack Sparrow", true); + assertDummyAccount(RESOURCE_DUMMY_RED_NAME, "jack", USER_JACK_FULL_NAME, true); assertDummyAccountAttribute(RESOURCE_DUMMY_RED_NAME, ACCOUNT_JACK_DUMMY_USERNAME, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "mouth", "pistol"); @@ -577,8 +1449,8 @@ public void test120ModifyUserAssignAccountDummyRed() throws Exception { } @Test - public void test121ModifyUserFullName() throws Exception { - final String TEST_NAME = "test121ModifyUserFullName"; + public void test161ModifyUserFullName() throws Exception { + final String TEST_NAME = "test161ModifyUserFullName"; displayTestTitle(TEST_NAME); // GIVEN @@ -588,16 +1460,16 @@ public void test121ModifyUserFullName() throws Exception { // WHEN modifyUserReplace(USER_JACK_OID, UserType.F_FULL_NAME, task, result, - PrismTestUtil.createPolyString("Captain Jack Sparrow")); + PrismTestUtil.createPolyString(CAPTAIN_JACK_FULL_NAME)); // THEN assertSuccess(result); PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); - assertAccountShip(userJack, "Captain Jack Sparrow", null, getDummyResourceController(RESOURCE_DUMMY_RED_NAME), task); + assertAccountShip(userJack, CAPTAIN_JACK_FULL_NAME, null, RESOURCE_DUMMY_RED_NAME, task); // Check audit display("Audit", dummyAuditService); @@ -611,8 +1483,8 @@ public void test121ModifyUserFullName() throws Exception { } @Test - public void test122ModifyUserOrganizationalUnit() throws Exception { - final String TEST_NAME = "test122ModifyUserOrganizationalUnit"; + public void test162ModifyUserOrganizationalUnit() throws Exception { + final String TEST_NAME = "test162ModifyUserOrganizationalUnit"; displayTestTitle(TEST_NAME); // GIVEN @@ -629,9 +1501,9 @@ public void test122ModifyUserOrganizationalUnit() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); - assertAccountShip(userJack, "Captain Jack Sparrow", "Black Pearl", getDummyResourceController(RESOURCE_DUMMY_RED_NAME), task); + assertAccountShip(userJack, CAPTAIN_JACK_FULL_NAME, "Black Pearl", RESOURCE_DUMMY_RED_NAME, task); // Check audit display("Audit", dummyAuditService); @@ -645,8 +1517,8 @@ public void test122ModifyUserOrganizationalUnit() throws Exception { } @Test - public void test123ModifyAccountShip() throws Exception { - final String TEST_NAME = "test123ModifyAccountShip"; + public void test163ModifyAccountShip() throws Exception { + final String TEST_NAME = "test163ModifyAccountShip"; displayTestTitle(TEST_NAME); // GIVEN @@ -679,9 +1551,9 @@ accountOid, getDummyResourceController(RESOURCE_DUMMY_RED_NAME).getAttributePath userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); - assertAccountShip(userJack, "Captain Jack Sparrow", "Black Pearl", getDummyResourceController(RESOURCE_DUMMY_RED_NAME), task); + assertAccountShip(userJack, CAPTAIN_JACK_FULL_NAME, "Black Pearl", RESOURCE_DUMMY_RED_NAME, task); // Check audit display("Audit", dummyAuditService); @@ -698,8 +1570,8 @@ accountOid, getDummyResourceController(RESOURCE_DUMMY_RED_NAME).getAttributePath * (replace with the same value that was already there). */ @Test - public void test124ModifyAccountShipReplaceEmpty() throws Exception { - final String TEST_NAME = "test124ModifyAccountShipReplaceEmpty"; + public void test164ModifyAccountShipReplaceEmpty() throws Exception { + final String TEST_NAME = "test164ModifyAccountShipReplaceEmpty"; displayTestTitle(TEST_NAME); // GIVEN @@ -724,9 +1596,9 @@ accountOid, getDummyResourceController(RESOURCE_DUMMY_RED_NAME).getAttributePath userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); - assertAccountShip(userJack, "Captain Jack Sparrow", "Black Pearl", getDummyResourceController(RESOURCE_DUMMY_RED_NAME), task); + assertAccountShip(userJack, CAPTAIN_JACK_FULL_NAME, "Black Pearl", RESOURCE_DUMMY_RED_NAME, task); // Check audit display("Audit", dummyAuditService); @@ -740,8 +1612,8 @@ accountOid, getDummyResourceController(RESOURCE_DUMMY_RED_NAME).getAttributePath } @Test - public void test126ModifyAccountShipDelete() throws Exception { - final String TEST_NAME = "test126ModifyAccountShipDelete"; + public void test166ModifyAccountShipDelete() throws Exception { + final String TEST_NAME = "test166ModifyAccountShipDelete"; displayTestTitle(TEST_NAME); // GIVEN @@ -774,9 +1646,9 @@ accountOid, getDummyResourceController(RESOURCE_DUMMY_RED_NAME).getAttributePath userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); - assertAccountShip(userJack, "Captain Jack Sparrow", "Black Pearl", getDummyResourceController(RESOURCE_DUMMY_RED_NAME), task); + assertAccountShip(userJack, CAPTAIN_JACK_FULL_NAME, "Black Pearl", RESOURCE_DUMMY_RED_NAME, task); // Check audit display("Audit", dummyAuditService); @@ -793,8 +1665,8 @@ accountOid, getDummyResourceController(RESOURCE_DUMMY_RED_NAME).getAttributePath * Reconciliation should be triggered. */ @Test - public void test128ModifyUserOrganization() throws Exception { - final String TEST_NAME = "test128ModifyUserOrganization"; + public void test168ModifyUserOrganization() throws Exception { + final String TEST_NAME = "test168ModifyUserOrganization"; displayTestTitle(TEST_NAME); // GIVEN @@ -811,9 +1683,9 @@ public void test128ModifyUserOrganization() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); - assertAccountShip(userJack, "Captain Jack Sparrow", "Brethren of the Coast / Black Pearl", getDummyResourceController(RESOURCE_DUMMY_RED_NAME), task); + assertAccountShip(userJack, CAPTAIN_JACK_FULL_NAME, "Brethren of the Coast / Black Pearl", RESOURCE_DUMMY_RED_NAME, task); // Check audit display("Audit", dummyAuditService); @@ -830,8 +1702,8 @@ public void test128ModifyUserOrganization() throws Exception { * Note: red resource disables account on unsassign, does NOT delete it */ @Test - public void test138ModifyUserUnassignAccountRed() throws Exception { - final String TEST_NAME = "test138ModifyUserUnassignAccountRed"; + public void test178ModifyUserUnassignAccountRed() throws Exception { + final String TEST_NAME = "test178ModifyUserUnassignAccountRed"; displayTestTitle(TEST_NAME); // GIVEN @@ -854,7 +1726,7 @@ public void test138ModifyUserUnassignAccountRed() throws Exception { XMLGregorianCalendar end = clock.currentTimeXMLGregorianCalendar(); PrismObject userJack = getUser(USER_JACK_OID); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); String accountRedOid = getLinkRefOid(userJack, RESOURCE_DUMMY_RED_OID); PrismObject accountRed = getShadowModel(accountRedOid); @@ -868,7 +1740,7 @@ public void test138ModifyUserUnassignAccountRed() throws Exception { XMLGregorianCalendar disableTimestamp = accountRed.asObjectable().getActivation().getDisableTimestamp(); TestUtil.assertBetween("Wrong disableTimestamp", start, end, disableTimestamp); - assertAccountShip(userJack, "Captain Jack Sparrow", "Brethren of the Coast / Black Pearl", false, getDummyResourceController(RESOURCE_DUMMY_RED_NAME), task); + assertAccountShip(userJack, CAPTAIN_JACK_FULL_NAME, "Brethren of the Coast / Black Pearl", false, getDummyResourceController(RESOURCE_DUMMY_RED_NAME), task); // Check if dummy resource account is gone assertNoDummyAccount("jack"); @@ -889,8 +1761,8 @@ public void test138ModifyUserUnassignAccountRed() throws Exception { * So let's delete the account explicitly to make room for the following tests */ @Test - public void test139DeleteAccountRed() throws Exception { - final String TEST_NAME = "test139DeleteAccountRed"; + public void test179DeleteAccountRed() throws Exception { + final String TEST_NAME = "test179DeleteAccountRed"; displayTestTitle(TEST_NAME); // GIVEN @@ -912,7 +1784,7 @@ public void test139DeleteAccountRed() throws Exception { assertSuccess(result); userJack = getUser(USER_JACK_OID); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); assertNoLinkedAccount(userJack); // Check if dummy resource accounts are gone @@ -935,8 +1807,8 @@ public void test139DeleteAccountRed() throws Exception { * Default dummy has combination of NORMAL, WEAK and STRONG mappings. */ @Test - public void test140ModifyUserAssignAccountDummyDefault() throws Exception { - final String TEST_NAME = "test140ModifyUserAssignAccountDummyDefault"; + public void test180ModifyUserAssignAccountDummyDefault() throws Exception { + final String TEST_NAME = "test180ModifyUserAssignAccountDummyDefault"; displayTestTitle(TEST_NAME); // GIVEN @@ -947,7 +1819,7 @@ public void test140ModifyUserAssignAccountDummyDefault() throws Exception { Collection> deltas = new ArrayList>(); ObjectDelta userDelta = createAccountAssignmentUserDelta(USER_JACK_OID, RESOURCE_DUMMY_OID, null, true); - userDelta.addModificationReplaceProperty(UserType.F_FULL_NAME, PrismTestUtil.createPolyString("Jack Sparrow")); + userDelta.addModificationReplaceProperty(UserType.F_FULL_NAME, PrismTestUtil.createPolyString(USER_JACK_FULL_NAME)); userDelta.addModificationReplaceProperty(UserType.F_ORGANIZATIONAL_UNIT); deltas.add(userDelta); @@ -971,7 +1843,7 @@ public void test140ModifyUserAssignAccountDummyDefault() throws Exception { assertAccountShadowModel(accountModel, accountOid, "jack", getDummyResourceType()); // Check account in dummy resource - assertDummyAccount(null, "jack", "Jack Sparrow", true); + assertDummyAccount(null, "jack", USER_JACK_FULL_NAME, true); // Check audit display("Audit", dummyAuditService); @@ -988,8 +1860,8 @@ public void test140ModifyUserAssignAccountDummyDefault() throws Exception { * fullName mapping is NORMAL, the change should go through */ @Test - public void test141ModifyUserFullName() throws Exception { - final String TEST_NAME = "test141ModifyUserFullName"; + public void test181ModifyUserFullName() throws Exception { + final String TEST_NAME = "test181ModifyUserFullName"; displayTestTitle(TEST_NAME); // GIVEN @@ -999,16 +1871,16 @@ public void test141ModifyUserFullName() throws Exception { // WHEN modifyUserReplace(USER_JACK_OID, UserType.F_FULL_NAME, task, result, - PrismTestUtil.createPolyString("Captain Jack Sparrow")); + PrismTestUtil.createPolyString(CAPTAIN_JACK_FULL_NAME)); // THEN assertSuccess(result); PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow"); - assertAccountShip(userJack, "Captain Jack Sparrow", null, dummyResourceCtl, task); + assertAccountShip(userJack, CAPTAIN_JACK_FULL_NAME, null, null, task); // Check audit display("Audit", dummyAuditService); @@ -1025,8 +1897,8 @@ public void test141ModifyUserFullName() throws Exception { * location mapping is STRONG */ @Test - public void test142ModifyUserLocality() throws Exception { - final String TEST_NAME = "test142ModifyUserLocality"; + public void test182ModifyUserLocality() throws Exception { + final String TEST_NAME = "test182ModifyUserLocality"; displayTestTitle(TEST_NAME); // GIVEN @@ -1043,9 +1915,9 @@ public void test142ModifyUserLocality() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow", "Fountain of Youth"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow", "Fountain of Youth"); - assertAccountLocation(userJack, "Captain Jack Sparrow", "Fountain of Youth", dummyResourceCtl, task); + assertAccountLocation(userJack, CAPTAIN_JACK_FULL_NAME, "Fountain of Youth", dummyResourceCtl, task); // Check audit display("Audit", dummyAuditService); @@ -1059,8 +1931,8 @@ public void test142ModifyUserLocality() throws Exception { } @Test - public void test143ModifyAccountLocation() throws Exception { - final String TEST_NAME = "test143ModifyAccountLocation"; + public void test183ModifyAccountLocation() throws Exception { + final String TEST_NAME = "test183ModifyAccountLocation"; displayTestTitle(TEST_NAME); // GIVEN @@ -1093,9 +1965,9 @@ public void test143ModifyAccountLocation() throws Exception { userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow", "Fountain of Youth"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow", "Fountain of Youth"); - assertAccountLocation(userJack, "Captain Jack Sparrow", "Fountain of Youth", dummyResourceCtl, task); + assertAccountLocation(userJack, CAPTAIN_JACK_FULL_NAME, "Fountain of Youth", dummyResourceCtl, task); // Check audit display("Audit", dummyAuditService); @@ -1112,8 +1984,8 @@ public void test143ModifyAccountLocation() throws Exception { * (replace with the same value that was already there). */ @Test - public void test144ModifyAccountLocationReplaceEmpty() throws Exception { - final String TEST_NAME = "test144ModifyAccountLocationReplaceEmpty"; + public void test184ModifyAccountLocationReplaceEmpty() throws Exception { + final String TEST_NAME = "test184ModifyAccountLocationReplaceEmpty"; displayTestTitle(TEST_NAME); // GIVEN @@ -1138,9 +2010,9 @@ public void test144ModifyAccountLocationReplaceEmpty() throws Exception { userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow", "Fountain of Youth"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow", "Fountain of Youth"); - assertAccountLocation(userJack, "Captain Jack Sparrow", "Fountain of Youth", dummyResourceCtl, task); + assertAccountLocation(userJack, CAPTAIN_JACK_FULL_NAME, "Fountain of Youth", dummyResourceCtl, task); // Check audit display("Audit", dummyAuditService); @@ -1154,8 +2026,8 @@ public void test144ModifyAccountLocationReplaceEmpty() throws Exception { } @Test - public void test145ModifyAccountLocationDelete() throws Exception { - final String TEST_NAME = "test145ModifyAccountLocationDelete"; + public void test185ModifyAccountLocationDelete() throws Exception { + final String TEST_NAME = "test185ModifyAccountLocationDelete"; displayTestTitle(TEST_NAME); // GIVEN @@ -1188,9 +2060,9 @@ public void test145ModifyAccountLocationDelete() throws Exception { userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "Captain Jack Sparrow", "Jack", "Sparrow", "Fountain of Youth"); + assertUserJack(userJack, CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow", "Fountain of Youth"); - assertAccountLocation(userJack, "Captain Jack Sparrow", "Fountain of Youth", dummyResourceCtl, task); + assertAccountLocation(userJack, CAPTAIN_JACK_FULL_NAME, "Fountain of Youth", dummyResourceCtl, task); // Check audit display("Audit", dummyAuditService); @@ -1202,8 +2074,8 @@ public void test145ModifyAccountLocationDelete() throws Exception { } @Test - public void test148ModifyUserRename() throws Exception { - final String TEST_NAME = "test148ModifyUserRename"; + public void test188ModifyUserRename() throws Exception { + final String TEST_NAME = "test188ModifyUserRename"; displayTestTitle(TEST_NAME); // GIVEN @@ -1220,9 +2092,9 @@ public void test148ModifyUserRename() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); - assertUserJack(userJack, "renamedJack", "Captain Jack Sparrow", "Jack", "Sparrow", "Fountain of Youth"); + assertUserJack(userJack, "renamedJack", CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow", "Fountain of Youth"); - assertAccountRename(userJack, "renamedJack", "Captain Jack Sparrow", dummyResourceCtl, task); + assertAccountRename(userJack, "renamedJack", CAPTAIN_JACK_FULL_NAME, dummyResourceCtl, task); // Check audit display("Audit", dummyAuditService); @@ -1237,8 +2109,8 @@ public void test148ModifyUserRename() throws Exception { @Test - public void test149ModifyUserUnassignAccountDummy() throws Exception { - final String TEST_NAME = "test149ModifyUserUnassignAccountDummy"; + public void test189ModifyUserUnassignAccountDummy() throws Exception { + final String TEST_NAME = "test189ModifyUserUnassignAccountDummy"; displayTestTitle(TEST_NAME); // GIVEN @@ -1257,7 +2129,7 @@ public void test149ModifyUserUnassignAccountDummy() throws Exception { assertSuccess(result); PrismObject userJack = getUser(USER_JACK_OID); - assertUserJack(userJack, "renamedJack", "Captain Jack Sparrow", "Jack", "Sparrow", "Fountain of Youth"); + assertUserJack(userJack, "renamedJack", CAPTAIN_JACK_FULL_NAME, "Jack", "Sparrow", "Fountain of Youth"); // Check accountRef assertUserNoAccountRefs(userJack); @@ -1277,8 +2149,8 @@ public void test149ModifyUserUnassignAccountDummy() throws Exception { private void assertAccountShip(PrismObject userJack, String expectedFullName, String expectedShip, - DummyResourceContoller resourceCtl, Task task) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, SchemaViolationException, ConflictException, ExpressionEvaluationException { - assertAccount(userJack, expectedFullName, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, expectedShip, true, resourceCtl, task); + String dummyResourceName, Task task) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, SchemaViolationException, ConflictException, ExpressionEvaluationException { + assertAccount(userJack, expectedFullName, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, expectedShip, true, getDummyResourceController(dummyResourceName), task); } private void assertAccountShip(PrismObject userJack, String expectedFullName, String expectedShip, diff --git a/model/model-intest/src/test/resources/logback-test.xml b/model/model-intest/src/test/resources/logback-test.xml index d89984add7f..b922173ae7c 100644 --- a/model/model-intest/src/test/resources/logback-test.xml +++ b/model/model-intest/src/test/resources/logback-test.xml @@ -50,13 +50,12 @@ - - + - + @@ -64,13 +63,15 @@ + - + + @@ -78,9 +79,9 @@ - + - + diff --git a/model/model-intest/src/test/resources/mapping/resource-dummy-cobalt.xml b/model/model-intest/src/test/resources/mapping/resource-dummy-cobalt.xml new file mode 100644 index 00000000000..9b0bd8c0352 --- /dev/null +++ b/model/model-intest/src/test/resources/mapping/resource-dummy-cobalt.xml @@ -0,0 +1,279 @@ + + + + + + + + Dummy Resource Cobalt + + + + + connectorType + com.evolveum.icf.dummy.connector.DummyConnector + + + connectorVersion + 2.0 + + + + + + + + cobalt + true + incomplete + + + + + + + account + default + Default Account + true + ri:AccountObjectClass + + icfs:name + Username + false + + weak + + name + + + + + + + + $user/name + + + + + icfs:uid + UID + + + ri:fullname + Full Name + false + + weak + + $user/fullName + + + + + + + weak + + $user/fullName + + + + + ri:ship + Ship + false + + weak + + $user/organizationalUnit + + + + + + ri:location + Location + false + + false + weak + + + $user/locality + + + + + + ri:drink + false + + weak + + + uuid + + + + + + ri:quote + Quote + false + + weak + + $user/description + + + $user/fullName + + + + + + + + ri:gossip + true + + weak + + $configuration/name + + + + + 5 + + + + + weak + + + + + + + + weak + + + + + + + + weak + + + + + + + + + + weak + + + + + + + + + + + + true + + + c:name + + + + + + + linked + true + + + deleted + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink + + + + unlinked + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + + + unmatched + http://midpoint.evolveum.com/xml/ns/public/provisioning/channels-3#NoNsEnSe + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#inactivateShadow + + + + + + diff --git a/model/model-intest/src/test/resources/mapping/role-blue-poetry.xml b/model/model-intest/src/test/resources/mapping/role-blue-poetry.xml new file mode 100644 index 00000000000..b45559cb74d --- /dev/null +++ b/model/model-intest/src/test/resources/mapping/role-blue-poetry.xml @@ -0,0 +1,39 @@ + + + + Blue Poetry + + + + account + + ri:quote + + strong + + Oh freddled gruntbuggly + + + + + + diff --git a/model/model-intest/src/test/resources/mapping/role-blue-titanic.xml b/model/model-intest/src/test/resources/mapping/role-blue-titanic.xml new file mode 100644 index 00000000000..abf84fc48c4 --- /dev/null +++ b/model/model-intest/src/test/resources/mapping/role-blue-titanic.xml @@ -0,0 +1,39 @@ + + + + Blue Titanic + + + + account + + ri:ship + + strong + + Titanic + + + + + + diff --git a/model/model-intest/src/test/resources/mapping/role-cobalt-neverland.xml b/model/model-intest/src/test/resources/mapping/role-cobalt-neverland.xml new file mode 100644 index 00000000000..28c5d4d5a27 --- /dev/null +++ b/model/model-intest/src/test/resources/mapping/role-cobalt-neverland.xml @@ -0,0 +1,39 @@ + + + + Cobalt Neverland + + + + account + + ri:location + + strong + + Neverland + + + + + + 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 f3429ca150c..7cdcc9e13d4 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 @@ -1171,6 +1171,17 @@ protected void modifyFocusAssignment(Class focusClass, modelService.executeChanges(deltas, options, task, result); } + protected void unassign(Class focusClass, String focusOid, AssignmentType currentAssignment, ModelExecuteOptions options, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, ObjectAlreadyExistsException, PolicyViolationException, SecurityViolationException { + Collection> modifications = new ArrayList<>(); + ContainerDelta assignmentDelta = ContainerDelta.createDelta(UserType.F_ASSIGNMENT, getUserDefinition()); + assignmentDelta.addValuesToDelete(currentAssignment.asPrismContainerValue().clone()); + modifications.add(assignmentDelta); + ObjectDelta focusDelta = ObjectDelta.createModifyDelta(focusOid, modifications, focusClass, prismContext); + Collection> deltas = MiscSchemaUtil.createCollection(focusDelta); + modelService.executeChanges(deltas, options, task, result); + } + /** * Executes unassign delta by removing each assignment individually by id. */ @@ -1748,31 +1759,40 @@ protected void assertNoShadow(String shadowOid) throws SchemaException { } protected AssignmentType getUserAssignment(String userOid, String roleOid) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + return getAssignment(getUser(userOid), roleOid); + } + + protected AssignmentType getUserAssignment(String userOid, String roleOid, QName relation) + throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { PrismObject user = getUser(userOid); List assignments = user.asObjectable().getAssignment(); for (AssignmentType assignment: assignments) { ObjectReferenceType targetRef = assignment.getTargetRef(); - if (targetRef != null && roleOid.equals(targetRef.getOid())) { + if (targetRef != null && roleOid.equals(targetRef.getOid()) && ObjectTypeUtil.relationMatches(relation, + targetRef.getRelation())) { return assignment; } } return null; } - - protected AssignmentType getUserAssignment(String userOid, String roleOid, QName relation) - throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - PrismObject user = getUser(userOid); - List assignments = user.asObjectable().getAssignment(); + + protected AssignmentType getAssignment(PrismObject focus, String roleOid) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + List assignments = focus.asObjectable().getAssignment(); for (AssignmentType assignment: assignments) { ObjectReferenceType targetRef = assignment.getTargetRef(); - if (targetRef != null && roleOid.equals(targetRef.getOid()) && ObjectTypeUtil.relationMatches(relation, - targetRef.getRelation())) { + if (targetRef != null && roleOid.equals(targetRef.getOid())) { return assignment; } } return null; } + protected ItemPath getAssignmentPath(long id) { + return new ItemPath( + new NameItemPathSegment(FocusType.F_ASSIGNMENT), + new IdItemPathSegment(id)); + } + protected void assertNoAssignments(PrismObject user) { MidPointAsserts.assertNoAssignments(user); }