com.evolveum.midpoint.model.impl.lens.projector.ObjectTemplateProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.model.impl.lens.projector.ObjectTemplateProcessor.java

Source

/*
 * 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 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 java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import org.apache.commons.collections4.CollectionUtils;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import com.evolveum.midpoint.model.common.expression.ObjectDeltaObject;
import com.evolveum.midpoint.model.common.mapping.Mapping;
import com.evolveum.midpoint.model.common.mapping.MappingFactory;
import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer;
import com.evolveum.midpoint.model.impl.ModelObjectResolver;
import com.evolveum.midpoint.model.impl.lens.ItemValueWithOrigin;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.model.impl.lens.LensFocusContext;
import com.evolveum.midpoint.model.impl.lens.LensUtil;
import com.evolveum.midpoint.model.impl.trigger.RecomputeTriggerHandler;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.Objectable;
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.PrismValue;
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.ObjectDelta;
import com.evolveum.midpoint.prism.delta.PropertyDelta;
import com.evolveum.midpoint.prism.match.MatchingRule;
import com.evolveum.midpoint.prism.match.MatchingRuleRegistry;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.util.ItemPathUtil;
import com.evolveum.midpoint.prism.util.PrismUtil;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.PolicyViolationException;
import com.evolveum.midpoint.util.exception.SchemaException;
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.MappingStrengthType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateItemDefinitionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateMappingEvaluationPhaseType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateMappingType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.TriggerType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.VariableBindingDefinitionType;

/**
 * Processor to handle object template.
 * 
 * @author Radovan Semancik
 * 
 */
@Component
public class ObjectTemplateProcessor {

    private static final Trace LOGGER = TraceManager.getTrace(ObjectTemplateProcessor.class);

    @Autowired
    private MappingFactory mappingFactory;

    @Autowired
    private PrismContext prismContext;

    @Autowired
    private ModelObjectResolver modelObjectResolver;

    @Autowired
    @Qualifier("cacheRepositoryService")
    private transient RepositoryService cacheRepositoryService;

    @Autowired
    private MappingEvaluator mappingEvaluator;

    @Autowired
    private MatchingRuleRegistry matchingRuleRegistry;

    public <F extends FocusType> void processTemplate(LensContext<F> context,
            ObjectTemplateMappingEvaluationPhaseType phase, XMLGregorianCalendar now, Task task,
            OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException,
            PolicyViolationException, ObjectAlreadyExistsException {
        LensFocusContext<F> focusContext = context.getFocusContext();
        if (focusContext.isDelete()) {
            LOGGER.trace("Skipping processing of object template: focus delete");
            return;
        }

        ObjectTemplateType objectTemplate = context.getFocusTemplate();
        if (objectTemplate == null) {
            // No applicable template
            LOGGER.trace("Skipping processing of object template: no object template");
            return;
        }

        int iteration = focusContext.getIteration();
        String iterationToken = focusContext.getIterationToken();
        ObjectDeltaObject<F> focusOdo = focusContext.getObjectDeltaObject();
        PrismObjectDefinition<F> focusDefinition = getObjectDefinition(focusContext.getObjectTypeClass());

        LOGGER.trace("Applying {} to {}, iteration {} ({}), phase {}", objectTemplate, focusContext.getObjectNew(),
                iteration, iterationToken, phase);

        Map<ItemPath, DeltaSetTriple<? extends ItemValueWithOrigin<?, ?>>> outputTripleMap = new HashMap<>();

        Map<ItemPath, ObjectTemplateItemDefinitionType> itemDefinitionsMap = collectItemDefinitionsFromTemplate(
                objectTemplate, objectTemplate.toString(), task, result);

        XMLGregorianCalendar nextRecomputeTime = collectTripleFromTemplate(context, objectTemplate, phase, focusOdo,
                outputTripleMap, iteration, iterationToken, now, objectTemplate.toString(), task, result);

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("outputTripleMap before item delta computation:\n{}",
                    DebugUtil.debugDumpMapMultiLine(outputTripleMap));
        }

        String contextDesc = "object template " + objectTemplate + " for focus " + focusOdo.getAnyObject();
        Collection<ItemDelta<?, ?>> itemDeltas = computeItemDeltas(outputTripleMap, itemDefinitionsMap, focusOdo,
                focusDefinition, contextDesc);

        focusContext.applyProjectionWaveSecondaryDeltas(itemDeltas);

        if (nextRecomputeTime != null) {

            boolean alreadyHasTrigger = false;
            PrismObject<F> objectCurrent = focusContext.getObjectCurrent();
            if (objectCurrent != null) {
                for (TriggerType trigger : objectCurrent.asObjectable().getTrigger()) {
                    if (RecomputeTriggerHandler.HANDLER_URI.equals(trigger.getHandlerUri())
                            && nextRecomputeTime.equals(trigger.getTimestamp())) {
                        alreadyHasTrigger = true;
                        break;
                    }
                }
            }

            if (!alreadyHasTrigger) {
                PrismObjectDefinition<F> objectDefinition = focusContext.getObjectDefinition();
                PrismContainerDefinition<TriggerType> triggerContDef = objectDefinition
                        .findContainerDefinition(ObjectType.F_TRIGGER);
                ContainerDelta<TriggerType> triggerDelta = triggerContDef
                        .createEmptyDelta(new ItemPath(ObjectType.F_TRIGGER));
                PrismContainerValue<TriggerType> triggerCVal = triggerContDef.createValue();
                triggerDelta.addValueToAdd(triggerCVal);
                TriggerType triggerType = triggerCVal.asContainerable();
                triggerType.setTimestamp(nextRecomputeTime);
                triggerType.setHandlerUri(RecomputeTriggerHandler.HANDLER_URI);

                focusContext.swallowToProjectionWaveSecondaryDelta(triggerDelta);
            }
        }

    }

    private Map<ItemPath, ObjectTemplateItemDefinitionType> collectItemDefinitionsFromTemplate(
            ObjectTemplateType objectTemplateType, String contextDesc, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException {
        Map<ItemPath, ObjectTemplateItemDefinitionType> definitions = new HashMap<>();
        // Process includes (TODO refactor as a generic method)
        for (ObjectReferenceType includeRef : objectTemplateType.getIncludeRef()) {
            PrismObject<ObjectTemplateType> includeObject = includeRef.asReferenceValue().getObject();
            if (includeObject == null) {
                ObjectTemplateType includeObjectType = modelObjectResolver.resolve(includeRef,
                        ObjectTemplateType.class, null,
                        "include reference in " + objectTemplateType + " in " + contextDesc, task, result);
                includeObject = includeObjectType.asPrismObject();
                // Store resolved object for future use (e.g. next waves).
                includeRef.asReferenceValue().setObject(includeObject);
            }
            LOGGER.trace("Including template {}", includeObject);
            ObjectTemplateType includeObjectType = includeObject.asObjectable();
            Map<ItemPath, ObjectTemplateItemDefinitionType> includedDefinitions = collectItemDefinitionsFromTemplate(
                    includeObjectType,
                    "include " + includeObject + " in " + objectTemplateType + " in " + contextDesc, task, result);
            if (includedDefinitions != null) {
                ItemPathUtil.putAllToMap(definitions, includedDefinitions);
            }
        }

        // Process own definitions
        for (ObjectTemplateItemDefinitionType def : objectTemplateType.getItem()) {
            if (def.getRef() == null) {
                throw new IllegalStateException("Item definition with null ref in " + contextDesc);
            }
            ItemPathUtil.putToMap(definitions, def.getRef().getItemPath(), def); // TODO check for incompatible overrides
        }
        return definitions;
    }

    <F extends FocusType> Collection<ItemDelta<?, ?>> computeItemDeltas(
            Map<ItemPath, DeltaSetTriple<? extends ItemValueWithOrigin<?, ?>>> outputTripleMap,
            @Nullable Map<ItemPath, ObjectTemplateItemDefinitionType> itemDefinitionsMap,
            ObjectDeltaObject<F> focusOdo, PrismObjectDefinition<F> focusDefinition, String contextDesc)
            throws ExpressionEvaluationException, PolicyViolationException, SchemaException {

        Collection<ItemDelta<?, ?>> itemDeltas = new ArrayList<>();
        ObjectDelta<F> focusDelta = focusOdo.getObjectDelta();
        PrismObject<F> focusNew = focusOdo.getNewObject();

        LOGGER.trace("Computing deltas in {}, focusDelta:\n{}", contextDesc, focusDelta);

        boolean addUnchangedValues = false;
        if (focusDelta != null && focusDelta.isAdd()) {
            addUnchangedValues = true;
        }

        for (Entry<ItemPath, DeltaSetTriple<? extends ItemValueWithOrigin<?, ?>>> entry : outputTripleMap
                .entrySet()) {
            ItemPath itemPath = entry.getKey();
            DeltaSetTriple<? extends ItemValueWithOrigin<?, ?>> outputTriple = entry.getValue();
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Computed triple for {}:\n{}", itemPath, outputTriple.debugDump());
            }
            final ObjectTemplateItemDefinitionType templateItemDefinition;
            if (itemDefinitionsMap != null) {
                templateItemDefinition = ItemPathUtil.getFromMap(itemDefinitionsMap, itemPath);
            } else {
                templateItemDefinition = null;
            }
            boolean isNonTolerant = templateItemDefinition != null
                    && Boolean.FALSE.equals(templateItemDefinition.isTolerant());

            ItemDelta aprioriItemDelta = getAprioriItemDelta(focusDelta, itemPath);
            boolean filterExistingValues = !isNonTolerant; // if non-tolerant, we want to gather ZERO & PLUS sets
            ItemDefinition itemDefinition = focusDefinition.findItemDefinition(itemPath);
            ItemDelta itemDelta = LensUtil.consolidateTripleToDelta(itemPath, (DeltaSetTriple) outputTriple,
                    itemDefinition, aprioriItemDelta, focusNew, null, null, addUnchangedValues,
                    filterExistingValues, false, contextDesc, true);

            // Do a quick version of reconciliation. There is not much to reconcile as both the source and the target
            // is focus. But there are few cases to handle, such as strong mappings, and sourceless normal mappings. 
            Collection<? extends ItemValueWithOrigin<?, ?>> zeroSet = outputTriple.getZeroSet();
            Item<PrismValue, ItemDefinition> itemNew = null;
            if (focusNew != null) {
                itemNew = focusNew.findItem(itemPath);
            }
            for (ItemValueWithOrigin<?, ?> zeroSetIvwo : zeroSet) {

                PrismValueDeltaSetTripleProducer<?, ?> mapping = zeroSetIvwo.getMapping();
                if ((mapping.getStrength() == null || mapping.getStrength() == MappingStrengthType.NORMAL)) {
                    if (aprioriItemDelta != null && !aprioriItemDelta.isEmpty()) {
                        continue;
                    }
                    if (!mapping.isSourceless()) {
                        continue;
                    }
                    LOGGER.trace("Adding zero values from normal mapping {}, a-priori delta: {}, isSourceless: {}",
                            mapping, aprioriItemDelta, mapping.isSourceless());
                } else if (mapping.getStrength() == MappingStrengthType.WEAK) {
                    if ((itemNew != null && !itemNew.isEmpty())
                            || (itemDelta != null && itemDelta.addsAnyValue())) {
                        continue;
                    }
                    LOGGER.trace("Adding zero values from weak mapping {}, itemNew: {}, itemDelta: {}", mapping,
                            itemNew, itemDelta);
                } else {
                    LOGGER.trace("Adding zero values from strong mapping {}", mapping);
                }

                PrismValue valueFromZeroSet = zeroSetIvwo.getItemValue();
                if (itemNew == null || !itemNew.containsRealValue(valueFromZeroSet)) {
                    LOGGER.trace("Reconciliation will add value {} for item {}. Existing item: {}",
                            valueFromZeroSet, itemPath, itemNew);
                    itemDelta.addValuesToAdd(valueFromZeroSet.clone());
                }
            }

            if (isNonTolerant) {
                if (itemDelta.isDelete()) {
                    LOGGER.trace("Non-tolerant item with values to DELETE => removing them");
                    itemDelta.resetValuesToDelete(); // these are useless now - we move everything to REPLACE
                }
                if (itemDelta.isReplace()) {
                    LOGGER.trace("Non-tolerant item with resulting REPLACE delta => doing nothing");
                } else {
                    for (ItemValueWithOrigin<?, ?> zeroSetIvwo : zeroSet) {
                        itemDelta.addValuesToAdd(zeroSetIvwo.getItemValue().clone());
                    }
                    itemDelta.addToReplaceDelta();
                    LOGGER.trace(
                            "Non-tolerant item with resulting ADD delta => converted ADD to REPLACE values: {}",
                            itemDelta.getValuesToReplace());
                }

                // To avoid phantom changes, compare with existing values (MID-2499).
                // TODO this should be maybe moved into LensUtil.consolidateTripleToDelta (along with the above code), e.g.
                // under a special option "createReplaceDelta", but for the time being, let's keep it here
                if (itemDelta instanceof PropertyDelta) {
                    PropertyDelta propertyDelta = ((PropertyDelta) itemDelta);
                    QName matchingRuleName = templateItemDefinition != null
                            ? templateItemDefinition.getMatchingRule()
                            : null;
                    MatchingRule matchingRule = matchingRuleRegistry.getMatchingRule(matchingRuleName, null);
                    if (propertyDelta.isRedundant(focusOdo.getNewObject(), matchingRule)) {
                        LOGGER.trace("Computed property delta is redundant => skipping it. Delta = \n{}",
                                propertyDelta.debugDump());
                        continue;
                    }
                } else {
                    if (itemDelta.isRedundant(focusOdo.getNewObject())) {
                        LOGGER.trace("Computed item delta is redundant => skipping it. Delta = \n{}",
                                itemDelta.debugDump());
                        continue;
                    }
                }
                PrismUtil.setDeltaOldValue(focusOdo.getNewObject(), itemDelta);
            }

            itemDelta.simplify();
            itemDelta.validate(contextDesc);
            itemDeltas.add(itemDelta);
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Computed delta:\n{}", itemDelta.debugDump());
            }
        }
        return itemDeltas;
    }

    private <F extends FocusType> ItemDelta getAprioriItemDelta(ObjectDelta<F> focusDelta, ItemPath itemPath) {
        return focusDelta != null ? focusDelta.findItemDelta(itemPath) : null;
    }

    private <F extends FocusType> XMLGregorianCalendar collectTripleFromTemplate(LensContext<F> context,
            ObjectTemplateType objectTemplateType, ObjectTemplateMappingEvaluationPhaseType phase,
            ObjectDeltaObject<F> focusOdo,
            Map<ItemPath, DeltaSetTriple<? extends ItemValueWithOrigin<?, ?>>> outputTripleMap, int iteration,
            String iterationToken, XMLGregorianCalendar now, String contextDesc, Task task, OperationResult result)
            throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException,
            PolicyViolationException {

        XMLGregorianCalendar nextRecomputeTime = null;

        // Process includes
        for (ObjectReferenceType includeRef : objectTemplateType.getIncludeRef()) {
            PrismObject<ObjectTemplateType> includeObject = includeRef.asReferenceValue().getObject();
            if (includeObject == null) {
                ObjectTemplateType includeObjectType = modelObjectResolver.resolve(includeRef,
                        ObjectTemplateType.class, null,
                        "include reference in " + objectTemplateType + " in " + contextDesc, task, result);
                includeObject = includeObjectType.asPrismObject();
                // Store resolved object for future use (e.g. next waves).
                includeRef.asReferenceValue().setObject(includeObject);
            }
            LOGGER.trace("Including template {}", includeObject);
            ObjectTemplateType includeObjectType = includeObject.asObjectable();
            XMLGregorianCalendar includeNextRecomputeTime = collectTripleFromTemplate(context, includeObjectType,
                    phase, focusOdo, outputTripleMap, iteration, iterationToken, now,
                    "include " + includeObject + " in " + objectTemplateType + " in " + contextDesc, task, result);
            if (includeNextRecomputeTime != null) {
                if (nextRecomputeTime == null
                        || nextRecomputeTime.compare(includeNextRecomputeTime) == DatatypeConstants.GREATER) {
                    nextRecomputeTime = includeNextRecomputeTime;
                }
            }
        }

        // Process own mappings
        List<ObjectTemplateMappingType> mappings = collectMappings(objectTemplateType);
        List<ObjectTemplateMappingType> sortedMappings = sortMappingsByDependencies(mappings);
        XMLGregorianCalendar templateNextRecomputeTime = collectTripleFromMappings(sortedMappings, phase, context,
                objectTemplateType, focusOdo, outputTripleMap, iteration, iterationToken, now, contextDesc, task,
                result);
        if (templateNextRecomputeTime != null) {
            if (nextRecomputeTime == null
                    || nextRecomputeTime.compare(templateNextRecomputeTime) == DatatypeConstants.GREATER) {
                nextRecomputeTime = templateNextRecomputeTime;
            }
        }

        return nextRecomputeTime;
    }

    /**
     * If M2 has a source of X, and M1 has a target of X, then M1 must be placed before M2; we want also to detect cycles.
     *
     * So let's stratify mappings according to their dependencies.
      */
    private List<ObjectTemplateMappingType> sortMappingsByDependencies(List<ObjectTemplateMappingType> mappings) {
        // map.get(X) = { Y1 ... Yn } means that mapping X depends on output of mappings Y1 ... Yn
        // using indices instead of actual mappings because of equality issues
        Map<Integer, Set<Integer>> dependencyMap = createDependencyMap(mappings);
        LOGGER.trace("sortMappingsByDependencies: dependencyMap: {}", dependencyMap);

        List<Integer> processed = new ArrayList<>();
        List<Integer> toProcess = Stream.iterate(0, t -> t + 1).limit(mappings.size()).collect(Collectors.toList()); // not a set: to preserve original order
        while (!toProcess.isEmpty()) {
            LOGGER.trace("sortMappingsByDependencies: toProcess: {}, processed: {}", toProcess, processed);
            Integer available = toProcess.stream()
                    .filter(i -> CollectionUtils.isSubCollection(dependencyMap.get(i), processed)) // cannot depend on yet-unprocessed mappings
                    .findFirst().orElse(null);
            if (available == null) {
                LOGGER.warn(
                        "Cannot sort mappings according to dependencies, there is a cycle. Processing in the original order: {}",
                        mappings);
                return mappings;
            }
            processed.add(available);
            toProcess.remove(available);
        }
        LOGGER.trace("sortMappingsByDependencies: final ordering: {}", processed);
        return processed.stream().map(i -> mappings.get(i)).collect(Collectors.toList());
    }

    private Map<Integer, Set<Integer>> createDependencyMap(List<ObjectTemplateMappingType> mappings) {
        Map<Integer, Set<Integer>> map = new HashMap<>();
        for (int i = 0; i < mappings.size(); i++) {
            Set<Integer> dependsOn = new HashSet<>();
            for (int j = 0; j < mappings.size(); j++) {
                if (i == j) {
                    continue;
                }
                if (dependsOn(mappings.get(i), mappings.get(j))) {
                    dependsOn.add(j);
                }
            }
            map.put(i, dependsOn);
        }
        return map;
    }

    // true if any source of mapping1 is equivalent to the target of mapping2
    private boolean dependsOn(ObjectTemplateMappingType mapping1, ObjectTemplateMappingType mapping2) {
        if (mapping2.getTarget() == null || mapping2.getTarget().getPath() == null) {
            return false;
        }
        ItemPath targetPath = mapping2.getTarget().getPath().getItemPath().stripVariableSegment();

        for (VariableBindingDefinitionType source : mapping1.getSource()) {
            ItemPath sourcePath = source.getPath() != null ? source.getPath().getItemPath() : null;
            if (sourcePath != null && stripFocusVariableSegment(sourcePath).equivalent(targetPath)) {
                return true;
            }
        }
        return false;
    }

    private <T extends Objectable> ItemPath stripFocusVariableSegment(ItemPath sourcePath) {
        if (sourcePath.startsWithVariable()
                && QNameUtil.matchAny(ItemPath.getFirstName(sourcePath), MappingEvaluator.FOCUS_VARIABLE_NAMES)) {
            return sourcePath.stripVariableSegment();
        } else {
            return sourcePath;
        }
    }

    private List<ObjectTemplateMappingType> collectMappings(ObjectTemplateType objectTemplateType) {
        List<ObjectTemplateMappingType> mappings = new ArrayList<ObjectTemplateMappingType>();
        mappings.addAll(objectTemplateType.getMapping());
        for (ObjectTemplateItemDefinitionType templateItemDefType : objectTemplateType.getItem()) {
            for (ObjectTemplateMappingType mapping : templateItemDefType.getMapping()) {
                VariableBindingDefinitionType target = mapping.getTarget();
                if (target == null) {
                    target = new VariableBindingDefinitionType();
                    target.setPath(templateItemDefType.getRef());
                    mapping.setTarget(target);
                } else if (target.getPath() == null) {
                    target = target.clone();
                    target.setPath(templateItemDefType.getRef());
                    mapping.setTarget(target);
                }
                mappings.add(mapping);
            }
        }
        return mappings;
    }

    private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> XMLGregorianCalendar collectTripleFromMappings(
            Collection<ObjectTemplateMappingType> mappings, ObjectTemplateMappingEvaluationPhaseType phase,
            LensContext<F> context, ObjectTemplateType objectTemplateType, ObjectDeltaObject<F> focusOdo,
            Map<ItemPath, DeltaSetTriple<? extends ItemValueWithOrigin<?, ?>>> outputTripleMap, int iteration,
            String iterationToken, XMLGregorianCalendar now, String contextDesc, Task task, OperationResult result)
            throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException,
            PolicyViolationException {

        XMLGregorianCalendar nextRecomputeTime = null;

        for (ObjectTemplateMappingType mappingType : mappings) {
            ObjectTemplateMappingEvaluationPhaseType mappingPhase = mappingType.getEvaluationPhase();
            if (mappingPhase == null) {
                mappingPhase = ObjectTemplateMappingEvaluationPhaseType.BEFORE_ASSIGNMENTS;
            }
            if (mappingPhase != phase) {
                continue;
            }
            LOGGER.trace("Starting evaluation of mapping '{}' in {}", mappingType.getName(), contextDesc);
            ObjectDeltaObject<F> updatedFocusOdo = getUpdatedFocusOdo(context, focusOdo, outputTripleMap,
                    mappingType, contextDesc); // for mapping chaining

            Mapping<V, D> mapping = mappingEvaluator.createFocusMapping(mappingFactory, context, mappingType,
                    objectTemplateType, updatedFocusOdo, null, iteration, iterationToken,
                    context.getSystemConfiguration(), now, contextDesc, task, result);
            if (mapping == null) {
                continue;
            }

            Boolean timeConstraintValid = mapping.evaluateTimeConstraintValid(task, result);

            if (timeConstraintValid != null && !timeConstraintValid) {
                // Delayed mapping. Just schedule recompute time
                XMLGregorianCalendar mappingNextRecomputeTime = mapping.getNextRecomputeTime();
                LOGGER.trace("Evaluation of mapping {} delayed to {}", mapping, mappingNextRecomputeTime);
                if (mappingNextRecomputeTime != null) {
                    if (nextRecomputeTime == null
                            || nextRecomputeTime.compare(mappingNextRecomputeTime) == DatatypeConstants.GREATER) {
                        nextRecomputeTime = mappingNextRecomputeTime;
                    }
                }
                continue;
            }

            mappingEvaluator.evaluateMapping(mapping, context, task, result);

            ItemPath itemPath = mapping.getOutputPath();
            if (itemPath == null) {
                continue;
            }
            DeltaSetTriple<ItemValueWithOrigin<V, D>> outputTriple = ItemValueWithOrigin
                    .createOutputTriple(mapping);
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Output triple for {}:\n{}", mapping, DebugUtil.debugDump(outputTriple));
            }
            if (outputTriple == null) {
                continue;
            }
            DeltaSetTriple<ItemValueWithOrigin<V, D>> mapTriple = (DeltaSetTriple<ItemValueWithOrigin<V, D>>) outputTripleMap
                    .get(itemPath);
            if (mapTriple == null) {
                outputTripleMap.put(itemPath, outputTriple);
            } else {
                mapTriple.merge(outputTriple);
            }
        }

        return nextRecomputeTime;
    }

    private <F extends FocusType> ObjectDeltaObject<F> getUpdatedFocusOdo(LensContext<F> context,
            ObjectDeltaObject<F> focusOdo,
            Map<ItemPath, DeltaSetTriple<? extends ItemValueWithOrigin<?, ?>>> outputTripleMap,
            ObjectTemplateMappingType mappingType, String contextDesc)
            throws ExpressionEvaluationException, PolicyViolationException, SchemaException {
        ObjectDeltaObject<F> focusOdoCloned = null;
        for (VariableBindingDefinitionType source : mappingType.getSource()) {
            if (source.getPath() == null) {
                continue;
            }
            ItemPath path = stripFocusVariableSegment(source.getPath().getItemPath());
            if (path.startsWithVariable()) {
                continue;
            }
            DeltaSetTriple<? extends ItemValueWithOrigin<?, ?>> triple = DeltaSetTriple.find(outputTripleMap, path);
            if (triple == null) {
                continue;
            }
            if (focusOdoCloned == null) {
                LOGGER.trace("Cloning and updating focusOdo because of chained mappings; chained source path: {}",
                        path);
                focusOdoCloned = focusOdo.clone();
            } else {
                LOGGER.trace("Updating focusOdo because of chained mappings; chained source path: {}", path);
            }
            Class<F> focusClass = context.getFocusContext().getObjectTypeClass();
            ItemDefinition<?> itemDefinition = getObjectDefinition(focusClass).findItemDefinition(path);
            // TODO not much sure about the parameters
            ItemDelta itemDelta = LensUtil.consolidateTripleToDelta(path, (DeltaSetTriple) triple, itemDefinition,
                    getAprioriItemDelta(focusOdo.getObjectDelta(), path), focusOdo.getNewObject(), null, null, true,
                    true, false, " updating chained source (" + path + ") in " + contextDesc, true);
            LOGGER.trace("Updating focus ODO with delta:\n{}", itemDelta.debugDumpLazily());
            focusOdoCloned.update(itemDelta);
        }
        return focusOdoCloned != null ? focusOdoCloned : focusOdo;
    }

    private <F extends ObjectType> PrismObjectDefinition<F> getObjectDefinition(Class<F> focusClass) {
        return prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(focusClass);
    }

}