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 99778717c0c..8174104c9a6 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 @@ -12905,6 +12905,7 @@ + @@ -12952,6 +12953,49 @@ + + + + TODO + + + + + + + Projection exists on the left side. There is no conflicting + projection on the right side. + + + + + + + + + + Projection exists on the right side. There is no conflicting + projection on the left side. The projection can be merged. + + + + + + + + + + There are two conflicting projections, one on the left side + other on the right side. + + + + + + + + + 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 8299d4b3ba7..b8461b93cf3 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 @@ -78,6 +78,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ProjectionMergeConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ProjectionMergeSituationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowDiscriminatorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; @@ -308,12 +309,13 @@ private void computeProjectionDeltas(final ObjectDelta ProjectionMergeConfigurationType defaultProjectionMergeConfig = null; for (ProjectionMergeConfigurationType projectionMergeConfig: mergeConfiguration.getProjection()) { - ShadowDiscriminatorType discriminatorType = projectionMergeConfig.getProjectionDiscriminator(); - if (discriminatorType == null) { + if (projectionMergeConfig.getProjectionDiscriminator() == null && projectionMergeConfig.getSituation() == null) { defaultProjectionMergeConfig = projectionMergeConfig; } else { - takeProjections(projectionMergeConfig.getLeft(), mergedProjections, matchedProjections, projectionsLeft, discriminatorType); - takeProjections(projectionMergeConfig.getRight(), mergedProjections, matchedProjections, projectionsRight, discriminatorType); + takeProjections(projectionMergeConfig.getLeft(), mergedProjections, matchedProjections, + projectionsLeft, projectionsLeft, projectionsRight, projectionMergeConfig); + takeProjections(projectionMergeConfig.getRight(), mergedProjections, matchedProjections, + projectionsRight, projectionsLeft, projectionsRight, projectionMergeConfig); } } @@ -410,13 +412,19 @@ private List getProjections(PrismObject ob private void takeProjections(MergeStategyType strategy, List mergedProjections, List matchedProjections, List candidateProjections, - ShadowDiscriminatorType discriminatorType) { + List projectionsLeft, List projectionsRight, + ProjectionMergeConfigurationType projectionMergeConfig) { - LOGGER.trace("TAKE: Evaluating discriminator: {}", discriminatorType); + if (LOGGER.isTraceEnabled()) { + + LOGGER.trace("TAKE: Evaluating situation {}, discriminator: {}", + projectionMergeConfig.getSituation(), projectionMergeConfig.getProjectionDiscriminator()); + } for (ShadowType candidateProjection: candidateProjections) { - if (ShadowUtil.matchesPattern(candidateProjection, discriminatorType)) { - LOGGER.trace("Discriminator matches {}", candidateProjection); + + if (projectionMatches(candidateProjection, projectionsLeft, projectionsRight, projectionMergeConfig)) { + LOGGER.trace("Projection matches {}", candidateProjection); matchedProjections.add(candidateProjection); if (strategy == MergeStategyType.TAKE) { @@ -437,6 +445,23 @@ private void takeProjections(MergeStategyType strategy, List mergedP } + private boolean projectionMatches(ShadowType candidateProjection, + List projectionsLeft, List projectionsRight, + ProjectionMergeConfigurationType projectionMergeConfig) { + ShadowDiscriminatorType discriminatorType = projectionMergeConfig.getProjectionDiscriminator(); + if (discriminatorType != null && !ShadowUtil.matchesPattern(candidateProjection, discriminatorType)) { + return false; + } + ProjectionMergeSituationType situationPattern = projectionMergeConfig.getSituation(); + if (situationPattern != null) { + ProjectionMergeSituationType projectionSituation = determineSituation(candidateProjection, projectionsLeft, projectionsRight); + if (situationPattern != projectionSituation) { + return false; + } + } + return true; + } + private void takeUnmatchedProjections(MergeStategyType strategy, List mergedProjections, List matchedProjections, List candidateProjections) { if (strategy == MergeStategyType.TAKE) { @@ -453,6 +478,31 @@ private void takeUnmatchedProjections(MergeStategyType strategy, List projectionsLeft, + List projectionsRight) { + boolean matchLeft = hasMatchingProjection(candidateProjection, projectionsLeft); + boolean matchRight = hasMatchingProjection(candidateProjection, projectionsRight); + if (matchLeft && matchRight) { + return ProjectionMergeSituationType.CONFLICT; + } else if (matchLeft) { + return ProjectionMergeSituationType.EXISTING; + } else if (matchRight) { + return ProjectionMergeSituationType.MERGEABLE; + } else { + throw new IllegalStateException("Booom! The universe has imploded."); + } + } + + private boolean hasMatchingProjection(ShadowType cprojection, List projections) { + for (ShadowType projection: projections) { + if (ShadowUtil.isConflicting(projection, cprojection)) { + return true; + } + } + return false; + } + private void checkConflict(List projections) throws SchemaException { for (ShadowType projection: projections) { @@ -464,9 +514,8 @@ private void checkConflict(List projections) throws SchemaException throw new SchemaException("Merge would result in projection conflict between "+projection+" and "+cprojection); } } - } + } } - private ShadowType getProjection(ObjectReferenceType linkRef, Task task, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException { Collection> options = SelectorOptions.createCollection(GetOperationOptions.createNoFetch()); 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 715659a0a52..87d20186402 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 @@ -48,6 +48,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_DEFAULT_SPECIFIC_NAME = "default-specific"; public static final String MERGE_CONFIG_EXPRESSION_NAME = "expression"; private String jackDummyAccountOid; @@ -430,6 +431,70 @@ public void test202MergeJackGuybrushExpressionPreviewObject() throws Exception { } + /** + * The default-specific config is almost the same as default (test1XX), + * just the projections are selected by specific resource. + * MID-3460 + */ + @Test + public void test300MergeJackGuybrushPreviewDeltaDefaultSpecific() throws Exception { + final String TEST_NAME = "test300MergeJackGuybrushPreviewDeltaDefaultSpecific"; + 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); + MergeDeltas deltas = + modelInteractionService.mergeObjectsPreviewDeltas(UserType.class, + USER_JACK_OID, USER_GUYBRUSH_OID, MERGE_CONFIG_DEFAULT_SPECIFIC_NAME, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + display("Deltas", deltas); + + ObjectDelta leftObjectdelta = deltas.getLeftObjectDelta(); + PrismAsserts.assertIsModify(leftObjectdelta); + assertEquals("Wrong delta OID", USER_JACK_OID, leftObjectdelta.getOid()); + PrismAsserts.assertNoItemDelta(leftObjectdelta, UserType.F_NAME); + PrismAsserts.assertNoItemDelta(leftObjectdelta, UserType.F_GIVEN_NAME); + PrismAsserts.assertPropertyReplace(leftObjectdelta, UserType.F_FAMILY_NAME); + PrismAsserts.assertPropertyReplace(leftObjectdelta, UserType.F_FULL_NAME, + createPolyString(USER_GUYBRUSH_FULL_NAME)); + PrismAsserts.assertPropertyReplace(leftObjectdelta, UserType.F_ADDITIONAL_NAME); + PrismAsserts.assertPropertyReplace(leftObjectdelta, UserType.F_LOCALITY, + createPolyString(USER_GUYBRUSH_LOCALITY)); + PrismAsserts.assertPropertyAdd(leftObjectdelta, UserType.F_EMPLOYEE_TYPE, + "SAILOR", "PIRATE WANNABE"); + PrismAsserts.assertPropertyAdd(leftObjectdelta, UserType.F_ORGANIZATION, + createPolyString("Pirate Wannabes"), createPolyString("Lovers")); + PrismAsserts.assertNoItemDelta(leftObjectdelta, UserType.F_ACTIVATION); + PrismAsserts.assertNoItemDelta(leftObjectdelta, + new ItemPath(UserType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS)); + PrismAsserts.assertNoItemDelta(leftObjectdelta, UserType.F_ROLE_MEMBERSHIP_REF); + + PrismAsserts.assertContainerAdd(leftObjectdelta, UserType.F_ASSIGNMENT, + FocusTypeUtil.createRoleAssignment(ROLE_THIEF_OID)); + + PrismAsserts.assertNoItemDelta(leftObjectdelta, UserType.F_LINK_REF); + + ObjectDelta leftLinkDelta = deltas.getLeftLinkDelta(); + PrismAsserts.assertReferenceAdd(leftLinkDelta, UserType.F_LINK_REF, guybrushDummyAccountCyanOid); + + ObjectDelta rightLinkDelta = deltas.getRightLinkDelta(); + PrismAsserts.assertReferenceDelete(rightLinkDelta, UserType.F_LINK_REF, guybrushDummyAccountCyanOid); + + } /** * MID-3460 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 8e43e39bd12..3f5a2d617bb 100644 --- a/model/model-intest/src/test/resources/common/system-configuration.xml +++ b/model/model-intest/src/test/resources/common/system-configuration.xml @@ -226,6 +226,64 @@ take take + + take + conflict + + + take + take + + + take + + + + + default-specific + + The default-specific config is almost the same as default, just the projections are selected + by specific resource. + + + name + take + + + givenName + take + ignore + + + familyName + + + fullName + take + + + employeeType + take + take + + + organization + take + take + + + organizationalUnit + take + + + activation + take + + + assignment + take + take + take