diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismValue.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismValue.java index 0275b824559..09af62ab801 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismValue.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismValue.java @@ -382,4 +382,16 @@ public static Set getRealValuesOfCollection(Collection boolean collectionContainsEquivalentValue(Collection collection, V value) { + if (collection == null) { + return false; + } + for (V collectionVal: collection) { + if (collectionVal.equals(value, true)) { + return true; + } + } + return false; + } } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java index ffcc1019d75..02ab0bda633 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java @@ -65,6 +65,11 @@ public class ExpressionConstants { */ public static final QName VAR_ITERATION_TOKEN = new QName(SchemaConstants.NS_C, "iterationToken"); + // Variables used in object mergign expressions + public static final QName VAR_SIDE = new QName(SchemaConstants.NS_C, "side"); + public static final QName VAR_OBJECT_LEFT = new QName(SchemaConstants.NS_C, "objectLeft"); + public static final QName VAR_OBJECT_RIGHT = new QName(SchemaConstants.NS_C, "objectRight"); + public static final QName OUTPUT_ELMENT_NAME = new QName(SchemaConstants.NS_C, "output"); } diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd index fb25ab3b0d2..4cef4c73c9a 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd @@ -12392,8 +12392,29 @@ - - + + + + Strategy to process values from the left-hand-side object. + + + + + + + Strategy to process values from the right-hand-side object. + + + + + + + Expression to process every value (if specified by strategy). The value that the + expression returns will be taken. If the expression returns null then the value will + be skipped. + + + @@ -12425,6 +12446,7 @@ + Ignore all the values. @@ -12434,12 +12456,24 @@ + Take all the values. + + + + Take only values that are selected and processed by the expression. + + + + + + + diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java index c0568df8135..8127a0cbec3 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java @@ -181,10 +181,10 @@ ConnectorOperationalStatus getConnectorOperationalStatus(String resourceOid, Ope ObjectDelta mergeObjectsPreviewDelta(Class type, String leftOid, String rightOid, String mergeConfigurationName, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ConfigurationException; + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException; PrismObject mergeObjectsPreviewObject(Class type, String leftOid, String rightOid, String mergeConfigurationName, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ConfigurationException; + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java index 7571a51146a..63e62bbc2e3 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java @@ -697,7 +697,7 @@ public ConnectorOperationalStatus getConnectorOperationalStatus(String resourceO @Override public ObjectDelta mergeObjectsPreviewDelta(Class type, String leftOid, String rightOid, String mergeConfigurationName, Task task, OperationResult parentResult) - throws ObjectNotFoundException, SchemaException, ConfigurationException { + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException { OperationResult result = parentResult.createMinorSubresult(MERGE_OBJECTS_PREVIEW_DELTA); try { @@ -707,7 +707,7 @@ public ObjectDelta mergeObjectsPreviewDelta(Class t result.computeStatus(); return objectDelta; - } catch (ObjectNotFoundException | SchemaException | ConfigurationException | RuntimeException | Error e) { + } catch (ObjectNotFoundException | SchemaException | ConfigurationException | ExpressionEvaluationException | RuntimeException | Error e) { result.recordFatalError(e); throw e; } @@ -716,7 +716,7 @@ public ObjectDelta mergeObjectsPreviewDelta(Class t @Override public PrismObject mergeObjectsPreviewObject(Class type, String leftOid, String rightOid, String mergeConfigurationName, Task task, OperationResult parentResult) - throws ObjectNotFoundException, SchemaException, ConfigurationException { + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException { OperationResult result = parentResult.createMinorSubresult(MERGE_OBJECTS_PREVIEW_OBJECT); try { @@ -735,7 +735,7 @@ public PrismObject mergeObjectsPreviewObject(Class result.computeStatus(); return objectLeft; - } catch (ObjectNotFoundException | SchemaException | ConfigurationException | RuntimeException | Error e) { + } catch (ObjectNotFoundException | SchemaException | ConfigurationException | ExpressionEvaluationException | RuntimeException | Error e) { result.recordFatalError(e); throw e; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ObjectMerger.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ObjectMerger.java index ad80f036362..b9bb70d8d32 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ObjectMerger.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ObjectMerger.java @@ -25,6 +25,10 @@ import com.evolveum.midpoint.model.api.PolicyViolationException; import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.common.expression.Expression; +import com.evolveum.midpoint.model.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.model.common.expression.ExpressionFactory; +import com.evolveum.midpoint.model.common.expression.ExpressionVariables; import com.evolveum.midpoint.model.impl.ModelObjectResolver; import com.evolveum.midpoint.prism.Item; import com.evolveum.midpoint.prism.ItemDefinition; @@ -37,9 +41,11 @@ import com.evolveum.midpoint.prism.Visitor; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.ItemPath.CompareResult; import com.evolveum.midpoint.schema.ObjectDeltaOperation; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.task.api.Task; @@ -54,6 +60,7 @@ import com.evolveum.midpoint.util.exception.TunnelException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ItemMergeConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ItemRefMergeConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.MergeConfigurationType; @@ -72,12 +79,18 @@ public class ObjectMerger { private static final Trace LOGGER = TraceManager.getTrace(ObjectMerger.class); + public static final String SIDE_LEFT = "left"; + public static final String SIDE_RIGHT = "right"; + @Autowired(required = true) private ModelObjectResolver objectResolver; @Autowired(required = true) private SystemObjectCache systemObjectCache; + @Autowired(required = true) + private ExpressionFactory expressionFactory; + @Autowired(required = true) PrismContext prismContext; @@ -115,8 +128,8 @@ public Collection ObjectDelta computeMergeDelta(Class type, String leftOid, String rightOid, - String mergeConfigurationName, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ConfigurationException { + final String mergeConfigurationName, final Task task, final OperationResult result) + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException { final PrismObject objectLeft = objectResolver.getObjectSimple(type, leftOid, null, task, result).asPrismObject(); final PrismObject objectRight = objectResolver.getObjectSimple(type, rightOid, null, task, result).asPrismObject(); @@ -134,7 +147,7 @@ public ObjectDelta computeMergeDelta(Class type, St for (ItemRefMergeConfigurationType itemMergeConfig: mergeConfiguration.getItem()) { ItemPath itemPath = itemMergeConfig.getRef().getItemPath(); processedPaths.add(itemPath); - ItemDelta itemDelta = mergeItem(objectLeft, objectRight, itemMergeConfig, itemPath); + ItemDelta itemDelta = mergeItem(objectLeft, objectRight, mergeConfigurationName, itemMergeConfig, itemPath, task, result); LOGGER.trace("Item {} delta: {}", itemPath, itemDelta); if (itemDelta != null && !itemDelta.isEmpty()) { leftObjectDelta.addModification(itemDelta); @@ -188,8 +201,9 @@ public void visit(Visitable visitable) { ItemDelta itemDelta; try { - itemDelta = mergeItem(objectLeft, objectRight, defaultItemMergeConfig, itemPath); - } catch (SchemaException e) { + itemDelta = mergeItem(objectLeft, objectRight, mergeConfigurationName, defaultItemMergeConfig, itemPath, + task, result); + } catch (SchemaException | ConfigurationException | ExpressionEvaluationException | ObjectNotFoundException e) { throw new TunnelException(e); } LOGGER.trace("Item {} delta (default): {}", itemPath, itemDelta); @@ -206,6 +220,12 @@ public void visit(Visitable visitable) { } catch (TunnelException te) { if (te.getCause() instanceof SchemaException) { throw (SchemaException)te.getCause(); + } else if (te.getCause() instanceof ConfigurationException) { + throw (ConfigurationException)te.getCause(); + } else if (te.getCause() instanceof ExpressionEvaluationException) { + throw (ExpressionEvaluationException)te.getCause(); + } else if (te.getCause() instanceof ObjectNotFoundException) { + throw (ObjectNotFoundException)te.getCause(); } else { throw new SystemException("Unexpected exception: "+te, te); } @@ -217,7 +237,8 @@ public void visit(Visitable visitable) { } private ItemDelta mergeItem(PrismObject objectLeft, PrismObject objectRight, - ItemMergeConfigurationType itemMergeConfig, ItemPath itemPath) throws SchemaException { + String mergeConfigurationName, ItemMergeConfigurationType itemMergeConfig, ItemPath itemPath, + Task task, OperationResult result) throws SchemaException, ConfigurationException, ExpressionEvaluationException, ObjectNotFoundException { I itemLeft = (I) objectLeft.findItem(itemPath); I itemRight = (I) objectRight.findItem(itemPath); if (itemLeft == null && itemRight == null) { @@ -234,6 +255,15 @@ private ItemDelta mergeItem(PrismObject valueExpression = null; + if (itemMergeConfig.getValueExpression() != null) { + ExpressionType expressionType = itemMergeConfig.getValueExpression(); + valueExpression = expressionFactory.makeExpression(expressionType, itemDefinition, + "value expression for item " + itemPath + " in merge configuration " + mergeConfigurationName, + task, result); + } + ItemDelta itemDelta = itemDefinition.createEmptyDelta(itemPath); MergeStategyType leftStrategy = itemMergeConfig.getLeft(); MergeStategyType rightStrategy = itemMergeConfig.getRight(); @@ -247,52 +277,140 @@ private ItemDelta mergeItem(PrismObject valuesToTake = getValuesToTake(objectLeft, objectRight, + SIDE_RIGHT, itemRight, rightStrategy, valueExpression, task, result); + itemDelta.setValuesToReplace(valuesToTake); } return itemDelta; } } else { if (rightStrategy == null || rightStrategy == MergeStategyType.IGNORE) { - // TAKE left, IGNORE right - return null; - } else { - // TAKE left, TAKE right - if (itemDefinition.isSingleValue()) { - if (itemLeft == null) { - itemDelta.setValuesToReplace(itemRight.getClonedValues()); + if (leftStrategy == MergeStategyType.TAKE) { + // TAKE left, IGNORE right + return null; + } else { + // EXPRESSION left, IGNORE right + Collection valuesToLeave = getValuesToTake(objectLeft, objectRight, + SIDE_LEFT, itemLeft, leftStrategy, valueExpression, task, result); + List currentLeftValues = itemLeft.getValues(); + Collection leftValuesToRemove = diffValues(currentLeftValues, valuesToLeave); + if (leftValuesToRemove != null && !leftValuesToRemove.isEmpty()) { + itemDelta.addValuesToDelete(leftValuesToRemove); return itemDelta; - } else if (itemRight != null) { - throw new SchemaException("Attempt to put multiple values in a single-valued item "+itemPath); } else { return null; } + } + } else { + // TAKE/EXPRESSION left, TAKE/EXPRESSION right + if (itemLeft == null) { + Collection valuesToTake = getValuesToTake(objectLeft, objectRight, + SIDE_RIGHT, itemRight, rightStrategy, valueExpression, task, result); + itemDelta.addValuesToAdd(valuesToTake); + return itemDelta; + } else { - if (itemLeft == null) { - itemDelta.addValuesToAdd(itemRight.getClonedValues()); - return itemDelta; - } else { - // We want to add only those values that are not yet there. - // E.g. adding assignments that are there can cause unneccesary churn - List valuesRight = itemRight.getValues(); - for (PrismValue valueRight: valuesRight) { - if (!itemLeft.containsEquivalentValue(valueRight)) { - PrismValue clonedValue = valueRight.clone(); - if (clonedValue instanceof PrismContainerValue) { - ((PrismContainerValue)clonedValue).setId(null); - } - itemDelta.addValueToAdd(clonedValue); - } + // We want to add only those values that are not yet there. + // E.g. adding assignments that are there can cause unnecessary churn + Collection leftValuesToLeave = getValuesToTake(objectLeft, objectRight, + SIDE_LEFT, itemLeft, leftStrategy, valueExpression, task, result); + Collection rightValuesToTake = getValuesToTake(objectLeft, objectRight, + SIDE_RIGHT, itemRight, rightStrategy, valueExpression, task, result); + + for (PrismValue rightValueToTake: rightValuesToTake) { + if (!PrismValue.collectionContainsEquivalentValue(leftValuesToLeave, rightValueToTake)) { + itemDelta.addValueToAdd(rightValueToTake); } - return itemDelta; } + + List currentLeftValues = itemLeft.getValues(); + Collection leftValuesToRemove = diffValues(currentLeftValues, leftValuesToLeave); + + if (leftValuesToRemove != null && !leftValuesToRemove.isEmpty()) { + itemDelta.addValuesToDelete(leftValuesToRemove); + } + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Merging item {} T/T case:\n leftValuesToLeave: {}\n rightValuesToTake: {}\n leftValuesToRemove: {}\n itemDelta:\n{}", + new Object[]{itemPath, leftValuesToLeave, rightValuesToTake, leftValuesToRemove, itemDelta.debugDump(2)}); + } + + + return itemDelta; } } } } + + private Collection diffValues(List currentValues, Collection valuesToLeave) { + if (valuesToLeave == null || valuesToLeave.isEmpty()) { + return PrismValue.cloneCollection(currentValues); + } + Collection diff = new ArrayList<>(); + for (PrismValue currentValue: currentValues) { + if (!PrismValue.collectionContainsEquivalentValue(valuesToLeave, currentValue)) { + diff.add(currentValue.clone()); + } + } + return diff; + } + + private Collection getValuesToTake(PrismObject objectLeft, PrismObject objectRight, + String side, I origItem, MergeStategyType strategy, Expression valueExpression, Task task, OperationResult result) + throws ConfigurationException, SchemaException, ExpressionEvaluationException, ObjectNotFoundException { + if (strategy == MergeStategyType.TAKE) { + return cleanContainerIds(origItem.getClonedValues()); + } else if (strategy == MergeStategyType.EXPRESSION) { + if (valueExpression == null) { + throw new ConfigurationException("Expression strategy specified but no expression present"); + } + List origValues = origItem.getValues(); + Collection valuesToTake = new ArrayList<>(origValues.size()); + for (PrismValue origValue: origValues) { + Collection expressionOutput = evaluateValueExpression(objectLeft, objectRight, side, origValue, valueExpression, task, result); + if (expressionOutput != null) { + valuesToTake.addAll(expressionOutput); + } + } + return cleanContainerIds(valuesToTake); + } else { + throw new ConfigurationException("Unknown strategy "+strategy); + } + } + + + private Collection cleanContainerIds(Collection pvals) { + if (pvals == null) { + return null; + } + for (PrismValue pval: pvals) { + if (pval instanceof PrismContainerValue) { + ((PrismContainerValue)pval).setId(null); + } + } + return pvals; + } + + + private Collection evaluateValueExpression(PrismObject objectLeft, PrismObject objectRight, String side, PrismValue origValue, Expression valueExpression, + Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { + ExpressionVariables variables = new ExpressionVariables(); + variables.addVariableDefinition(ExpressionConstants.VAR_SIDE, side); + variables.addVariableDefinition(ExpressionConstants.VAR_OBJECT_LEFT, side); + variables.addVariableDefinition(ExpressionConstants.VAR_OBJECT_RIGHT, side); + variables.addVariableDefinition(ExpressionConstants.VAR_INPUT, origValue); + variables.addVariableDefinition(ExpressionConstants.VAR_VALUE, origValue); + ExpressionEvaluationContext exprContext = new ExpressionEvaluationContext(null, variables, "for value "+origValue, task, result); + PrismValueDeltaSetTriple triple = valueExpression.evaluate(exprContext); + if (triple == null) { + return null; + } + return triple.getNonNegativeValues(); + } private MergeConfigurationType selectConfiguration( PrismObject systemConfiguration, String mergeConfigurationName) throws ConfigurationException { diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMerge.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMerge.java index bec18881859..0a62bab0246 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMerge.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMerge.java @@ -47,6 +47,7 @@ public class TestMerge extends AbstractInitializedModelIntegrationTest { public static final File TEST_DIR = new File("src/test/resources/merge"); public static final String MERGE_CONFIG_DEFAULT_NAME = "default"; + public static final String MERGE_CONFIG_EXPRESSION_NAME = "expression"; @Override public void initSystem(Task initTask, OperationResult initResult) throws Exception { @@ -55,13 +56,13 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti modifyUserAdd(USER_GUYBRUSH_OID, UserType.F_EMPLOYEE_TYPE, initTask, initResult, "SAILOR", "PIRATE WANNABE"); modifyUserAdd(USER_GUYBRUSH_OID, UserType.F_ORGANIZATION, initTask, initResult, - createPolyString("Pirate Wannabes"), createPolyString("Scurvy Seadogs")); + createPolyString("Pirate Wannabes"), createPolyString("Sailors"), createPolyString("Rum Club"), createPolyString("Lovers")); assignRole(USER_GUYBRUSH_OID, ROLE_SAILOR_OID, initTask, initResult); assignRole(USER_GUYBRUSH_OID, ROLE_EMPTY_OID, initTask, initResult); assignRole(USER_GUYBRUSH_OID, ROLE_THIEF_OID, initTask, initResult); modifyUserAdd(USER_JACK_OID, UserType.F_ORGANIZATION, initTask, initResult, - createPolyString("Pirate Brethren"), createPolyString("Scurvy Seadogs")); + createPolyString("Pirate Brethren"), createPolyString("Sailors"), createPolyString("Rum Club"), createPolyString("Drinkers")); assignRole(USER_JACK_OID, ROLE_SAILOR_OID, initTask, initResult); assignRole(USER_JACK_OID, ROLE_EMPTY_OID, initTask, initResult); assignRole(USER_JACK_OID, ROLE_PIRATE_OID, initTask, initResult); @@ -108,7 +109,7 @@ public void test100MergeJackGuybrushPreviewDelta() throws Exception { PrismAsserts.assertPropertyAdd(delta, UserType.F_EMPLOYEE_TYPE, "SAILOR", "PIRATE WANNABE"); PrismAsserts.assertPropertyAdd(delta, UserType.F_ORGANIZATION, - createPolyString("Pirate Wannabes")); + createPolyString("Pirate Wannabes"), createPolyString("Lovers")); PrismAsserts.assertNoItemDelta(delta, UserType.F_ACTIVATION); PrismAsserts.assertNoItemDelta(delta, new ItemPath(UserType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS)); @@ -155,7 +156,8 @@ public void test102MergeJackGuybrushPreviewObject() throws Exception { UserType.F_EMPLOYEE_TYPE, USER_JACK_EMPLOYEE_TYPE, "SAILOR", "PIRATE WANNABE"); PrismAsserts.assertPropertyValue(object, UserType.F_ORGANIZATION, - createPolyString("Pirate Brethren"), createPolyString("Scurvy Seadogs"), createPolyString("Pirate Wannabes")); + createPolyString("Pirate Brethren"), createPolyString("Sailors"), createPolyString("Rum Club"), + createPolyString("Pirate Wannabes"), createPolyString("Lovers"), createPolyString("Drinkers")); assertAssignedRoles(object, ROLE_SAILOR_OID, ROLE_EMPTY_OID, ROLE_THIEF_OID, ROLE_PIRATE_OID, ROLE_NICE_PIRATE_OID); @@ -202,7 +204,7 @@ public void test110MergeGuybrushJackPreviewDelta() throws Exception { PrismAsserts.assertPropertyAdd(delta, UserType.F_EMPLOYEE_TYPE, USER_JACK_EMPLOYEE_TYPE); PrismAsserts.assertPropertyAdd(delta, UserType.F_ORGANIZATION, - createPolyString("Pirate Brethren")); + createPolyString("Pirate Brethren"), createPolyString("Drinkers")); PrismAsserts.assertNoItemDelta(delta, UserType.F_ACTIVATION); PrismAsserts.assertNoItemDelta(delta, new ItemPath(UserType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS)); @@ -252,16 +254,119 @@ public void test112MergeGuybrushJackPreviewObject() throws Exception { UserType.F_EMPLOYEE_TYPE, USER_JACK_EMPLOYEE_TYPE, "SAILOR", "PIRATE WANNABE"); PrismAsserts.assertPropertyValue(object, UserType.F_ORGANIZATION, - createPolyString("Pirate Brethren"), createPolyString("Scurvy Seadogs"), createPolyString("Pirate Wannabes")); + createPolyString("Pirate Brethren"), createPolyString("Sailors"), createPolyString("Rum Club"), + createPolyString("Pirate Wannabes"), createPolyString("Lovers"), createPolyString("Drinkers")); assertAssignedRoles(object, ROLE_SAILOR_OID, ROLE_EMPTY_OID, ROLE_THIEF_OID, ROLE_PIRATE_OID, ROLE_NICE_PIRATE_OID); } + + /** + * MID-3460 + */ + @Test + public void test200MergeJackGuybrushExpressionPreviewDelta() throws Exception { + final String TEST_NAME = "test200MergeJackGuybrushExpressionPreviewDelta"; + TestUtil.displayTestTile(this, TEST_NAME); + + Task task = taskManager.createTaskInstance(TestMerge.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userJackBefore = getUser(USER_JACK_OID); + display("Jack before", userJackBefore); + + PrismObject userGuybrushBefore = getUser(USER_GUYBRUSH_OID); + display("Guybrush before", userGuybrushBefore); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + ObjectDelta delta = + modelInteractionService.mergeObjectsPreviewDelta(UserType.class, + USER_JACK_OID, USER_GUYBRUSH_OID, MERGE_CONFIG_EXPRESSION_NAME, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + display("Delta", delta); + + PrismAsserts.assertIsModify(delta); + assertEquals("Wrong delta OID", USER_JACK_OID, delta.getOid()); + PrismAsserts.assertNoItemDelta(delta, UserType.F_NAME); + PrismAsserts.assertNoItemDelta(delta, UserType.F_GIVEN_NAME); + PrismAsserts.assertNoItemDelta(delta, UserType.F_FAMILY_NAME); + PrismAsserts.assertNoItemDelta(delta, UserType.F_FULL_NAME); + PrismAsserts.assertNoItemDelta(delta, UserType.F_ADDITIONAL_NAME); + PrismAsserts.assertPropertyAdd(delta, UserType.F_EMPLOYEE_TYPE, + "SAILOR"); + PrismAsserts.assertPropertyAdd(delta, UserType.F_ORGANIZATION, + createPolyString("Pirate Wannabes")); + PrismAsserts.assertPropertyDelete(delta, UserType.F_ORGANIZATION, + createPolyString("Sailors"), createPolyString("Drinkers")); + PrismAsserts.assertNoItemDelta(delta, UserType.F_ACTIVATION); + PrismAsserts.assertNoItemDelta(delta, + new ItemPath(UserType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS)); + PrismAsserts.assertNoItemDelta(delta, UserType.F_ROLE_MEMBERSHIP_REF); + + PrismAsserts.assertContainerAdd(delta, UserType.F_ASSIGNMENT, + FocusTypeUtil.createRoleAssignment(ROLE_THIEF_OID)); + } + + /** + * MID-3460 + */ + @Test + public void test202MergeJackGuybrushExpressionPreviewObject() throws Exception { + final String TEST_NAME = "test202MergeJackGuybrushExpressionPreviewObject"; + TestUtil.displayTestTile(this, TEST_NAME); + + Task task = taskManager.createTaskInstance(TestMerge.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + PrismObject object = + modelInteractionService.mergeObjectsPreviewObject(UserType.class, + USER_JACK_OID, USER_GUYBRUSH_OID, MERGE_CONFIG_EXPRESSION_NAME, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + display("Object", object); + + assertEquals("Wrong object OID", USER_JACK_OID, object.getOid()); + PrismAsserts.assertPropertyValue(object, + UserType.F_NAME, createPolyString(USER_JACK_USERNAME)); + PrismAsserts.assertPropertyValue(object, + UserType.F_GIVEN_NAME, createPolyString(USER_JACK_GIVEN_NAME)); + PrismAsserts.assertPropertyValue(object, + UserType.F_FAMILY_NAME, createPolyString(USER_JACK_FAMILY_NAME)); + PrismAsserts.assertPropertyValue(object, + UserType.F_FULL_NAME, createPolyString(USER_JACK_FULL_NAME)); + PrismAsserts.assertPropertyValue(object, + UserType.F_ADDITIONAL_NAME, createPolyString(USER_JACK_ADDITIONAL_NAME)); + PrismAsserts.assertPropertyValue(object, + UserType.F_LOCALITY, createPolyString(USER_JACK_LOCALITY)); + PrismAsserts.assertPropertyValue(object, + UserType.F_EMPLOYEE_TYPE, USER_JACK_EMPLOYEE_TYPE, "SAILOR"); + PrismAsserts.assertPropertyValue(object, + UserType.F_ORGANIZATION, + createPolyString("Pirate Brethren"), createPolyString("Rum Club"), + createPolyString("Pirate Wannabes")); + + assertAssignedRoles(object, ROLE_SAILOR_OID, ROLE_EMPTY_OID, ROLE_THIEF_OID, + ROLE_PIRATE_OID, ROLE_NICE_PIRATE_OID); + } + + @Test - public void test200MergeJackGuybrush() throws Exception { - final String TEST_NAME = "test200MergeJackGuybrush"; + public void test500MergeJackGuybrush() throws Exception { + final String TEST_NAME = "test500MergeJackGuybrush"; TestUtil.displayTestTile(this, TEST_NAME); Task task = taskManager.createTaskInstance(TestMerge.class.getName() + "." + TEST_NAME); diff --git a/model/model-intest/src/test/resources/common/system-configuration.xml b/model/model-intest/src/test/resources/common/system-configuration.xml index 979c886a42f..4d93bcc4508 100644 --- a/model/model-intest/src/test/resources/common/system-configuration.xml +++ b/model/model-intest/src/test/resources/common/system-configuration.xml @@ -226,7 +226,7 @@ take take - + linkRef take @@ -252,4 +252,77 @@ take + + + expression + + name + take + + + givenName + take + + + familyName + take + + + fullName + take + + + employeeType + take + expression + + + + + + organization + expression + expression + + + + + + organizationalUnit + take + + + activation + take + + + assignment + take + take + + + linkRef + take + + + take + + +