/* * Copyright (c) 2010-2017 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.common.refinery.*; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.common.mapping.Mapping; import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; import com.evolveum.midpoint.model.impl.lens.Construction; import com.evolveum.midpoint.model.impl.lens.ItemValueWithOrigin; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; 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.PrismProperty; import com.evolveum.midpoint.prism.PrismPropertyDefinition; import com.evolveum.midpoint.prism.PrismReference; import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.prism.delta.ContainerDelta; import com.evolveum.midpoint.prism.delta.DeltaSetTriple; import com.evolveum.midpoint.prism.delta.ItemDelta; 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.match.MatchingRuleRegistry; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LayerType; import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingStrengthType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAssociationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import org.apache.commons.lang.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.xml.namespace.QName; import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * This processor consolidate delta set triples acquired from account sync context and transforms them to * property deltas. It converts mappings to deltas considering exclusions, authoritativeness and strength of individual * mappings. It also (somehow indirectly) merges all the mappings together. It considers also property deltas from sync, * which already happened. * * @author Radovan Semancik * @author lazyman */ @Component public class ConsolidationProcessor { public static final String PROCESS_CONSOLIDATION = ConsolidationProcessor.class.getName() + ".consolidateValues"; private static final Trace LOGGER = TraceManager.getTrace(ConsolidationProcessor.class); private PrismContainerDefinition<ShadowAssociationType> associationDefinition; @Autowired private ContextLoader contextLoader; @Autowired private MatchingRuleRegistry matchingRuleRegistry; @Autowired PrismContext prismContext; /** * Converts delta set triples to a secondary account deltas. */ <F extends FocusType> void consolidateValues(LensContext<F> context, LensProjectionContext accCtx, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { //todo filter changes which were already in account sync delta //account was deleted, no changes are needed. if (wasProjectionDeleted(accCtx)) { dropAllProjectionDelta(accCtx); return; } SynchronizationPolicyDecision policyDecision = accCtx.getSynchronizationPolicyDecision(); if (consistencyChecks) context.checkConsistence(); if (policyDecision == SynchronizationPolicyDecision.DELETE) { // Nothing to do } else { // This is ADD, KEEP, UNLINK or null. All are in fact the same as KEEP consolidateValuesModifyProjection(context, accCtx, task, result); } if (consistencyChecks) context.checkConsistence(); } private void dropAllProjectionDelta(LensProjectionContext accContext) { accContext.setPrimaryDelta(null); accContext.setSecondaryDelta(null); } private boolean wasProjectionDeleted(LensProjectionContext accContext) { ObjectDelta<ShadowType> delta = accContext.getSyncDelta(); if (delta != null && ChangeType.DELETE.equals(delta.getChangeType())) { return true; } return false; } private <F extends FocusType> ObjectDelta<ShadowType> consolidateValuesToModifyDelta(LensContext<F> context, LensProjectionContext projCtx, boolean addUnchangedValues, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { // "Squeeze" all the relevant mappings into a data structure that we can process conveniently. We want to have all the // (meta)data about relevant for a specific attribute in one data structure, not spread over several account constructions. Map<QName, DeltaSetTriple<ItemValueWithOrigin<PrismPropertyValue<?>,PrismPropertyDefinition<?>>>> squeezedAttributes = sqeeze(projCtx, construction -> (Collection)construction.getAttributeMappings()); projCtx.setSqueezedAttributes(squeezedAttributes); Map<QName, DeltaSetTriple<ItemValueWithOrigin<PrismContainerValue<ShadowAssociationType>,PrismContainerDefinition<ShadowAssociationType>>>> squeezedAssociations = sqeeze(projCtx, construction -> construction.getAssociationMappings()); projCtx.setSqueezedAssociations(squeezedAssociations); // Association values in squeezed associations do not contain association name attribute. // It is hacked-in later for use in this class, but not for other uses (e.g. in ReconciliationProcessor). // So, we do it here - once and for all. if (!squeezedAssociations.isEmpty()) { fillInAssociationNames(squeezedAssociations); } MappingExtractor<PrismPropertyValue<QName>,PrismPropertyDefinition<QName>,F> auxiliaryObjectClassExtractor = construction -> { PrismValueDeltaSetTripleProducer<PrismPropertyValue<QName>,PrismPropertyDefinition<QName>> prod = new PrismValueDeltaSetTripleProducer<PrismPropertyValue<QName>,PrismPropertyDefinition<QName>>() { @Override public QName getMappingQName() { return ShadowType.F_AUXILIARY_OBJECT_CLASS; } @Override public PrismValueDeltaSetTriple<PrismPropertyValue<QName>> getOutputTriple() { PrismValueDeltaSetTriple<PrismPropertyValue<QName>> triple = new PrismValueDeltaSetTriple<>(); if (construction.getAuxiliaryObjectClassDefinitions() != null) { for (RefinedObjectClassDefinition auxiliaryObjectClassDefinition: construction.getAuxiliaryObjectClassDefinitions()) { triple.addToZeroSet(new PrismPropertyValue<QName>(auxiliaryObjectClassDefinition.getTypeName())); } } return triple; } @Override public MappingStrengthType getStrength() { return MappingStrengthType.STRONG; } @Override public PrismValueDeltaSetTripleProducer<PrismPropertyValue<QName>,PrismPropertyDefinition<QName>> clone() { return this; } @Override public boolean isExclusive() { return false; } @Override public boolean isAuthoritative() { return true; } @Override public boolean isSourceless() { return false; } }; Collection<PrismValueDeltaSetTripleProducer<PrismPropertyValue<QName>,PrismPropertyDefinition<QName>>> col = new ArrayList<>(1); col.add(prod); return col; }; Map<QName, DeltaSetTriple<ItemValueWithOrigin<PrismPropertyValue<QName>,PrismPropertyDefinition<QName>>>> squeezedAuxiliaryObjectClasses = sqeeze(projCtx, auxiliaryObjectClassExtractor); projCtx.setSqueezedAuxiliaryObjectClasses(squeezedAuxiliaryObjectClasses); ResourceShadowDiscriminator discr = projCtx.getResourceShadowDiscriminator(); ObjectDelta<ShadowType> objectDelta = new ObjectDelta<ShadowType>(ShadowType.class, ChangeType.MODIFY, prismContext); objectDelta.setOid(projCtx.getOid()); // Do not automatically load the full projection now. Even if we have weak mapping. // That may be a waste of resources if the weak mapping results in no change anyway. // Let's be very very lazy about fetching the account from the resource. if (!projCtx.hasFullShadow() && (hasActiveWeakMapping(squeezedAttributes, projCtx) || hasActiveWeakMapping(squeezedAssociations, projCtx) || (hasActiveStrongMapping(squeezedAttributes, projCtx) || hasActiveStrongMapping(squeezedAssociations, projCtx)))) { // Full account was not yet loaded. This will cause problems as // the weak mapping may be applied even though it should not be // applied // and also same changes may be discarded because of unavailability // of all // account's attributes.Therefore load the account now, but with // doNotDiscovery options.. // We also need to get account if there are strong mappings. Strong mappings // should always be applied. So reading the account now will indirectly // trigger reconciliation which makes sure that the strong mappings are // applied. // By getting accounts from provisioning, there might be a problem with // resource availability. We need to know, if the account was read full // or we have only the shadow from the repository. If we have only // shadow, the weak mappings may applied even if they should not be. contextLoader.loadFullShadow(context, projCtx, task, result); if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { return null; } } boolean completeAccount = projCtx.hasFullShadow(); ObjectDelta<ShadowType> existingDelta = projCtx.getDelta(); // AUXILIARY OBJECT CLASSES ItemPath auxiliaryObjectClassItemPath = new ItemPath(ShadowType.F_AUXILIARY_OBJECT_CLASS); PrismPropertyDefinition<QName> auxiliaryObjectClassPropertyDef = projCtx.getObjectDefinition().findPropertyDefinition(auxiliaryObjectClassItemPath); PropertyDelta<QName> auxiliaryObjectClassAPrioriDelta = null; RefinedResourceSchema refinedSchema = projCtx.getRefinedResourceSchema(); List<QName> auxOcNames = new ArrayList<>(); List<RefinedObjectClassDefinition> auxOcDefs = new ArrayList<>(); ObjectDelta<ShadowType> projDelta = projCtx.getDelta(); if (projDelta != null) { auxiliaryObjectClassAPrioriDelta = projDelta.findPropertyDelta(auxiliaryObjectClassItemPath); } for (Entry<QName, DeltaSetTriple<ItemValueWithOrigin<PrismPropertyValue<QName>, PrismPropertyDefinition<QName>>>> entry : squeezedAuxiliaryObjectClasses.entrySet()) { DeltaSetTriple<ItemValueWithOrigin<PrismPropertyValue<QName>, PrismPropertyDefinition<QName>>> ivwoTriple = entry.getValue(); LOGGER.trace("CONSOLIDATE auxiliary object classes ({})", new Object[]{ discr }); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Auxiliary object class triple:\n{}",ivwoTriple.debugDump()); } for (ItemValueWithOrigin<PrismPropertyValue<QName>,PrismPropertyDefinition<QName>> ivwo: ivwoTriple.getAllValues()) { QName auxObjectClassName = ivwo.getItemValue().getValue(); if (auxOcNames.contains(auxObjectClassName)) { continue; } auxOcNames.add(auxObjectClassName); RefinedObjectClassDefinition auxOcDef = refinedSchema.getRefinedDefinition(auxObjectClassName); if (auxOcDef == null) { LOGGER.error("Auxiliary object class definition {} for {} not found in the schema, but it should be there, dumping context:\n{}", auxObjectClassName, discr, context.debugDump()); throw new IllegalStateException("Auxiliary object class definition " + auxObjectClassName + " for "+ discr + " not found in the context, but it should be there"); } auxOcDefs.add(auxOcDef); } ItemDelta<PrismPropertyValue<QName>, PrismPropertyDefinition<QName>> itemDelta = LensUtil.consolidateTripleToDelta( auxiliaryObjectClassItemPath, ivwoTriple, auxiliaryObjectClassPropertyDef, auxiliaryObjectClassAPrioriDelta, projCtx.getObjectNew(), null, null, addUnchangedValues, completeAccount, false, discr.toHumanReadableDescription(), false); PropertyDelta<QName> propDelta = (PropertyDelta)itemDelta; if (LOGGER.isTraceEnabled()) { LOGGER.trace("Auxiliary object class delta:\n{}",propDelta.debugDump()); } if (!propDelta.isEmpty()) { objectDelta.addModification(propDelta); } } RefinedObjectClassDefinition structuralObjectClassDefinition = projCtx.getStructuralObjectClassDefinition(); if (structuralObjectClassDefinition == null) { LOGGER.error("Structural object class definition for {} not found in the context, but it should be there, dumping context:\n{}", discr, context.debugDump()); throw new IllegalStateException("Structural object class definition for " + discr + " not found in the context, but it should be there"); } RefinedObjectClassDefinition rOcDef = new CompositeRefinedObjectClassDefinitionImpl( structuralObjectClassDefinition, auxOcDefs); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Object class definition for {} consolidation:\n{}", discr, rOcDef.debugDump()); } // ATTRIBUTES // Iterate and process each attribute separately. Now that we have squeezed the data we can process each attribute just // with the data in ItemValueWithOrigin triples. for (Map.Entry<QName, DeltaSetTriple<ItemValueWithOrigin<PrismPropertyValue<?>,PrismPropertyDefinition<?>>>> entry : squeezedAttributes.entrySet()) { QName attributeName = entry.getKey(); DeltaSetTriple<ItemValueWithOrigin<PrismPropertyValue<?>,PrismPropertyDefinition<?>>> triple = entry.getValue(); PropertyDelta<?> propDelta = consolidateAttribute(rOcDef, discr, existingDelta, projCtx, addUnchangedValues, completeAccount, attributeName, (DeltaSetTriple)triple); if (propDelta != null) { objectDelta.addModification(propDelta); } } // ASSOCIATIONS for (Entry<QName, DeltaSetTriple<ItemValueWithOrigin<PrismContainerValue<ShadowAssociationType>,PrismContainerDefinition<ShadowAssociationType>>>> entry : squeezedAssociations.entrySet()) { QName associationName = entry.getKey(); DeltaSetTriple<ItemValueWithOrigin<PrismContainerValue<ShadowAssociationType>,PrismContainerDefinition<ShadowAssociationType>>> triple = entry.getValue(); ContainerDelta<ShadowAssociationType> containerDelta = consolidateAssociation(rOcDef, discr, existingDelta, projCtx, addUnchangedValues, completeAccount, associationName, triple); if (containerDelta != null) { objectDelta.addModification(containerDelta); } } return objectDelta; } private void fillInAssociationNames(Map<QName, DeltaSetTriple<ItemValueWithOrigin<PrismContainerValue<ShadowAssociationType>,PrismContainerDefinition<ShadowAssociationType>>>> squeezedAssociations) throws SchemaException { PrismPropertyDefinition<QName> nameDefinition = prismContext.getSchemaRegistry() .findContainerDefinitionByCompileTimeClass(ShadowAssociationType.class) .findPropertyDefinition(ShadowAssociationType.F_NAME); for (Entry<QName, DeltaSetTriple<ItemValueWithOrigin<PrismContainerValue<ShadowAssociationType>,PrismContainerDefinition<ShadowAssociationType>>>> entry : squeezedAssociations.entrySet()) { DeltaSetTriple<ItemValueWithOrigin<PrismContainerValue<ShadowAssociationType>,PrismContainerDefinition<ShadowAssociationType>>> deltaSetTriple = entry.getValue(); for (ItemValueWithOrigin<PrismContainerValue<ShadowAssociationType>,PrismContainerDefinition<ShadowAssociationType>> ivwo : deltaSetTriple.getAllValues()) { PrismContainerValue<ShadowAssociationType> value = ivwo.getItemValue(); if (value != null && value.findProperty(ShadowAssociationType.F_NAME) == null) { // just for safety PrismProperty<QName> nameProperty = value.createProperty(nameDefinition); nameProperty.setRealValue(entry.getKey()); } } } LOGGER.trace("Names for squeezed associations filled-in."); } private <T,V extends PrismValue> PropertyDelta<T> consolidateAttribute(RefinedObjectClassDefinition rOcDef, ResourceShadowDiscriminator discr, ObjectDelta<ShadowType> existingDelta, LensProjectionContext projCtx, boolean addUnchangedValues, boolean completeShadow, QName itemName, DeltaSetTriple<ItemValueWithOrigin<PrismPropertyValue<T>,PrismPropertyDefinition<T>>> triple) throws SchemaException, ExpressionEvaluationException, PolicyViolationException { if (triple == null || triple.isEmpty()) { return null; } RefinedAttributeDefinition<T> attributeDefinition = triple.getAnyValue().getConstruction().findAttributeDefinition(itemName); ItemPath itemPath = new ItemPath(ShadowType.F_ATTRIBUTES, itemName); if (attributeDefinition.isIgnored(LayerType.MODEL)) { LOGGER.trace("Skipping processing mappings for attribute {} because it is ignored", itemName); return null; } ValueMatcher<T> valueMatcher = ValueMatcher.createMatcher(attributeDefinition, matchingRuleRegistry); return (PropertyDelta<T>) consolidateItem(rOcDef, discr, existingDelta, projCtx, addUnchangedValues, completeShadow, attributeDefinition.isExlusiveStrong(), itemPath, attributeDefinition, triple, valueMatcher, null, "attribute "+itemName); } private <V extends PrismValue> ContainerDelta<ShadowAssociationType> consolidateAssociation(RefinedObjectClassDefinition rOcDef, ResourceShadowDiscriminator discr, ObjectDelta<ShadowType> existingDelta, LensProjectionContext projCtx, boolean addUnchangedValues, boolean completeShadow, QName associationName, DeltaSetTriple<ItemValueWithOrigin<PrismContainerValue<ShadowAssociationType>,PrismContainerDefinition<ShadowAssociationType>>> triple) throws SchemaException, ExpressionEvaluationException, PolicyViolationException { ItemPath itemPath = new ItemPath(ShadowType.F_ASSOCIATION); PrismContainerDefinition<ShadowAssociationType> asspcContainerDef = getAssociationDefinition(); RefinedAssociationDefinition associationDef = rOcDef.findAssociationDefinition(associationName); Comparator<PrismContainerValue<ShadowAssociationType>> comparator = new Comparator<PrismContainerValue<ShadowAssociationType>>() { @Override public int compare(PrismContainerValue<ShadowAssociationType> o1, PrismContainerValue<ShadowAssociationType> o2) { if (o1 == null && o2 == null){ LOGGER.trace("Comparing {} and {}: 0 (A)", o1, o2); return 0; } if (o1 == null || o2 == null){ LOGGER.trace("Comparing {} and {}: 2 (B)", o1, o2); return 1; } PrismReference ref1 = o1.findReference(ShadowAssociationType.F_SHADOW_REF); PrismReference ref2 = o2.findReference(ShadowAssociationType.F_SHADOW_REF); // We do not want to compare references in details. Comparing OIDs suffices. // Otherwise we get into problems, as one of the references might be e.g. without type, // causing unpredictable behavior (MID-2368) String oid1 = ref1 != null ? ref1.getOid() : null; String oid2 = ref2 != null ? ref2.getOid() : null; if (ObjectUtils.equals(oid1, oid2)) { LOGGER.trace("Comparing {} and {}: 0 (C)", o1, o2); return 0; } LOGGER.trace("Comparing {} and {}: 1 (D)", o1, o2); return 1; } }; ContainerDelta<ShadowAssociationType> delta = (ContainerDelta<ShadowAssociationType>) consolidateItem(rOcDef, discr, existingDelta, projCtx, addUnchangedValues, completeShadow, associationDef.isExclusiveStrong(), itemPath, asspcContainerDef, triple, null, comparator, "association "+associationName); if (delta != null) { setAssociationName(delta.getValuesToAdd(), associationName); setAssociationName(delta.getValuesToDelete(), associationName); setAssociationName(delta.getValuesToReplace(), associationName); } return delta; } private void setAssociationName(Collection<PrismContainerValue<ShadowAssociationType>> values, QName itemName) { if (values == null) { return; } for (PrismContainerValue<ShadowAssociationType> val: values) { val.asContainerable().setName(itemName); } } private PrismContainerDefinition<ShadowAssociationType> getAssociationDefinition() { if (associationDefinition == null) { associationDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class) .findContainerDefinition(ShadowType.F_ASSOCIATION); } return associationDefinition; } private <V extends PrismValue,D extends ItemDefinition> ItemDelta<V,D> consolidateItem(RefinedObjectClassDefinition rOcDef, ResourceShadowDiscriminator discr, ObjectDelta<ShadowType> existingDelta, LensProjectionContext projCtx, boolean addUnchangedValues, boolean completeShadow, boolean isExclusiveStrong, ItemPath itemPath, D itemDefinition, DeltaSetTriple<ItemValueWithOrigin<V,D>> triple, ValueMatcher<?> valueMatcher, Comparator<V> comparator, String itemDesc) throws SchemaException, ExpressionEvaluationException, PolicyViolationException { boolean forceAddUnchangedValues = false; ItemDelta<V,D> existingItemDelta = null; if (existingDelta != null) { existingItemDelta = existingDelta.findItemDelta(itemPath); } if (existingItemDelta != null && existingItemDelta.isReplace()) { // We need to add all values if there is replace delta. Otherwise the zero-set values will be // lost forceAddUnchangedValues = true; } LOGGER.trace("CONSOLIDATE {}\n({}) completeShadow={}, addUnchangedValues={}, forceAddUnchangedValues={}", itemDesc, discr, completeShadow, addUnchangedValues, forceAddUnchangedValues); // Use this common utility method to do the computation. It does most of the work. ItemDelta<V,D> itemDelta = LensUtil.consolidateTripleToDelta( itemPath, (DeltaSetTriple)triple, itemDefinition, existingItemDelta, projCtx.getObjectNew(), valueMatcher, comparator, addUnchangedValues || forceAddUnchangedValues, completeShadow, isExclusiveStrong, discr.toHumanReadableDescription(), completeShadow); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Consolidated delta (before sync filter) for {}:\n{}",discr,itemDelta==null?"null":itemDelta.debugDump()); } if (existingItemDelta != null && existingItemDelta.isReplace()) { // We cannot filter out any values if there is an replace delta. The replace delta cleans all previous // state and all the values needs to be passed on LOGGER.trace("Skipping consolidation with sync delta as there was a replace delta on top of that already"); } else { // Also consider a synchronization delta (if it is present). This may filter out some deltas. itemDelta = consolidateItemWithSync(projCtx, itemDelta, valueMatcher); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Consolidated delta (after sync filter) for {}:\n{}",discr,itemDelta==null?"null":itemDelta.debugDump()); } } if (itemDelta != null && !itemDelta.isEmpty()) { if (existingItemDelta == null || !existingItemDelta.isReplace()) { // We cannot simplify if there is already a replace delta. This might result in // two replace deltas and therefore some information may be lost itemDelta.simplify(); } // Validate the delta. i.e. make sure it conforms to schema (that it does not have more values than allowed, etc.) if (existingItemDelta != null) { // Let's make sure that both the previous delta and this delta makes sense ItemDelta<V,D> mergedDelta = existingItemDelta.clone(); mergedDelta.merge(itemDelta); mergedDelta.validate(); } else { itemDelta.validate(); } return itemDelta; } return null; } private <V extends PrismValue,D extends ItemDefinition> boolean hasActiveWeakMapping( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedAttributes, LensProjectionContext accCtx) throws SchemaException { for (Map.Entry<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> entry : squeezedAttributes.entrySet()) { DeltaSetTriple<ItemValueWithOrigin<V,D>> ivwoTriple = entry.getValue(); boolean hasWeak = false; for (ItemValueWithOrigin<V,D> ivwo: ivwoTriple.getAllValues()) { PrismValueDeltaSetTripleProducer<V,D> mapping = ivwo.getMapping(); if (mapping.getStrength() == MappingStrengthType.WEAK) { // We only care about mappings that change something. If the weak mapping is not // changing anything then it will not be applied in this step anyway. Therefore // there is no point in loading the real values just because there is such mapping. // Note: we can be sure that we are NOT doing reconciliation. If we do reconciliation // then we cannot get here in the first place (the projection is already loaded). PrismValueDeltaSetTriple<?> outputTriple = mapping.getOutputTriple(); if (outputTriple != null && !outputTriple.isEmpty() && !outputTriple.isZeroOnly()) { return true; } hasWeak = true; } } if (hasWeak) { // If we have a weak mapping for this attribute and there is also any // other mapping with a minus set then we need to get the real current value. // The minus value may cause that the result of consolidation is empty value. // In that case we should apply the weak mapping. But we will not know this // unless we fetch the real values. if (ivwoTriple.hasMinusSet()) { for (ItemValueWithOrigin<V,D> ivwo: ivwoTriple.getMinusSet()) { PrismValueDeltaSetTripleProducer<V, D> mapping = ivwo.getMapping(); PrismValueDeltaSetTriple<?> outputTriple = mapping.getOutputTriple(); if (outputTriple != null && !outputTriple.isEmpty()) { return true; } } } for (ItemValueWithOrigin<V,D> ivwo: ivwoTriple.getNonNegativeValues()) { PrismValueDeltaSetTripleProducer<V, D> mapping = ivwo.getMapping(); PrismValueDeltaSetTriple<?> outputTriple = mapping.getOutputTriple(); if (outputTriple != null && outputTriple.hasMinusSet()) { return true; } } ObjectDelta<ShadowType> projectionDelta = accCtx.getDelta(); if (projectionDelta != null) { PropertyDelta<?> aPrioriAttributeDelta = projectionDelta.findPropertyDelta(new ItemPath(ShadowType.F_ATTRIBUTES, entry.getKey())); if (aPrioriAttributeDelta != null && aPrioriAttributeDelta.isDelete()) { return true; } if (aPrioriAttributeDelta != null && aPrioriAttributeDelta.isReplace() && aPrioriAttributeDelta.getValuesToReplace().isEmpty()) { return true; } } } } return false; } private <V extends PrismValue,D extends ItemDefinition> boolean hasActiveStrongMapping( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedAttributes, LensProjectionContext accCtx) throws SchemaException { for (Map.Entry<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> entry : squeezedAttributes.entrySet()) { DeltaSetTriple<ItemValueWithOrigin<V,D>> ivwoTriple = entry.getValue(); for (ItemValueWithOrigin<V,D> ivwo: ivwoTriple.getAllValues()) { PrismValueDeltaSetTripleProducer<V,D> mapping = ivwo.getMapping(); if (mapping.getStrength() == MappingStrengthType.STRONG) { // Do not optimize for "nothing changed" case here. We want to make // sure that the values of strong mappings are applied even if nothing // has changed. return true; } } } return false; } private <F extends FocusType> void consolidateValuesModifyProjection(LensContext<F> context, LensProjectionContext accCtx, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { boolean addUnchangedValues = false; if (accCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.ADD) { addUnchangedValues = true; } ObjectDelta<ShadowType> modifyDelta = consolidateValuesToModifyDelta(context, accCtx, addUnchangedValues, task, result); if (modifyDelta == null || modifyDelta.isEmpty()) { return; } ObjectDelta<ShadowType> accountSecondaryDelta = accCtx.getSecondaryDelta(); if (accountSecondaryDelta != null) { accountSecondaryDelta.merge(modifyDelta); } else { accCtx.setSecondaryDelta(modifyDelta); } } private <V extends PrismValue,D extends ItemDefinition> ItemDelta<V,D> consolidateItemWithSync(LensProjectionContext accCtx, ItemDelta<V,D> delta, ValueMatcher<?> valueMatcher) { if (delta == null) { return null; } if (delta instanceof PropertyDelta<?>) { return (ItemDelta<V,D>) consolidatePropertyWithSync(accCtx, (PropertyDelta) delta, valueMatcher); } return delta; } /** * This method checks {@link com.evolveum.midpoint.prism.delta.PropertyDelta} created during consolidation with * account sync deltas. If changes from property delta are in account sync deltas than they must be removed, * because they already had been applied (they came from sync, already happened). * * @param accCtx current account sync context * @param delta new delta created during consolidation process * @return method return updated delta, or null if delta was empty after filtering (removing unnecessary values). */ private <T> PropertyDelta<T> consolidatePropertyWithSync(LensProjectionContext accCtx, PropertyDelta<T> delta, ValueMatcher<T> valueMatcher) { if (delta == null) { return null; } ObjectDelta<ShadowType> syncDelta = accCtx.getSyncDelta(); if (syncDelta == null) { return consolidateWithSyncAbsolute(accCtx, delta, valueMatcher); } PropertyDelta<T> alreadyDoneDelta = syncDelta.findPropertyDelta(delta.getPath()); if (alreadyDoneDelta == null) { return delta; } cleanupValues(delta.getValuesToAdd(), alreadyDoneDelta, valueMatcher); cleanupValues(delta.getValuesToDelete(), alreadyDoneDelta, valueMatcher); if (delta.getValues(Object.class).isEmpty()) { return null; } return delta; } /** * This method consolidate property delta against account absolute state which came from sync (not as delta) * * @param accCtx * @param delta * @return method return updated delta, or null if delta was empty after filtering (removing unnecessary values). */ private <T> PropertyDelta<T> consolidateWithSyncAbsolute(LensProjectionContext accCtx, PropertyDelta<T> delta, ValueMatcher<T> valueMatcher) { if (delta == null || accCtx.getObjectCurrent() == null) { return delta; } PrismObject<ShadowType> absoluteAccountState = accCtx.getObjectCurrent(); PrismProperty<T> absoluteProperty = absoluteAccountState.findProperty(delta.getPath()); if (absoluteProperty == null) { return delta; } cleanupAbsoluteValues(delta.getValuesToAdd(), true, absoluteProperty, valueMatcher); cleanupAbsoluteValues(delta.getValuesToDelete(), false, absoluteProperty, valueMatcher); if (delta.getValues(Object.class).isEmpty()) { return null; } return delta; } /** * Method removes values from property delta values list (first parameter). * * @param values collection with {@link PrismPropertyValue} objects to add or delete (from {@link PropertyDelta} * @param adding if true we removing {@link PrismPropertyValue} from {@link Collection} values parameter if they * already are in {@link PrismProperty} parameter. Otherwise we're removing {@link PrismPropertyValue} * from {@link Collection} values parameter if they already are not in {@link PrismProperty} parameter. * @param property property with absolute state */ private <T> void cleanupAbsoluteValues(Collection<PrismPropertyValue<T>> values, boolean adding, PrismProperty<T> property, ValueMatcher<T> valueMatcher) { if (values == null) { return; } Iterator<PrismPropertyValue<T>> iterator = values.iterator(); while (iterator.hasNext()) { PrismPropertyValue<T> value = iterator.next(); if (adding && valueMatcher.hasRealValue(property,value)) { iterator.remove(); } if (!adding && !valueMatcher.hasRealValue(property,value)) { iterator.remove(); } } } /** * Simple util method which checks property values against already done delta from sync. See method * {@link ConsolidationProcessor#consolidateWithSync(com.evolveum.midpoint.model.AccountSyncContext, * com.evolveum.midpoint.prism.delta.PropertyDelta)}. * * @param values collection which has to be filtered * @param alreadyDoneDelta already applied delta from sync */ private <T> void cleanupValues(Collection<PrismPropertyValue<T>> values, PropertyDelta<T> alreadyDoneDelta, ValueMatcher<T> valueMatcher) { if (values == null) { return; } Iterator<PrismPropertyValue<T>> iterator = values.iterator(); while (iterator.hasNext()) { PrismPropertyValue<T> valueToAdd = iterator.next(); if (valueMatcher.isRealValueToAdd(alreadyDoneDelta, valueToAdd)) { iterator.remove(); } } } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> sqeeze( LensProjectionContext accCtx, MappingExtractor<V,D,F> extractor) { Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap = new HashMap<>(); if (accCtx.getConstructionDeltaSetTriple() != null) { sqeezeAttributesFromConstructionTriple(squeezedMap, (PrismValueDeltaSetTriple)accCtx.getConstructionDeltaSetTriple(), extractor); } if (accCtx.getOutboundConstruction() != null) { // The plus-minus-zero status of outbound account construction is determined by the type of account delta if (accCtx.isAdd()) { sqeezeAttributesFromConstructionNonminusToPlus(squeezedMap, accCtx.getOutboundConstruction(), extractor); } else if (accCtx.isDelete()) { sqeezeAttributesFromConstructionNonminusToMinus(squeezedMap, accCtx.getOutboundConstruction(), extractor); } else { sqeezeAttributesFromConstruction(squeezedMap, accCtx.getOutboundConstruction(), extractor); } } return squeezedMap; } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> void sqeezeAttributesFromConstructionTriple( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap, PrismValueDeltaSetTriple<PrismPropertyValue<Construction<F>>> constructionDeltaSetTriple, MappingExtractor<V,D,F> extractor) { // Zero account constructions go normally, plus to plus, minus to minus sqeezeAttributesFromAccountConstructionSet(squeezedMap, constructionDeltaSetTriple.getZeroSet(), extractor); // Plus accounts: zero and plus values go to plus sqeezeAttributesFromAccountConstructionSetNonminusToPlus(squeezedMap, constructionDeltaSetTriple.getPlusSet(), extractor); // Minus accounts: all values go to minus sqeezeAttributesFromConstructionSetAllToMinus(squeezedMap, constructionDeltaSetTriple.getMinusSet(), extractor); // Why all values in the last case: imagine that mapping M evaluated to "minus: A" on delta D. // The mapping itself is in minus set, so it disappears when delta D is applied. Therefore, value of A // was originally produced by the mapping M, and the mapping was originally active. So originally there was value of A // present, and we have to remove it. See MID-3325 / TestNullAttribute story. // // The same argument is valid for zero set of mapping output. // // Finally, the plus set of mapping output goes to resulting minus just for historical reasons... it was implemented // in this way for a long time. It seems to be unnecessary but also harmless. So let's keep it there, for now. } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> void sqeezeAttributesFromAccountConstructionSet( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap, Collection<PrismPropertyValue<Construction<F>>> constructionSet, MappingExtractor<V,D,F> extractor) { if (constructionSet == null) { return; } for (PrismPropertyValue<Construction<F>> construction: constructionSet) { sqeezeAttributesFromConstruction(squeezedMap, construction.getValue(), extractor); } } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> void sqeezeAttributesFromAccountConstructionSetNonminusToPlus( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap, Collection<PrismPropertyValue<Construction<F>>> constructionSet, MappingExtractor<V,D,F> extractor) { if (constructionSet == null) { return; } for (PrismPropertyValue<Construction<F>> construction: constructionSet) { sqeezeAttributesFromConstructionNonminusToPlus(squeezedMap, construction.getValue(), extractor); } } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> void sqeezeAttributesFromConstructionSetNonminusToMinus( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap, Collection<PrismPropertyValue<Construction<F>>> constructionSet, MappingExtractor<V,D,F> extractor) { if (constructionSet == null) { return; } for (PrismPropertyValue<Construction<F>> construction: constructionSet) { sqeezeAttributesFromConstructionNonminusToMinus(squeezedMap, construction.getValue(), extractor); } } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> void sqeezeAttributesFromConstructionSetAllToMinus( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap, Collection<PrismPropertyValue<Construction<F>>> constructionSet, MappingExtractor<V,D,F> extractor) { if (constructionSet == null) { return; } for (PrismPropertyValue<Construction<F>> construction: constructionSet) { sqeezeAttributesFromConstructionAllToMinus(squeezedMap, construction.getValue(), extractor); } } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> void sqeezeAttributesFromConstruction( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap, Construction<F> construction, MappingExtractor<V,D,F> extractor) { for (PrismValueDeltaSetTripleProducer<V, D> mapping: extractor.getMappings(construction)) { PrismValueDeltaSetTriple<V> vcTriple = mapping.getOutputTriple(); if (vcTriple == null) { continue; } QName name = mapping.getMappingQName(); DeltaSetTriple<ItemValueWithOrigin<V,D>> squeezeTriple = getSqueezeMapTriple(squeezedMap, name); convertSqueezeSet(vcTriple.getZeroSet(), squeezeTriple.getZeroSet(), mapping, construction); convertSqueezeSet(vcTriple.getPlusSet(), squeezeTriple.getPlusSet(), mapping, construction); convertSqueezeSet(vcTriple.getMinusSet(), squeezeTriple.getMinusSet(), mapping, construction); } } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> void sqeezeAttributesFromConstructionNonminusToPlus( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap, Construction<F> construction, MappingExtractor<V,D,F> extractor) { for (PrismValueDeltaSetTripleProducer<V, D> mapping: extractor.getMappings(construction)) { PrismValueDeltaSetTriple<V> vcTriple = mapping.getOutputTriple(); if (vcTriple == null) { continue; } QName name = mapping.getMappingQName(); DeltaSetTriple<ItemValueWithOrigin<V,D>> squeezeTriple = getSqueezeMapTriple(squeezedMap, name); convertSqueezeSet(vcTriple.getZeroSet(), squeezeTriple.getPlusSet(), mapping, construction); convertSqueezeSet(vcTriple.getPlusSet(), squeezeTriple.getPlusSet(), mapping, construction); // Ignore minus set } } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> void sqeezeAttributesFromConstructionNonminusToMinus( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap, Construction<F> construction, MappingExtractor<V,D,F> extractor) { for (PrismValueDeltaSetTripleProducer<V, D> mapping: extractor.getMappings(construction)) { PrismValueDeltaSetTriple<V> vcTriple = mapping.getOutputTriple(); if (vcTriple == null) { continue; } QName name = mapping.getMappingQName(); DeltaSetTriple<ItemValueWithOrigin<V,D>> squeezeTriple = getSqueezeMapTriple(squeezedMap, name); convertSqueezeSet(vcTriple.getZeroSet(), squeezeTriple.getMinusSet(), mapping, construction); convertSqueezeSet(vcTriple.getPlusSet(), squeezeTriple.getMinusSet(), mapping, construction); } } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> void sqeezeAttributesFromConstructionAllToMinus( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap, Construction<F> construction, MappingExtractor<V,D,F> extractor) { for (PrismValueDeltaSetTripleProducer<V, D> mapping: extractor.getMappings(construction)) { PrismValueDeltaSetTriple<V> vcTriple = mapping.getOutputTriple(); if (vcTriple == null) { continue; } QName name = mapping.getMappingQName(); DeltaSetTriple<ItemValueWithOrigin<V,D>> squeezeTriple = getSqueezeMapTriple(squeezedMap, name); convertSqueezeSet(vcTriple.getZeroSet(), squeezeTriple.getMinusSet(), mapping, construction); convertSqueezeSet(vcTriple.getPlusSet(), squeezeTriple.getMinusSet(), mapping, construction); convertSqueezeSet(vcTriple.getMinusSet(), squeezeTriple.getMinusSet(), mapping, construction); } } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> void convertSqueezeSet(Collection<V> fromSet, Collection<ItemValueWithOrigin<V,D>> toSet, PrismValueDeltaSetTripleProducer<V, D> mapping, Construction<F> construction) { if (fromSet != null) { for (V from: fromSet) { ItemValueWithOrigin<V,D> pvwo = new ItemValueWithOrigin<V,D>(from, mapping, construction); toSet.add(pvwo); } } } private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> DeltaSetTriple<ItemValueWithOrigin<V,D>> getSqueezeMapTriple( Map<QName, DeltaSetTriple<ItemValueWithOrigin<V,D>>> squeezedMap, QName itemName) { DeltaSetTriple<ItemValueWithOrigin<V,D>> triple = squeezedMap.get(itemName); if (triple == null) { triple = new DeltaSetTriple<ItemValueWithOrigin<V,D>>(); squeezedMap.put(itemName, triple); } return triple; } }