com.evolveum.midpoint.model.common.mapping.Mapping.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.model.common.mapping.Mapping.java

Source

/*
 * Copyright (c) 2010-2015 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.common.mapping;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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

import com.evolveum.midpoint.security.api.SecurityEnforcer;
import com.evolveum.prism.xml.ns._public.types_3.ItemPathType;

import org.apache.commons.lang.Validate;
import org.w3c.dom.Element;

import com.evolveum.midpoint.common.InternalsConfig;
import com.evolveum.midpoint.common.filter.Filter;
import com.evolveum.midpoint.common.filter.FilterManager;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.model.common.expression.Expression;
import com.evolveum.midpoint.model.common.expression.ExpressionEvaluationContext;
import com.evolveum.midpoint.model.common.expression.ExpressionFactory;
import com.evolveum.midpoint.model.common.expression.ExpressionUtil;
import com.evolveum.midpoint.model.common.expression.ExpressionVariables;
import com.evolveum.midpoint.model.common.expression.ItemDeltaItem;
import com.evolveum.midpoint.model.common.expression.ObjectDeltaObject;
import com.evolveum.midpoint.model.common.expression.Source;
import com.evolveum.midpoint.model.common.expression.StringPolicyResolver;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.OriginType;
import com.evolveum.midpoint.prism.Visitable;
import com.evolveum.midpoint.prism.Visitor;
import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.SchemaDebugUtil;
import com.evolveum.midpoint.schema.util.ObjectResolver;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionVariableDefinitionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingSourceDeclarationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingStrengthType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingTargetDeclarationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingTimeDeclarationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ValueFilterType;

/**
 * 
 * Mapping is non-recyclable single-use object. Once evaluated it should not be evaluated again. It will retain its original
 * inputs and outputs that can be read again and again. But these should not be changed after evaluation.
 * 
 * @author Radovan Semancik
 *
 */
public class Mapping<V extends PrismValue, D extends ItemDefinition>
        implements DebugDumpable, PrismValueDeltaSetTripleProducer<V, D> {

    private static final QName CONDITION_OUTPUT_NAME = new QName(SchemaConstants.NS_C, "condition");

    private ExpressionFactory expressionFactory;
    private ExpressionVariables variables = new ExpressionVariables();
    private String contextDescription;
    private String mappingContextDescription = null;
    private MappingType mappingType;
    private ObjectResolver objectResolver = null;
    private SecurityEnforcer securityEnforcer; // in order to get c:actor variable
    private Source<?, ?> defaultSource = null;
    private D defaultTargetDefinition = null;
    private ItemPath defaultTargetPath = null;
    private ObjectDeltaObject<?> sourceContext = null;
    private PrismObjectDefinition<?> targetContext = null;
    private PrismValueDeltaSetTriple<V> outputTriple = null;
    private D outputDefinition;
    private ItemPath outputPath;
    private Collection<Source<?, ?>> sources = new ArrayList<Source<?, ?>>();
    private boolean conditionMaskOld = true;
    private boolean conditionMaskNew = true;
    private PrismValueDeltaSetTriple<PrismPropertyValue<Boolean>> conditionOutputTriple;
    private OriginType originType = null;
    private ObjectType originObject = null;
    private FilterManager<Filter> filterManager;
    private StringPolicyResolver stringPolicyResolver;
    private XMLGregorianCalendar now;
    private XMLGregorianCalendar defaultReferenceTime = null;
    private Boolean timeConstraintValid = null;
    private XMLGregorianCalendar nextRecomputeTime = null;
    private boolean profiling = false;
    private Long evaluationStartTime = null;
    private Long evaluationEndTime = null;
    // This is sometimes used to identify the element that mapping produces
    // if it is different from itemName. E.g. this happens with associations.
    private QName mappingQName;
    private RefinedObjectClassDefinition refinedObjectClassDefinition;

    // This is single-use only. Once evaluated it is not used any more
    // it is remembered only for tracing purposes.
    private Expression<V, D> expression;

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

    Mapping(MappingType mappingType, String contextDescription, ExpressionFactory expressionFactory,
            SecurityEnforcer securityEnforcer) {
        Validate.notNull(mappingType);
        this.contextDescription = contextDescription;
        this.mappingType = mappingType;
        this.expressionFactory = expressionFactory;
        this.securityEnforcer = securityEnforcer;
    }

    public ObjectResolver getObjectResolver() {
        return objectResolver;
    }

    public void setObjectResolver(ObjectResolver objectResolver) {
        this.objectResolver = objectResolver;
    }

    public QName getItemName() {
        if (outputDefinition != null) {
            return outputDefinition.getName();
        }
        return null;
    }

    public OriginType getOriginType() {
        return originType;
    }

    public void setOriginType(OriginType sourceType) {
        this.originType = sourceType;
    }

    public ObjectType getOriginObject() {
        return originObject;
    }

    public void setOriginObject(ObjectType originObject) {
        this.originObject = originObject;
    }

    public void addSource(Source<?, ?> source) {
        sources.add(source);
    }

    public Source<?, ?> getDefaultSource() {
        return defaultSource;
    }

    public void setDefaultSource(Source<?, ?> defaultSource) {
        this.defaultSource = defaultSource;
    }

    public D getDefaultTargetDefinition() {
        return defaultTargetDefinition;
    }

    public void setDefaultTargetDefinition(D defaultTargetDefinition) {
        this.defaultTargetDefinition = defaultTargetDefinition;
    }

    public ItemPath getDefaultTargetPath() {
        return defaultTargetPath;
    }

    public void setDefaultTargetPath(ItemPath defaultTargetPath) {
        this.defaultTargetPath = defaultTargetPath;
    }

    public ObjectDeltaObject<?> getSourceContext() {
        return sourceContext;
    }

    public void setSourceContext(ObjectDeltaObject<?> sourceContext) {
        this.sourceContext = sourceContext;
    }

    public PrismObjectDefinition<?> getTargetContext() {
        return targetContext;
    }

    public void setTargetContext(PrismObjectDefinition<?> targetContext) {
        this.targetContext = targetContext;
    }

    public String getContextDescription() {
        return contextDescription;
    }

    public void setContextDescription(String contextDescription) {
        this.contextDescription = contextDescription;
    }

    public String getMappingContextDescription() {
        if (mappingContextDescription == null) {
            StringBuilder sb = new StringBuilder("mapping ");
            if (mappingType.getName() != null) {
                sb.append("'").append(mappingType.getName()).append("' ");
            }
            sb.append("in ");
            sb.append(contextDescription);
            mappingContextDescription = sb.toString();
        }
        return mappingContextDescription;
    }

    public MappingType getMappingType() {
        return mappingType;
    }

    public void setRootNode(ObjectReferenceType objectRef) {
        addVariableDefinition(null, (Object) objectRef);
    }

    public void setRootNode(ObjectDeltaObject<?> odo) {
        addVariableDefinition(null, (Object) odo);
    }

    public void setRootNode(ObjectType objectType) {
        addVariableDefinition(null, (Object) objectType);
    }

    public void setRootNode(PrismObject<? extends ObjectType> mpObject) {
        addVariableDefinition(null, (Object) mpObject);
    }

    public void addVariableDefinition(ExpressionVariableDefinitionType varDef) throws SchemaException {
        if (varDef.getObjectRef() != null) {
            ObjectReferenceType ref = varDef.getObjectRef();
            ref.setType(getPrismContext().getSchemaRegistry().qualifyTypeName(ref.getType()));
            addVariableDefinition(varDef.getName(), ref);
        } else if (varDef.getValue() != null) {
            addVariableDefinition(varDef.getName(), varDef.getValue());
        } else {
            LOGGER.warn("Empty definition of variable {} in {}, ignoring it", varDef.getName(),
                    getMappingContextDescription());
        }
    }

    public void addVariableDefinition(QName name, ObjectReferenceType objectRef) {
        addVariableDefinition(name, (Object) objectRef);
    }

    public void addVariableDefinition(QName name, ObjectType objectType) {
        addVariableDefinition(name, (Object) objectType);
    }

    public void addVariableDefinition(QName name, PrismObject<? extends ObjectType> midpointObject) {
        addVariableDefinition(name, (Object) midpointObject);
    }

    public void addVariableDefinition(QName name, String value) {
        addVariableDefinition(name, (Object) value);
    }

    public void addVariableDefinition(QName name, int value) {
        addVariableDefinition(name, (Object) value);
    }

    public void addVariableDefinition(QName name, Element value) {
        addVariableDefinition(name, (Object) value);
    }

    public void addVariableDefinition(QName name, PrismValue value) {
        addVariableDefinition(name, (Object) value);
    }

    public void addVariableDefinition(QName name, ObjectDeltaObject<?> value) {
        addVariableDefinition(name, (Object) value);
    }

    public void addVariableDefinitions(Map<QName, Object> extraVariables) {
        variables.addVariableDefinitions(extraVariables);
    }

    public void addVariableDefinition(QName name, Object value) {
        variables.addVariableDefinition(name, value);
    }

    public boolean hasVariableDefinition(QName varName) {
        return variables.containsKey(varName);
    }

    public MappingStrengthType getStrength() {
        if (mappingType == null) {
            return MappingStrengthType.NORMAL;
        }
        MappingStrengthType value = mappingType.getStrength();
        if (value == null) {
            value = MappingStrengthType.NORMAL;
        }
        return value;
    }

    public boolean isAuthoritative() {
        if (mappingType == null) {
            return true;
        }
        Boolean value = mappingType.isAuthoritative();
        if (value == null) {
            value = true;
        }
        return value;
    }

    public boolean isExclusive() {
        if (mappingType == null) {
            return false;
        }
        Boolean value = mappingType.isExclusive();
        if (value == null) {
            value = false;
        }
        return value;
    }

    public boolean isConditionMaskOld() {
        return conditionMaskOld;
    }

    public void setConditionMaskOld(boolean conditionMaskOld) {
        this.conditionMaskOld = conditionMaskOld;
    }

    public boolean isConditionMaskNew() {
        return conditionMaskNew;
    }

    public void setConditionMaskNew(boolean conditionMaskNew) {
        this.conditionMaskNew = conditionMaskNew;
    }

    private PrismContext getPrismContext() {
        return outputDefinition.getPrismContext();
    }

    public FilterManager<Filter> getFilterManager() {
        return filterManager;
    }

    public void setFilterManager(FilterManager<Filter> filterManager) {
        this.filterManager = filterManager;
    }

    public StringPolicyResolver getStringPolicyResolver() {
        return stringPolicyResolver;
    }

    public void setStringPolicyResolver(StringPolicyResolver stringPolicyResolver) {
        this.stringPolicyResolver = stringPolicyResolver;
    }

    public boolean isApplicableToChannel(String channelUri) {
        List<String> exceptChannel = mappingType.getExceptChannel();
        if (exceptChannel != null && !exceptChannel.isEmpty()) {
            return !exceptChannel.contains(channelUri);
        }
        List<String> applicableChannels = mappingType.getChannel();
        if (applicableChannels == null || applicableChannels.isEmpty()) {
            return true;
        }
        return applicableChannels.contains(channelUri);
    }

    public XMLGregorianCalendar getNow() {
        return now;
    }

    public void setNow(XMLGregorianCalendar now) {
        this.now = now;
    }

    public XMLGregorianCalendar getDefaultReferenceTime() {
        return defaultReferenceTime;
    }

    public void setDefaultReferenceTime(XMLGregorianCalendar defaultReferenceTime) {
        this.defaultReferenceTime = defaultReferenceTime;
    }

    public XMLGregorianCalendar getNextRecomputeTime() {
        return nextRecomputeTime;
    }

    public void setNextRecomputeTime(XMLGregorianCalendar nextRecomputeTime) {
        this.nextRecomputeTime = nextRecomputeTime;
    }

    public boolean isProfiling() {
        return profiling;
    }

    public void setProfiling(boolean profiling) {
        this.profiling = profiling;
    }

    public Long getEvaluationStartTime() {
        return evaluationStartTime;
    }

    public Long getEvaluationEndTime() {
        return evaluationEndTime;
    }

    public Long getEtime() {
        if (evaluationStartTime == null || evaluationEndTime == null) {
            return null;
        }
        return evaluationEndTime - evaluationStartTime;
    }

    /* (non-Javadoc)
     * @see com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer#getMappingQName()
     */
    @Override
    public QName getMappingQName() {
        return mappingQName;
    }

    public void setMappingQName(QName mappingQName) {
        this.mappingQName = mappingQName;
    }

    public RefinedObjectClassDefinition getRefinedObjectClassDefinition() {
        return refinedObjectClassDefinition;
    }

    public void setRefinedObjectClassDefinition(RefinedObjectClassDefinition refinedObjectClassDefinition) {
        this.refinedObjectClassDefinition = refinedObjectClassDefinition;
    }

    public void evaluate(Task task, OperationResult parentResult)
            throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException {

        OperationResult result = parentResult.createMinorSubresult(Mapping.class.getName() + ".evaluate");

        ExpressionUtil.addActorVariable(variables, securityEnforcer);

        traceEvaluationStart();

        try {
            evaluateTimeConstraintValid(task, result);

            if (!timeConstraintValid) {
                outputTriple = null;
                result.recordNotApplicableIfUnknown();
                traceDeferred();
                return;
            }

            parseSources(task, result);
            parseTarget();

            if (outputPath != null && outputDefinition == null) {
                throw new IllegalArgumentException(
                        "No output definition, cannot evaluate " + getMappingContextDescription());
            }

            evaluateCondition(task, result);

            boolean conditionOutputOld = computeConditionResult(conditionOutputTriple.getNonPositiveValues());
            boolean conditionResultOld = conditionOutputOld && conditionMaskOld;

            boolean conditionOutputNew = computeConditionResult(conditionOutputTriple.getNonNegativeValues());
            boolean conditionResultNew = conditionOutputNew && conditionMaskNew;

            if (!conditionResultOld && !conditionResultNew) {
                result.recordSuccess();
                traceSuccess(conditionResultOld, conditionResultNew);
                return;
            }
            // TODO: input filter
            evaluateExpression(task, result, conditionResultOld, conditionResultNew);
            fixDefinition();
            recomputeValues();
            setOrigin();
            // TODO: output filter

            result.recordSuccess();
            traceSuccess(conditionResultOld, conditionResultNew);

        } catch (ExpressionEvaluationException e) {
            result.recordFatalError(e);
            traceFailure(e);
            throw e;
        } catch (ObjectNotFoundException e) {
            result.recordFatalError(e);
            traceFailure(e);
            throw e;
        } catch (SchemaException e) {
            result.recordFatalError(e);
            traceFailure(e);
            throw e;
        } catch (RuntimeException e) {
            result.recordFatalError(e);
            traceFailure(e);
            throw e;
        }
    }

    public boolean isSatisfyCondition() {
        boolean conditionOutputOld = computeConditionResult(conditionOutputTriple.getNonPositiveValues());
        boolean conditionResultOld = conditionOutputOld && conditionMaskOld;

        boolean conditionOutputNew = computeConditionResult(conditionOutputTriple.getNonNegativeValues());
        boolean conditionResultNew = conditionOutputNew && conditionMaskNew;
        return (conditionResultOld || conditionResultNew);
    }

    private void traceEvaluationStart() {
        if (profiling) {
            evaluationStartTime = System.currentTimeMillis();
        }
    }

    private void traceEvaluationEnd() {
        if (profiling) {
            evaluationEndTime = System.currentTimeMillis();
        }
    }

    private void traceSuccess(boolean conditionResultOld, boolean conditionResultNew) {
        traceEvaluationEnd();
        if (!LOGGER.isTraceEnabled()) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        sb.append("Mapping trace:\n");
        appendTraceHeader(sb);
        sb.append("\nCondition: ").append(conditionResultOld).append(" -> ").append(conditionResultNew);
        if (nextRecomputeTime != null) {
            sb.append("\nNext recompute: ");
            sb.append(nextRecomputeTime);
        }
        sb.append("\nResult: ");
        if (outputTriple == null) {
            sb.append("null");
        } else {
            sb.append(outputTriple.toHumanReadableString());
        }
        if (profiling) {
            sb.append("\nEtime: ");
            sb.append(getEtime());
            sb.append(" ms");
        }
        appendTraceFooter(sb);
        LOGGER.trace(sb.toString());
    }

    private void traceDeferred() {
        traceEvaluationEnd();
        if (!LOGGER.isTraceEnabled()) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        sb.append("Mapping trace:\n");
        appendTraceHeader(sb);
        sb.append("\nEvaluation DEFERRED to: ");
        if (nextRecomputeTime == null) {
            sb.append("null");
        } else {
            sb.append(nextRecomputeTime);
        }
        if (profiling) {
            sb.append("\nEtime: ");
            sb.append(getEtime());
            sb.append(" ms");
        }
        appendTraceFooter(sb);
        LOGGER.trace(sb.toString());
    }

    private void traceFailure(Exception e) {
        LOGGER.error("Error evaluating {}: {}", new Object[] { getMappingContextDescription(), e.getMessage(), e });
        traceEvaluationEnd();
        if (!LOGGER.isTraceEnabled()) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        sb.append("Mapping failure:\n");
        appendTraceHeader(sb);
        sb.append("\nERROR: ").append(e.getClass().getSimpleName()).append(": ").append(e.getMessage());
        if (profiling) {
            sb.append("\nEtime: ");
            sb.append(getEtime());
            sb.append(" ms");
        }
        appendTraceFooter(sb);
        LOGGER.trace(sb.toString());
    }

    private void appendTraceHeader(StringBuilder sb) {
        sb.append("---[ MAPPING ");
        if (mappingType.getName() != null) {
            sb.append("'").append(mappingType.getName()).append("' ");
        }
        sb.append(" in ");
        sb.append(contextDescription);
        sb.append("]---------------------------");
        for (Source<?, ?> source : sources) {
            sb.append("\nSource: ");
            sb.append(source.shortDebugDump());
        }
        sb.append("\nTarget: ").append(MiscUtil.toString(outputDefinition));
        sb.append("\nExpression: ");
        if (expression == null) {
            sb.append("null");
        } else {
            sb.append(expression.shortDebugDump());
        }
    }

    private void appendTraceFooter(StringBuilder sb) {
        sb.append("\n------------------------------------------------------");
    }

    private boolean computeConditionResult(Collection<PrismPropertyValue<Boolean>> booleanPropertyValues) {
        if (mappingType.getCondition() == null) {
            // If condition is not present at all consider it to be true
            return true;
        }
        return ExpressionUtil.computeConditionResult(booleanPropertyValues);
    }

    public Boolean evaluateTimeConstraintValid(Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException {
        if (timeConstraintValid == null) {
            parseTimeConstraints(task, result);
        }
        return timeConstraintValid;
    }

    private void parseTimeConstraints(Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException {
        MappingTimeDeclarationType timeFromType = mappingType.getTimeFrom();
        MappingTimeDeclarationType timeToType = mappingType.getTimeTo();
        if (timeFromType == null && timeToType == null) {
            timeConstraintValid = true;
            return;
        }

        XMLGregorianCalendar timeFrom = parseTime(timeFromType, task, result);
        if (timeFrom == null && timeFromType != null) {
            // Time is specified but there is no value for it.
            // This means that event that should start validity haven't happened yet
            // therefore the mapping is not yet valid. 
            timeConstraintValid = false;
            return;
        }
        XMLGregorianCalendar timeTo = parseTime(timeToType, task, result);

        if (timeFrom != null && timeFrom.compare(now) == DatatypeConstants.GREATER) {
            // before timeFrom
            nextRecomputeTime = timeFrom;
            timeConstraintValid = false;
            return;
        }

        if (timeTo == null && timeToType != null) {
            // Time is specified but there is no value for it.
            // This means that event that should stop validity haven't happened yet
            // therefore the mapping is still valid. 
            timeConstraintValid = true;
            return;
        }

        if (timeTo != null && timeTo.compare(now) == DatatypeConstants.GREATER) {
            // between timeFrom and timeTo (also no timeFrom and before timeTo)
            nextRecomputeTime = timeTo;
            timeConstraintValid = true;
            return;
        }

        if (timeTo == null) {
            // after timeFrom and no timeTo
            // no nextRecomputeTime set, there is nothing to recompute in the future
            timeConstraintValid = true;
            return;

        } else {
            // after timeTo
            // no nextRecomputeTime set, there is nothing to recompute in the future
            timeConstraintValid = false;
            return;
        }

    }

    private XMLGregorianCalendar parseTime(MappingTimeDeclarationType timeType, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException {
        if (timeType == null) {
            return null;
        }
        XMLGregorianCalendar time = null;
        MappingSourceDeclarationType referenceTimeType = timeType.getReferenceTime();
        if (referenceTimeType == null) {
            if (time == null) {
                throw new SchemaException(
                        "No reference time specified (and there is also no default) in time specification in "
                                + getMappingContextDescription());
            } else {
                time = (XMLGregorianCalendar) defaultReferenceTime.clone();
            }
        } else {
            time = parseTimeSource(referenceTimeType, task, result);
            if (time == null) {
                // Reference time is specified but the value is not present.
                return null;
            }
            time = (XMLGregorianCalendar) time.clone();
        }
        Duration offset = timeType.getOffset();
        if (offset != null) {
            time.add(offset);
        }
        return time;
    }

    private XMLGregorianCalendar parseTimeSource(MappingSourceDeclarationType sourceType, Task task,
            OperationResult result) throws SchemaException, ObjectNotFoundException {
        ItemPathType itemPathType = sourceType.getPath();
        if (itemPathType == null) {
            throw new SchemaException("No path in source definition in " + getMappingContextDescription());
        }
        ItemPath path = itemPathType.getItemPath();
        if (path.isEmpty()) {
            throw new SchemaException("Empty source path in " + getMappingContextDescription());
        }

        Object sourceObject = ExpressionUtil.resolvePath(path, variables, sourceContext, objectResolver,
                "reference time definition in " + getMappingContextDescription(), task, result);
        if (sourceObject == null) {
            return null;
        }
        PrismProperty<XMLGregorianCalendar> timeProperty;
        if (sourceObject instanceof ItemDeltaItem<?, ?>) {
            timeProperty = (PrismProperty<XMLGregorianCalendar>) ((ItemDeltaItem<?, ?>) sourceObject).getItemNew();
        } else if (sourceObject instanceof Item<?, ?>) {
            timeProperty = (PrismProperty<XMLGregorianCalendar>) sourceObject;
        } else {
            throw new IllegalStateException("Unknown resolve result " + sourceObject);
        }
        if (timeProperty == null) {
            return null;
        }
        return timeProperty.getRealValue();
    }

    private Collection<Source<?, ?>> parseSources(Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException {
        List<MappingSourceDeclarationType> sourceTypes = mappingType.getSource();
        if (defaultSource != null) {
            defaultSource.recompute();
            sources.add(defaultSource);
            defaultSource.recompute();
        }
        if (sourceTypes != null) {
            for (MappingSourceDeclarationType sourceType : sourceTypes) {
                Source<?, ?> source = parseSource(sourceType, task, result);
                source.recompute();

                // Override existing sources (e.g. default source)
                Iterator<Source<?, ?>> iterator = sources.iterator();
                while (iterator.hasNext()) {
                    Source<?, ?> next = iterator.next();
                    if (next.getName().equals(source.getName())) {
                        iterator.remove();
                    }
                }

                sources.add(source);
            }
        }
        return sources;
    }

    private <IV extends PrismValue, ID extends ItemDefinition> Source<IV, ID> parseSource(
            MappingSourceDeclarationType sourceType, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException {
        ItemPathType itemPathType = sourceType.getPath();
        if (itemPathType == null) {
            throw new SchemaException("No path in source definition in " + getMappingContextDescription());
        }
        ItemPath path = itemPathType.getItemPath();
        if (path.isEmpty()) {
            throw new SchemaException("Empty source path in " + getMappingContextDescription());
        }
        QName name = sourceType.getName();
        if (name == null) {
            name = ItemPath.getName(path.last());
        }
        ItemPath resolvePath = path;
        Object sourceObject = ExpressionUtil.resolvePath(path, variables, sourceContext, objectResolver,
                "source definition in " + getMappingContextDescription(), task, result);
        Item<IV, ID> itemOld = null;
        ItemDelta<IV, ID> delta = null;
        Item<IV, ID> itemNew = null;
        ItemPath residualPath = null;
        Collection<? extends ItemDelta<?, ?>> subItemDeltas = null;
        if (sourceObject != null) {
            if (sourceObject instanceof ItemDeltaItem<?, ?>) {
                itemOld = ((ItemDeltaItem<IV, ID>) sourceObject).getItemOld();
                delta = ((ItemDeltaItem<IV, ID>) sourceObject).getDelta();
                itemNew = ((ItemDeltaItem<IV, ID>) sourceObject).getItemNew();
                residualPath = ((ItemDeltaItem<IV, ID>) sourceObject).getResidualPath();
                resolvePath = ((ItemDeltaItem<IV, ID>) sourceObject).getResolvePath();
                subItemDeltas = ((ItemDeltaItem<IV, ID>) sourceObject).getSubItemDeltas();
            } else if (sourceObject instanceof Item<?, ?>) {
                itemOld = (Item<IV, ID>) sourceObject;
                itemNew = (Item<IV, ID>) sourceObject;
            } else {
                throw new IllegalStateException("Unknown resolve result " + sourceObject);
            }
        }
        Source<IV, ID> source = new Source<>(itemOld, delta, itemNew, name);
        source.setResidualPath(residualPath);
        source.setResolvePath(resolvePath);
        source.setSubItemDeltas(subItemDeltas);
        return source;
    }

    private void parseTarget() throws SchemaException {
        MappingTargetDeclarationType targetType = mappingType.getTarget();
        if (targetType == null) {
            outputDefinition = defaultTargetDefinition;
            outputPath = defaultTargetPath;
        } else {
            ItemPathType itemPathType = targetType.getPath();
            if (itemPathType == null) {
                throw new SchemaException("No path in target definition in " + getMappingContextDescription());
            }
            ItemPath path = itemPathType.getItemPath();
            outputDefinition = ExpressionUtil.resolveDefinitionPath(path, variables, targetContext,
                    "target definition in " + getMappingContextDescription());
            if (outputDefinition == null) {
                throw new SchemaException("No target item that would conform to the path " + path + " in "
                        + getMappingContextDescription());
            }

            // Make the path relative if needed
            if (!path.isEmpty() && (path.first() instanceof NameItemPathSegment)
                    && ((NameItemPathSegment) path.first()).isVariable()) {
                outputPath = path.rest();
            } else {
                outputPath = path;
            }
        }
        if (stringPolicyResolver != null) {
            stringPolicyResolver.setOutputDefinition(outputDefinition);
            stringPolicyResolver.setOutputPath(outputPath);
        }
    }

    public D getOutputDefinition() throws SchemaException {
        if (outputDefinition == null) {
            parseTarget();
        }
        return outputDefinition;
    }

    public ItemPath getOutputPath() throws SchemaException {
        if (outputDefinition == null) {
            parseTarget();
        }
        return outputPath;
    }

    /**
     * Applies definition to the output if needed.
     */
    private void fixDefinition() throws SchemaException {
        if (outputTriple == null) {
            return;
        }
        if (outputTriple.isRaw()) {
            outputTriple.applyDefinition(outputDefinition);
        }
    }

    private void recomputeValues() {
        if (outputTriple == null) {
            return;
        }
        Visitor visitor = new Visitor() {
            @Override
            public void visit(Visitable visitable) {
                if (visitable instanceof PrismValue) {
                    ((PrismValue) visitable).recompute(getPrismContext());
                }
            }
        };
        outputTriple.accept(visitor);
    }

    private void setOrigin() {
        if (outputTriple == null) {
            return;
        }
        if (originType != null) {
            outputTriple.setOriginType(originType);
        }
        if (originObject != null) {
            outputTriple.setOriginObject(originObject);
        }
    }

    private void evaluateCondition(Task task, OperationResult result)
            throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {
        ExpressionType conditionExpressionType = mappingType.getCondition();
        if (conditionExpressionType == null) {
            // True -> True
            conditionOutputTriple = new PrismValueDeltaSetTriple<PrismPropertyValue<Boolean>>();
            conditionOutputTriple.addToZeroSet(new PrismPropertyValue<Boolean>(Boolean.TRUE));
            return;
        }
        PrismPropertyDefinition<Boolean> conditionOutput = new PrismPropertyDefinition<>(CONDITION_OUTPUT_NAME,
                DOMUtil.XSD_BOOLEAN, expressionFactory.getPrismContext());
        Expression<PrismPropertyValue<Boolean>, PrismPropertyDefinition<Boolean>> expression = expressionFactory
                .makeExpression(conditionExpressionType, conditionOutput,
                        "condition in " + getMappingContextDescription(), task, result);
        ExpressionEvaluationContext params = new ExpressionEvaluationContext(sources, variables,
                "condition in " + getMappingContextDescription(), task, result);
        params.setStringPolicyResolver(stringPolicyResolver);
        params.setExpressionFactory(expressionFactory);
        params.setDefaultSource(defaultSource);
        params.setDefaultTargetContext(getTargetContext());
        params.setRefinedObjectClassDefinition(getRefinedObjectClassDefinition());
        params.setMappingQName(mappingQName);
        conditionOutputTriple = expression.evaluate(params);
    }

    private void evaluateExpression(Task task, OperationResult result, boolean conditionResultOld,
            boolean conditionResultNew)
            throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {
        ExpressionType expressionType = null;
        if (mappingType != null) {
            expressionType = mappingType.getExpression();
        }
        expression = expressionFactory.makeExpression(expressionType, outputDefinition,
                "expression in " + getMappingContextDescription(), task, result);
        ExpressionEvaluationContext params = new ExpressionEvaluationContext(sources, variables,
                "expression in " + getMappingContextDescription(), task, result);
        params.setDefaultSource(defaultSource);
        params.setSkipEvaluationMinus(!conditionResultOld);
        params.setSkipEvaluationPlus(!conditionResultNew);
        params.setStringPolicyResolver(stringPolicyResolver);
        params.setExpressionFactory(expressionFactory);
        params.setDefaultTargetContext(getTargetContext());
        params.setRefinedObjectClassDefinition(getRefinedObjectClassDefinition());
        params.setMappingQName(mappingQName);
        outputTriple = expression.evaluate(params);

        if (outputTriple == null) {
            return;
        }

        // reflect condition change
        if (!conditionResultOld && conditionResultNew) {
            // Condition change false -> true
            outputTriple.addAllToPlusSet(outputTriple.getZeroSet());
            outputTriple.clearZeroSet();
            outputTriple.clearMinusSet();
        }
        if (conditionResultOld && !conditionResultNew) {
            // Condition change true -> false
            outputTriple.addAllToMinusSet(outputTriple.getZeroSet());
            outputTriple.clearZeroSet();
            outputTriple.clearPlusSet();
        }
    }

    /* (non-Javadoc)
     * @see com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer#getOutputTriple()
     */
    @Override
    public PrismValueDeltaSetTriple<V> getOutputTriple() {
        if (outputTriple != null && InternalsConfig.consistencyChecks) {
            try {
                outputTriple.checkNoParent();
            } catch (IllegalStateException e) {
                throw new IllegalStateException(e.getMessage() + " in output triple in " + getContextDescription(),
                        e);
            }
        }
        return outputTriple;
    }

    public Item<V, D> getOutput() throws SchemaException {
        if (outputTriple == null) {
            return null;
        }
        Item<V, D> output = outputDefinition.instantiate();
        output.addAll(PrismValue.cloneCollection(outputTriple.getNonNegativeValues()));
        return output;
    }

    public ItemDelta<V, D> createEmptyDelta(ItemPath path) {
        return outputDefinition.createEmptyDelta(path);
    }

    private <T> PrismPropertyValue<T> filterValue(PrismPropertyValue<T> propertyValue,
            List<ValueFilterType> filters) {
        PrismPropertyValue<T> filteredValue = propertyValue.clone();
        filteredValue.setOriginType(OriginType.INBOUND);

        if (filters == null || filters.isEmpty()) {
            return filteredValue;
        }

        for (ValueFilterType filter : filters) {
            Filter filterInstance = filterManager.getFilterInstance(filter.getType(), filter.getAny());
            filterInstance.apply(filteredValue);
        }

        return filteredValue;
    }

    /**
     * Shallow clone. Only the output is cloned deeply.
     */
    public PrismValueDeltaSetTripleProducer<V, D> clone() {
        Mapping<V, D> clone = new Mapping<>(mappingType, contextDescription, expressionFactory, securityEnforcer);
        clone.conditionMaskNew = this.conditionMaskNew;
        clone.conditionMaskOld = this.conditionMaskOld;
        if (this.conditionOutputTriple != null) {
            clone.conditionOutputTriple = this.conditionOutputTriple.clone();
        }
        clone.defaultSource = this.defaultSource;
        clone.defaultTargetDefinition = this.defaultTargetDefinition;
        clone.expressionFactory = this.expressionFactory;
        clone.mappingType = this.mappingType;
        clone.objectResolver = this.objectResolver;
        clone.originObject = this.originObject;
        clone.originType = this.originType;
        clone.outputDefinition = this.outputDefinition;
        clone.outputPath = this.outputPath;
        if (this.outputTriple != null) {
            clone.outputTriple = this.outputTriple.clone();
        }
        clone.contextDescription = this.contextDescription;
        clone.sourceContext = this.sourceContext;
        clone.sources = this.sources;
        clone.targetContext = this.targetContext;
        clone.variables = this.variables;

        return clone;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (conditionMaskNew ? 1231 : 1237);
        result = prime * result + (conditionMaskOld ? 1231 : 1237);
        result = prime * result + ((conditionOutputTriple == null) ? 0 : conditionOutputTriple.hashCode());
        result = prime * result + ((defaultSource == null) ? 0 : defaultSource.hashCode());
        result = prime * result + ((defaultTargetDefinition == null) ? 0 : defaultTargetDefinition.hashCode());
        result = prime * result + ((expressionFactory == null) ? 0 : expressionFactory.hashCode());
        result = prime * result + ((mappingType == null) ? 0 : mappingType.hashCode());
        result = prime * result + ((objectResolver == null) ? 0 : objectResolver.hashCode());
        result = prime * result + ((originObject == null) ? 0 : originObject.hashCode());
        result = prime * result + ((originType == null) ? 0 : originType.hashCode());
        result = prime * result + ((outputDefinition == null) ? 0 : outputDefinition.hashCode());
        result = prime * result + ((outputTriple == null) ? 0 : outputTriple.hashCode());
        result = prime * result + ((contextDescription == null) ? 0 : contextDescription.hashCode());
        result = prime * result + ((sourceContext == null) ? 0 : sourceContext.hashCode());
        result = prime * result + ((sources == null) ? 0 : sources.hashCode());
        result = prime * result + ((targetContext == null) ? 0 : targetContext.hashCode());
        result = prime * result + ((variables == null) ? 0 : variables.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Mapping other = (Mapping) obj;
        if (conditionMaskNew != other.conditionMaskNew)
            return false;
        if (conditionMaskOld != other.conditionMaskOld)
            return false;
        if (conditionOutputTriple == null) {
            if (other.conditionOutputTriple != null)
                return false;
        } else if (!conditionOutputTriple.equals(other.conditionOutputTriple))
            return false;
        if (defaultSource == null) {
            if (other.defaultSource != null)
                return false;
        } else if (!defaultSource.equals(other.defaultSource))
            return false;
        if (defaultTargetDefinition == null) {
            if (other.defaultTargetDefinition != null)
                return false;
        } else if (!defaultTargetDefinition.equals(other.defaultTargetDefinition))
            return false;
        if (expressionFactory == null) {
            if (other.expressionFactory != null)
                return false;
        } else if (!expressionFactory.equals(other.expressionFactory))
            return false;
        if (mappingType == null) {
            if (other.mappingType != null)
                return false;
        } else if (!mappingType.equals(other.mappingType))
            return false;
        if (objectResolver == null) {
            if (other.objectResolver != null)
                return false;
        } else if (!objectResolver.equals(other.objectResolver))
            return false;
        if (originObject == null) {
            if (other.originObject != null)
                return false;
        } else if (!originObject.equals(other.originObject))
            return false;
        if (originType != other.originType)
            return false;
        if (outputDefinition == null) {
            if (other.outputDefinition != null)
                return false;
        } else if (!outputDefinition.equals(other.outputDefinition))
            return false;
        if (outputTriple == null) {
            if (other.outputTriple != null)
                return false;
        } else if (!outputTriple.equals(other.outputTriple))
            return false;
        if (contextDescription == null) {
            if (other.contextDescription != null)
                return false;
        } else if (!contextDescription.equals(other.contextDescription))
            return false;
        if (sourceContext == null) {
            if (other.sourceContext != null)
                return false;
        } else if (!sourceContext.equals(other.sourceContext))
            return false;
        if (sources == null) {
            if (other.sources != null)
                return false;
        } else if (!sources.equals(other.sources))
            return false;
        if (targetContext == null) {
            if (other.targetContext != null)
                return false;
        } else if (!targetContext.equals(other.targetContext))
            return false;
        if (variables == null) {
            if (other.variables != null)
                return false;
        } else if (!variables.equals(other.variables))
            return false;
        return true;
    }

    @Override
    public String debugDump() {
        return debugDump(0);
    }

    @Override
    public String debugDump(int indent) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < indent; i++) {
            sb.append(INDENT_STRING);
        }
        sb.append(toString());
        return sb.toString();
    }

    @Override
    public String toString() {
        return "M(" + getMappingDisplayName() + " = " + outputTriple + toStringStrength() + ")";
    }

    private String getMappingDisplayName() {
        if (mappingQName != null) {
            return SchemaDebugUtil.prettyPrint(mappingQName);
        }
        if (outputDefinition == null) {
            return null;
        }
        return SchemaDebugUtil.prettyPrint(outputDefinition.getName());
    }

    private String toStringStrength() {
        switch (getStrength()) {
        case NORMAL:
            return "";
        case WEAK:
            return ", weak";
        case STRONG:
            return ", strong";
        }
        return null;
    }

}