From 049147dbab551d907112a2898612451bc85a1547 Mon Sep 17 00:00:00 2001 From: Radovan Semancik Date: Tue, 11 Oct 2016 16:28:06 +0200 Subject: [PATCH] Basic implementation of object merge (model) (MID-3460) --- .../midpoint/prism/util/PrismAsserts.java | 10 +- .../xml/ns/public/common/common-core-3.xsd | 92 ++++++ .../model/api/ModelInteractionService.java | 11 + .../midpoint/model/api/ModelService.java | 20 +- .../impl/controller/ModelController.java | 39 ++- .../ModelInteractionServiceImpl.java | 50 ++++ .../model/impl/controller/ObjectMerger.java | 272 ++++++++++++++++++ ...bstractConfiguredModelIntegrationTest.java | 4 + .../midpoint/model/intest/TestMerge.java | 242 ++++++++++++++++ .../resources/common/system-configuration.xml | 27 ++ .../src/test/resources/logback-test.xml | 3 +- model/model-intest/testng-integration.xml | 1 + 12 files changed, 764 insertions(+), 7 deletions(-) create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ObjectMerger.java create mode 100644 model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMerge.java diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/PrismAsserts.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/PrismAsserts.java index a185112ce6c..421a62ecdf8 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/PrismAsserts.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/util/PrismAsserts.java @@ -417,11 +417,15 @@ public static void assertPropertyDelete(Collection modifica assertSet("delta "+propertyDelta+" for "+propertyPath.last(), "delete", propertyDelta.getValuesToDelete(), expectedValues); } - public static void assertNoItemDelta(ObjectDelta userDelta, ItemPath propertyPath) { - if (userDelta == null) { + public static void assertNoItemDelta(ObjectDelta objectDelta, QName itemName) { + assertNoItemDelta(objectDelta, new ItemPath(itemName)); + } + + public static void assertNoItemDelta(ObjectDelta objectDelta, ItemPath itemPath) { + if (objectDelta == null) { return; } - assert !userDelta.hasItemDelta(propertyPath) : "Delta for item "+propertyPath+" present while not expecting it"; + assert !objectDelta.hasItemDelta(itemPath) : "Delta for item "+itemPath+" present while not expecting it"; } public static ContainerDelta assertContainerAdd(ObjectDelta objectDelta, QName name) { 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 db891ac359d..661a9eed916 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 @@ -9360,6 +9360,20 @@ + + + + + Configuration for object merging. E.g. for merging two users. + Note: this is a single-valued item now. But it will most likely be + switched to multi-valued item in future midPoint versions. + + + 3.5 + + + + @@ -12318,5 +12332,83 @@ + + + + + TODO + + + + + + + + + + + + + + + + + TODO + + + + + + + + + + + + + + + TODO + + + + + + + + + + + + + + + + + + TODO + + + + + + + + + + + + + + + + + + + + + + + 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 7042a4b4156..3d88576a2d8 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 @@ -56,6 +56,8 @@ public interface ModelInteractionService { static final String GET_CREDENTIALS_POLICY = CLASS_NAME_WITH_DOT + "getCredentialsPolicy"; static final String CHECK_PASSWORD = CLASS_NAME_WITH_DOT + "checkPassword"; static final String GET_CONNECTOR_OPERATIONAL_STATUS = CLASS_NAME_WITH_DOT + "getConnectorOperationalStatus"; + static final String MERGE_OBJECTS_PREVIEW_DELTA = CLASS_NAME_WITH_DOT + "mergeObjectsPreviewDelta"; + static final String MERGE_OBJECTS_PREVIEW_OBJECT = CLASS_NAME_WITH_DOT + "mergeObjectsPreviewObject"; /** * Computes the most likely changes triggered by the provided delta. The delta may be any change of any object, e.g. @@ -176,4 +178,13 @@ ModelContext previewChanges( ConnectorOperationalStatus getConnectorOperationalStatus(String resourceOid, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException; + + ObjectDelta mergeObjectsPreviewDelta(Class type, + String leftOid, String rightOid, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ConfigurationException; + + PrismObject mergeObjectsPreviewObject(Class type, + String leftOid, String rightOid, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ConfigurationException; + } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelService.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelService.java index 9ff27b8f442..8f0a3b79ba4 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelService.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelService.java @@ -107,6 +107,7 @@ public interface ModelService { static final String IMPORT_OBJECTS_FROM_STREAM = CLASS_NAME_WITH_DOT + "importObjectsFromStream"; static final String POST_INIT = CLASS_NAME_WITH_DOT + "postInit"; static final String DISCOVER_CONNECTORS = CLASS_NAME_WITH_DOT + "discoverConnectors"; + static final String MERGE_OBJECTS = CLASS_NAME_WITH_DOT + "mergeObjects"; static final String AUTZ_NAMESPACE = AuthorizationConstants.NS_AUTHORIZATION_MODEL; @@ -641,7 +642,7 @@ public Set discoverConnectors(ConnectorHostType hostType, Task ta * @param ignoreItemPaths * @param task * @param result - * @param + * @param * @return * @throws SchemaException * @throws ObjectNotFoundException @@ -649,9 +650,24 @@ public Set discoverConnectors(ConnectorHostType hostType, Task ta * @throws CommunicationException * @throws ConfigurationException */ - CompareResultType compareObject(PrismObject object, + CompareResultType compareObject(PrismObject object, Collection> readOptions, ModelCompareOptions compareOptions, @NotNull List ignoreItemPaths, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException; + + /** + * Merge two objects into one. + * + * EXPERIMENTAL feature. The method signature is likely to change in the future. + * + * @param type object type + * @param leftOid left-side object OID + * @param rightOid right-side object OID + * @param task + * @param result + * @return + */ + Collection> mergeObjects(Class type, String leftOid, String rightOid, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ConfigurationException, ObjectAlreadyExistsException, ExpressionEvaluationException, CommunicationException, PolicyViolationException, SecurityViolationException; + } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java index 5e8e1be648d..7d622abf5a0 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java @@ -191,7 +191,10 @@ public class ModelController implements ModelService, TaskService, WorkflowServi @Autowired private SchemaTransformer schemaTransformer; - + + @Autowired(required = true) + private ObjectMerger objectMerger; + public ModelObjectResolver getObjectResolver() { return objectResolver; } @@ -2071,4 +2074,38 @@ public AccessCertificationCampaignType createCampaign(String definitionOid, Task return getCertificationManagerChecked().createCampaign(definitionOid, task, parentResult); } //endregion + + @Override + public Collection> mergeObjects(Class type, String leftOid, String rightOid, Task task, + OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ConfigurationException, ObjectAlreadyExistsException, ExpressionEvaluationException, CommunicationException, PolicyViolationException, SecurityViolationException { + + OperationResult result = parentResult.createSubresult(MERGE_OBJECTS); + result.addParam("leftOid", leftOid); + result.addParam("rightOid", rightOid); + result.addParam("class", type); + + RepositoryCache.enter(); + + try { + + Collection> deltas = + objectMerger.mergeObjects(type, leftOid, rightOid, task, result); + + result.computeStatus(); + return deltas; + + } catch (ObjectNotFoundException | SchemaException | ConfigurationException + | ObjectAlreadyExistsException | ExpressionEvaluationException | CommunicationException + | PolicyViolationException | SecurityViolationException e) { + ModelUtils.recordFatalError(result, e); + throw e; + } catch (RuntimeException | Error e) { + ModelUtils.recordFatalError(result, e); + throw e; + } finally { + QNameUtil.setTemporarilyTolerateUndeclaredPrefixes(false); + RepositoryCache.exit(); + } + + } } \ No newline at end of file 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 ed2c652dc27..521ef287b1e 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 @@ -113,6 +113,9 @@ public class ModelInteractionServiceImpl implements ModelInteractionService { @Autowired(required = true) private ModelObjectResolver objectResolver; + + @Autowired(required = true) + private ObjectMerger objectMerger; @Autowired(required = true) @Qualifier("cacheRepositoryService") @@ -691,4 +694,51 @@ public ConnectorOperationalStatus getConnectorOperationalStatus(String resourceO return status; } + @Override + public ObjectDelta mergeObjectsPreviewDelta(Class type, String leftOid, + String rightOid, Task task, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException, ConfigurationException { + OperationResult result = parentResult.createMinorSubresult(MERGE_OBJECTS_PREVIEW_DELTA); + + try { + + ObjectDelta objectDelta = objectMerger.computeMergeDelta(type, leftOid, rightOid, task, result); + + result.computeStatus(); + return objectDelta; + + } catch (ObjectNotFoundException | SchemaException | ConfigurationException | RuntimeException | Error e) { + result.recordFatalError(e); + throw e; + } + } + + @Override + public PrismObject mergeObjectsPreviewObject(Class type, String leftOid, + String rightOid, Task task, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException, ConfigurationException { + OperationResult result = parentResult.createMinorSubresult(MERGE_OBJECTS_PREVIEW_OBJECT); + + try { + + ObjectDelta objectDelta = objectMerger.computeMergeDelta(type, leftOid, rightOid, task, result); + + final PrismObject objectLeft = objectResolver.getObjectSimple(type, leftOid, null, task, result).asPrismObject(); + + if (objectDelta == null) { + result.computeStatus(); + return objectLeft; + } + + objectDelta.applyTo(objectLeft); + + result.computeStatus(); + return objectLeft; + + } catch (ObjectNotFoundException | SchemaException | ConfigurationException | 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 new file mode 100644 index 00000000000..4ee585510da --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ObjectMerger.java @@ -0,0 +1,272 @@ +/** + * Copyright (c) 2016 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.controller; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.model.api.PolicyViolationException; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.prism.Visitable; +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.path.ItemPath; +import com.evolveum.midpoint.schema.ObjectDeltaOperation; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.CommunicationException; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.util.exception.SystemException; +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.ItemMergeConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ItemRefMergeConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MergeConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MergeStategyType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; + +/** + * Class responsible for object merging. This acts as a controller + * for the merge operation and merge preview. + * + * @author semancik + */ +@Component +public class ObjectMerger { + + private static final Trace LOGGER = TraceManager.getTrace(ObjectMerger.class); + + @Autowired(required = true) + private ModelObjectResolver objectResolver; + + @Autowired(required = true) + private SystemObjectCache systemObjectCache; + + @Autowired(required = true) + PrismContext prismContext; + + // TODO: circular dependency to model controller. Not good. + // But cannot fix it right now. TODO: later refactor. + // MID-3459 + @Autowired(required = true) + private ModelController modelController; + + public Collection> mergeObjects(Class type, + String leftOid, String rightOid, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ConfigurationException, + ObjectAlreadyExistsException, ExpressionEvaluationException, CommunicationException, + PolicyViolationException, SecurityViolationException { + ObjectDelta objectDelta = computeMergeDelta(type, leftOid, rightOid, task, result); + + if (objectDelta != null && !objectDelta.isEmpty()) { + Collection> executedDeltas = + modelController.executeChanges(MiscSchemaUtil.createCollection(objectDelta), null, task, result); + + result.computeStatus(); + if (result.isSuccess()) { + // Do not delete the other object if the execution was not success. + // We might need to re-try the merge if it has failed and for that we need the right object. + ObjectDelta deleteDelta = ObjectDelta.createDeleteDelta(type, rightOid, prismContext); + Collection> executedDeleteDeltas = modelController.executeChanges(MiscSchemaUtil.createCollection(deleteDelta), null, task, result); + executedDeltas.addAll(executedDeleteDeltas); + } + + return executedDeltas; + + } else { + return null; + } + } + + public ObjectDelta computeMergeDelta(Class type, String leftOid, String rightOid, + Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ConfigurationException { + + final PrismObject objectLeft = objectResolver.getObjectSimple(type, leftOid, null, task, result).asPrismObject(); + final PrismObject objectRight = objectResolver.getObjectSimple(type, rightOid, null, task, result).asPrismObject(); + + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); + MergeConfigurationType mergeConfiguration = systemConfiguration.asObjectable().getMergeConfiguration(); + if (mergeConfiguration == null) { + throw new ConfigurationException("No merge configuration defined"); + } + + // The "left" object is always the one that will be the result. We will use its OID. + final ObjectDelta leftObjectDelta = objectLeft.createModifyDelta(); + + final List processedPaths = new ArrayList<>(); + for (ItemRefMergeConfigurationType itemMergeConfig: mergeConfiguration.getItem()) { + ItemPath itemPath = itemMergeConfig.getRef().getItemPath(); + processedPaths.add(itemPath); + ItemDelta itemDelta = mergeItem(objectLeft, objectRight, itemMergeConfig, itemPath); + LOGGER.trace("Item {} delta: {}", itemPath, itemDelta); + if (itemDelta != null && !itemDelta.isEmpty()) { + leftObjectDelta.addModification(itemDelta); + } + } + + final ItemMergeConfigurationType defaultItemMergeConfig = mergeConfiguration.getDefault(); + if (defaultItemMergeConfig != null) { + try { + + Visitor visitor = new Visitor() { + @Override + public void visit(Visitable visitable) { + if (!(visitable instanceof Item)) { + return; + } + Item item = (Item)visitable; + + ItemPath itemPath = item.getPath(); + if (itemPath == null || itemPath.isEmpty()) { + return; + } + + boolean found = false; + for (ItemPath processedPath: processedPaths) { + // TODO: We might need to check for super-paths here. + // E.g. if we have already processed metadata, we do not want to process + // metadata/modifyTimestamp + if (processedPath.equivalent(itemPath)) { + found = true; + break; + } + } + if (found) { + return; + } + processedPaths.add(itemPath); + + if (item instanceof PrismContainer) { + if (item.getDefinition().isSingleValue()) { + // Ignore single-valued containers such as extension or activation + // we will handle every individual property there. + return; + } else { + // TODO: we may need special handling for multi-value containers + // such as assignment + } + } + + + ItemDelta itemDelta; + try { + itemDelta = mergeItem(objectLeft, objectRight, defaultItemMergeConfig, itemPath); + } catch (SchemaException e) { + throw new TunnelException(e); + } + LOGGER.trace("Item {} delta (default): {}", itemPath, itemDelta); + if (itemDelta != null && !itemDelta.isEmpty()) { + leftObjectDelta.addModification(itemDelta); + } + } + }; + + objectLeft.accept(visitor); + objectRight.accept(visitor); + + + } catch (TunnelException te) { + if (te.getCause() instanceof SchemaException) { + throw (SchemaException)te.getCause(); + } else { + throw new SystemException("Unexpected exception: "+te, te); + } + } + } + + return leftObjectDelta; + + } + + private ItemDelta mergeItem(PrismObject objectLeft, PrismObject objectRight, + ItemMergeConfigurationType itemMergeConfig, ItemPath itemPath) throws SchemaException { + I itemLeft = (I) objectLeft.findItem(itemPath); + I itemRight = (I) objectRight.findItem(itemPath); + if (itemLeft == null && itemRight == null) { + return null; + } + ItemDefinition itemDefinition = null; + if (itemLeft != null) { + itemDefinition = itemLeft.getDefinition(); + } else { + itemDefinition = itemRight.getDefinition(); + } + ItemDelta itemDelta = itemDefinition.createEmptyDelta(itemPath); + MergeStategyType leftStrategy = itemMergeConfig.getLeft(); + MergeStategyType rightStrategy = itemMergeConfig.getRight(); + if (leftStrategy == null || leftStrategy == MergeStategyType.IGNORE) { + if (rightStrategy == null || rightStrategy == MergeStategyType.IGNORE) { + // IGNORE both + if (itemLeft == null) { + return null; + } else { + itemDelta.setValueToReplace(); + return itemDelta; + } + } else { + // IGNORE left, TAKE right + if (itemRight == null) { + itemDelta.setValueToReplace(); + } else { + itemDelta.setValuesToReplace(itemRight.getClonedValues()); + } + 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()); + return itemDelta; + } else if (itemRight != null) { + throw new SchemaException("Attempt to put multiple values in a single-valued item "+itemPath); + } else { + return null; + } + } else { + itemDelta.addValuesToAdd(itemRight.getClonedValues()); + return 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 6567a79392b..68373bc7157 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 @@ -223,6 +223,9 @@ public class AbstractConfiguredModelIntegrationTest extends AbstractModelIntegra protected static final String USER_JACK_FULL_NAME = "Jack Sparrow"; 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_EMPLOYEE_TYPE = "CAPTAIN"; + protected static final String USER_JACK_LOCALITY = "Caribbean"; protected static final String USER_JACK_PASSWORD = "deadmentellnotales"; protected static final File USER_BARBOSSA_FILE = new File(COMMON_DIR, "user-barbossa.xml"); @@ -236,6 +239,7 @@ public class AbstractConfiguredModelIntegrationTest extends AbstractModelIntegra protected static final String USER_GUYBRUSH_FULL_NAME = "Guybrush Threepwood"; protected static final String USER_GUYBRUSH_GIVEN_NAME = "Guybrush"; protected static final String USER_GUYBRUSH_FAMILY_NAME = "Threepwood"; + protected static final String USER_GUYBRUSH_LOCALITY = "Melee Island"; // Largo does not have a full name set, employeeType=PIRATE protected static final File USER_LARGO_FILE = new File(COMMON_DIR, "user-largo.xml"); 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 new file mode 100644 index 00000000000..c0d48bad3f4 --- /dev/null +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMerge.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2016 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.intest; + +import static com.evolveum.midpoint.test.IntegrationTestTools.display; +import static org.testng.AssertJUnit.assertEquals; + +import java.io.File; + +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.Test; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.util.PrismAsserts; +import com.evolveum.midpoint.prism.util.PrismTestUtil; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; + +/** + * @author semancik + * + */ +@ContextConfiguration(locations = {"classpath:ctx-model-intest-test-main.xml"}) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class TestMerge extends AbstractInitializedModelIntegrationTest { + + public static final File TEST_DIR = new File("src/test/resources/merge"); + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + + modifyUserAdd(USER_GUYBRUSH_OID, UserType.F_EMPLOYEE_TYPE, initTask, initResult, + "SAILOR", "PIRATE WANNABE"); + } + + @Test + public void test100MergeJackGuybrushPreviewDelta() throws Exception { + final String TEST_NAME = "test100MergeJackGuybrushPreviewDelta"; + TestUtil.displayTestTile(this, TEST_NAME); + + Task task = taskManager.createTaskInstance(TestMerge.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + ObjectDelta delta = + modelInteractionService.mergeObjectsPreviewDelta(UserType.class, USER_JACK_OID, USER_GUYBRUSH_OID, 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.assertPropertyReplace(delta, UserType.F_FAMILY_NAME); + PrismAsserts.assertPropertyReplace(delta, UserType.F_FULL_NAME, + PrismTestUtil.createPolyString(USER_GUYBRUSH_FULL_NAME)); + PrismAsserts.assertPropertyReplace(delta, UserType.F_ADDITIONAL_NAME); + PrismAsserts.assertPropertyReplace(delta, UserType.F_LOCALITY, + PrismTestUtil.createPolyString(USER_GUYBRUSH_LOCALITY)); + PrismAsserts.assertPropertyAdd(delta, UserType.F_EMPLOYEE_TYPE, + "SAILOR", "PIRATE WANNABE"); + + } + + @Test + public void test102MergeJackGuybrushPreviewObject() throws Exception { + final String TEST_NAME = "test102MergeJackGuybrushPreviewObject"; + 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, 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, PrismTestUtil.createPolyString(USER_JACK_USERNAME)); + PrismAsserts.assertPropertyValue(object, + UserType.F_GIVEN_NAME, PrismTestUtil.createPolyString(USER_JACK_GIVEN_NAME)); + PrismAsserts.assertNoItem(object, UserType.F_FAMILY_NAME); + PrismAsserts.assertPropertyValue(object, + UserType.F_FULL_NAME, PrismTestUtil.createPolyString(USER_GUYBRUSH_FULL_NAME)); + PrismAsserts.assertNoItem(object, UserType.F_ADDITIONAL_NAME); + PrismAsserts.assertPropertyValue(object, + UserType.F_LOCALITY, PrismTestUtil.createPolyString(USER_GUYBRUSH_LOCALITY)); + PrismAsserts.assertPropertyValue(object, + UserType.F_EMPLOYEE_TYPE, USER_JACK_EMPLOYEE_TYPE, "SAILOR", "PIRATE WANNABE"); + + } + + @Test + public void test110MergeGuybrushJackPreviewDelta() throws Exception { + final String TEST_NAME = "test110MergeGuybrushJackPreviewDelta"; + TestUtil.displayTestTile(this, TEST_NAME); + + Task task = taskManager.createTaskInstance(TestMerge.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userGuybrushBefore = getUser(USER_GUYBRUSH_OID); + display("Guybrush before", userGuybrushBefore); + + PrismObject userJackBefore = getUser(USER_JACK_OID); + display("Jack before", userJackBefore); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + ObjectDelta delta = + modelInteractionService.mergeObjectsPreviewDelta(UserType.class, USER_GUYBRUSH_OID, USER_JACK_OID, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + display("Delta", delta); + + PrismAsserts.assertIsModify(delta); + assertEquals("Wrong delta OID", USER_GUYBRUSH_OID, delta.getOid()); + PrismAsserts.assertNoItemDelta(delta, UserType.F_NAME); + PrismAsserts.assertNoItemDelta(delta, UserType.F_GIVEN_NAME); + PrismAsserts.assertPropertyReplace(delta, UserType.F_FAMILY_NAME); + PrismAsserts.assertPropertyReplace(delta, UserType.F_FULL_NAME, + PrismTestUtil.createPolyString(USER_JACK_FULL_NAME)); + PrismAsserts.assertPropertyReplace(delta, UserType.F_ADDITIONAL_NAME, + PrismTestUtil.createPolyString(USER_JACK_ADDITIONAL_NAME)); + PrismAsserts.assertPropertyReplace(delta, UserType.F_LOCALITY, + PrismTestUtil.createPolyString(USER_JACK_LOCALITY)); + PrismAsserts.assertPropertyAdd(delta, UserType.F_EMPLOYEE_TYPE, + USER_JACK_EMPLOYEE_TYPE); + + } + + @Test + public void test112MergeGuybrushJackPreviewObject() throws Exception { + final String TEST_NAME = "test112MergeGuybrushJackPreviewObject"; + 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_GUYBRUSH_OID, USER_JACK_OID, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + display("Object", object); + + assertEquals("Wrong object OID", USER_GUYBRUSH_OID, object.getOid()); + PrismAsserts.assertPropertyValue(object, + UserType.F_NAME, PrismTestUtil.createPolyString(USER_GUYBRUSH_USERNAME)); + PrismAsserts.assertPropertyValue(object, + UserType.F_GIVEN_NAME, PrismTestUtil.createPolyString(USER_GUYBRUSH_GIVEN_NAME)); + PrismAsserts.assertNoItem(object, UserType.F_FAMILY_NAME); + PrismAsserts.assertPropertyValue(object, + UserType.F_FULL_NAME, PrismTestUtil.createPolyString(USER_JACK_FULL_NAME)); + PrismAsserts.assertPropertyValue(object, + UserType.F_ADDITIONAL_NAME, PrismTestUtil.createPolyString(USER_JACK_ADDITIONAL_NAME)); + PrismAsserts.assertPropertyValue(object, + UserType.F_LOCALITY, PrismTestUtil.createPolyString(USER_JACK_LOCALITY)); + PrismAsserts.assertPropertyValue(object, + UserType.F_EMPLOYEE_TYPE, USER_JACK_EMPLOYEE_TYPE, "SAILOR", "PIRATE WANNABE"); + + } + + @Test + public void test200MergeJackGuybrush() throws Exception { + final String TEST_NAME = "test200MergeJackGuybrush"; + TestUtil.displayTestTile(this, TEST_NAME); + + Task task = taskManager.createTaskInstance(TestMerge.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + modelService.mergeObjects(UserType.class, USER_JACK_OID, USER_GUYBRUSH_OID, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject object = getObject(UserType.class, USER_JACK_OID); + display("Object", object); + + assertEquals("Wrong object OID", USER_JACK_OID, object.getOid()); + PrismAsserts.assertPropertyValue(object, + UserType.F_NAME, PrismTestUtil.createPolyString(USER_JACK_USERNAME)); + PrismAsserts.assertPropertyValue(object, + UserType.F_GIVEN_NAME, PrismTestUtil.createPolyString(USER_JACK_GIVEN_NAME)); + PrismAsserts.assertNoItem(object, UserType.F_FAMILY_NAME); + PrismAsserts.assertPropertyValue(object, + UserType.F_FULL_NAME, PrismTestUtil.createPolyString(USER_GUYBRUSH_FULL_NAME)); + PrismAsserts.assertNoItem(object, UserType.F_ADDITIONAL_NAME); + PrismAsserts.assertPropertyValue(object, + UserType.F_LOCALITY, PrismTestUtil.createPolyString(USER_GUYBRUSH_LOCALITY)); + PrismAsserts.assertPropertyValue(object, + UserType.F_EMPLOYEE_TYPE, USER_JACK_EMPLOYEE_TYPE, "SAILOR", "PIRATE WANNABE"); + + assertNoObject(UserType.class, USER_GUYBRUSH_OID); + + } +} 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 f1fd9694190..93f7741d057 100644 --- a/model/model-intest/src/test/resources/common/system-configuration.xml +++ b/model/model-intest/src/test/resources/common/system-configuration.xml @@ -184,4 +184,31 @@ Jamaica + + default + + name + take + + + givenName + take + ignore + + + familyName + + + fullName + take + + + employeeType + take + take + + + take + + diff --git a/model/model-intest/src/test/resources/logback-test.xml b/model/model-intest/src/test/resources/logback-test.xml index c1e26eb4089..71150abe2ee 100644 --- a/model/model-intest/src/test/resources/logback-test.xml +++ b/model/model-intest/src/test/resources/logback-test.xml @@ -80,7 +80,7 @@ - + @@ -99,6 +99,7 @@ + diff --git a/model/model-intest/testng-integration.xml b/model/model-intest/testng-integration.xml index eb1c412227e..8db923044c9 100644 --- a/model/model-intest/testng-integration.xml +++ b/model/model-intest/testng-integration.xml @@ -55,6 +55,7 @@ +