com.evolveum.midpoint.model.impl.lens.LensUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.model.impl.lens.LensUtil.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.impl.lens;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import javax.xml.bind.JAXBElement;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.polystring.PrismDefaultPolyStringNormalizer;
import com.evolveum.midpoint.schema.util.ObjectResolver;
import com.evolveum.midpoint.util.logging.LoggingUtils;

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.mutable.MutableBoolean;

import com.evolveum.midpoint.common.ActivationComputer;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchema;
import com.evolveum.midpoint.model.api.PolicyViolationException;
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.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.model.common.expression.script.ScriptExpression;
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.expr.ModelExpressionThreadLocalHolder;
import com.evolveum.midpoint.model.impl.lens.projector.ValueMatcher;
import com.evolveum.midpoint.model.impl.util.Utils;
import com.evolveum.midpoint.prism.*;
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.PrismValueDeltaSetTriple;
import com.evolveum.midpoint.prism.delta.PropertyDelta;
import com.evolveum.midpoint.prism.delta.ReferenceDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.util.PrismUtil;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.GenerateExpressionEvaluatorType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.IterationSpecificationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LayerType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingStrengthType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MetadataType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectPolicyConfigurationType;
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.PasswordType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.PropertyConstraintType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectTypeDependencyType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionReturnTypeType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.StringPolicyType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.TimeIntervalStatusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;

/**
 * @author semancik
 *
 */
public class LensUtil {

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

    public static <F extends ObjectType> void traceContext(Trace logger, String activity, String phase,
            boolean important, LensContext<F> context, boolean showTriples) throws SchemaException {
        if (logger.isTraceEnabled()) {
            logger.trace("Lens context:\n" + "---[ {} context {} ]--------------------------------\n" + "{}\n",
                    new Object[] { activity, phase, context.dump(showTriples) });
        }
    }

    public static <F extends ObjectType> ResourceType getResource(LensContext<F> context, String resourceOid,
            ProvisioningService provisioningService, Task task, OperationResult result)
            throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException,
            SecurityViolationException {
        ResourceType resourceType = context.getResource(resourceOid);
        if (resourceType == null) {
            // Fetching from provisioning to take advantage of caching and
            // pre-parsed schema
            resourceType = provisioningService.getObject(ResourceType.class, resourceOid, null, task, result)
                    .asObjectable();
            context.rememberResource(resourceType);
        }
        return resourceType;
    }

    public static <F extends ObjectType> ResourceType getResource(LensContext<F> context, String resourceOid,
            ObjectResolver objectResolver, Task task, OperationResult result) throws ObjectNotFoundException,
            CommunicationException, SchemaException, ConfigurationException, SecurityViolationException {
        ResourceType resourceType = context.getResource(resourceOid);
        if (resourceType == null) {
            ObjectReferenceType ref = new ObjectReferenceType();
            ref.setType(ResourceType.COMPLEX_TYPE);
            ref.setOid(resourceOid);
            resourceType = objectResolver.resolve(ref, ResourceType.class, null, "resource fetch in lens", task,
                    result);
            context.rememberResource(resourceType);
        }
        return resourceType;
    }

    public static String refineProjectionIntent(ShadowKindType kind, String intent, ResourceType resource,
            PrismContext prismContext) throws SchemaException {
        RefinedResourceSchema refinedSchema = RefinedResourceSchema.getRefinedSchema(resource, LayerType.MODEL,
                prismContext);
        RefinedObjectClassDefinition rObjClassDef = refinedSchema.getRefinedDefinition(kind, intent);
        if (rObjClassDef == null) {
            throw new SchemaException(
                    "No projection definition for kind=" + kind + " intent=" + intent + " in " + resource);
        }
        return rObjClassDef.getIntent();
    }

    public static <F extends FocusType> LensProjectionContext getProjectionContext(LensContext<F> context,
            PrismObject<ShadowType> equivalentAccount, ProvisioningService provisioningService,
            PrismContext prismContext, Task task, OperationResult result) throws ObjectNotFoundException,
            CommunicationException, SchemaException, ConfigurationException, SecurityViolationException {
        ShadowType equivalentAccountType = equivalentAccount.asObjectable();
        ShadowKindType kind = ShadowUtil.getKind(equivalentAccountType);
        return getProjectionContext(context, ShadowUtil.getResourceOid(equivalentAccountType), kind,
                equivalentAccountType.getIntent(), provisioningService, prismContext, task, result);
    }

    public static <F extends FocusType> LensProjectionContext getProjectionContext(LensContext<F> context,
            String resourceOid, ShadowKindType kind, String intent, ProvisioningService provisioningService,
            PrismContext prismContext, Task task, OperationResult result) throws ObjectNotFoundException,
            CommunicationException, SchemaException, ConfigurationException, SecurityViolationException {
        ResourceType resource = getResource(context, resourceOid, provisioningService, task, result);
        String refinedIntent = refineProjectionIntent(kind, intent, resource, prismContext);
        ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, refinedIntent);
        return context.findProjectionContext(rsd);
    }

    public static <F extends ObjectType> LensProjectionContext getOrCreateProjectionContext(LensContext<F> context,
            ResourceShadowDiscriminator rsd) {
        LensProjectionContext accountSyncContext = context.findProjectionContext(rsd);
        if (accountSyncContext == null) {
            accountSyncContext = context.createProjectionContext(rsd);
            ResourceType resource = context.getResource(rsd.getResourceOid());
            accountSyncContext.setResource(resource);
        }
        return accountSyncContext;
    }

    public static <F extends ObjectType> LensProjectionContext createAccountContext(LensContext<F> context,
            ResourceShadowDiscriminator rsd) {
        return new LensProjectionContext(context, rsd);
    }

    /**
     * Consolidate the mappings of a single item to a delta. It takes the convenient structure of ItemValueWithOrigin triple.
     * It produces the delta considering the mapping exclusion, authoritativeness and strength.
      *
      * filterExistingValues: if true, then values that already exist in the item are not added (and those that don't exist are not removed)
     */
    public static <V extends PrismValue, D extends ItemDefinition, I extends ItemValueWithOrigin<V, D>> ItemDelta<V, D> consolidateTripleToDelta(
            ItemPath itemPath, DeltaSetTriple<I> triple, D itemDefinition, ItemDelta<V, D> aprioriItemDelta,
            PrismContainer<?> itemContainer, ValueMatcher<?> valueMatcher, Comparator<V> comparator,
            boolean addUnchangedValues, boolean filterExistingValues, boolean isExclusiveStrong,
            String contextDescription, boolean applyWeak)
            throws ExpressionEvaluationException, PolicyViolationException, SchemaException {

        ItemDelta<V, D> itemDelta = itemDefinition.createEmptyDelta(itemPath);

        Item<V, D> itemExisting = null;
        if (itemContainer != null) {
            itemExisting = itemContainer.findItem(itemPath);
        }

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Consolidating {} triple:\n{}\nApriori Delta:\n{}\nExisting item:\n{}",
                    new Object[] { itemPath, triple.debugDump(),
                            aprioriItemDelta == null ? "null" : aprioriItemDelta.debugDump(),
                            itemExisting == null ? "null" : itemExisting.debugDump(), });
        }

        Collection<V> allValues = collectAllValues(triple);

        final MutableBoolean itemHasStrongMutable = new MutableBoolean(false);
        SimpleVisitor<I> visitor = new SimpleVisitor<I>() {
            @Override
            public void visit(I pvwo) {
                if (pvwo.getMapping().getStrength() == MappingStrengthType.STRONG) {
                    itemHasStrongMutable.setValue(true);
                }
            }
        };
        triple.accept(visitor);
        boolean ignoreNormalMappings = itemHasStrongMutable.booleanValue() && isExclusiveStrong;

        // We will process each value individually. I really mean each value. This whole method deals with
        // a single item (e.g. attribute). But this loop iterates over every potential value of that item.
        for (V value : allValues) {

            LOGGER.trace("item existing: {}, consolidating value: {}", itemExisting, value);
            // Check what to do with the value using the usual "triple routine". It means that if a value is
            // in zero set than we need no delta, plus set means add delta and minus set means delete delta.
            // The first set that the value is present determines the result.
            // TODO shouldn't we use valueMatcher here? [med]
            Collection<ItemValueWithOrigin<V, D>> zeroPvwos = collectPvwosFromSet(value, triple.getZeroSet());
            Collection<ItemValueWithOrigin<V, D>> plusPvwos = collectPvwosFromSet(value, triple.getPlusSet());
            Collection<ItemValueWithOrigin<V, D>> minusPvwos = collectPvwosFromSet(value, triple.getMinusSet());

            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("PVWOs for value {}:\nzero = {}\nplus = {}\nminus = {}",
                        new Object[] { value, zeroPvwos, plusPvwos, minusPvwos });
            }

            boolean zeroHasStrong = false;
            if (!zeroPvwos.isEmpty()) {
                for (ItemValueWithOrigin<V, D> pvwo : zeroPvwos) {
                    PrismValueDeltaSetTripleProducer<V, D> mapping = pvwo.getMapping();
                    if (mapping.getStrength() == MappingStrengthType.STRONG) {
                        zeroHasStrong = true;
                    }
                }
            }

            if (zeroHasStrong && aprioriItemDelta != null && aprioriItemDelta.isValueToDelete(value, true)) {
                throw new PolicyViolationException("Attempt to delete value " + value + " from item " + itemPath
                        + " but that value is mandated by a strong mapping (in " + contextDescription + ")");
            }
            if (!zeroPvwos.isEmpty() && !addUnchangedValues) {
                // Value unchanged, nothing to do
                LOGGER.trace("Value {} unchanged, doing nothing", value);
                continue;
            }

            PrismValueDeltaSetTripleProducer<V, D> exclusiveMapping = null;
            Collection<ItemValueWithOrigin<V, D>> pvwosToAdd = null;
            if (addUnchangedValues) {
                pvwosToAdd = MiscUtil.union(zeroPvwos, plusPvwos);
            } else {
                pvwosToAdd = plusPvwos;
            }

            if (!pvwosToAdd.isEmpty()) {
                boolean weakOnly = true;
                boolean hasStrong = false;
                // There may be several mappings that imply that value. So check them all for
                // exclusions and strength
                for (ItemValueWithOrigin<V, D> pvwoToAdd : pvwosToAdd) {
                    PrismValueDeltaSetTripleProducer<V, D> mapping = pvwoToAdd.getMapping();
                    if (mapping.getStrength() != MappingStrengthType.WEAK) {
                        weakOnly = false;
                    }
                    if (mapping.getStrength() == MappingStrengthType.STRONG) {
                        hasStrong = true;
                    }
                    if (mapping.isExclusive()) {
                        if (exclusiveMapping == null) {
                            exclusiveMapping = mapping;
                        } else {
                            String message = "Exclusion conflict in " + contextDescription + ", item " + itemPath
                                    + ", conflicting constructions: " + exclusiveMapping + " and " + mapping;
                            LOGGER.error(message);
                            throw new ExpressionEvaluationException(message);
                        }
                    }
                }
                if (weakOnly) {
                    // Postpone processing of weak values until we process all other values
                    LOGGER.trace("Value {} mapping is weak in item {}, postponing processing in {}",
                            new Object[] { value, itemPath, contextDescription });
                    continue;
                }
                if (!hasStrong && ignoreNormalMappings) {
                    LOGGER.trace(
                            "Value {} mapping is normal in item {} and we have exclusiveStrong, skipping processing in {}",
                            new Object[] { value, itemPath, contextDescription });
                    continue;
                }
                if (hasStrong && aprioriItemDelta != null && aprioriItemDelta.isValueToDelete(value, true)) {
                    throw new PolicyViolationException("Attempt to delete value " + value + " from item " + itemPath
                            + " but that value is mandated by a strong mapping (in " + contextDescription + ")");
                }
                if (!hasStrong && (aprioriItemDelta != null && !aprioriItemDelta.isEmpty())) {
                    // There is already a delta, skip this
                    LOGGER.trace(
                            "Value {} mapping is not strong and the item {} already has a delta that is more concrete, "
                                    + "skipping adding in {}",
                            new Object[] { value, itemPath, contextDescription });
                    continue;
                }
                if (filterExistingValues && hasValue(itemExisting, value, valueMatcher, comparator)) {
                    LOGGER.trace(
                            "Value {} NOT added to delta for item {} because the item already has that value in {}",
                            new Object[] { value, itemPath, contextDescription });
                    continue;
                }
                LOGGER.trace("Value {} added to delta for item {} in {}",
                        new Object[] { value, itemPath, contextDescription });
                itemDelta.addValueToAdd((V) value.clone());
                continue;
            }

            // We need to check for empty plus set. Values that are both in plus and minus are considered to be added.
            // So check for that special case here to avoid removing them.
            if (!minusPvwos.isEmpty() && plusPvwos.isEmpty()) {
                boolean weakOnly = true;
                boolean hasStrong = false;
                boolean hasAuthoritative = false;
                // There may be several mappings that imply that value. So check them all for
                // exclusions and strength
                for (ItemValueWithOrigin<V, D> pvwo : minusPvwos) {
                    PrismValueDeltaSetTripleProducer<V, D> mapping = pvwo.getMapping();
                    if (mapping.getStrength() != MappingStrengthType.WEAK) {
                        weakOnly = false;
                    }
                    if (mapping.getStrength() == MappingStrengthType.STRONG) {
                        hasStrong = true;
                    }
                    if (mapping.isAuthoritative()) {
                        hasAuthoritative = true;
                    }
                }
                if (!hasAuthoritative) {
                    LOGGER.trace("Value {} has no authoritative mapping for item {}, skipping deletion in {}",
                            new Object[] { value, itemPath, contextDescription });
                    continue;
                }
                if (!hasStrong && (aprioriItemDelta != null && !aprioriItemDelta.isEmpty())) {
                    // There is already a delta, skip this
                    LOGGER.trace(
                            "Value {} mapping is not strong and the item {} already has a delta that is more concrete, skipping deletion in {}",
                            new Object[] { value, itemPath, contextDescription });
                    continue;
                }
                if (weakOnly && (itemExisting != null && !itemExisting.isEmpty())) {
                    // There is already a value, skip this
                    LOGGER.trace(
                            "Value {} mapping is weak and the item {} already has a value, skipping deletion in {}",
                            new Object[] { value, itemPath, contextDescription });
                    continue;
                }

                if (weakOnly && !applyWeak && (itemExisting == null || itemExisting.isEmpty())) {
                    // There is a weak mapping on a property, but we do not have full account available, so skipping deletion of the value is better way
                    LOGGER.trace(
                            "Value {} mapping is weak and the full account could not be fetched, skipping deletion in {}",
                            new Object[] { value, itemPath, contextDescription });
                    continue;
                }
                if (filterExistingValues && !hasValue(itemExisting, value, valueMatcher, comparator)) {
                    LOGGER.trace(
                            "Value {} NOT deleted from delta for item {} the item does not have that value in {} (matcher: {})",
                            new Object[] { value, itemPath, contextDescription, valueMatcher });
                    continue;
                }
                LOGGER.trace("Value {} deleted to delta for item {} in {}",
                        new Object[] { value, itemPath, contextDescription });
                itemDelta.addValueToDelete((V) value.clone());
            }

            if (!zeroPvwos.isEmpty()) {
                boolean weakOnly = true;
                boolean hasStrong = false;
                boolean hasAuthoritative = false;
                // There may be several mappings that imply that value. So check them all for
                // exclusions and strength
                for (ItemValueWithOrigin<V, D> pvwo : zeroPvwos) {
                    PrismValueDeltaSetTripleProducer<V, D> mapping = pvwo.getMapping();
                    if (mapping.getStrength() != MappingStrengthType.WEAK) {
                        weakOnly = false;
                    }
                    if (mapping.getStrength() == MappingStrengthType.STRONG) {
                        hasStrong = true;
                    }
                    if (mapping.isAuthoritative()) {
                        hasAuthoritative = true;
                    }
                }

                if (aprioriItemDelta != null && aprioriItemDelta.isReplace()) {
                    // Any strong mappings in the zero set needs to be re-applied as otherwise the replace will destroy it
                    if (hasStrong) {
                        LOGGER.trace(
                                "Value {} added to delta for item {} in {} because there is strong mapping in the zero set",
                                new Object[] { value, itemPath, contextDescription });
                        itemDelta.addValueToAdd((V) value.clone());
                        continue;
                    }
                }
            }
        }

        Item<V, D> itemNew = null;
        if (itemContainer != null) {
            itemNew = itemContainer.findItem(itemPath);
        }
        if (!hasValue(itemNew, itemDelta)) {
            // The application of computed delta results in no value, apply weak mappings
            Collection<? extends ItemValueWithOrigin<V, D>> nonNegativePvwos = triple.getNonNegativeValues();
            Collection<V> valuesToAdd = addWeakValues(nonNegativePvwos, OriginType.ASSIGNMENTS, applyWeak);
            if (valuesToAdd.isEmpty()) {
                valuesToAdd = addWeakValues(nonNegativePvwos, OriginType.OUTBOUND, applyWeak);
            }
            if (valuesToAdd.isEmpty()) {
                valuesToAdd = addWeakValues(nonNegativePvwos, null, applyWeak);
            }
            LOGGER.trace("No value for item {} in {}, weak mapping processing yielded values: {}",
                    new Object[] { itemPath, contextDescription, valuesToAdd });
            itemDelta.addValuesToAdd(valuesToAdd);
        } else {
            LOGGER.trace("Existing values for item {} in {}, weak mapping processing skipped",
                    new Object[] { itemPath, contextDescription });
        }

        return itemDelta;

    }

    public static boolean isSyncChannel(String channel) {
        if (channel == null) {
            return false;
        }

        return (channel.equals(SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC_URI)
                || channel.equals(SchemaConstants.CHANGE_CHANNEL_RECON_URI));
    }

    private static <V extends PrismValue, D extends ItemDefinition> boolean hasValue(Item<V, D> item,
            ItemDelta<V, D> itemDelta) throws SchemaException {
        if (item == null || item.isEmpty()) {
            if (itemDelta != null && itemDelta.addsAnyValue()) {
                return true;
            } else {
                return false;
            }
        } else {
            if (itemDelta == null || itemDelta.isEmpty()) {
                return true;
            } else {
                Item<V, D> clonedItem = item.clone();
                itemDelta.applyToMatchingPath(clonedItem);
                return !clonedItem.isEmpty();
            }
        }
    }

    private static <V extends PrismValue, D extends ItemDefinition> Collection<V> addWeakValues(
            Collection<? extends ItemValueWithOrigin<V, D>> pvwos, OriginType origin, boolean applyWeak) {
        Collection<V> values = new ArrayList<V>();
        for (ItemValueWithOrigin<V, D> pvwo : pvwos) {
            if (pvwo.getMapping().getStrength() == MappingStrengthType.WEAK && applyWeak) {
                if (origin == null || origin == pvwo.getPropertyValue().getOriginType()) {
                    values.add((V) pvwo.getPropertyValue().clone());
                }
            }
        }
        return values;
    }

    private static <V extends PrismValue, D extends ItemDefinition> boolean hasValue(Item<V, D> existingUserItem,
            V newValue, ValueMatcher<?> valueMatcher, Comparator<V> comparator) {
        if (existingUserItem == null) {
            return false;
        }
        if (valueMatcher != null && newValue instanceof PrismPropertyValue) {
            return valueMatcher.hasRealValue((PrismProperty) existingUserItem, (PrismPropertyValue) newValue);
        } else {
            return existingUserItem.contains(newValue, true, comparator);
        }
    }

    private static <V extends PrismValue, D extends ItemDefinition> Collection<V> collectAllValues(
            DeltaSetTriple<? extends ItemValueWithOrigin<V, D>> triple) {
        Collection<V> allValues = new HashSet<>();
        collectAllValuesFromSet(allValues, triple.getZeroSet());
        collectAllValuesFromSet(allValues, triple.getPlusSet());
        collectAllValuesFromSet(allValues, triple.getMinusSet());
        return allValues;
    }

    private static <V extends PrismValue, D extends ItemDefinition> void collectAllValuesFromSet(
            Collection<V> allValues, Collection<? extends ItemValueWithOrigin<V, D>> collection) {
        if (collection == null) {
            return;
        }
        for (ItemValueWithOrigin<V, D> pvwo : collection) {
            V pval = pvwo.getPropertyValue();
            if (!PrismValue.containsRealValue(allValues, pval)) {
                allValues.add(pval);
            }
        }
    }

    private static <V extends PrismValue, D extends ItemDefinition> Collection<ItemValueWithOrigin<V, D>> collectPvwosFromSet(
            V pvalue, Collection<? extends ItemValueWithOrigin<V, D>> deltaSet) {
        Collection<ItemValueWithOrigin<V, D>> pvwos = new ArrayList<>();
        for (ItemValueWithOrigin<V, D> setPvwo : deltaSet) {
            if (setPvwo.equalsRealValue(pvalue)) {
                pvwos.add(setPvwo);
            }
        }
        return pvwos;
    }

    public static PropertyDelta<XMLGregorianCalendar> createActivationTimestampDelta(ActivationStatusType status,
            XMLGregorianCalendar now, PrismContainerDefinition<ActivationType> activationDefinition,
            OriginType origin) {
        QName timestampPropertyName;
        if (status == ActivationStatusType.ENABLED) {
            timestampPropertyName = ActivationType.F_ENABLE_TIMESTAMP;
        } else if (status == ActivationStatusType.DISABLED) {
            timestampPropertyName = ActivationType.F_DISABLE_TIMESTAMP;
        } else if (status == ActivationStatusType.ARCHIVED) {
            timestampPropertyName = ActivationType.F_ARCHIVE_TIMESTAMP;
        } else {
            throw new IllegalArgumentException("Unknown activation status " + status);
        }

        PrismPropertyDefinition<XMLGregorianCalendar> timestampDef = activationDefinition
                .findPropertyDefinition(timestampPropertyName);
        PropertyDelta<XMLGregorianCalendar> timestampDelta = timestampDef
                .createEmptyDelta(new ItemPath(FocusType.F_ACTIVATION, timestampPropertyName));
        timestampDelta.setValueToReplace(new PrismPropertyValue<XMLGregorianCalendar>(now, origin, null));
        return timestampDelta;
    }

    public static <F extends ObjectType> void moveTriggers(LensProjectionContext projCtx,
            LensFocusContext<F> focusCtx) throws SchemaException {
        ObjectDelta<ShadowType> projSecondaryDelta = projCtx.getSecondaryDelta();
        if (projSecondaryDelta == null) {
            return;
        }
        Collection<? extends ItemDelta> modifications = projSecondaryDelta.getModifications();
        Iterator<? extends ItemDelta> iterator = modifications.iterator();
        while (iterator.hasNext()) {
            ItemDelta projModification = iterator.next();
            LOGGER.trace("MOD: {}\n{}", projModification.getPath(), projModification.debugDump());
            if (projModification.getPath().equivalent(SchemaConstants.PATH_TRIGGER)) {
                focusCtx.swallowToProjectionWaveSecondaryDelta(projModification);
                iterator.remove();
            }
        }
    }

    public static <V extends PrismValue, F extends ObjectType> void evaluateScript(
            ScriptExpression scriptExpression, LensContext<F> lensContext, ExpressionVariables variables,
            String shortDesc, Task task, OperationResult parentResult)
            throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException {
        ModelExpressionThreadLocalHolder.pushLensContext(lensContext);
        ModelExpressionThreadLocalHolder.pushCurrentResult(parentResult);
        ModelExpressionThreadLocalHolder.pushCurrentTask(task);
        try {
            scriptExpression.evaluate(variables, ScriptExpressionReturnTypeType.SCALAR, false, shortDesc, task,
                    parentResult);
        } finally {
            ModelExpressionThreadLocalHolder.popLensContext();
            ModelExpressionThreadLocalHolder.popCurrentResult();
            ModelExpressionThreadLocalHolder.popCurrentTask();
            //         if (lensContext.getDebugListener() != null) {
            //            lensContext.getDebugListener().afterScriptEvaluation(lensContext, scriptExpression);
            //         }
        }
    }

    public static Object getIterationVariableValue(LensProjectionContext accCtx) {
        Integer iterationOld = null;
        PrismObject<ShadowType> shadowCurrent = accCtx.getObjectCurrent();
        if (shadowCurrent != null) {
            iterationOld = shadowCurrent.asObjectable().getIteration();
        }
        if (iterationOld == null) {
            return accCtx.getIteration();
        }
        PrismPropertyDefinition<Integer> propDef = new PrismPropertyDefinition<Integer>(
                ExpressionConstants.VAR_ITERATION, DOMUtil.XSD_INT, accCtx.getPrismContext());
        PrismProperty<Integer> propOld = propDef.instantiate();
        propOld.setRealValue(iterationOld);
        PropertyDelta<Integer> propDelta = propDef
                .createEmptyDelta(new ItemPath(ExpressionConstants.VAR_ITERATION));
        propDelta.setValueToReplace(new PrismPropertyValue<Integer>(accCtx.getIteration()));
        PrismProperty<Integer> propNew = propDef.instantiate();
        propNew.setRealValue(accCtx.getIteration());
        ItemDeltaItem<PrismPropertyValue<Integer>, PrismPropertyDefinition<Integer>> idi = new ItemDeltaItem<>(
                propOld, propDelta, propNew);
        return idi;
    }

    public static Object getIterationTokenVariableValue(LensProjectionContext accCtx) {
        String iterationTokenOld = null;
        PrismObject<ShadowType> shadowCurrent = accCtx.getObjectCurrent();
        if (shadowCurrent != null) {
            iterationTokenOld = shadowCurrent.asObjectable().getIterationToken();
        }
        if (iterationTokenOld == null) {
            return accCtx.getIterationToken();
        }
        PrismPropertyDefinition<String> propDef = new PrismPropertyDefinition<String>(
                ExpressionConstants.VAR_ITERATION_TOKEN, DOMUtil.XSD_STRING, accCtx.getPrismContext());
        PrismProperty<String> propOld = propDef.instantiate();
        propOld.setRealValue(iterationTokenOld);
        PropertyDelta<String> propDelta = propDef
                .createEmptyDelta(new ItemPath(ExpressionConstants.VAR_ITERATION_TOKEN));
        propDelta.setValueToReplace(new PrismPropertyValue<String>(accCtx.getIterationToken()));
        PrismProperty<String> propNew = propDef.instantiate();
        propNew.setRealValue(accCtx.getIterationToken());
        ItemDeltaItem<PrismPropertyValue<String>, PrismPropertyDefinition<String>> idi = new ItemDeltaItem<>(
                propOld, propDelta, propNew);
        return idi;
    }

    /**
     * Extracts the delta from this projection context and also from all other projection contexts that have 
     * equivalent discriminator.
     */
    public static <F extends ObjectType, T> PropertyDelta<T> findAPrioriDelta(LensContext<F> context,
            LensProjectionContext projCtx, ItemPath projectionPropertyPath) throws SchemaException {
        PropertyDelta<T> aPrioriDelta = null;
        for (LensProjectionContext aProjCtx : findRelatedContexts(context, projCtx)) {
            ObjectDelta<ShadowType> aProjDelta = aProjCtx.getDelta();
            if (aProjDelta != null) {
                PropertyDelta<T> aPropProjDelta = aProjDelta.findPropertyDelta(projectionPropertyPath);
                if (aPropProjDelta != null) {
                    if (aPrioriDelta == null) {
                        aPrioriDelta = aPropProjDelta.clone();
                    } else {
                        aPrioriDelta.merge(aPropProjDelta);
                    }
                }
            }
        }
        return aPrioriDelta;
    }

    /**
     * Extracts the delta from this projection context and also from all other projection contexts that have 
     * equivalent discriminator.
     */
    public static <F extends ObjectType, T> ObjectDelta<ShadowType> findAPrioriDelta(LensContext<F> context,
            LensProjectionContext projCtx) throws SchemaException {
        ObjectDelta<ShadowType> aPrioriDelta = null;
        for (LensProjectionContext aProjCtx : findRelatedContexts(context, projCtx)) {
            ObjectDelta<ShadowType> aProjDelta = aProjCtx.getDelta();
            if (aProjDelta != null) {
                if (aPrioriDelta == null) {
                    aPrioriDelta = aProjDelta.clone();
                } else {
                    aPrioriDelta.merge(aProjDelta);
                }
            }
        }
        return aPrioriDelta;
    }

    /**
     * Returns a list of context that have equivalent discriminator with the reference context. Ordered by "order" in the
     * discriminator.
     */
    public static <F extends ObjectType> List<LensProjectionContext> findRelatedContexts(LensContext<F> context,
            LensProjectionContext refProjCtx) {
        List<LensProjectionContext> projCtxs = new ArrayList<LensProjectionContext>();
        ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator();
        if (refDiscr == null) {
            return projCtxs;
        }
        for (LensProjectionContext aProjCtx : context.getProjectionContexts()) {
            ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator();
            if (refDiscr.equivalent(aDiscr)) {
                projCtxs.add(aProjCtx);
            }
        }
        Comparator<? super LensProjectionContext> orderComparator = new Comparator<LensProjectionContext>() {
            @Override
            public int compare(LensProjectionContext ctx1, LensProjectionContext ctx2) {
                int order1 = ctx1.getResourceShadowDiscriminator().getOrder();
                int order2 = ctx2.getResourceShadowDiscriminator().getOrder();
                return Integer.compare(order1, order2);
            }
        };
        Collections.sort(projCtxs, orderComparator);
        return projCtxs;
    }

    public static <F extends ObjectType> boolean hasLowerOrderContext(LensContext<F> context,
            LensProjectionContext refProjCtx) {
        ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator();
        for (LensProjectionContext aProjCtx : context.getProjectionContexts()) {
            ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator();
            if (refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() > aDiscr.getOrder())) {
                return true;
            }
        }
        return false;
    }

    public static <F extends ObjectType> boolean hasDependentContext(LensContext<F> context,
            LensProjectionContext targetProjectionContext) {
        for (LensProjectionContext projectionContext : context.getProjectionContexts()) {
            for (ResourceObjectTypeDependencyType dependency : projectionContext.getDependencies()) {
                if (isDependencyTargetContext(projectionContext, targetProjectionContext, dependency)) {
                    return true;
                }
            }
        }
        return false;
    }

    public static <F extends ObjectType> boolean isDependencyTargetContext(LensProjectionContext sourceProjContext,
            LensProjectionContext targetProjectionContext, ResourceObjectTypeDependencyType dependency) {
        ResourceShadowDiscriminator refDiscr = new ResourceShadowDiscriminator(dependency,
                sourceProjContext.getResource().getOid(), sourceProjContext.getKind());
        return targetProjectionContext.compareResourceShadowDiscriminator(refDiscr, false);
    }

    public static <F extends ObjectType> LensProjectionContext findLowerOrderContext(LensContext<F> context,
            LensProjectionContext refProjCtx) {
        int minOrder = -1;
        LensProjectionContext foundCtx = null;
        ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator();
        for (LensProjectionContext aProjCtx : context.getProjectionContexts()) {
            ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator();
            if (refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() > aDiscr.getOrder())) {
                if (minOrder < 0 || (aDiscr.getOrder() < minOrder)) {
                    minOrder = aDiscr.getOrder();
                    foundCtx = aProjCtx;
                }
            }
        }
        return foundCtx;
    }

    public static <T extends ObjectType, F extends ObjectType> void setContextOid(LensContext<F> context,
            LensElementContext<T> objectContext, String oid) {
        objectContext.setOid(oid);
        // Check if we need to propagate this oid also to higher-order contexts
        if (!(objectContext instanceof LensProjectionContext)) {
            return;
        }
        LensProjectionContext refProjCtx = (LensProjectionContext) objectContext;
        ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator();
        if (refDiscr == null) {
            return;
        }
        for (LensProjectionContext aProjCtx : context.getProjectionContexts()) {
            ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator();
            if (aDiscr != null && refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() < aDiscr.getOrder())) {
                aProjCtx.setOid(oid);
            }
        }
    }

    public static PrismObject<SystemConfigurationType> getSystemConfiguration(LensContext context,
            RepositoryService repositoryService, OperationResult result)
            throws ObjectNotFoundException, SchemaException {
        PrismObject<SystemConfigurationType> systemConfiguration = context.getSystemConfiguration();
        if (systemConfiguration == null) {
            systemConfiguration = Utils.getSystemConfiguration(repositoryService, result);
            context.setSystemConfiguration(systemConfiguration);
        }
        return systemConfiguration;
    }

    public static <F extends FocusType> PrismObjectDefinition<F> getFocusDefinition(LensContext<F> context) {
        LensFocusContext<F> focusContext = context.getFocusContext();
        if (focusContext == null) {
            return null;
        }
        Class<F> typeClass = focusContext.getObjectTypeClass();
        return context.getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(typeClass);
    }

    public static int determineMaxIterations(IterationSpecificationType iterationSpecType) {
        if (iterationSpecType != null) {
            return iterationSpecType.getMaxIterations();
        } else {
            return 0;
        }
    }

    public static <F extends ObjectType> String formatIterationToken(LensContext<F> context,
            LensElementContext<?> accountContext, IterationSpecificationType iterationType, int iteration,
            ExpressionFactory expressionFactory, ExpressionVariables variables, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {
        if (iterationType == null) {
            return formatIterationTokenDefault(iteration);
        }
        ExpressionType tokenExpressionType = iterationType.getTokenExpression();
        if (tokenExpressionType == null) {
            return formatIterationTokenDefault(iteration);
        }
        PrismPropertyDefinition<String> outputDefinition = new PrismPropertyDefinition<String>(
                ExpressionConstants.VAR_ITERATION_TOKEN, DOMUtil.XSD_STRING, context.getPrismContext());
        Expression<PrismPropertyValue<String>, PrismPropertyDefinition<String>> expression = expressionFactory
                .makeExpression(tokenExpressionType, outputDefinition,
                        "iteration token expression in " + accountContext.getHumanReadableName(), task, result);

        Collection<Source<?, ?>> sources = new ArrayList<>();
        PrismPropertyDefinition<Integer> inputDefinition = new PrismPropertyDefinition<Integer>(
                ExpressionConstants.VAR_ITERATION, DOMUtil.XSD_INT, context.getPrismContext());
        inputDefinition.setMaxOccurs(1);
        PrismProperty<Integer> input = inputDefinition.instantiate();
        input.add(new PrismPropertyValue<Integer>(iteration));
        ItemDeltaItem<PrismPropertyValue<Integer>, PrismPropertyDefinition<Integer>> idi = new ItemDeltaItem<>(
                input);
        Source<PrismPropertyValue<Integer>, PrismPropertyDefinition<Integer>> iterationSource = new Source<>(idi,
                ExpressionConstants.VAR_ITERATION);
        sources.add(iterationSource);

        ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(sources, variables,
                "iteration token expression in " + accountContext.getHumanReadableName(), task, result);
        PrismValueDeltaSetTriple<PrismPropertyValue<String>> outputTriple = expression.evaluate(expressionContext);
        Collection<PrismPropertyValue<String>> outputValues = outputTriple.getNonNegativeValues();
        if (outputValues.isEmpty()) {
            return "";
        }
        if (outputValues.size() > 1) {
            throw new ExpressionEvaluationException(
                    "Iteration token expression in " + accountContext.getHumanReadableName()
                            + " returned more than one value (" + outputValues.size() + " values)");
        }
        String realValue = outputValues.iterator().next().getValue();
        if (realValue == null) {
            return "";
        }
        return realValue;
    }

    public static String formatIterationTokenDefault(int iteration) {
        if (iteration == 0) {
            return "";
        }
        return Integer.toString(iteration);
    }

    public static <F extends ObjectType> boolean evaluateIterationCondition(LensContext<F> context,
            LensElementContext<?> accountContext, IterationSpecificationType iterationType, int iteration,
            String iterationToken, boolean beforeIteration, ExpressionFactory expressionFactory,
            ExpressionVariables variables, Task task, OperationResult result)
            throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException {
        if (iterationType == null) {
            return true;
        }
        ExpressionType expressionType;
        String desc;
        if (beforeIteration) {
            expressionType = iterationType.getPreIterationCondition();
            desc = "pre-iteration expression in " + accountContext.getHumanReadableName();
        } else {
            expressionType = iterationType.getPostIterationCondition();
            desc = "post-iteration expression in " + accountContext.getHumanReadableName();
        }
        if (expressionType == null) {
            return true;
        }
        PrismPropertyDefinition<Boolean> outputDefinition = new PrismPropertyDefinition<Boolean>(
                ExpressionConstants.OUTPUT_ELMENT_NAME, DOMUtil.XSD_BOOLEAN, context.getPrismContext());
        Expression<PrismPropertyValue<Boolean>, PrismPropertyDefinition<Boolean>> expression = expressionFactory
                .makeExpression(expressionType, outputDefinition, desc, task, result);

        variables.addVariableDefinition(ExpressionConstants.VAR_ITERATION, iteration);
        variables.addVariableDefinition(ExpressionConstants.VAR_ITERATION_TOKEN, iterationToken);

        ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null, variables, desc, task,
                result);
        PrismValueDeltaSetTriple<PrismPropertyValue<Boolean>> outputTriple = expression.evaluate(expressionContext);
        Collection<PrismPropertyValue<Boolean>> outputValues = outputTriple.getNonNegativeValues();
        if (outputValues.isEmpty()) {
            return false;
        }
        if (outputValues.size() > 1) {
            throw new ExpressionEvaluationException(
                    desc + " returned more than one value (" + outputValues.size() + " values)");
        }
        Boolean realValue = outputValues.iterator().next().getValue();
        if (realValue == null) {
            return false;
        }
        return realValue;

    }

    public static boolean isValid(AssignmentType assignmentType, XMLGregorianCalendar now,
            ActivationComputer activationComputer) {
        ActivationType activationType = assignmentType.getActivation();
        if (activationType == null) {
            return true;
        }
        TimeIntervalStatusType validityStatus = activationComputer.getValidityStatus(activationType, now);
        ActivationStatusType effectiveStatus = activationComputer.getEffectiveStatus(activationType, validityStatus,
                ActivationStatusType.ENABLED);
        return effectiveStatus == ActivationStatusType.ENABLED;
    }

    public static <V extends PrismValue, D extends ItemDefinition, F extends FocusType> Mapping<V, D> createFocusMapping(
            final MappingFactory mappingFactory, final LensContext<F> context, final MappingType mappingType,
            ObjectType originObject, ObjectDeltaObject<F> focusOdo, AssignmentPathVariables assignmentPathVariables,
            PrismObject<SystemConfigurationType> configuration, XMLGregorianCalendar now, String contextDesc,
            Task task, OperationResult result)
            throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {
        Integer iteration = null;
        String iterationToken = null;
        if (focusOdo.getNewObject() != null) {
            F focusNewType = focusOdo.getNewObject().asObjectable();
            iteration = focusNewType.getIteration();
            iterationToken = focusNewType.getIterationToken();
        } else if (focusOdo.getOldObject() != null) {
            F focusOldType = focusOdo.getOldObject().asObjectable();
            iteration = focusOldType.getIteration();
            iterationToken = focusOldType.getIterationToken();
        }
        return createFocusMapping(mappingFactory, context, mappingType, originObject, focusOdo,
                assignmentPathVariables, iteration, iterationToken, configuration, now, contextDesc, task, result);
    }

    public static <V extends PrismValue, D extends ItemDefinition, F extends FocusType> Mapping<V, D> createFocusMapping(
            final MappingFactory mappingFactory, final LensContext<F> context, final MappingType mappingType,
            ObjectType originObject, ObjectDeltaObject<F> focusOdo, AssignmentPathVariables assignmentPathVariables,
            Integer iteration, String iterationToken, PrismObject<SystemConfigurationType> configuration,
            XMLGregorianCalendar now, String contextDesc, final Task task, OperationResult result)
            throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {
        Mapping<V, D> mapping = mappingFactory.createMapping(mappingType, contextDesc);

        if (!mapping.isApplicableToChannel(context.getChannel())) {
            return null;
        }

        mapping.setSourceContext(focusOdo);
        mapping.setTargetContext(context.getFocusContext().getObjectDefinition());
        mapping.setRootNode(focusOdo);
        mapping.addVariableDefinition(ExpressionConstants.VAR_USER, focusOdo);
        mapping.addVariableDefinition(ExpressionConstants.VAR_FOCUS, focusOdo);
        mapping.addVariableDefinition(ExpressionConstants.VAR_ITERATION, iteration);
        mapping.addVariableDefinition(ExpressionConstants.VAR_ITERATION_TOKEN, iterationToken);
        mapping.addVariableDefinition(ExpressionConstants.VAR_CONFIGURATION, configuration);
        addAssignmentPathVariables(mapping, assignmentPathVariables);
        mapping.setOriginType(OriginType.USER_POLICY);
        mapping.setOriginObject(originObject);
        mapping.setNow(now);

        ItemPath itemPath = mapping.getOutputPath();
        if (itemPath == null) {
            // no output element, i.e. this is a "validation mapping"
            return mapping;
        }

        PrismObject<F> focusNew = focusOdo.getNewObject();
        if (focusNew != null) {
            Item<V, D> existingUserItem = (Item<V, D>) focusNew.findItem(itemPath);
            if (existingUserItem != null && !existingUserItem.isEmpty()
                    && mapping.getStrength() == MappingStrengthType.WEAK) {
                // This valueConstruction only applies if the property does not have a value yet.
                // ... but it does
                return null;
            }
        }

        StringPolicyResolver stringPolicyResolver = new StringPolicyResolver() {
            private ItemPath outputPath;
            private ItemDefinition outputDefinition;

            @Override
            public void setOutputPath(ItemPath outputPath) {
                this.outputPath = outputPath;
            }

            @Override
            public void setOutputDefinition(ItemDefinition outputDefinition) {
                this.outputDefinition = outputDefinition;
            }

            @Override
            public StringPolicyType resolve() {
                if (outputDefinition.getName().equals(PasswordType.F_VALUE)) {
                    ValuePolicyType passwordPolicy = context.getEffectivePasswordPolicy();
                    if (passwordPolicy == null) {
                        return null;
                    }
                    return passwordPolicy.getStringPolicy();
                }
                if (mappingType.getExpression() != null) {
                    List<JAXBElement<?>> evaluators = mappingType.getExpression().getExpressionEvaluator();
                    if (evaluators != null) {
                        for (JAXBElement jaxbEvaluator : evaluators) {
                            Object object = jaxbEvaluator.getValue();
                            if (object != null && object instanceof GenerateExpressionEvaluatorType
                                    && ((GenerateExpressionEvaluatorType) object).getValuePolicyRef() != null) {
                                ObjectReferenceType ref = ((GenerateExpressionEvaluatorType) object)
                                        .getValuePolicyRef();
                                try {
                                    ValuePolicyType valuePolicyType = mappingFactory.getObjectResolver().resolve(
                                            ref, ValuePolicyType.class, null,
                                            "resolving value policy for generate attribute "
                                                    + outputDefinition.getName() + " value",
                                            task, new OperationResult("Resolving value policy"));
                                    if (valuePolicyType != null) {
                                        return valuePolicyType.getStringPolicy();
                                    }
                                } catch (Exception ex) {
                                    throw new SystemException(ex.getMessage(), ex);
                                }
                            }
                        }

                    }
                }
                return null;

            }
        };
        mapping.setStringPolicyResolver(stringPolicyResolver);

        return mapping;
    }

    public static AssignmentPathVariables computeAssignmentPathVariables(AssignmentPath assignmentPath)
            throws SchemaException {
        if (assignmentPath == null || assignmentPath.isEmpty()) {
            return null;
        }
        AssignmentPathVariables vars = new AssignmentPathVariables();

        Iterator<AssignmentPathSegment> iterator = assignmentPath.getSegments().iterator();
        while (iterator.hasNext()) {
            AssignmentPathSegment segment = iterator.next();
            ItemDeltaItem<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> segmentAssignmentIdi = segment
                    .getAssignmentIdi();

            ItemDeltaItem<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> magicAssignmentIdi;
            // Magic assignment
            if (vars.getMagicAssignment() == null) {
                magicAssignmentIdi = segmentAssignmentIdi.clone();
                vars.setMagicAssignment(magicAssignmentIdi);
            } else {
                // Collect extension values from the assignment extension
                magicAssignmentIdi = vars.getMagicAssignment();
                mergeExtension(magicAssignmentIdi, segmentAssignmentIdi);
            }

            // Collect extension values from the source object extension
            ObjectType segmentSource = segment.getSource();
            if (segmentSource != null) {
                mergeExtension(magicAssignmentIdi, segmentSource.asPrismObject());
            }

            // immediate assignment (use assignment from previous iteration)
            vars.setImmediateAssignment(vars.getThisAssignment());

            // this assignment
            ItemDeltaItem<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> thisAssignment = segmentAssignmentIdi
                    .clone();
            vars.setThisAssignment(thisAssignment);

            if (iterator.hasNext() && segmentSource instanceof AbstractRoleType) {
                vars.setImmediateRole(segmentSource.asPrismObject());
            }
        }

        AssignmentPathSegment focusAssignmentSegment = assignmentPath.getFirstAssignmentSegment();
        ItemDeltaItem<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> focusAssignment = focusAssignmentSegment
                .getAssignmentIdi().clone();
        vars.setFocusAssignment(focusAssignment);

        return vars;
    }

    private static void mergeExtension(
            ItemDeltaItem<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> destIdi,
            ItemDeltaItem<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> srcIdi)
            throws SchemaException {
        mergeExtension(destIdi.getItemOld(), srcIdi.getItemOld());
        mergeExtension(destIdi.getItemNew(), srcIdi.getItemNew());
        if (srcIdi.getDelta() != null || srcIdi.getSubItemDeltas() != null) {
            throw new UnsupportedOperationException("Merge of IDI with deltas not supported");
        }
    }

    private static void mergeExtension(
            Item<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> dstItem,
            Item<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> srcItem)
            throws SchemaException {
        if (srcItem == null || dstItem == null) {
            return;
        }
        PrismContainer<Containerable> srcExtension = ((PrismContainer<AssignmentType>) srcItem)
                .findContainer(AssignmentType.F_EXTENSION);
        mergeExtensionContainers(dstItem, srcExtension);
    }

    private static <O extends ObjectType> void mergeExtension(
            ItemDeltaItem<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> destIdi,
            PrismObject<O> srcObject) throws SchemaException {
        if (srcObject == null) {
            return;
        }

        PrismContainer<Containerable> srcExtension = srcObject.findContainer(ObjectType.F_EXTENSION);

        mergeExtensionContainers(destIdi.getItemNew(), srcExtension);
        mergeExtensionContainers(destIdi.getItemOld(), srcExtension);
    }

    private static void mergeExtensionContainers(
            Item<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> dstItem,
            PrismContainer<Containerable> srcExtension) throws SchemaException {
        if (dstItem == null) {
            return;
        }
        PrismContainer<AssignmentType> dstContainer = (PrismContainer<AssignmentType>) dstItem;
        if (srcExtension != null && !srcExtension.getValue().isEmpty()) {
            PrismContainer<Containerable> dstExtension = dstContainer
                    .findOrCreateContainer(AssignmentType.F_EXTENSION);
            for (Item<?, ?> srcExtensionItem : srcExtension.getValue().getItems()) {
                Item<?, ?> magicItem = dstExtension.findItem(srcExtensionItem.getElementName());
                if (magicItem == null) {
                    dstExtension.add(srcExtensionItem.clone());
                }
            }
        }
    }

    private static void mergeExtension(PrismContainer<Containerable> magicExtension,
            PrismContainer<Containerable> segmentExtension) throws SchemaException {
        if (segmentExtension != null && !segmentExtension.getValue().isEmpty()) {
            for (Item<?, ?> segmentItem : segmentExtension.getValue().getItems()) {
                Item<?, ?> magicItem = magicExtension.findItem(segmentItem.getElementName());
                if (magicItem == null) {
                    magicExtension.add(segmentItem.clone());
                }
            }
        }
    }

    public static <V extends PrismValue, D extends ItemDefinition> void addAssignmentPathVariables(
            Mapping<V, D> mapping, AssignmentPathVariables assignmentPathVariables) {
        if (assignmentPathVariables != null) {
            mapping.addVariableDefinition(ExpressionConstants.VAR_ASSIGNMENT,
                    assignmentPathVariables.getMagicAssignment());
            mapping.addVariableDefinition(ExpressionConstants.VAR_IMMEDIATE_ASSIGNMENT,
                    assignmentPathVariables.getImmediateAssignment());
            mapping.addVariableDefinition(ExpressionConstants.VAR_THIS_ASSIGNMENT,
                    assignmentPathVariables.getThisAssignment());
            mapping.addVariableDefinition(ExpressionConstants.VAR_FOCUS_ASSIGNMENT,
                    assignmentPathVariables.getFocusAssignment());
            mapping.addVariableDefinition(ExpressionConstants.VAR_IMMEDIATE_ROLE,
                    assignmentPathVariables.getImmediateRole());
        }
    }

    public static <F extends ObjectType> void checkContextSanity(LensContext<F> context, String activityDescription,
            OperationResult result) throws SchemaException, PolicyViolationException {
        LensFocusContext<F> focusContext = context.getFocusContext();
        if (focusContext != null) {
            PrismObject<F> focusObjectNew = focusContext.getObjectNew();
            if (focusObjectNew != null) {
                PolyStringType namePolyType = focusObjectNew.asObjectable().getName();
                if (namePolyType == null) {
                    throw new SchemaException(
                            "Focus " + focusObjectNew + " does not have a name after " + activityDescription);
                }
                ObjectPolicyConfigurationType objectPolicyConfigurationType = focusContext
                        .getObjectPolicyConfigurationType();
                checkObjectPolicy(focusContext, objectPolicyConfigurationType);
            }
        }
    }

    private static <F extends ObjectType> void checkObjectPolicy(LensFocusContext<F> focusContext,
            ObjectPolicyConfigurationType objectPolicyConfigurationType)
            throws SchemaException, PolicyViolationException {
        if (objectPolicyConfigurationType == null) {
            return;
        }
        PrismObject<F> focusObjectNew = focusContext.getObjectNew();
        ObjectDelta<F> focusDelta = focusContext.getDelta();

        for (PropertyConstraintType propertyConstraintType : objectPolicyConfigurationType
                .getPropertyConstraint()) {
            ItemPath itemPath = propertyConstraintType.getPath().getItemPath();
            if (BooleanUtils.isTrue(propertyConstraintType.isOidBound())) {
                if (focusDelta != null) {
                    if (focusDelta.isAdd()) {
                        PrismProperty<Object> propNew = focusObjectNew.findProperty(itemPath);
                        if (propNew != null) {
                            // prop delta is OK, but it has to match
                            if (focusObjectNew.getOid() != null) {
                                if (!focusObjectNew.getOid().equals(propNew.getRealValue().toString())) {
                                    throw new PolicyViolationException("Cannot set " + itemPath
                                            + " to a value different than OID in oid bound mode");
                                }
                            }
                        }
                    } else {
                        PropertyDelta<Object> nameDelta = focusDelta.findPropertyDelta(itemPath);
                        if (nameDelta != null) {
                            if (nameDelta.isReplace()) {
                                Collection<PrismPropertyValue<Object>> valuesToReplace = nameDelta
                                        .getValuesToReplace();
                                if (valuesToReplace.size() == 1) {
                                    String stringValue = valuesToReplace.iterator().next().getValue().toString();
                                    if (focusContext.getOid().equals(stringValue)) {
                                        // This is OK. It is most likely a correction made by a recompute.
                                        continue;
                                    }
                                }
                            }
                            throw new PolicyViolationException("Cannot change " + itemPath + " in oid bound mode");
                        }
                    }
                }
            }
        }

        // Deprecated
        if (BooleanUtils.isTrue(objectPolicyConfigurationType.isOidNameBoundMode())) {
            if (focusDelta != null) {
                if (focusDelta.isAdd()) {
                    PolyStringType namePolyType = focusObjectNew.asObjectable().getName();
                    if (namePolyType != null) {
                        // name delta is OK, but it has to match
                        if (focusObjectNew.getOid() != null) {
                            if (!focusObjectNew.getOid().equals(namePolyType.getOrig())) {
                                throw new PolicyViolationException(
                                        "Cannot set name to a value different than OID in name-oid bound mode");
                            }
                        }
                    }
                } else {
                    PropertyDelta<Object> nameDelta = focusDelta.findPropertyDelta(FocusType.F_NAME);
                    if (nameDelta != null) {
                        throw new PolicyViolationException("Cannot change name in name-oid bound mode");
                    }
                }
            }
        }
    }

    public static PrismContainer<AssignmentType> createAssignmentSingleValueContainerClone(
            AssignmentType assignmentType) throws SchemaException {
        PrismContainerValue<AssignmentType> assignmentCVal = assignmentType.asPrismContainerValue();
        PrismContainerDefinition<AssignmentType> def = assignmentCVal.getParent().getDefinition().clone();
        // Make it appear to be single-value. Therefore paths without segment IDs will work.
        def.setMaxOccurs(1);
        PrismContainer<AssignmentType> assignmentCont = def.instantiate();
        assignmentCont.add(assignmentCVal.clone());
        return assignmentCont;
    }

    public static AssignmentType getAssignmentType(
            ItemDeltaItem<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> assignmentIdi,
            boolean old) {
        if (old) {
            return ((PrismContainer<AssignmentType>) assignmentIdi.getItemOld()).getValue(0).asContainerable();
        } else {
            return ((PrismContainer<AssignmentType>) assignmentIdi.getItemNew()).getValue(0).asContainerable();
        }
    }

    public static <F extends ObjectType> MetadataType createCreateMetadata(LensContext<F> context,
            XMLGregorianCalendar now, Task task) {
        MetadataType metaData = new MetadataType();
        String channel = getChannel(context, task);
        metaData.setCreateChannel(channel);
        metaData.setCreateTimestamp(now);
        if (task.getOwner() != null) {
            metaData.setCreatorRef(ObjectTypeUtil.createObjectRef(task.getOwner()));
        }
        return metaData;
    }

    public static <F extends ObjectType, T extends ObjectType> Collection<? extends ItemDelta<?, ?>> createModifyMetadataDeltas(
            LensContext<F> context, ItemPath metadataPath, PrismObjectDefinition<T> def, XMLGregorianCalendar now,
            Task task) {
        Collection<? extends ItemDelta<?, ?>> deltas = new ArrayList<>();
        String channel = getChannel(context, task);
        if (channel != null) {
            PropertyDelta<String> delta = PropertyDelta.createModificationReplaceProperty(
                    metadataPath.subPath(MetadataType.F_MODIFY_CHANNEL), def, channel);
            ((Collection) deltas).add(delta);
        }
        PropertyDelta<XMLGregorianCalendar> delta = PropertyDelta
                .createModificationReplaceProperty(metadataPath.subPath(MetadataType.F_MODIFY_TIMESTAMP), def, now);
        ((Collection) deltas).add(delta);
        if (task.getOwner() != null) {
            ReferenceDelta refDelta = ReferenceDelta.createModificationReplace(
                    metadataPath.subPath(MetadataType.F_MODIFIER_REF), def, task.getOwner().getOid());
            ((Collection) deltas).add(refDelta);
        }
        return deltas;
    }

    public static <F extends ObjectType> String getChannel(LensContext<F> context, Task task) {
        if (context != null && context.getChannel() != null) {
            return context.getChannel();
        } else if (task.getChannel() != null) {
            return task.getChannel();
        }
        return null;
    }

    public static <O extends ObjectType> void setDeltaOldValue(LensElementContext<O> ctx,
            ItemDelta<?, ?> itemDelta) {
        if (itemDelta.getEstimatedOldValues() != null) {
            return;
        }
        if (ctx.getObjectOld() != null) {
            Item<PrismValue, ItemDefinition> itemOld = ctx.getObjectOld().findItem(itemDelta.getPath());
            if (itemOld != null) {
                itemDelta.setEstimatedOldValues((Collection) PrismValue.cloneCollection(itemOld.getValues()));
            } else {
                // get the old data from current object. Still better estimate than nothing
                if (ctx.getObjectCurrent() != null) {
                    itemOld = ctx.getObjectCurrent().findItem(itemDelta.getPath());
                    if (itemOld != null) {
                        itemDelta.setEstimatedOldValues(
                                (Collection) PrismValue.cloneCollection(itemOld.getValues()));
                    }
                }
            }
        }
    }

    public static <O extends ObjectType> void setDeltaOldValue(LensElementContext<O> ctx,
            ObjectDelta<O> objectDelta) {
        if (objectDelta == null) {
            return;
        }
        if (!objectDelta.isModify()) {
            return;
        }
        for (ItemDelta<?, ?> modification : objectDelta.getModifications()) {
            setDeltaOldValue(ctx, modification);
        }
    }

    public static <F extends ObjectType> LensObjectDeltaOperation<F> createObjectDeltaOperation(
            ObjectDelta<F> focusDelta, OperationResult result, LensElementContext<F> focusContext,
            LensProjectionContext projCtx) {
        return createObjectDeltaOperation(focusDelta, result, focusContext, projCtx, null);
    }

    // projCtx may or may not be present (object itself can be focus or projection)
    public static <T extends ObjectType> LensObjectDeltaOperation<T> createObjectDeltaOperation(
            ObjectDelta<T> objectDelta, OperationResult result, LensElementContext<T> objectContext,
            LensProjectionContext projCtx, ResourceType resource) {
        LensObjectDeltaOperation<T> objectDeltaOp = new LensObjectDeltaOperation<T>(objectDelta.clone());
        objectDeltaOp.setExecutionResult(result);
        PrismObject<T> object = objectContext.getObjectAny();
        if (object != null) {
            PolyString name = object.getName();
            if (name == null && object.asObjectable() instanceof ShadowType) {
                try {
                    name = ShadowUtil.determineShadowName((PrismObject<ShadowType>) object);
                    if (name == null) {
                        LOGGER.debug("No name for shadow:\n{}", object.debugDump());
                    } else if (name.getNorm() == null) {
                        name.recompute(new PrismDefaultPolyStringNormalizer());
                    }
                } catch (SchemaException e) {
                    LoggingUtils.logUnexpectedException(LOGGER,
                            "Couldn't determine name for shadow -- continuing with no name; shadow:\n{}", e,
                            object.debugDump());
                }
            }
            objectDeltaOp.setObjectName(name);
        }
        if (resource == null && projCtx != null) {
            resource = projCtx.getResource();
        }
        if (resource != null) {
            objectDeltaOp.setResourceOid(resource.getOid());
            objectDeltaOp.setResourceName(PolyString.toPolyString(resource.getName()));
        } else if (objectContext instanceof LensProjectionContext) {
            objectDeltaOp.setResourceOid(((LensProjectionContext) objectContext).getResourceOid());
        }
        return objectDeltaOp;
    }
}