diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/PageMergeObjects.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/PageMergeObjects.java index 128bd57a81a..cf3cab6aab7 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/PageMergeObjects.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/PageMergeObjects.java @@ -19,6 +19,7 @@ import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.util.MergeDeltas; import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.schema.result.OperationResult; @@ -205,9 +206,9 @@ public boolean isEditingFocus() { @Override public void saveOrPreviewPerformed(AjaxRequestTarget target, OperationResult result, boolean previewOnly) { - ObjectDelta mergeDelta = mergeObjectsPanel.getMergeDelta(); + MergeDeltas mergeDeltas = mergeObjectsPanel.getMergeDeltas(); - ((ObjectWrapper)getObjectModel().getObject()).setOldDelta(mergeDelta); + ((ObjectWrapper)getObjectModel().getObject()).setOldDelta(mergeDeltas.getLeftObjectDelta()); super.saveOrPreviewPerformed(target, result, previewOnly); deleteUser(mergeWithObject.getOid(), target); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/MergeObjectsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/MergeObjectsPanel.java index 0b1dedb415e..7673ef8280f 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/MergeObjectsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/MergeObjectsPanel.java @@ -17,6 +17,7 @@ import com.evolveum.midpoint.gui.api.component.BasePanel; import com.evolveum.midpoint.gui.api.page.PageBase; +import com.evolveum.midpoint.model.api.util.MergeDeltas; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.schema.result.OperationResult; @@ -68,7 +69,7 @@ public class MergeObjectsPanel extends BasePanel{ private IModel mergeObjectModel; private IModel mergeWithObjectModel; private PrismObject mergeResultObject; - private ObjectDelta mergeDelta; + private MergeDeltas mergeDeltas; private Class type; private PageBase pageBase; private IModel mergeTypeModel; @@ -292,14 +293,15 @@ private List getMergeTypeNames(){ } return mergeTypeNamesList; } - private PrismObject getMergeObjectsResult(){ + + private PrismObject getMergeObjectsResult() { OperationResult result = new OperationResult(OPERATION_GET_MERGE_OBJECT_PREVIEW); PrismObject mergeResultObject = null; try { Task task = pageBase.createSimpleTask(OPERATION_GET_MERGE_OBJECT_PREVIEW); mergeResultObject = pageBase.getModelInteractionService().mergeObjectsPreviewObject(type, mergeObjectModel.getObject().getOid(), mergeWithObjectModel.getObject().getOid(), currentMergeType, task, result); - mergeDelta = pageBase.getModelInteractionService().mergeObjectsPreviewDelta(type, + mergeDeltas = pageBase.getModelInteractionService().mergeObjectsPreviewDeltas(type, mergeObjectModel.getObject().getOid(), mergeWithObjectModel.getObject().getOid(), currentMergeType, task, result); } catch (Exception ex) { result.recomputeStatus(); @@ -314,8 +316,8 @@ public PrismObject getMergeResultObject() { return mergeResultObject; } - public ObjectDelta getMergeDelta(){ - return mergeDelta; + public MergeDeltas getMergeDeltas() { + return mergeDeltas; } } diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java index 213e394b1d8..a9d98c8b9b0 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java @@ -31,6 +31,8 @@ import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; import com.evolveum.prism.xml.ns._public.types_3.ItemDeltaType; import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; +import com.evolveum.prism.xml.ns._public.types_3.ObjectReferenceType; + import org.apache.commons.lang.Validate; import java.io.Serializable; @@ -931,6 +933,18 @@ protected static void fillInModificationDeleteProperty } } + public void addModificationAddReference(QName propertyQName, PrismReferenceValue... refValues) { + fillInModificationAddReference(this, new ItemPath(propertyQName), refValues); + } + + public void addModificationDeleteReference(QName propertyQName, PrismReferenceValue... refValues) { + fillInModificationDeleteReference(this, new ItemPath(propertyQName), refValues); + } + + public void addModificationReplaceReference(QName propertyQName, PrismReferenceValue... refValues) { + fillInModificationReplaceReference(this, new ItemPath(propertyQName), refValues); + } + protected static void fillInModificationReplaceReference(ObjectDelta objectDelta, ItemPath refPath, PrismReferenceValue... refValues) { ReferenceDelta refDelta = objectDelta.createReferenceModification(refPath); @@ -939,6 +953,24 @@ protected static void fillInModificationReplaceReference( objectDelta.addModification(refDelta); } } + + protected static void fillInModificationAddReference(ObjectDelta objectDelta, + ItemPath refPath, PrismReferenceValue... refValues) { + ReferenceDelta refDelta = objectDelta.createReferenceModification(refPath); + if (refValues != null) { + refDelta.addValuesToAdd(refValues); + objectDelta.addModification(refDelta); + } + } + + protected static void fillInModificationDeleteReference(ObjectDelta objectDelta, + ItemPath refPath, PrismReferenceValue... refValues) { + ReferenceDelta refDelta = objectDelta.createReferenceModification(refPath); + if (refValues != null) { + refDelta.addValuesToDelete(refValues); + objectDelta.addModification(refDelta); + } + } private ReferenceDelta createReferenceModification(ItemPath refPath) { PrismObjectDefinition objDef = getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(getObjectTypeClass()); 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 4055627d0e4..9f154d4ecd5 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 @@ -61,6 +61,7 @@ import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; +import com.evolveum.midpoint.prism.delta.ReferenceDelta; import com.evolveum.midpoint.prism.match.MatchingRule; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; @@ -344,6 +345,14 @@ public static void assertIsAdd(ObjectDelta objectDelta) { public static void assertIsDelete(ObjectDelta objectDelta) { assert objectDelta.isDelete() : "Expected that object delta "+objectDelta+" is DELETE, but it is "+objectDelta.getChangeType(); } + + public static void assertEmpty(ObjectDelta objectDelta) { + assert objectDelta.isEmpty() : "Expected that object delta "+objectDelta+" is empty, but it is not"; + } + + public static void assertEmpty(String message, ObjectDelta objectDelta) { + assert objectDelta.isEmpty() : "Expected that object delta "+message+" is empty, but it is: "+objectDelta; + } public static void assertPropertyReplace(ObjectDelta objectDelta, QName propertyName, Object... expectedValues) { PropertyDelta propertyDelta = objectDelta.findPropertyDelta(propertyName); @@ -417,6 +426,24 @@ public static void assertPropertyDelete(Collection modifica assertSet("delta "+propertyDelta+" for "+propertyPath.last(), "delete", propertyDelta.getValuesToDelete(), expectedValues); } + public static void assertReferenceAdd(ObjectDelta objectDelta, QName refName, String... expectedOids) { + ReferenceDelta refDelta = objectDelta.findReferenceModification(refName); + assertNotNull("Reference delta for "+refName+" not found",refDelta); + assertOidSet("delta "+refDelta+" for "+refName, "add", refDelta.getValuesToAdd(), expectedOids); + } + + public static void assertReferenceDelete(ObjectDelta objectDelta, QName refName, String... expectedOids) { + ReferenceDelta refDelta = objectDelta.findReferenceModification(refName); + assertNotNull("Reference delta for "+refName+" not found",refDelta); + assertOidSet("delta "+refDelta+" for "+refName, "delete", refDelta.getValuesToDelete(), expectedOids); + } + + public static void assertReferenceReplace(ObjectDelta objectDelta, QName refName, String... expectedOids) { + ReferenceDelta refDelta = objectDelta.findReferenceModification(refName); + assertNotNull("Reference delta for "+refName+" not found",refDelta); + assertOidSet("delta "+refDelta+" for "+refName, "replace", refDelta.getValuesToReplace(), expectedOids); + } + public static void assertNoItemDelta(ObjectDelta objectDelta, QName itemName) { assertNoItemDelta(objectDelta, new ItemPath(itemName)); } @@ -809,6 +836,30 @@ public static void assertValues(String message, Collection actualPValues, String... expectedOids) { + assertOidValues(setName + " set in " + inMessage, actualPValues, expectedOids); + } + + public static void assertOidValues(String message, Collection actualRValues, String... expectedOids) { + assertNotNull("Null set in " + message, actualRValues); + if (expectedOids.length != actualRValues.size()) { + fail("Wrong number of values in " + message+ "; expected "+expectedOids.length+" (oids) " + +PrettyPrinter.prettyPrint(expectedOids)+"; has "+actualRValues.size()+" (rvalues) "+actualRValues); + } + for (PrismReferenceValue actualRValue: actualRValues) { + boolean found = false; + for (String oid: expectedOids) { + if (oid.equals(actualRValue.getOid())) { + found = true; + } + } + if (!found) { + fail("Unexpected value "+actualRValue+" in " + message + "; expected (oids) " + +PrettyPrinter.prettyPrint(expectedOids)+"; has (rvalues) "+actualRValues); + } + } + } + public static void assertSets(String message, Collection actualValues, T... expectedValues) { try { assertSets(message, null, actualValues, expectedValues); diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java index 0f1b7abadd3..b354f10694e 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java @@ -28,6 +28,7 @@ import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType; +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.OperationalStateType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType; @@ -161,6 +162,7 @@ public abstract class SchemaConstants { public static final ItemPath PATH_TRIGGER = new ItemPath(ObjectType.F_TRIGGER); public static final ItemPath PATH_CREDENTIALS_PASSWORD_FAILED_LOGINS = new ItemPath( UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_FAILED_LOGINS); + public static final ItemPath PATH_LINK_REF = new ItemPath(FocusType.F_LINK_REF); public static final String NS_PROVISIONING = NS_MIDPOINT_PUBLIC + "/provisioning"; public static final String NS_PROVISIONING_LIVE_SYNC = NS_PROVISIONING + "/liveSync-3"; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java index 1870c411a0c..45f7e336103 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java @@ -502,10 +502,18 @@ public static boolean matches(ShadowType shadowType, String resourceOid, ShadowK return MiscUtil.equals(intent, shadowType.getIntent()); } + /** + * Strict mathcing. E.g. null discriminator kind is intepreted as ACCOUNT and it must match the kind + * in the shadow. + */ public static boolean matches(PrismObject shadow, ResourceShadowDiscriminator discr) { return matches(shadow.asObjectable(), discr); } + /** + * Strict mathcing. E.g. null discriminator kind is intepreted as ACCOUNT and it must match the kind + * in the shadow. + */ public static boolean matches(ShadowType shadowType, ResourceShadowDiscriminator discr) { if (shadowType == null) { return false; @@ -519,7 +527,35 @@ public static boolean matches(ShadowType shadowType, ResourceShadowDiscriminator return ResourceShadowDiscriminator.equalsIntent(shadowType.getIntent(), discr.getIntent()); } + /** + * Interprets ResourceShadowDiscriminator as a pattern. E.g. null discriminator kind is + * interpreted to match any shadow kind. + */ + public static boolean matchesPattern(ShadowType shadowType, ShadowDiscriminatorType discr) { + if (shadowType == null) { + return false; + } + if (!discr.getResourceRef().getOid().equals(shadowType.getResourceRef().getOid())) { + return false; + } + if (discr.getKind() != null && !MiscUtil.equals(discr.getKind(), shadowType.getKind())) { + return false; + } + if (discr.getIntent() == null) { + return true; + } + return ResourceShadowDiscriminator.equalsIntent(shadowType.getIntent(), discr.getIntent()); + } + public static boolean isConflicting(ShadowType shadow1, ShadowType shadow2) { + if (!shadow1.getResourceRef().getOid().equals(shadow2.getResourceRef().getOid())) { + return false; + } + if (!MiscUtil.equals(getKind(shadow1), getKind(shadow2))) { + return false; + } + return ResourceShadowDiscriminator.equalsIntent(shadow1.getIntent(), shadow2.getIntent()); + } public static String getHumanReadableName(PrismObject shadow) { if (shadow == null) { 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 4cef4c73c9a..414ccdfc2ee 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 @@ -12371,6 +12371,14 @@ + + + + Projection merge configuration. It will be applied to merge projections (linkRefs) + of the objects. + + + @@ -12436,6 +12444,24 @@ + + + + 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 8127a0cbec3..e9850b0c892 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 @@ -17,6 +17,7 @@ import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.api.util.MergeDeltas; import com.evolveum.midpoint.model.api.visualizer.Scene; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; @@ -179,12 +180,12 @@ ModelContext previewChanges( ConnectorOperationalStatus getConnectorOperationalStatus(String resourceOid, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException; - ObjectDelta mergeObjectsPreviewDelta(Class type, + MergeDeltas mergeObjectsPreviewDeltas(Class type, String leftOid, String rightOid, String mergeConfigurationName, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException; + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException ; PrismObject mergeObjectsPreviewObject(Class type, String leftOid, String rightOid, String mergeConfigurationName, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException; + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException ; } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/MergeDeltas.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/MergeDeltas.java new file mode 100644 index 00000000000..22513e4dacd --- /dev/null +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/MergeDeltas.java @@ -0,0 +1,121 @@ +/** + * 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.api.util; + +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +/** + * @author semancik + * + */ +public class MergeDeltas implements DebugDumpable { + + private ObjectDelta leftObjectDelta; + private ObjectDelta leftLinkDelta; + private ObjectDelta rightLinkDelta; + + public MergeDeltas(ObjectDelta leftObjectDelta, ObjectDelta leftLinkDelta, + ObjectDelta rightLinkDelta) { + super(); + this.leftObjectDelta = leftObjectDelta; + this.leftLinkDelta = leftLinkDelta; + this.rightLinkDelta = rightLinkDelta; + } + + public ObjectDelta getLeftObjectDelta() { + return leftObjectDelta; + } + + public ObjectDelta getLeftLinkDelta() { + return leftLinkDelta; + } + + public ObjectDelta getRightLinkDelta() { + return rightLinkDelta; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((leftLinkDelta == null) ? 0 : leftLinkDelta.hashCode()); + result = prime * result + ((leftObjectDelta == null) ? 0 : leftObjectDelta.hashCode()); + result = prime * result + ((rightLinkDelta == null) ? 0 : rightLinkDelta.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MergeDeltas other = (MergeDeltas) obj; + if (leftLinkDelta == null) { + if (other.leftLinkDelta != null) { + return false; + } + } else if (!leftLinkDelta.equals(other.leftLinkDelta)) { + return false; + } + if (leftObjectDelta == null) { + if (other.leftObjectDelta != null) { + return false; + } + } else if (!leftObjectDelta.equals(other.leftObjectDelta)) { + return false; + } + if (rightLinkDelta == null) { + if (other.rightLinkDelta != null) { + return false; + } + } else if (!rightLinkDelta.equals(other.rightLinkDelta)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "MergeDeltas(leftObjectDelta=" + leftObjectDelta + ", leftLinkDelta=" + leftLinkDelta + + ", rightLinkDelta=" + rightLinkDelta + ")"; + } + + @Override + public String debugDump() { + return debugDump(0); + } + + @Override + public String debugDump(int indent) { + StringBuilder sb = new StringBuilder(); + DebugUtil.indentDebugDump(sb, indent); + sb.append("MergeDeltas\n"); + DebugUtil.debugDumpWithLabelLn(sb, "leftObjectDelta", leftObjectDelta, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "leftLinkDelta", leftLinkDelta, indent + 1); + DebugUtil.debugDumpWithLabel(sb, "rightLinkDelta", rightLinkDelta, indent + 1); + return sb.toString(); + } + +} 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 63e62bbc2e3..d934d81382c 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 @@ -35,6 +35,7 @@ import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.api.util.MergeDeltas; import com.evolveum.midpoint.model.impl.ModelObjectResolver; import com.evolveum.midpoint.model.impl.lens.ContextFactory; import com.evolveum.midpoint.model.impl.lens.LensContext; @@ -695,19 +696,20 @@ public ConnectorOperationalStatus getConnectorOperationalStatus(String resourceO } @Override - public ObjectDelta mergeObjectsPreviewDelta(Class type, String leftOid, + public MergeDeltas mergeObjectsPreviewDeltas(Class type, String leftOid, String rightOid, String mergeConfigurationName, Task task, OperationResult parentResult) - throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException { + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException { OperationResult result = parentResult.createMinorSubresult(MERGE_OBJECTS_PREVIEW_DELTA); try { - ObjectDelta objectDelta = objectMerger.computeMergeDelta(type, leftOid, rightOid, mergeConfigurationName, task, result); + MergeDeltas mergeDeltas = objectMerger.computeMergeDeltas(type, leftOid, rightOid, mergeConfigurationName, task, result); result.computeStatus(); - return objectDelta; + return mergeDeltas; - } catch (ObjectNotFoundException | SchemaException | ConfigurationException | ExpressionEvaluationException | RuntimeException | Error e) { + } catch (ObjectNotFoundException | SchemaException | ConfigurationException | ExpressionEvaluationException | + CommunicationException | SecurityViolationException | RuntimeException | Error e) { result.recordFatalError(e); throw e; } @@ -716,26 +718,32 @@ 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, ExpressionEvaluationException { + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException { OperationResult result = parentResult.createMinorSubresult(MERGE_OBJECTS_PREVIEW_OBJECT); try { - ObjectDelta objectDelta = objectMerger.computeMergeDelta(type, leftOid, rightOid, mergeConfigurationName, task, result); + MergeDeltas mergeDeltas = objectMerger.computeMergeDeltas(type, leftOid, rightOid, mergeConfigurationName, task, result); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Merge preview {} + {} deltas:\n{}", leftOid, rightOid, mergeDeltas.debugDump(1)); + } final PrismObject objectLeft = objectResolver.getObjectSimple(type, leftOid, null, task, result).asPrismObject(); - if (objectDelta == null) { + if (mergeDeltas == null) { result.computeStatus(); return objectLeft; } - objectDelta.applyTo(objectLeft); + mergeDeltas.getLeftObjectDelta().applyTo(objectLeft); + mergeDeltas.getLeftLinkDelta().applyTo(objectLeft); result.computeStatus(); return objectLeft; - } catch (ObjectNotFoundException | SchemaException | ConfigurationException | ExpressionEvaluationException | RuntimeException | Error e) { + } catch (ObjectNotFoundException | SchemaException | ConfigurationException | ExpressionEvaluationException | + CommunicationException | SecurityViolationException | 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 4ea432c9bdf..eccf27e284f 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 @@ -24,7 +24,9 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.api.PolicyViolationException; +import com.evolveum.midpoint.model.api.util.MergeDeltas; import com.evolveum.midpoint.model.common.expression.Expression; import com.evolveum.midpoint.model.common.expression.ExpressionEvaluationContext; import com.evolveum.midpoint.model.common.expression.ExpressionFactory; @@ -36,6 +38,8 @@ import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReference; +import com.evolveum.midpoint.prism.PrismReferenceValue; import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.prism.Visitable; import com.evolveum.midpoint.prism.Visitor; @@ -45,10 +49,15 @@ import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.ItemPath.CompareResult; import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.ObjectDeltaOperation; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; @@ -62,11 +71,16 @@ 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.FocusType; 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.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.ShadowDiscriminatorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemObjectsType; @@ -108,31 +122,55 @@ public Collection objectDelta = computeMergeDelta(type, leftOid, rightOid, mergeConfigurationName, task, result); + MergeDeltas deltas = computeMergeDeltas(type, leftOid, rightOid, mergeConfigurationName, task, result); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Merge {} + {} = (computed deltas)\n{}", leftOid, rightOid, deltas.debugDump(1)); + } + + Collection> executedDeltas = new ArrayList<>(); + + LOGGER.trace("Executing right link delta (raw): {}", deltas.getRightLinkDelta()); + executeDelta(deltas.getRightLinkDelta(), ModelExecuteOptions.createRaw(), executedDeltas, task, result); + + LOGGER.trace("Executing left link delta (raw): {}", deltas.getLeftLinkDelta()); + executeDelta(deltas.getLeftLinkDelta(), ModelExecuteOptions.createRaw(), executedDeltas, task, result); + + LOGGER.trace("Executing left object delta: {}", deltas.getLeftObjectDelta()); + executeDelta(deltas.getLeftObjectDelta(), null, executedDeltas, 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; + } + + private void executeDelta(ObjectDelta objectDelta, ModelExecuteOptions options, + Collection> executedDeltas, + Task task, OperationResult result) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, PolicyViolationException, SecurityViolationException { + + result.computeStatus(); + if (!result.isSuccess()) { + return; + } if (objectDelta != null && !objectDelta.isEmpty()) { - Collection> executedDeltas = - modelController.executeChanges(MiscSchemaUtil.createCollection(objectDelta), null, task, result); + Collection> deltaExecutedDeltas = + modelController.executeChanges(MiscSchemaUtil.createCollection(objectDelta), options, 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; + executedDeltas.addAll(deltaExecutedDeltas); } } - - public ObjectDelta computeMergeDelta(Class type, String leftOid, String rightOid, + + public MergeDeltas computeMergeDeltas(Class type, String leftOid, String rightOid, final String mergeConfigurationName, final Task task, final OperationResult result) - throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException { + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException { final PrismObject objectLeft = objectResolver.getObjectSimple(type, leftOid, null, task, result).asPrismObject(); final PrismObject objectRight = objectResolver.getObjectSimple(type, rightOid, null, task, result).asPrismObject(); @@ -146,8 +184,22 @@ public ObjectDelta computeMergeDelta(Class type, St // The "left" object is always the one that will be the result. We will use its OID. final ObjectDelta leftObjectDelta = objectLeft.createModifyDelta(); - + final ObjectDelta leftLinkDelta = objectLeft.createModifyDelta(); + final ObjectDelta rightLinkDelta = objectRight.createModifyDelta(); final List processedPaths = new ArrayList<>(); + + computeItemDeltas(leftObjectDelta, objectLeft, objectRight, processedPaths, mergeConfiguration, mergeConfigurationName, task, result); + computeDefaultDeltas(leftObjectDelta, objectLeft, objectRight, processedPaths, mergeConfiguration, mergeConfigurationName, task, result); + + computeProjectionDeltas(leftLinkDelta, rightLinkDelta, objectLeft, objectRight, mergeConfiguration, mergeConfigurationName, task, result); + + return new MergeDeltas<>(leftObjectDelta, leftLinkDelta, rightLinkDelta); + } + + private void computeItemDeltas(final ObjectDelta leftObjectDelta, + final PrismObject objectLeft, final PrismObject objectRight, final List processedPaths, + MergeConfigurationType mergeConfiguration, final String mergeConfigurationName, final Task task, final OperationResult result) throws SchemaException, ConfigurationException, ExpressionEvaluationException, ObjectNotFoundException { + for (ItemRefMergeConfigurationType itemMergeConfig: mergeConfiguration.getItem()) { ItemPath itemPath = itemMergeConfig.getRef().getItemPath(); processedPaths.add(itemPath); @@ -158,6 +210,12 @@ public ObjectDelta computeMergeDelta(Class type, St } } + } + + private void computeDefaultDeltas(final ObjectDelta leftObjectDelta, + final PrismObject objectLeft, final PrismObject objectRight, final List processedPaths, + MergeConfigurationType mergeConfiguration, final String mergeConfigurationName, final Task task, final OperationResult result) throws SchemaException, ConfigurationException, ExpressionEvaluationException, ObjectNotFoundException { + final ItemMergeConfigurationType defaultItemMergeConfig = mergeConfiguration.getDefault(); if (defaultItemMergeConfig != null) { try { @@ -174,6 +232,11 @@ public void visit(Visitable visitable) { if (itemPath == null || itemPath.isEmpty()) { return; } + + if (SchemaConstants.PATH_LINK_REF.equivalent(itemPath)) { + // Skip. There is a special processing for this. + return; + } boolean found = false; for (ItemPath processedPath: processedPaths) { @@ -236,10 +299,179 @@ public void visit(Visitable visitable) { } } - return leftObjectDelta; + } + + private void computeProjectionDeltas(final ObjectDelta leftLinkDelta, ObjectDelta rightLinkDelta, + final PrismObject objectLeft, final PrismObject objectRight, + MergeConfigurationType mergeConfiguration, final String mergeConfigurationName, final Task task, final OperationResult result) throws SchemaException, ConfigurationException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, SecurityViolationException { + + List projectionsLeft = getProjections(objectLeft, task, result); + List projectionsRight = getProjections(objectRight, task, result); + List mergedProjections = new ArrayList<>(); + List matchedProjections = new ArrayList<>(); + + ProjectionMergeConfigurationType defaultProjectionMergeConfig = null; + for (ProjectionMergeConfigurationType projectionMergeConfig: mergeConfiguration.getProjection()) { + ShadowDiscriminatorType discriminatorType = projectionMergeConfig.getProjectionDiscriminator(); + if (discriminatorType == null) { + defaultProjectionMergeConfig = projectionMergeConfig; + } else { + takeProjections(projectionMergeConfig.getLeft(), mergedProjections, matchedProjections, projectionsLeft, discriminatorType); + takeProjections(projectionMergeConfig.getRight(), mergedProjections, matchedProjections, projectionsRight, discriminatorType); + } + } + + LOGGER.trace("Merged projections (before default): {}", mergedProjections); + LOGGER.trace("Matched projections (before default): {}", matchedProjections); + + if (defaultProjectionMergeConfig != null) { + takeUnmatchedProjections(defaultProjectionMergeConfig.getLeft(), mergedProjections, matchedProjections, projectionsLeft); + takeUnmatchedProjections(defaultProjectionMergeConfig.getRight(), mergedProjections, matchedProjections, projectionsRight); + } + + LOGGER.trace("Merged projections: {}", mergedProjections); + + checkConflict(mergedProjections); + + for (ShadowType mergedProjection: mergedProjections) { + PrismReferenceValue leftLinkRef = findLinkRef(objectLeft, mergedProjection); + if (leftLinkRef == null) { + PrismReferenceValue linkRefRight = findLinkRef(objectRight, mergedProjection); + LOGGER.trace("Moving projection right->left: {}", mergedProjection); + addUnlinkDelta(rightLinkDelta, linkRefRight); + addLinkDelta(leftLinkDelta, linkRefRight); + } else { + LOGGER.trace("Projection already at the left: {}", mergedProjection); + } + } + + for (PrismReferenceValue leftLinkRef: getLinkRefs(objectLeft)) { + if (!hasProjection(mergedProjections, leftLinkRef)) { + LOGGER.trace("Removing left projection: {}", leftLinkRef); + addUnlinkDelta(leftLinkDelta, leftLinkRef); + } else { + LOGGER.trace("Left projection stays: {}", leftLinkRef); + } + } } + + private void addLinkDelta(ObjectDelta objectDelta, PrismReferenceValue linkRef) { + objectDelta.addModificationAddReference(FocusType.F_LINK_REF, linkRef.clone()); + } + + private void addUnlinkDelta(ObjectDelta objectDelta, PrismReferenceValue linkRef) { + objectDelta.addModificationDeleteReference(FocusType.F_LINK_REF, linkRef.clone()); + } + + private PrismReferenceValue findLinkRef(PrismObject object, ShadowType projection) { + for (PrismReferenceValue linkRef: getLinkRefs(object)) { + if (linkRef.getOid().equals(projection.getOid())) { + return linkRef; + } + } + return null; + } + private List getLinkRefs(PrismObject object) { + PrismReference ref = object.findReference(FocusType.F_LINK_REF); + if (ref == null) { + return new ArrayList<>(0); + } else { + return ref.getValues(); + } + } + + private boolean hasProjection(List mergedProjections, PrismReferenceValue leftLinkRef) { + for (ShadowType projection: mergedProjections) { + if (projection.getOid().equals(leftLinkRef.getOid())) { + return true; + } + } + return false; + } + + private boolean hasProjection(List mergedProjections, ShadowType candidateProjection) { + for (ShadowType projection: mergedProjections) { + if (projection.getOid().equals(candidateProjection.getOid())) { + return true; + } + } + return false; + } + + private List getProjections(PrismObject objectRight, Task task, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException { + if (!objectRight.canRepresent(FocusType.class)) { + return new ArrayList<>(0); + } + List linkRefs = ((FocusType)objectRight.asObjectable()).getLinkRef(); + List projections = new ArrayList<>(linkRefs.size()); + for (ObjectReferenceType linkRef: linkRefs) { + projections.add(getProjection(linkRef, task, result)); + } + return projections; + } + + private void takeProjections(MergeStategyType strategy, List mergedProjections, + List matchedProjections, List candidateProjections, + ShadowDiscriminatorType discriminatorType) { + if (strategy == MergeStategyType.TAKE) { + + LOGGER.trace("TAKE: Evaluating discriminator: {}", discriminatorType); + + for (ShadowType candidateProjection: candidateProjections) { + if (ShadowUtil.matchesPattern(candidateProjection, discriminatorType)) { + LOGGER.trace("Discriminator matches {}", candidateProjection); + mergedProjections.add(candidateProjection); + matchedProjections.add(candidateProjection); + } else { + LOGGER.trace("Discriminator does NOT match {}", candidateProjection); + } + } + + } else if (strategy == null || strategy == MergeStategyType.IGNORE) { + return; + } else { + throw new UnsupportedOperationException("Merge strategy "+strategy+" is not supported"); + } + } + + private void takeUnmatchedProjections(MergeStategyType strategy, List mergedProjections, + List matchedProjections, List candidateProjections) { + if (strategy == MergeStategyType.TAKE) { + + for (ShadowType candidateProjection: candidateProjections) { + if (!hasProjection(matchedProjections, candidateProjection)) { + mergedProjections.add(candidateProjection); + } + } + + } else if (strategy == null || strategy == MergeStategyType.IGNORE) { + return; + } else { + throw new UnsupportedOperationException("Merge strategy "+strategy+" is not supported"); + } + } + + private void checkConflict(List projections) throws SchemaException { + for (ShadowType projection: projections) { + for (ShadowType cprojection: projections) { + if (cprojection == projection) { + continue; + } + if (ShadowUtil.isConflicting(projection, cprojection)) { + 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()); + return objectResolver.getObject(ShadowType.class, linkRef.getOid(), options, task, result); + } + private ItemDelta mergeItem(PrismObject objectLeft, PrismObject objectRight, String mergeConfigurationName, ItemMergeConfigurationType itemMergeConfig, ItemPath itemPath, Task task, OperationResult result) throws SchemaException, ConfigurationException, ExpressionEvaluationException, ObjectNotFoundException { 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 68373bc7157..e9c0dc345af 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 @@ -113,13 +113,19 @@ public class AbstractConfiguredModelIntegrationTest extends AbstractModelIntegra protected static final String RESOURCE_DUMMY_RED_NAME = "red"; protected static final String RESOURCE_DUMMY_RED_NAMESPACE = MidPointConstants.NS_RI; - // BLUE resource has WEAK mappings + // BLUE resource has WEAK mappings, outbound/inbound protected static final File RESOURCE_DUMMY_BLUE_FILE = new File(COMMON_DIR, "resource-dummy-blue.xml"); protected static final File RESOURCE_DUMMY_BLUE_DEPRECATED_FILE = new File(COMMON_DIR, "resource-dummy-blue-deprecated.xml"); protected static final String RESOURCE_DUMMY_BLUE_OID = "10000000-0000-0000-0000-000000000204"; protected static final String RESOURCE_DUMMY_BLUE_NAME = "blue"; protected static final String RESOURCE_DUMMY_BLUE_NAMESPACE = MidPointConstants.NS_RI; + // CYAN has WEAK mappings, outbound only + protected static final File RESOURCE_DUMMY_CYAN_FILE = new File(COMMON_DIR, "resource-dummy-cyan.xml"); + protected static final String RESOURCE_DUMMY_CYAN_OID = "10000000-0000-0000-0000-00000000c204"; + protected static final String RESOURCE_DUMMY_CYAN_NAME = "cyan"; + protected static final String RESOURCE_DUMMY_CYAN_NAMESPACE = MidPointConstants.NS_RI; + // WHITE dummy resource has almost no configuration: no schema, no schemahandling, no synchronization, ... protected static final String RESOURCE_DUMMY_WHITE_FILENAME = COMMON_DIR + "/resource-dummy-white.xml"; protected static final String RESOURCE_DUMMY_WHITE_OID = "10000000-0000-0000-0000-000000000304"; @@ -216,6 +222,12 @@ public class AbstractConfiguredModelIntegrationTest extends AbstractModelIntegra protected static final File ROLE_SAILOR_FILE = new File(COMMON_DIR, "role-sailor.xml"); protected static final String ROLE_SAILOR_OID = "12345111-1111-2222-1111-121212111113"; protected static final String ROLE_SAILOR_DRINK = "grog"; + + protected static final File ROLE_RED_SAILOR_FILE = new File(COMMON_DIR, "role-red-sailor.xml"); + protected static final String ROLE_RED_SAILOR_OID = "12345111-1111-2222-1111-121212111223"; + + protected static final File ROLE_CYAN_SAILOR_FILE = new File(COMMON_DIR, "role-cyan-sailor.xml"); + protected static final String ROLE_CYAN_SAILOR_OID = "d3abd794-9c30-11e6-bb5a-af14bf2cc29b"; protected static final File USER_JACK_FILE = new File(COMMON_DIR, "user-jack.xml"); protected static final String USER_JACK_OID = "c0c010c0-d34d-b33f-f00d-111111111111"; diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractInitializedModelIntegrationTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractInitializedModelIntegrationTest.java index 0e5b8031bdd..ecd4f07a7bb 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractInitializedModelIntegrationTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/AbstractInitializedModelIntegrationTest.java @@ -116,7 +116,12 @@ public class AbstractInitializedModelIntegrationTest extends AbstractConfiguredM protected DummyResourceContoller dummyResourceCtlBlue; protected ResourceType resourceDummyBlueType; protected PrismObject resourceDummyBlue; - + + protected DummyResource dummyResourceCyan; + protected DummyResourceContoller dummyResourceCtlCyan; + protected ResourceType resourceDummyCyanType; + protected PrismObject resourceDummyCyan; + protected DummyResource dummyResourceWhite; protected DummyResourceContoller dummyResourceCtlWhite; protected ResourceType resourceDummyWhiteType; @@ -192,7 +197,14 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti dummyResourceBlue = dummyResourceCtlBlue.getDummyResource(); resourceDummyBlue = importAndGetObjectFromFile(ResourceType.class, getResourceDummyBlueFile(), RESOURCE_DUMMY_BLUE_OID, initTask, initResult); resourceDummyBlueType = resourceDummyBlue.asObjectable(); - dummyResourceCtlBlue.setResource(resourceDummyBlue); + dummyResourceCtlBlue.setResource(resourceDummyBlue); + + dummyResourceCtlCyan = DummyResourceContoller.create(RESOURCE_DUMMY_CYAN_NAME, resourceDummyBlue); + dummyResourceCtlCyan.extendSchemaPirate(); + dummyResourceCyan = dummyResourceCtlCyan.getDummyResource(); + resourceDummyCyan = importAndGetObjectFromFile(ResourceType.class, RESOURCE_DUMMY_CYAN_FILE, RESOURCE_DUMMY_CYAN_OID, initTask, initResult); + resourceDummyCyanType = resourceDummyCyan.asObjectable(); + dummyResourceCtlCyan.setResource(resourceDummyCyan); dummyResourceCtlWhite = DummyResourceContoller.create(RESOURCE_DUMMY_WHITE_NAME, resourceDummyWhite); dummyResourceCtlWhite.extendSchemaPirate(); @@ -295,6 +307,8 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti repoAddObjectFromFile(ROLE_THIEF_FILE, RoleType.class, initResult); repoAddObjectFromFile(ROLE_EMPTY_FILE, RoleType.class, initResult); repoAddObjectFromFile(ROLE_SAILOR_FILE, RoleType.class, initResult); + repoAddObjectFromFile(ROLE_RED_SAILOR_FILE, RoleType.class, initResult); + repoAddObjectFromFile(ROLE_CYAN_SAILOR_FILE, RoleType.class, initResult); // Orgstruct if (doAddOrgstruct()) { 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 0a62bab0246..9326fba30dc 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 @@ -25,6 +25,7 @@ import org.springframework.test.context.ContextConfiguration; import org.testng.annotations.Test; +import com.evolveum.midpoint.model.api.util.MergeDeltas; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.ItemPath; @@ -48,6 +49,11 @@ public class TestMerge extends AbstractInitializedModelIntegrationTest { public static final String MERGE_CONFIG_DEFAULT_NAME = "default"; public static final String MERGE_CONFIG_EXPRESSION_NAME = "expression"; + + private String jackDummyAccountOid; + private String jackDummyAccountRedOid; + private String guybrushDummyAccountOid; + private String guybrushDummyAccountCyanOid; @Override public void initSystem(Task initTask, OperationResult initResult) throws Exception { @@ -58,16 +64,43 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti modifyUserAdd(USER_GUYBRUSH_OID, UserType.F_ORGANIZATION, initTask, initResult, createPolyString("Pirate Wannabes"), createPolyString("Sailors"), createPolyString("Rum Club"), createPolyString("Lovers")); assignRole(USER_GUYBRUSH_OID, ROLE_SAILOR_OID, initTask, initResult); + assignRole(USER_GUYBRUSH_OID, ROLE_CYAN_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("Sailors"), createPolyString("Rum Club"), createPolyString("Drinkers")); assignRole(USER_JACK_OID, ROLE_SAILOR_OID, initTask, initResult); + assignRole(USER_JACK_OID, ROLE_RED_SAILOR_OID, initTask, initResult); assignRole(USER_JACK_OID, ROLE_EMPTY_OID, initTask, initResult); assignRole(USER_JACK_OID, ROLE_PIRATE_OID, initTask, initResult); assignRole(USER_JACK_OID, ROLE_NICE_PIRATE_OID, initTask, initResult); } + + @Test + public void test000Sanity() throws Exception { + final String TEST_NAME = "test000Sanity"; + TestUtil.displayTestTile(this, TEST_NAME); + + PrismObject userJackBefore = getUser(USER_JACK_OID); + display("Jack before", userJackBefore); + + jackDummyAccountOid = assertAccount(userJackBefore, RESOURCE_DUMMY_OID); + jackDummyAccountRedOid = assertAccount(userJackBefore, RESOURCE_DUMMY_RED_OID); + assertLinks(userJackBefore, 2); + + PrismObject userGuybrushBefore = getUser(USER_GUYBRUSH_OID); + display("Guybrush before", userGuybrushBefore); + + guybrushDummyAccountOid = assertAccount(userGuybrushBefore, RESOURCE_DUMMY_OID); + guybrushDummyAccountCyanOid = assertAccount(userGuybrushBefore, RESOURCE_DUMMY_CYAN_OID); + assertLinks(userGuybrushBefore, 2); + + display("Jack DUMMY account", jackDummyAccountOid); + display("Jack RED account", jackDummyAccountRedOid); + display("Guybrush DUMMY account", guybrushDummyAccountOid); + display("Guybrush CYAN account", guybrushDummyAccountCyanOid); + } @Test public void test100MergeJackGuybrushPreviewDelta() throws Exception { @@ -85,8 +118,8 @@ public void test100MergeJackGuybrushPreviewDelta() throws Exception { // WHEN TestUtil.displayWhen(TEST_NAME); - ObjectDelta delta = - modelInteractionService.mergeObjectsPreviewDelta(UserType.class, + MergeDeltas deltas = + modelInteractionService.mergeObjectsPreviewDeltas(UserType.class, USER_JACK_OID, USER_GUYBRUSH_OID, MERGE_CONFIG_DEFAULT_NAME, task, result); // THEN @@ -94,30 +127,39 @@ public void test100MergeJackGuybrushPreviewDelta() throws Exception { result.computeStatus(); TestUtil.assertSuccess(result); - display("Delta", delta); + display("Deltas", deltas); - 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, + 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(delta, UserType.F_ADDITIONAL_NAME); - PrismAsserts.assertPropertyReplace(delta, UserType.F_LOCALITY, + PrismAsserts.assertPropertyReplace(leftObjectdelta, UserType.F_ADDITIONAL_NAME); + PrismAsserts.assertPropertyReplace(leftObjectdelta, UserType.F_LOCALITY, createPolyString(USER_GUYBRUSH_LOCALITY)); - PrismAsserts.assertPropertyAdd(delta, UserType.F_EMPLOYEE_TYPE, + PrismAsserts.assertPropertyAdd(leftObjectdelta, UserType.F_EMPLOYEE_TYPE, "SAILOR", "PIRATE WANNABE"); - PrismAsserts.assertPropertyAdd(delta, UserType.F_ORGANIZATION, + PrismAsserts.assertPropertyAdd(leftObjectdelta, UserType.F_ORGANIZATION, createPolyString("Pirate Wannabes"), createPolyString("Lovers")); - PrismAsserts.assertNoItemDelta(delta, UserType.F_ACTIVATION); - PrismAsserts.assertNoItemDelta(delta, + PrismAsserts.assertNoItemDelta(leftObjectdelta, UserType.F_ACTIVATION); + PrismAsserts.assertNoItemDelta(leftObjectdelta, new ItemPath(UserType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS)); - PrismAsserts.assertNoItemDelta(delta, UserType.F_ROLE_MEMBERSHIP_REF); + PrismAsserts.assertNoItemDelta(leftObjectdelta, UserType.F_ROLE_MEMBERSHIP_REF); - PrismAsserts.assertContainerAdd(delta, UserType.F_ASSIGNMENT, + 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); + } @Test @@ -159,8 +201,13 @@ public void test102MergeJackGuybrushPreviewObject() throws Exception { 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); + assertAssignedRoles(object, ROLE_SAILOR_OID, ROLE_RED_SAILOR_OID, ROLE_CYAN_SAILOR_OID, + ROLE_EMPTY_OID, ROLE_THIEF_OID, ROLE_PIRATE_OID, ROLE_NICE_PIRATE_OID); + + assertLinked(object, jackDummyAccountOid); + assertLinked(object, jackDummyAccountRedOid); + assertLinked(object, guybrushDummyAccountCyanOid); + assertLinks(object, 3); } @Test @@ -179,8 +226,8 @@ public void test110MergeGuybrushJackPreviewDelta() throws Exception { // WHEN TestUtil.displayWhen(TEST_NAME); - ObjectDelta delta = - modelInteractionService.mergeObjectsPreviewDelta(UserType.class, + MergeDeltas deltas = + modelInteractionService.mergeObjectsPreviewDeltas(UserType.class, USER_GUYBRUSH_OID, USER_JACK_OID, MERGE_CONFIG_DEFAULT_NAME, task, result); // THEN @@ -188,8 +235,9 @@ public void test110MergeGuybrushJackPreviewDelta() throws Exception { result.computeStatus(); TestUtil.assertSuccess(result); - display("Delta", delta); + display("Deltas", deltas); + ObjectDelta delta = deltas.getLeftObjectDelta(); PrismAsserts.assertIsModify(delta); assertEquals("Wrong delta OID", USER_GUYBRUSH_OID, delta.getOid()); PrismAsserts.assertNoItemDelta(delta, UserType.F_NAME); @@ -213,8 +261,14 @@ public void test110MergeGuybrushJackPreviewDelta() throws Exception { PrismAsserts.assertContainerAdd(delta, UserType.F_ASSIGNMENT, FocusTypeUtil.createRoleAssignment(ROLE_PIRATE_OID), FocusTypeUtil.createRoleAssignment(ROLE_NICE_PIRATE_OID)); + + PrismAsserts.assertNoItemDelta(delta, UserType.F_LINK_REF); + ObjectDelta leftLinkDelta = deltas.getLeftLinkDelta(); + PrismAsserts.assertEmpty("leftLinkDelta", leftLinkDelta); + ObjectDelta rightLinkDelta = deltas.getRightLinkDelta(); + PrismAsserts.assertEmpty("rightLinkDelta", rightLinkDelta); } @Test @@ -257,8 +311,8 @@ public void test112MergeGuybrushJackPreviewObject() throws Exception { 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); + assertAssignedRoles(object, ROLE_SAILOR_OID, ROLE_RED_SAILOR_OID, ROLE_CYAN_SAILOR_OID, + ROLE_EMPTY_OID, ROLE_THIEF_OID, ROLE_PIRATE_OID, ROLE_NICE_PIRATE_OID); } @@ -281,8 +335,8 @@ public void test200MergeJackGuybrushExpressionPreviewDelta() throws Exception { // WHEN TestUtil.displayWhen(TEST_NAME); - ObjectDelta delta = - modelInteractionService.mergeObjectsPreviewDelta(UserType.class, + MergeDeltas deltas = + modelInteractionService.mergeObjectsPreviewDeltas(UserType.class, USER_JACK_OID, USER_GUYBRUSH_OID, MERGE_CONFIG_EXPRESSION_NAME, task, result); // THEN @@ -290,8 +344,9 @@ public void test200MergeJackGuybrushExpressionPreviewDelta() throws Exception { result.computeStatus(); TestUtil.assertSuccess(result); - display("Delta", delta); + display("Deltas", deltas); + ObjectDelta delta = deltas.getLeftObjectDelta(); PrismAsserts.assertIsModify(delta); assertEquals("Wrong delta OID", USER_JACK_OID, delta.getOid()); PrismAsserts.assertNoItemDelta(delta, UserType.F_NAME); @@ -358,8 +413,8 @@ public void test202MergeJackGuybrushExpressionPreviewObject() throws Exception { 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); + assertAssignedRoles(object, ROLE_SAILOR_OID, ROLE_RED_SAILOR_OID, ROLE_CYAN_SAILOR_OID, + ROLE_EMPTY_OID, ROLE_THIEF_OID, ROLE_PIRATE_OID, ROLE_NICE_PIRATE_OID); } @@ -399,8 +454,8 @@ public void test500MergeJackGuybrush() throws Exception { PrismAsserts.assertPropertyValue(object, UserType.F_EMPLOYEE_TYPE, USER_JACK_EMPLOYEE_TYPE, "SAILOR", "PIRATE WANNABE"); - assertAssignedRoles(object, ROLE_SAILOR_OID, ROLE_EMPTY_OID, ROLE_THIEF_OID, - ROLE_PIRATE_OID, ROLE_NICE_PIRATE_OID); + assertAssignedRoles(object, ROLE_SAILOR_OID, ROLE_RED_SAILOR_OID, ROLE_CYAN_SAILOR_OID, + ROLE_EMPTY_OID, ROLE_THIEF_OID, ROLE_PIRATE_OID, ROLE_NICE_PIRATE_OID); assertNoObject(UserType.class, USER_GUYBRUSH_OID); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestValidityRecomputeTask.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestValidityRecomputeTask.java index cc798084092..e4f4ff3f8c8 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestValidityRecomputeTask.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestValidityRecomputeTask.java @@ -82,10 +82,7 @@ public class TestValidityRecomputeTask extends AbstractInitializedModelIntegrati protected static final File ROLE_RED_JUDGE_FILE = new File(TEST_DIR, "role-red-judge.xml"); protected static final String ROLE_RED_JUDGE_OID = "12345111-1111-2222-1111-121212111222"; - - protected static final File ROLE_RED_SAILOR_FILE = new File(TEST_DIR, "role-red-sailor.xml"); - protected static final String ROLE_RED_SAILOR_OID = "12345111-1111-2222-1111-121212111223"; - + protected static final File ROLE_BIG_JUDGE_FILE = new File(TEST_DIR, "role-big-judge.xml"); protected static final String ROLE_BIG_JUDGE_OID = "12345111-1111-2222-1111-121212111224"; @@ -99,7 +96,6 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti super.initSystem(initTask, initResult); repoAddObjectFromFile(ROLE_RED_JUDGE_FILE, RoleType.class, initResult); - repoAddObjectFromFile(ROLE_RED_SAILOR_FILE, RoleType.class, initResult); repoAddObjectFromFile(ROLE_BIG_JUDGE_FILE, RoleType.class, initResult); DebugUtil.setDetailedDebugDump(true); diff --git a/model/model-intest/src/test/resources/common/resource-dummy-cyan.xml b/model/model-intest/src/test/resources/common/resource-dummy-cyan.xml new file mode 100644 index 00000000000..d640008ac51 --- /dev/null +++ b/model/model-intest/src/test/resources/common/resource-dummy-cyan.xml @@ -0,0 +1,196 @@ + + + + + + + + Dummy Resource Cyan + + + + + connectorType + com.evolveum.icf.dummy.connector.DummyConnector + + + connectorVersion + 2.0 + + + + + + + + cyan + true + + + + + + + account + default + Default Account + true + ri:AccountObjectClass + + icfs:name + Username + + weak + + name + + + + + + + + icfs:uid + UID + + + ri:fullname + Full Name + + weak + + $user/fullName + + + + + + + + ri:ship + Ship + + weak + + $user/organizationalUnit + + + + + ri:location + Location + + weak + + + $user/locality + + + + + + ri:drink + + weak + + + uuid + + + + + + ri:quote + Quote + false + + weak + + $user/description + + + $user/fullName + + + + + + + + ri:gossip + true + + weak + + $configuration/name + + + + + 5 + + + + + weak + + + + + + + + weak + + + + + + + + weak + + + + + + + + + + diff --git a/model/model-intest/src/test/resources/common/role-cyan-sailor.xml b/model/model-intest/src/test/resources/common/role-cyan-sailor.xml new file mode 100644 index 00000000000..46689584e40 --- /dev/null +++ b/model/model-intest/src/test/resources/common/role-cyan-sailor.xml @@ -0,0 +1,36 @@ + + + Cyan Sailor + + + + + account + + ri:drink + + + grog + + + + + + diff --git a/model/model-intest/src/test/resources/sync/role-red-sailor.xml b/model/model-intest/src/test/resources/common/role-red-sailor.xml similarity index 100% rename from model/model-intest/src/test/resources/sync/role-red-sailor.xml rename to model/model-intest/src/test/resources/common/role-red-sailor.xml 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 4d93bcc4508..22bf8a8a66c 100644 --- a/model/model-intest/src/test/resources/common/system-configuration.xml +++ b/model/model-intest/src/test/resources/common/system-configuration.xml @@ -226,10 +226,15 @@ take take - - linkRef + + take + + + + + take - + take @@ -253,6 +258,21 @@ + + allLeftProjectionsBoth + + Takes all projections. There may be conflicting projections. + Attempt to merge should end with an error. + + + take + + + take + take + + + expression @@ -316,10 +336,9 @@ take take - - linkRef + take - + take 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 752e7a64840..f486d158de0 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 @@ -426,9 +426,10 @@ protected void assertNoLinkedAccount(PrismObject user) { + accountRef.getValues(); } - protected void assertAccount(PrismObject user, String resourceOid) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException { + protected String assertAccount(PrismObject user, String resourceOid) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException { String accountOid = getLinkRefOid(user, resourceOid); assertNotNull("User " + user + " has no account on resource " + resourceOid, accountOid); + return accountOid; } protected void assertAccounts(String userOid, int numAccounts) throws ObjectNotFoundException, SchemaException {