diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/ObjectWrapperUtil.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/ObjectWrapperUtil.java index 55a72bd91f2..cd531b0c1f8 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/ObjectWrapperUtil.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/util/ObjectWrapperUtil.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.web.component.prism.ContainerStatus; import com.evolveum.midpoint.web.component.prism.ObjectWrapper; import com.evolveum.midpoint.web.page.PageBase; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationPhaseType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; @@ -24,7 +25,8 @@ public static ObjectWrapper createObjectWrapper(String di public static ObjectWrapper createObjectWrapper(String displayName, String description, PrismObject object, ContainerStatus status, boolean delayContainerCreation, PageBase pageBase) { try { - PrismContainerDefinition objectDefinitionForEditing = pageBase.getModelInteractionService().getEditObjectDefinition(object); + + PrismContainerDefinition objectDefinitionForEditing = pageBase.getModelInteractionService().getEditObjectDefinition(object, AuthorizationPhaseType.REQUEST); RefinedObjectClassDefinition objectClassDefinitionForEditing = null; if (isShadow(object)) { PrismReference resourceRef = object.findReference(ShadowType.F_RESOURCE_REF); @@ -39,6 +41,7 @@ public static ObjectWrapper createObjectWrapper(String di } } + private static boolean isShadow(PrismObject object){ return (object.getCompileTimeClass() != null && ShadowType.class.isAssignableFrom(object .getCompileTimeClass())) diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismValue.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismValue.java index a225bc9336d..e71ab662569 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismValue.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismValue.java @@ -19,6 +19,7 @@ import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.exception.SchemaException; @@ -27,6 +28,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.HashSet; import java.util.Set; @@ -170,6 +172,19 @@ public static boolean containsRealValue(Collection col return false; } + public static boolean equalsRealValues(Collection collection1, Collection collection2) { + Comparator comparator = new Comparator() { + @Override + public int compare(V v1, V v2) { + if (v1.equalsRealValue(v2)) { + return 0; + }; + return 1; + } + }; + return MiscUtil.unorderedCollectionEquals(collection1, collection2, comparator); + } + public abstract boolean isEmpty(); public void normalize() { diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/xml/XmlTypeConverter.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/xml/XmlTypeConverter.java index 52977e8376a..0756f279468 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/xml/XmlTypeConverter.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/xml/XmlTypeConverter.java @@ -584,4 +584,8 @@ public static XMLGregorianCalendar addMillis(XMLGregorianCalendar now, long dura return createXMLGregorianCalendar(toMillis(now) + duration); } + public static String formatDateXml(Date date) { + return createXMLGregorianCalendar(date).toXMLFormat(); + } + } 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 1f6f01f64a8..3bc82d748fa 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 @@ -263,6 +263,13 @@ private static void applyObjectClass(PrismObject shadow, objectClassDefinition, objectClassDefinition.getPrismContext()); attributesContainer.applyDefinition(racDef, true); } + + public static PrismObjectDefinition applyObjectClass(PrismObjectDefinition shadowDefinition, + ObjectClassComplexTypeDefinition objectClassDefinition) throws SchemaException { + PrismObjectDefinition shadowDefClone = shadowDefinition.cloneWithReplacedDefinition(ShadowType.F_ATTRIBUTES, + objectClassDefinition.toResourceAttributeContainerDefinition()); + return shadowDefClone; + } /** * Returns intent from the shadow. Backwards compatible with older accountType. May also adjust for default diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/DOMUtil.java b/infra/util/src/main/java/com/evolveum/midpoint/util/DOMUtil.java index 66d73bf9b0c..c8d2491c171 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/DOMUtil.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/DOMUtil.java @@ -25,6 +25,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; 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 a8aea94a7d0..892cb146687 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 @@ -32,6 +32,7 @@ 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.xml.ns._public.common.common_3.AuthorizationPhaseType; 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.ResourceType; @@ -99,7 +100,7 @@ ModelContext previewChanges( * @return schema with correctly set constraint parts or null * @throws SchemaException */ - PrismObjectDefinition getEditObjectDefinition(PrismObject object) throws SchemaException; + PrismObjectDefinition getEditObjectDefinition(PrismObject object, AuthorizationPhaseType phase) throws SchemaException; RefinedObjectClassDefinition getEditObjectClassDefinition(PrismObject shadow, PrismObject resource) throws SchemaException; diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java index 8767dacd187..a9da7a8a99f 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionUtil.java @@ -83,6 +83,7 @@ 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.MappingType; 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.ResourceType; @@ -539,4 +540,17 @@ public static Map compileVariablesAndSources(ExpressionEvaluation return variablesAndSources; } + public static boolean hasExplicitTarget(List mappingTypes) { + for (MappingType mappingType: mappingTypes) { + if (hasExplicitTarget(mappingType)) { + return true; + } + } + return false; + } + + private static boolean hasExplicitTarget(MappingType mappingType) { + return mappingType.getTarget() != null; + } + } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java index 8a3972e11e4..52380fbbf91 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/Mapping.java @@ -116,6 +116,7 @@ public class Mapping implements DebugDumpable { private ObjectResolver objectResolver = null; private Source defaultSource = null; private ItemDefinition defaultTargetDefinition = null; + private ItemPath defaultTargetPath = null; private ObjectDeltaObject sourceContext = null; private PrismObjectDefinition targetContext = null; private PrismValueDeltaSetTriple outputTriple = null; @@ -205,6 +206,14 @@ public void setDefaultTargetDefinition(ItemDefinition defaultTargetDefinition) { this.defaultTargetDefinition = defaultTargetDefinition; } + public ItemPath getDefaultTargetPath() { + return defaultTargetPath; + } + + public void setDefaultTargetPath(ItemPath defaultTargetPath) { + this.defaultTargetPath = defaultTargetPath; + } + public ObjectDeltaObject getSourceContext() { return sourceContext; } @@ -846,6 +855,7 @@ private void parseTarget() throws SchemaException { MappingTargetDeclarationType targetType = mappingType.getTarget(); if (targetType == null) { outputDefinition = defaultTargetDefinition; + outputPath = defaultTargetPath; } else { ItemPathType itemPathType = targetType.getPath(); if (itemPathType == null) { 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 e51cf91d151..0ffac61dfaf 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 @@ -133,6 +133,7 @@ import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ImportOptionsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationDecisionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationPhaseType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorHostType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; @@ -705,7 +706,7 @@ public ModelContext previewChanges( } @Override - public PrismObjectDefinition getEditObjectDefinition(PrismObject object) throws SchemaException { + public PrismObjectDefinition getEditObjectDefinition(PrismObject object, AuthorizationPhaseType phase) throws SchemaException { PrismObjectDefinition origDefinition = object.getDefinition(); // TODO: maybe we need to expose owner resolver in the interface? ObjectSecurityConstraints securityConstraints = securityEnforcer.compileSecurityConstraints(object, null); @@ -716,9 +717,9 @@ public PrismObjectDefinition getEditObjectDefinition(P return null; } PrismObjectDefinition finalDefinition = applySecurityContraints(origDefinition, new ItemPath(), securityConstraints, - securityConstraints.getActionDecision(ModelAuthorizationAction.READ.getUrl(), null), - securityConstraints.getActionDecision(ModelAuthorizationAction.ADD.getUrl(), null), - securityConstraints.getActionDecision(ModelAuthorizationAction.MODIFY.getUrl(), null)); + securityConstraints.getActionDecision(ModelAuthorizationAction.READ.getUrl(), phase), + securityConstraints.getActionDecision(ModelAuthorizationAction.ADD.getUrl(), phase), + securityConstraints.getActionDecision(ModelAuthorizationAction.MODIFY.getUrl(), phase)); return finalDefinition; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java index eb46520e797..2cf4c4010cc 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java @@ -164,6 +164,9 @@ public class LensProjectionContext extends LensElementContext implem private transient Map>>> squeezedAttributes; private transient Map>>> squeezedAssociations; + // Cached copy, to avoid constructing it over and over again + private transient PrismObjectDefinition shadowDefinition = null; + private ValuePolicyType accountPasswordPolicy; @@ -313,7 +316,20 @@ public void setResource(ResourceType resource) { this.resource = resource; } - public boolean isAssigned() { + @Override + public PrismObjectDefinition getObjectDefinition() { + if (shadowDefinition == null) { + try { + shadowDefinition = ShadowUtil.applyObjectClass(super.getObjectDefinition(), getRefinedAccountDefinition()); + } catch (SchemaException e) { + // This should not happen + throw new SystemException(e.getMessage(), e); + } + } + return shadowDefinition; + } + + public boolean isAssigned() { return isAssigned; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index 5fd89ffd453..388271a1ba0 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -698,6 +698,26 @@ public static PropertyDelta findAPrioriDelta(LensCo return aPrioriDelta; } + /** + * Extracts the delta from this projection context and also from all other projection contexts that have + * equivalent discriminator. + */ + public static ObjectDelta findAPrioriDelta(LensContext context, + LensProjectionContext projCtx) throws SchemaException { + ObjectDelta aPrioriDelta = null; + for (LensProjectionContext aProjCtx: findRelatedContexts(context, projCtx)) { + ObjectDelta aProjDelta = aProjCtx.getDelta(); + if (aProjDelta != null) { + if (aPrioriDelta == null) { + aPrioriDelta = aProjDelta.clone(); + } else { + aPrioriDelta.merge(aProjDelta); + } + } + } + return aPrioriDelta; + } + /** * Returns a list of context that have equivalent discriminator with the reference context. Ordered by "order" in the * discriminator. diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java index 0dca057adde..90a435647cd 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java @@ -17,6 +17,7 @@ import com.evolveum.midpoint.model.api.PolicyViolationException; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.common.expression.ExpressionUtil; import com.evolveum.midpoint.model.common.expression.ItemDeltaItem; import com.evolveum.midpoint.model.common.expression.Source; import com.evolveum.midpoint.model.common.mapping.Mapping; @@ -42,8 +43,10 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; @@ -272,20 +275,26 @@ public void processActivationUserCurrent(LensContext co LOGGER.trace("Skipping activation status processing because {} does not have activation status capability", accCtx.getResource()); } - if (capValidFrom != null) { + ResourceBidirectionalMappingType validFromMappingType = activationType.getValidFrom(); + if (validFromMappingType == null || validFromMappingType.getOutbound() == null) { + LOGGER.trace("Skipping activation validFrom processing because {} does not have appropriate outbound mapping", accCtx.getResource()); + } else if (capValidFrom == null && !ExpressionUtil.hasExplicitTarget(validFromMappingType.getOutbound())) { + LOGGER.trace("Skipping activation validFrom processing because {} does not have activation validFrom capability nor outbound mapping with explicit target", accCtx.getResource()); + } else { evaluateActivationMapping(context, accCtx, activationType.getValidFrom(), SchemaConstants.PATH_ACTIVATION_VALID_FROM, SchemaConstants.PATH_ACTIVATION_VALID_FROM, null, now, true, ActivationType.F_VALID_FROM.getLocalPart(), task, result); - } else { - LOGGER.trace("Skipping activation validFrom processing because {} does not have activation validFrom capability", accCtx.getResource()); } - if (capValidTo != null) { + ResourceBidirectionalMappingType validToMappingType = activationType.getValidTo(); + if (validToMappingType == null || validToMappingType.getOutbound() == null) { + LOGGER.trace("Skipping activation validTo processing because {} does not have appropriate outbound mapping", accCtx.getResource()); + } else if (capValidFrom == null && !ExpressionUtil.hasExplicitTarget(validToMappingType.getOutbound())) { + LOGGER.trace("Skipping activation validTo processing because {} does not have activation validTo capability nor outbound mapping with explicit target", accCtx.getResource()); + } else { evaluateActivationMapping(context, accCtx, activationType.getValidTo(), SchemaConstants.PATH_ACTIVATION_VALID_TO, SchemaConstants.PATH_ACTIVATION_VALID_TO, null, now, true, ActivationType.F_VALID_TO.getLocalPart(), task, result); - } else { - LOGGER.trace("Skipping activation validTo processing because {} does not have activation validTo capability", accCtx.getResource()); } } @@ -401,9 +410,9 @@ private boolean evaluateExistenceMapping(final LensContext final LensProjectionContext accCtx, final XMLGregorianCalendar now, final boolean current, Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { - String accCtxDesc = accCtx.toHumanReadableString(); + final String accCtxDesc = accCtx.toHumanReadableString(); - Boolean legal = accCtx.isLegal(); + final Boolean legal = accCtx.isLegal(); if (legal == null) { throw new IllegalStateException("Null 'legal' for "+accCtxDesc); } @@ -429,13 +438,6 @@ private boolean evaluateExistenceMapping(final LensContext MappingInitializer> initializer = new MappingInitializer>() { @Override public void initialize(Mapping> existenceMapping) throws SchemaException { - // Target - PrismPropertyDefinition shadowExistsDef = new PrismPropertyDefinition(SHADOW_EXISTS_PROPERTY_NAME, - DOMUtil.XSD_BOOLEAN, prismContext); - shadowExistsDef.setMinOccurs(1); - shadowExistsDef.setMaxOccurs(1); - existenceMapping.setDefaultTargetDefinition(shadowExistsDef); - // Source: legal ItemDeltaItem> legalSourceIdi = getLegalIdi(accCtx); Source> legalSource @@ -466,53 +468,84 @@ public void initialize(Mapping> existenceMapping) th }; - PrismValueDeltaSetTriple> outputTriple = mappingHelper.evaluateMappingSetProjection( - outbound, "outbound existence mapping in projection " + accCtxDesc, - now, initializer, null, null, accCtx.getObjectOld(), current, null, context, accCtx, task, result); - - if (outputTriple == null) { - // The "default existence mapping" - return legal; - } - - Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); - if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { - throw new ExpressionEvaluationException("Activation existence expression resulted in null or empty value for projection " + accCtxDesc); - } - if (nonNegativeValues.size() > 1) { - throw new ExpressionEvaluationException("Activation existence expression resulted in too many values ("+nonNegativeValues.size()+") for projection " + accCtxDesc); - } + final MutableBoolean output = new MutableBoolean(false); + + MappingOutputProcessor> processor = new MappingOutputProcessor>() { + @Override + public void process(ItemPath mappingOutputPath, + PrismValueDeltaSetTriple> outputTriple) throws ExpressionEvaluationException { + if (outputTriple == null) { + // The "default existence mapping" + output.setValue(legal); + return; + } + + Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); + if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { + throw new ExpressionEvaluationException("Activation existence expression resulted in null or empty value for projection " + accCtxDesc); + } + if (nonNegativeValues.size() > 1) { + throw new ExpressionEvaluationException("Activation existence expression resulted in too many values ("+nonNegativeValues.size()+") for projection " + accCtxDesc); + } + + output.setValue(nonNegativeValues.iterator().next().getValue()); + } + }; + + MappingEvaluatorHelperParams, ShadowType, F> params = new MappingEvaluatorHelperParams<>(); + params.setMappingTypes(outbound); + params.setMappingDesc("outbound existence mapping in projection " + accCtxDesc); + params.setNow(now); + params.setInitializer(initializer); + params.setProcessor(processor); + params.setAPrioriTargetObject(accCtx.getObjectOld()); + params.setEvaluateCurrent(current); + params.setTargetContext(accCtx); + params.setFixTarget(true); + params.setContext(context); + + PrismPropertyDefinition shadowExistsDef = new PrismPropertyDefinition(SHADOW_EXISTS_PROPERTY_NAME, + DOMUtil.XSD_BOOLEAN, prismContext); + shadowExistsDef.setMinOccurs(1); + shadowExistsDef.setMaxOccurs(1); + params.setTargetItemDefinition(shadowExistsDef); + mappingHelper.evaluateMappingSetProjection(params, task, result); + +// PrismValueDeltaSetTriple> outputTriple = mappingHelper.evaluateMappingSetProjection( +// outbound, "outbound existence mapping in projection " + accCtxDesc, +// now, initializer, null, null, accCtx.getObjectOld(), current, null, context, accCtx, task, result); - return nonNegativeValues.iterator().next().getValue(); + return (boolean) output.getValue(); + } - private PropertyDelta evaluateActivationMapping(final LensContext context, - final LensProjectionContext accCtx, ResourceBidirectionalMappingType bidirectionalMappingType, + private void evaluateActivationMapping(final LensContext context, + final LensProjectionContext projCtx, ResourceBidirectionalMappingType bidirectionalMappingType, final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, final ActivationCapabilityType capActivation, XMLGregorianCalendar now, final boolean current, String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { - String accCtxDesc = accCtx.toHumanReadableString(); + String accCtxDesc = projCtx.toHumanReadableString(); if (bidirectionalMappingType == null) { LOGGER.trace("No '{}' definition in activation in projection {}, skipping", desc, accCtxDesc); - return null; + return; } List outbound = bidirectionalMappingType.getOutbound(); if (outbound == null || outbound.isEmpty()) { LOGGER.trace("No outbound definition in '{}' definition in activation in projection {}, skipping", desc, accCtxDesc); - return null; + return; } - ObjectDelta projectionDelta = accCtx.getDelta(); - PropertyDelta shadowPropertyDelta = LensUtil.findAPrioriDelta(context, accCtx, projectionPropertyPath); + ObjectDelta projectionDelta = projCtx.getDelta(); + PropertyDelta shadowPropertyDelta = LensUtil.findAPrioriDelta(context, projCtx, projectionPropertyPath); - PrismObject shadowNew = accCtx.getObjectNew(); + PrismObject shadowNew = projCtx.getObjectNew(); PrismProperty shadowPropertyNew = null; if (shadowNew != null) { shadowPropertyNew = shadowNew.findProperty(projectionPropertyPath); } - + MappingInitializer> initializer = new MappingInitializer>() { @Override public void initialize(Mapping> mapping) throws SchemaException { @@ -548,7 +581,7 @@ public void initialize(Mapping> mapping) throws SchemaExce } // Source: legal - ItemDeltaItem> legalIdi = getLegalIdi(accCtx); + ItemDeltaItem> legalIdi = getLegalIdi(projCtx); Source> legalSource = new Source>(legalIdi, ExpressionConstants.VAR_LEGAL); mapping.addSource(legalSource); @@ -564,90 +597,29 @@ public void initialize(Mapping> mapping) throws SchemaExce // Variable: user (for convenience, same as "focus") mapping.addVariableDefinition(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject()); - mapping.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, accCtx.getResource()); - - // Target - PrismObjectDefinition shadowDefinition = - prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class); - PrismPropertyDefinition shadowPropertyDefinition = - shadowDefinition.findPropertyDefinition(projectionPropertyPath); - mapping.setDefaultTargetDefinition(shadowPropertyDefinition); + mapping.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, projCtx.getResource()); mapping.setOriginType(OriginType.OUTBOUND); - mapping.setOriginObject(accCtx.getResource()); + mapping.setOriginObject(projCtx.getResource()); } }; - MutableBoolean strongMappingWasUsed = new MutableBoolean(); - - PrismValueDeltaSetTriple> outputTriple = mappingHelper.evaluateMappingSetProjection( - outbound, desc + " outbound activation mapping in projection " + accCtxDesc, - now, initializer, shadowPropertyNew, shadowPropertyDelta, shadowNew, current, strongMappingWasUsed, - context, accCtx, task, result); - - LOGGER.trace("evaluateActivationMapping for {} after evaluateMappingSetProjection: accCtx.isFullShadow = {}, accCtx.isFresh = {}, shadowPropertyDelta = {}, shadowPropertyNew = {}, outputTriple = {}, strongMappingWasUsed = {}", - new Object[] { desc, accCtx.isFullShadow(), accCtx.isFresh(), shadowPropertyDelta, shadowPropertyNew, outputTriple, strongMappingWasUsed}); - - if (outputTriple == null) { - LOGGER.trace("Activation '{}' expression resulted in null triple for projection {}, skipping", desc, accCtxDesc); - return null; - } - - PropertyDelta projectionPropertyDelta = PropertyDelta.createDelta(projectionPropertyPath, ShadowType.class, prismContext); - - if (accCtx.isAdd()) { - - Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); - if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { - LOGGER.trace("Activation '{}' expression resulted in null or empty value for projection {}, skipping", desc, accCtxDesc); - return null; - } - projectionPropertyDelta.setValuesToReplace(PrismValue.cloneCollection(nonNegativeValues)); - - } else { - - // if we have fresh information (full shadow) AND the mapping used to derive the information was strong, - // we will consider all values (zero & plus sets) -- otherwise, we take only the "plus" (i.e. changed) set - - // the first case is necessary, because in some situations (e.g. when mapping is changed) - // the evaluator sees no differences w.r.t. real state, even if there is a difference - // - and we must have a way to push new information onto the resource + MappingEvaluatorHelperParams, ShadowType, F> params = new MappingEvaluatorHelperParams<>(); + params.setMappingTypes(outbound); + params.setMappingDesc(desc + " outbound activation mapping in projection " + accCtxDesc); + params.setNow(now); + params.setInitializer(initializer); + params.setProcessor(null); + params.setAPrioriTargetObject(shadowNew); + params.setAPrioriTargetDelta(LensUtil.findAPrioriDelta(context, projCtx)); + params.setTargetContext(projCtx); + params.setDefaultTargetItemPath(projectionPropertyPath); + params.setEvaluateCurrent(current); + params.setContext(context); + params.setHasFullTargetObject(projCtx.hasFullShadow()); + mappingHelper.evaluateMappingSetProjection(params, task, result); - Collection> valuesToReplace; - - if (accCtx.isFullShadow() && strongMappingWasUsed.isTrue()) { - valuesToReplace = outputTriple.getNonNegativeValues(); - } else { - valuesToReplace = outputTriple.getPlusSet(); - } - - if (valuesToReplace != null && !valuesToReplace.isEmpty()) { - - // if what we want to set is the same as is already in the shadow, we skip that - // (we insist on having full shadow, to be sure we work with current data) - - if (accCtx.isFullShadow() && accCtx.isFresh() && shadowPropertyNew != null) { - Set realValuesPresent = new HashSet(shadowPropertyNew.getRealValues()); - Set realValuesComputed = PrismValue.getRealValuesOfCollection(valuesToReplace); - if (realValuesPresent.equals(realValuesComputed)) { - LOGGER.trace("Activation '{}' expression resulted in existing values for projection {}, skipping creation of a delta", desc, accCtxDesc); - return null; - } - } - projectionPropertyDelta.setValuesToReplace(PrismValue.cloneCollection(valuesToReplace)); - } - - } - - if (projectionPropertyDelta.isEmpty()) { - return null; - } - - LOGGER.trace("Adding new '{}' delta for account {}: {}", new Object[]{desc, accCtxDesc, projectionPropertyDelta}); - accCtx.swallowToSecondaryDelta(projectionPropertyDelta); - - return projectionPropertyDelta; } private ItemDeltaItem> getLegalIdi(LensProjectionContext accCtx) throws SchemaException { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/InboundProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/InboundProcessor.java index a1ebb74ad20..8180ce826c6 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/InboundProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/InboundProcessor.java @@ -558,11 +558,6 @@ private void processSpecialPropertyInbound(Collection property = newUser.findOrCreateProperty(sourcePath); - ObjectDelta userPrimaryDelta = context.getFocusContext().getPrimaryDelta(); PropertyDelta primaryPropDelta = null; if (userPrimaryDelta != null) { @@ -573,6 +568,14 @@ private void processSpecialPropertyInbound(Collection userSecondaryDelta = context.getFocusContext().getProjectionWaveSecondaryDelta(); + if (userSecondaryDelta != null) { + PropertyDelta delta = userSecondaryDelta.findPropertyDelta(sourcePath); + if (delta != null) { + //remove delta if exists, it will be handled by inbound + userSecondaryDelta.getModifications().remove(delta); + } + } MappingInitializer initializer = new MappingInitializer() { @Override @@ -591,10 +594,10 @@ public void initialize(Mapping mapping) throws SchemaException { } } - ObjectDelta aPrioriDelta = getAPrioriDelta(context, accContext); + ObjectDelta aPrioriShadowDelta = getAPrioriDelta(context, accContext); ItemDelta> specialAttributeDelta = null; - if (aPrioriDelta != null){ - specialAttributeDelta = aPrioriDelta.findItemDelta(sourcePath); + if (aPrioriShadowDelta != null){ + specialAttributeDelta = aPrioriShadowDelta.findItemDelta(sourcePath); } ItemDeltaItem> sourceIdi = accContext.getObjectDeltaObject().findIdi(sourcePath); if (specialAttributeDelta == null){ @@ -604,8 +607,6 @@ public void initialize(Mapping mapping) throws SchemaException { sourceIdi.getItemOld(), ExpressionConstants.VAR_INPUT); mapping.setDefaultSource(source); - mapping.setDefaultTargetDefinition(property.getDefinition()); - mapping.addVariableDefinition(ExpressionConstants.VAR_USER, newUser); mapping.addVariableDefinition(ExpressionConstants.VAR_FOCUS, newUser); @@ -620,33 +621,57 @@ public void initialize(Mapping mapping) throws SchemaException { } }; - MutableBoolean strongMappingWasUsed = new MutableBoolean(); - PrismValueDeltaSetTriple> outputTriple = mappingEvaluatorHelper.evaluateMappingSetProjection( - inboundMappingTypes, "inbound mapping for " + sourcePath + " in " + accContext.getResource(), now, initializer, property, primaryPropDelta, newUser, true, strongMappingWasUsed, context, accContext, task, opResult); - + MappingOutputProcessor processor = new MappingOutputProcessor() { + @Override + public void process(ItemPath mappingOutputPath, PrismValueDeltaSetTriple outputTriple) + throws ExpressionEvaluationException, SchemaException { + if (outputTriple == null){ + LOGGER.trace("Mapping for property {} evaluated to null. Skipping inboud processing for that property.", sourcePath); + return; + } + + ObjectDelta userSecondaryDelta = context.getFocusContext().getProjectionWaveSecondaryDelta(); + if (userSecondaryDelta != null) { + PropertyDelta delta = userSecondaryDelta.findPropertyDelta(sourcePath); + if (delta != null) { + //remove delta if exists, it will be handled by inbound + userSecondaryDelta.getModifications().remove(delta); + } + } + + PrismObjectDefinition focusDefinition = context.getFocusContext().getObjectDefinition(); + PrismProperty result = focusDefinition.findPropertyDefinition(sourcePath).instantiate(); + result.addAll(PrismValue.cloneCollection(outputTriple.getNonNegativeValues())); + + PrismProperty targetPropertyNew = newUser.findOrCreateProperty(sourcePath); + PropertyDelta delta = targetPropertyNew.diff(result); + if (delta != null && !delta.isEmpty()) { + delta.setParentPath(sourcePath.allExceptLast()); + context.getFocusContext().swallowToProjectionWaveSecondaryDelta(delta); + } - if (outputTriple == null){ - LOGGER.trace("Mapping for property {} evaluated to null. Skipping inboud processing for that property.", sourcePath); - return; - } + } + }; - ObjectDelta userSecondaryDelta = context.getFocusContext().getProjectionWaveSecondaryDelta(); - if (userSecondaryDelta != null) { - PropertyDelta delta = userSecondaryDelta.findPropertyDelta(sourcePath); - if (delta != null) { - //remove delta if exists, it will be handled by inbound - userSecondaryDelta.getModifications().remove(delta); - } - } + MappingEvaluatorHelperParams params = new MappingEvaluatorHelperParams<>(); + params.setMappingTypes(inboundMappingTypes); + params.setMappingDesc("inbound mapping for " + sourcePath + " in " + accContext.getResource()); + params.setNow(now); + params.setInitializer(initializer); + params.setProcessor(processor); + params.setAPrioriTargetObject(newUser); + params.setAPrioriTargetDelta(userPrimaryDelta); + params.setTargetContext(context.getFocusContext()); + params.setDefaultTargetItemPath(sourcePath); + params.setEvaluateCurrent(true); + params.setContext(context); + params.setHasFullTargetObject(true); + mappingEvaluatorHelper.evaluateMappingSetProjection(params, task, opResult); - PrismProperty result = property.getDefinition().instantiate(); - result.addAll(PrismValue.cloneCollection(outputTriple.getNonNegativeValues())); - - PropertyDelta delta = property.diff(result); - if (delta != null && !delta.isEmpty()) { - delta.setParentPath(sourcePath.allExceptLast()); - context.getFocusContext().swallowToProjectionWaveSecondaryDelta(delta); - } +// MutableBoolean strongMappingWasUsed = new MutableBoolean(); +// PrismValueDeltaSetTriple> outputTriple = mappingEvaluatorHelper.evaluateMappingSetProjection( +// inboundMappingTypes, "inbound mapping for " + sourcePath + " in " + accContext.getResource(), now, initializer, targetPropertyNew, primaryPropDelta, newUser, true, strongMappingWasUsed, context, accContext, task, opResult); + } private Collection getMappingApplicableToChannel( diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/MappingEvaluationHelper.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/MappingEvaluationHelper.java index 257b714d8cc..e571d27c37a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/MappingEvaluationHelper.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/MappingEvaluationHelper.java @@ -17,8 +17,12 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import javax.xml.datatype.DatatypeConstants; import javax.xml.datatype.XMLGregorianCalendar; @@ -30,16 +34,19 @@ import com.evolveum.midpoint.model.common.mapping.Mapping; import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensElementContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.model.impl.trigger.RecomputeTriggerHandler; import com.evolveum.midpoint.prism.Containerable; import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContainerDefinition; import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.prism.delta.ContainerDelta; import com.evolveum.midpoint.prism.delta.ItemDelta; @@ -82,40 +89,58 @@ public class MappingEvaluationHelper { * Used to know whether (when doing reconciliation) this value should be forcibly put onto the resource, even * if it was not changed (i.e. if it's only in the zero set). */ - public PrismValueDeltaSetTriple evaluateMappingSetProjection(Collection mappingTypes, String mappingDesc, - XMLGregorianCalendar now, MappingInitializer initializer, - Item aPrioriValue, ItemDelta aPrioriDelta, PrismObject aPrioriObject, - Boolean evaluateCurrent, MutableBoolean strongMappingWasUsed, - LensContext context, LensProjectionContext accCtx, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { +// public void evaluateMappingSetProjection( +// Collection mappingTypes, String mappingDesc, +// XMLGregorianCalendar now, MappingInitializer initializer, MappingOutputProcessor processor, +// Item aPrioriValue, ItemDelta aPrioriDelta, PrismObject aPrioriObject, +// Boolean evaluateCurrent, MutableBoolean strongMappingWasUsed, +// LensContext context, LensElementContext targetContext, +// Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { - PrismValueDeltaSetTriple outputTriple = null; + + public void evaluateMappingSetProjection( + MappingEvaluatorHelperParams params, + Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { + + String mappingDesc = params.getMappingDesc(); + LensElementContext targetContext = params.getTargetContext(); + PrismObjectDefinition targetObjectDefinition = targetContext.getObjectDefinition(); + ItemPath defaultTargetItemPath = params.getDefaultTargetItemPath(); + + Map> outputTripleMap = new HashMap<>(); XMLGregorianCalendar nextRecomputeTime = null; + Collection mappingTypes = params.getMappingTypes(); Collection> mappings = new ArrayList>(mappingTypes.size()); - if (strongMappingWasUsed != null) { - strongMappingWasUsed.setValue(false); - } - for (MappingType mappingType: mappingTypes) { Mapping mapping = valueConstructionFactory.createMapping(mappingType, mappingDesc); - if (!mapping.isApplicableToChannel(context.getChannel())) { + if (!mapping.isApplicableToChannel(params.getContext().getChannel())) { continue; } - mapping.setNow(now); + mapping.setNow(params.getNow()); + if (defaultTargetItemPath != null && targetObjectDefinition != null) { + ItemDefinition defaultTargetItemDef = targetObjectDefinition.findItemDefinition(defaultTargetItemPath); + mapping.setDefaultTargetDefinition(defaultTargetItemDef); + mapping.setDefaultTargetPath(defaultTargetItemPath); + } else { + mapping.setDefaultTargetDefinition(params.getTargetItemDefinition()); + mapping.setDefaultTargetPath(defaultTargetItemPath); + } + mapping.setTargetContext(targetObjectDefinition); // Initialize mapping (using Inversion of Control) - initializer.initialize(mapping); + params.getInitializer().initialize(mapping); Boolean timeConstraintValid = mapping.evaluateTimeConstraintValid(result); - if (evaluateCurrent != null) { - if (evaluateCurrent && !timeConstraintValid) { + if (params.getEvaluateCurrent() != null) { + if (params.getEvaluateCurrent() && !timeConstraintValid) { continue; } - if (!evaluateCurrent && timeConstraintValid) { + if (!params.getEvaluateCurrent() && timeConstraintValid) { continue; } } @@ -130,23 +155,38 @@ public PrismValueDeltaSetTriple e continue; } - if (mapping.getStrength() != MappingStrengthType.STRONG) { - if (aPrioriDelta != null && !aPrioriDelta.isEmpty()) { - continue; - } - } + ItemPath mappingOutputPath = mapping.getOutputPath(); + if (params.isFixTarget() && mappingOutputPath != null && defaultTargetItemPath != null && !mappingOutputPath.equivalent(defaultTargetItemPath)) { + throw new ExpressionEvaluationException("Target cannot be overridden in "+mappingDesc); + } + + if (params.getAPrioriTargetDelta() != null && mappingOutputPath != null) { + ItemDelta aPrioriItemDelta = params.getAPrioriTargetDelta().findItemDelta(mappingOutputPath); + if (mapping.getStrength() != MappingStrengthType.STRONG) { + if (aPrioriItemDelta != null && !aPrioriItemDelta.isEmpty()) { + continue; + } + } + } - LensUtil.evaluateMapping(mapping, context, task, result); + LensUtil.evaluateMapping(mapping, params.getContext(), task, result); PrismValueDeltaSetTriple mappingOutputTriple = mapping.getOutputTriple(); if (mappingOutputTriple != null) { - if (mapping.getStrength() == MappingStrengthType.STRONG && strongMappingWasUsed != null) { - strongMappingWasUsed.setValue(true); + MappingOutputStruct mappingOutputStruct = outputTripleMap.get(mappingOutputPath); + if (mappingOutputStruct == null) { + mappingOutputStruct = new MappingOutputStruct<>(); + outputTripleMap.put(mappingOutputPath, mappingOutputStruct); + } + + if (mapping.getStrength() == MappingStrengthType.STRONG) { + mappingOutputStruct.setStrongMappingWasUsed(true); } + PrismValueDeltaSetTriple outputTriple = mappingOutputStruct.getOutputTriple(); if (outputTriple == null) { - outputTriple = mappingOutputTriple; + mappingOutputStruct.setOutputTriple(mappingOutputTriple); } else { outputTriple.merge(mappingOutputTriple); } @@ -154,20 +194,40 @@ public PrismValueDeltaSetTriple e } - if ((aPrioriValue == null || aPrioriValue.isEmpty()) && outputTriple == null) { - // Second pass, evaluate only weak mappings - for (Mapping mapping: mappings) { + + // Second pass, evaluate only weak mappings + for (Mapping mapping: mappings) { + ItemPath mappingOutputPath = mapping.getOutputPath(); + if (params.isFixTarget() && mappingOutputPath != null && defaultTargetItemPath != null && !mappingOutputPath.equivalent(defaultTargetItemPath)) { + throw new ExpressionEvaluationException("Target cannot be overridden in "+mappingDesc); + } + + MappingOutputStruct mappingOutputStruct = outputTripleMap.get(mappingOutputPath); + if (mappingOutputStruct == null) { + mappingOutputStruct = new MappingOutputStruct<>(); + outputTripleMap.put(mappingOutputPath, mappingOutputStruct); + } + + PrismValueDeltaSetTriple outputTriple = mappingOutputStruct.getOutputTriple(); + + Item aPrioriTargetItem = null; + PrismObject aPrioriTargetObject = params.getAPrioriTargetObject(); + if (aPrioriTargetObject != null && mappingOutputPath != null) { + aPrioriTargetItem = aPrioriTargetObject.findItem(mappingOutputPath); + } + if ((aPrioriTargetItem == null || aPrioriTargetItem.isEmpty()) && outputTriple == null) { + if (mapping.getStrength() != MappingStrengthType.WEAK) { continue; } - LensUtil.evaluateMapping(mapping, context, task, result); + LensUtil.evaluateMapping(mapping, params.getContext(), task, result); PrismValueDeltaSetTriple mappingOutputTriple = mapping.getOutputTriple(); if (mappingOutputTriple != null) { if (outputTriple == null) { - outputTriple = mappingOutputTriple; + mappingOutputStruct.setOutputTriple(mappingOutputTriple); } else { outputTriple.merge(mappingOutputTriple); } @@ -176,6 +236,93 @@ public PrismValueDeltaSetTriple e } } + MappingOutputProcessor processor = params.getProcessor(); + for (Entry> outputTripleMapEntry: outputTripleMap.entrySet()) { + ItemPath mappingOutputPath = outputTripleMapEntry.getKey(); + MappingOutputStruct mappingOutputStruct = outputTripleMapEntry.getValue(); + PrismValueDeltaSetTriple outputTriple = mappingOutputStruct.getOutputTriple(); + + if (processor != null) { + processor.process(mappingOutputPath, outputTriple); + } else { + + if (outputTriple == null) { + LOGGER.trace("{} expression resulted in null triple for {}, skipping", mappingDesc, targetContext); + continue; + } + + ItemDefinition targetItemDefinition = null; + if (mappingOutputPath != null) { + targetItemDefinition = targetObjectDefinition.findItemDefinition(mappingOutputPath); + if (targetItemDefinition == null) { + throw new SchemaException("No definition for item "+mappingOutputPath+" in "+targetObjectDefinition); + } + } else { + targetItemDefinition = params.getTargetItemDefinition(); + } + ItemDelta targetItemDelta = targetItemDefinition.createEmptyDelta(mappingOutputPath); + + Item aPrioriTargetItem = null; + PrismObject aPrioriTargetObject = params.getAPrioriTargetObject(); + if (aPrioriTargetObject != null) { + aPrioriTargetItem = (Item) aPrioriTargetObject.findItem(mappingOutputPath); + } + + if (targetContext.isAdd()) { + + Collection nonNegativeValues = outputTriple.getNonNegativeValues(); + if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { + LOGGER.trace("{} resulted in null or empty value for {}, skipping", mappingDesc, targetContext); + continue; + } + targetItemDelta.setValuesToReplace(PrismValue.cloneCollection(nonNegativeValues)); + + } else { + + // if we have fresh information (full shadow) AND the mapping used to derive the information was strong, + // we will consider all values (zero & plus sets) -- otherwise, we take only the "plus" (i.e. changed) set + + // the first case is necessary, because in some situations (e.g. when mapping is changed) + // the evaluator sees no differences w.r.t. real state, even if there is a difference + // - and we must have a way to push new information onto the resource + + Collection valuesToReplace; + + if (params.hasFullTargetObject() && mappingOutputStruct.isStrongMappingWasUsed()) { + valuesToReplace = outputTriple.getNonNegativeValues(); + } else { + valuesToReplace = outputTriple.getPlusSet(); + } + + if (valuesToReplace != null && !valuesToReplace.isEmpty()) { + + // if what we want to set is the same as is already in the shadow, we skip that + // (we insist on having full shadow, to be sure we work with current data) + + if (params.hasFullTargetObject() && targetContext.isFresh() && aPrioriTargetItem != null) { + Collection valuesPresent = aPrioriTargetItem.getValues(); + if (PrismValue.equalsRealValues(valuesPresent, valuesToReplace)) { + LOGGER.trace("{} resulted in existing values for {}, skipping creation of a delta", mappingDesc, targetContext); + continue; + } + } + targetItemDelta.setValuesToReplace(PrismValue.cloneCollection(valuesToReplace)); + } + + } + + if (targetItemDelta.isEmpty()) { + continue; + } + + LOGGER.trace("{} adding new delta for {}: {}", new Object[]{mappingDesc, targetContext, targetItemDelta}); + targetContext.swallowToSecondaryDelta(targetItemDelta); + } + + } + + // Figure out recompute time + for (Mapping mapping: mappings) { XMLGregorianCalendar mappingNextRecomputeTime = mapping.getNextRecomputeTime(); if (mappingNextRecomputeTime != null) { @@ -188,8 +335,8 @@ public PrismValueDeltaSetTriple e if (nextRecomputeTime != null) { boolean alreadyHasTrigger = false; - if (aPrioriObject != null) { - for (TriggerType trigger: aPrioriObject.asObjectable().getTrigger()) { + if (params.getAPrioriTargetObject() != null) { + for (TriggerType trigger: params.getAPrioriTargetObject().asObjectable().getTrigger()) { if (RecomputeTriggerHandler.HANDLER_URI.equals(trigger.getHandlerUri()) && nextRecomputeTime.equals(trigger.getTimestamp())) { alreadyHasTrigger = true; @@ -199,8 +346,7 @@ public PrismValueDeltaSetTriple e } if (!alreadyHasTrigger) { - PrismObjectDefinition objectDefinition = accCtx.getObjectDefinition(); - PrismContainerDefinition triggerContDef = objectDefinition.findContainerDefinition(ObjectType.F_TRIGGER); + PrismContainerDefinition triggerContDef = targetObjectDefinition.findContainerDefinition(ObjectType.F_TRIGGER); ContainerDelta triggerDelta = triggerContDef.createEmptyDelta(new ItemPath(ObjectType.F_TRIGGER)); PrismContainerValue triggerCVal = triggerContDef.createValue(); triggerDelta.addValueToAdd(triggerCVal); @@ -208,11 +354,34 @@ public PrismValueDeltaSetTriple e triggerType.setTimestamp(nextRecomputeTime); triggerType.setHandlerUri(RecomputeTriggerHandler.HANDLER_URI); - accCtx.swallowToSecondaryDelta(triggerDelta); + targetContext.swallowToSecondaryDelta(triggerDelta); } - } + } + } + + private class MappingOutputStruct { + private PrismValueDeltaSetTriple outputTriple = null; + private boolean strongMappingWasUsed = false; + + public PrismValueDeltaSetTriple getOutputTriple() { + return outputTriple; + } + + public void setOutputTriple(PrismValueDeltaSetTriple outputTriple) { + this.outputTriple = outputTriple; + } + + public boolean isStrongMappingWasUsed() { + return strongMappingWasUsed; + } + + public void setStrongMappingWasUsed(boolean strongMappingWasUsed) { + this.strongMappingWasUsed = strongMappingWasUsed; + } + - return outputTriple; } + + } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/MappingEvaluatorHelperParams.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/MappingEvaluatorHelperParams.java new file mode 100644 index 00000000000..d3060d2df34 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/MappingEvaluatorHelperParams.java @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2014 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.model.impl.lens.projector; + +import java.util.Collection; + +import javax.xml.datatype.XMLGregorianCalendar; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensElementContext; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +/** + * @author semancik + * + */ +public class MappingEvaluatorHelperParams { + + private Collection mappingTypes; + private String mappingDesc; + private XMLGregorianCalendar now; + private MappingInitializer initializer; + private MappingOutputProcessor processor; + private PrismObject aPrioriTargetObject; + private ObjectDelta aPrioriTargetDelta; + private LensElementContext targetContext; + private ItemPath defaultTargetItemPath; + // Only needed if defaultTargetItemPath == null + private ItemDefinition targetItemDefinition; + private Boolean evaluateCurrent; + private LensContext context; + private boolean hasFullTargetObject; + // If set to true then the target cannot be overridden in mapping + private boolean fixTarget = false; + + public Collection getMappingTypes() { + return mappingTypes; + } + + public void setMappingTypes(Collection mappingTypes) { + this.mappingTypes = mappingTypes; + } + public String getMappingDesc() { + return mappingDesc; + } + public void setMappingDesc(String mappingDesc) { + this.mappingDesc = mappingDesc; + } + public XMLGregorianCalendar getNow() { + return now; + } + public void setNow(XMLGregorianCalendar now) { + this.now = now; + } + public MappingInitializer getInitializer() { + return initializer; + } + public void setInitializer(MappingInitializer initializer) { + this.initializer = initializer; + } + public MappingOutputProcessor getProcessor() { + return processor; + } + public void setProcessor(MappingOutputProcessor processor) { + this.processor = processor; + } + public PrismObject getAPrioriTargetObject() { + return aPrioriTargetObject; + } + public void setAPrioriTargetObject(PrismObject aPrioriTargetObject) { + this.aPrioriTargetObject = aPrioriTargetObject; + } + public ObjectDelta getAPrioriTargetDelta() { + return aPrioriTargetDelta; + } + public void setAPrioriTargetDelta(ObjectDelta aPrioriTargetDelta) { + this.aPrioriTargetDelta = aPrioriTargetDelta; + } + public LensElementContext getTargetContext() { + return targetContext; + } + public void setTargetContext(LensElementContext targetContext) { + this.targetContext = targetContext; + } + public Boolean getEvaluateCurrent() { + return evaluateCurrent; + } + public void setEvaluateCurrent(Boolean evaluateCurrent) { + this.evaluateCurrent = evaluateCurrent; + } + public LensContext getContext() { + return context; + } + public void setContext(LensContext context) { + this.context = context; + } + + public boolean hasFullTargetObject() { + return hasFullTargetObject; + } + + public void setHasFullTargetObject(boolean hasFullTargetObject) { + this.hasFullTargetObject = hasFullTargetObject; + } + + public ItemPath getDefaultTargetItemPath() { + return defaultTargetItemPath; + } + + public void setDefaultTargetItemPath(ItemPath defaultTargetItemPath) { + this.defaultTargetItemPath = defaultTargetItemPath; + } + + public boolean isFixTarget() { + return fixTarget; + } + + public void setFixTarget(boolean fixTarget) { + this.fixTarget = fixTarget; + } + + public ItemDefinition getTargetItemDefinition() { + return targetItemDefinition; + } + + public void setTargetItemDefinition(ItemDefinition targetItemDefinition) { + this.targetItemDefinition = targetItemDefinition; + } + + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/MappingOutputProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/MappingOutputProcessor.java new file mode 100644 index 00000000000..9a0df32df2b --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/MappingOutputProcessor.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2014 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.model.impl.lens.projector; + +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.SchemaException; + +/** + * @author semancik + * + */ +public interface MappingOutputProcessor { + + void process(ItemPath mappingOutputPath, PrismValueDeltaSetTriple outputTriple) throws SchemaException, ExpressionEvaluationException; + +} 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 9f4dc8e2aa9..b0bd267cfff 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 @@ -30,6 +30,7 @@ import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.IntegrationTestTools; +import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; @@ -57,6 +58,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Date; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; @@ -193,6 +195,8 @@ public class AbstractConfiguredModelIntegrationTest extends AbstractModelIntegra protected static final String USER_HERMAN_OID = "c0c010c0-d34d-b33f-f00d-111111111122"; protected static final String USER_HERMAN_USERNAME = "herman"; protected static final String USER_HERMAN_FULL_NAME = "Herman Toothrot"; + protected static final Date USER_HERMAN_VALID_FROM_DATE = MiscUtil.asDate(1700, 5, 30, 11, 00, 00); + protected static final Date USER_HERMAN_VALID_TO_DATE = MiscUtil.asDate(2233, 3, 23, 18, 30, 00); // Has null name, doesn not have given name, no employeeType protected static final String USER_THREE_HEADED_MONKEY_FILENAME = COMMON_DIR_NAME + "/user-three-headed-monkey.xml"; diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestActivation.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestActivation.java index 15c8697d291..5e071af7e6a 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestActivation.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestActivation.java @@ -58,6 +58,7 @@ import com.evolveum.midpoint.test.DummyResourceContoller; import com.evolveum.midpoint.test.IntegrationTestTools; import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; @@ -1503,6 +1504,40 @@ public void test400AddHerman() throws Exception { PrismObject userHermanAfter = getUser(USER_HERMAN_OID); assertEffectiveActivation(userHermanAfter, ActivationStatusType.ENABLED); } + + /** + * Herman has validTo/validFrom. Khaki resource has strange mappings for these. + */ + @Test + public void test410AssignHermanKhakiAccount() throws Exception { + final String TEST_NAME = "test410AssignHermanKhakiAccount"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestModelServiceContract.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // WHEN + assignAccount(USER_HERMAN_OID, RESOURCE_DUMMY_KHAKI_OID, null, task, result); + + // THEN + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject user = getUser(USER_HERMAN_OID); + display("User after change execution", user); + assertLinks(user, 1); + + DummyAccount khakiAccount = getDummyAccount(RESOURCE_DUMMY_KHAKI_NAME, USER_HERMAN_USERNAME); + assertNotNull("No khaki account", khakiAccount); + assertTrue("khaki account not enabled", khakiAccount.isEnabled()); + assertEquals("Wrong quote (validFrom) in khaki account", "from: 1700-05-30T11:00:00Z", + khakiAccount.getAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_QUOTE_NAME)); + assertEquals("Wrong drink (validTo) in khaki account", "to: 2233-03-23T18:30:00Z", + khakiAccount.getAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_DRINK_NAME)); + } + + private void assertDummyActivationEnabledState(String userId, boolean expectedEnabled) { assertDummyActivationEnabledState(null, userId, expectedEnabled); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestSecurity.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestSecurity.java index 488dcd4a629..b32d12b0944 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestSecurity.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestSecurity.java @@ -721,7 +721,7 @@ public void testAutzJackPropReadSomeModifySome(final String TEST_NAME, String ro PrismAsserts.assertNoItem(userJack, new ItemPath(UserType.F_ACTIVATION, ActivationType.F_EFFECTIVE_STATUS)); assertAssignmentsWithTargets(userJack, 1); - PrismObjectDefinition userJackEditSchema = modelInteractionService.getEditObjectDefinition(userJack); + PrismObjectDefinition userJackEditSchema = modelInteractionService.getEditObjectDefinition(userJack, null); display("Jack's edit schema", userJackEditSchema); assertItemFlags(userJackEditSchema, UserType.F_NAME, true, false, false); assertItemFlags(userJackEditSchema, UserType.F_FULL_NAME, true, false, true); diff --git a/model/model-intest/src/test/resources/activation/resource-dummy-khaki.xml b/model/model-intest/src/test/resources/activation/resource-dummy-khaki.xml index 444794ef208..01a39466a92 100644 --- a/model/model-intest/src/test/resources/activation/resource-dummy-khaki.xml +++ b/model/model-intest/src/test/resources/activation/resource-dummy-khaki.xml @@ -15,7 +15,8 @@ ~ limitations under the License. --> - + + + + + + + + attributes/quote + + + + + + + + + + attributes/drink + + +