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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2013 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 javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.lang.mutable.MutableBoolean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple;
import com.evolveum.midpoint.prism.delta.PropertyDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.Handler;
import com.evolveum.midpoint.util.PrettyPrinter;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
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.MappingType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.TriggerType;

/**
 * @author Radovan Semancik
 *
 */
@Component
public class MappingEvaluationHelper {

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

    @Autowired(required = true)
    private PrismContext prismContext;

    @Autowired(required = true)
    private MappingFactory mappingFactory;

    /**
     * strongMappingWasUsed: Returns true here if the value was (at least partly) determined by a strong mapping.
     * 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 <V extends PrismValue, F extends FocusType, O extends ObjectType> void evaluateMappingSetProjection(
    //         Collection<MappingType> mappingTypes, String mappingDesc,
    //         XMLGregorianCalendar now, MappingInitializer<V> initializer, MappingOutputProcessor<V> processor,
    //         Item<V> aPrioriValue, ItemDelta<V> aPrioriDelta, PrismObject<? extends ObjectType> aPrioriObject,
    //         Boolean evaluateCurrent, MutableBoolean strongMappingWasUsed,
    //         LensContext<F> context, LensElementContext<O> targetContext, 
    //         Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException {

    public <V extends PrismValue, T extends ObjectType, F extends FocusType> void evaluateMappingSetProjection(
            MappingEvaluatorHelperParams<V, T, F> params, Task task, OperationResult result)
            throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException {

        String mappingDesc = params.getMappingDesc();
        LensElementContext<T> targetContext = params.getTargetContext();
        PrismObjectDefinition<T> targetObjectDefinition = targetContext.getObjectDefinition();
        ItemPath defaultTargetItemPath = params.getDefaultTargetItemPath();

        Map<ItemPath, MappingOutputStruct<V>> outputTripleMap = new HashMap<>();
        XMLGregorianCalendar nextRecomputeTime = null;
        Collection<MappingType> mappingTypes = params.getMappingTypes();
        Collection<Mapping<V>> mappings = new ArrayList<Mapping<V>>(mappingTypes.size());

        for (MappingType mappingType : mappingTypes) {

            Mapping<V> mapping = mappingFactory.createMapping(mappingType, mappingDesc);

            if (!mapping.isApplicableToChannel(params.getContext().getChannel())) {
                continue;
            }

            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)
            params.getInitializer().initialize(mapping);

            Boolean timeConstraintValid = mapping.evaluateTimeConstraintValid(result);

            if (params.getEvaluateCurrent() != null) {
                if (params.getEvaluateCurrent() && !timeConstraintValid) {
                    continue;
                }
                if (!params.getEvaluateCurrent() && timeConstraintValid) {
                    continue;
                }
            }

            mappings.add(mapping);
        }

        for (Mapping<V> mapping : mappings) {

            if (mapping.getStrength() == MappingStrengthType.WEAK) {
                // Evaluate weak mappings in a second run.
                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<PrismValue> aPrioriItemDelta = params.getAPrioriTargetDelta()
                        .findItemDelta(mappingOutputPath);
                if (mapping.getStrength() != MappingStrengthType.STRONG) {
                    if (aPrioriItemDelta != null && !aPrioriItemDelta.isEmpty()) {
                        continue;
                    }
                }
            }

            LensUtil.evaluateMapping(mapping, params.getContext(), task, result);

            PrismValueDeltaSetTriple<V> mappingOutputTriple = mapping.getOutputTriple();
            if (mappingOutputTriple != null) {

                MappingOutputStruct<V> mappingOutputStruct = outputTripleMap.get(mappingOutputPath);
                if (mappingOutputStruct == null) {
                    mappingOutputStruct = new MappingOutputStruct<>();
                    outputTripleMap.put(mappingOutputPath, mappingOutputStruct);
                }

                if (mapping.getStrength() == MappingStrengthType.STRONG) {
                    mappingOutputStruct.setStrongMappingWasUsed(true);
                }

                PrismValueDeltaSetTriple<V> outputTriple = mappingOutputStruct.getOutputTriple();
                if (outputTriple == null) {
                    mappingOutputStruct.setOutputTriple(mappingOutputTriple);
                } else {
                    outputTriple.merge(mappingOutputTriple);
                }
            }

        }

        // Second pass, evaluate only weak mappings
        for (Mapping<V> 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<V> mappingOutputStruct = outputTripleMap.get(mappingOutputPath);
            if (mappingOutputStruct == null) {
                mappingOutputStruct = new MappingOutputStruct<>();
                outputTripleMap.put(mappingOutputPath, mappingOutputStruct);
            }

            PrismValueDeltaSetTriple<V> outputTriple = mappingOutputStruct.getOutputTriple();

            Item<?> aPrioriTargetItem = null;
            PrismObject<T> 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, params.getContext(), task, result);

                PrismValueDeltaSetTriple<V> mappingOutputTriple = mapping.getOutputTriple();
                if (mappingOutputTriple != null) {
                    if (outputTriple == null) {
                        mappingOutputStruct.setOutputTriple(mappingOutputTriple);
                    } else {
                        outputTriple.merge(mappingOutputTriple);
                    }
                }

            }
        }

        MappingOutputProcessor<V> processor = params.getProcessor();
        for (Entry<ItemPath, MappingOutputStruct<V>> outputTripleMapEntry : outputTripleMap.entrySet()) {
            ItemPath mappingOutputPath = outputTripleMapEntry.getKey();
            MappingOutputStruct<V> mappingOutputStruct = outputTripleMapEntry.getValue();
            PrismValueDeltaSetTriple<V> 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<V> targetItemDelta = targetItemDefinition.createEmptyDelta(mappingOutputPath);

                Item<V> aPrioriTargetItem = null;
                PrismObject<T> aPrioriTargetObject = params.getAPrioriTargetObject();
                if (aPrioriTargetObject != null) {
                    aPrioriTargetItem = (Item<V>) aPrioriTargetObject.findItem(mappingOutputPath);
                }

                if (targetContext.isAdd()) {

                    Collection<V> 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<V> 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<V> 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<V> mapping : mappings) {
            XMLGregorianCalendar mappingNextRecomputeTime = mapping.getNextRecomputeTime();
            if (mappingNextRecomputeTime != null) {
                if (nextRecomputeTime == null
                        || nextRecomputeTime.compare(mappingNextRecomputeTime) == DatatypeConstants.GREATER) {
                    nextRecomputeTime = mappingNextRecomputeTime;
                }
            }
        }

        if (nextRecomputeTime != null) {

            boolean alreadyHasTrigger = false;
            if (params.getAPrioriTargetObject() != null) {
                for (TriggerType trigger : params.getAPrioriTargetObject().asObjectable().getTrigger()) {
                    if (RecomputeTriggerHandler.HANDLER_URI.equals(trigger.getHandlerUri())
                            && nextRecomputeTime.equals(trigger.getTimestamp())) {
                        alreadyHasTrigger = true;
                        break;
                    }
                }
            }

            if (!alreadyHasTrigger) {
                PrismContainerDefinition<TriggerType> triggerContDef = targetObjectDefinition
                        .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);

                targetContext.swallowToSecondaryDelta(triggerDelta);
            }
        }
    }

    private class MappingOutputStruct<V extends PrismValue> {
        private PrismValueDeltaSetTriple<V> outputTriple = null;
        private boolean strongMappingWasUsed = false;

        public PrismValueDeltaSetTriple<V> getOutputTriple() {
            return outputTriple;
        }

        public void setOutputTriple(PrismValueDeltaSetTriple<V> outputTriple) {
            this.outputTriple = outputTriple;
        }

        public boolean isStrongMappingWasUsed() {
            return strongMappingWasUsed;
        }

        public void setStrongMappingWasUsed(boolean strongMappingWasUsed) {
            this.strongMappingWasUsed = strongMappingWasUsed;
        }

    }

}