text:
package com.evolveum.midpoint.model.impl.lens;

import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.FOCUS_OPERATION;
import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.RESOURCE_OBJECT_OPERATION;
import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING;
import static com.evolveum.midpoint.prism.PrismContainerValue.asContainerables;
import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks;

import com.evolveum.midpoint.common.Clock;
import com.evolveum.midpoint.common.SynchronizationUtils;
import com.evolveum.midpoint.prism.delta.*;
import com.evolveum.midpoint.repo.api.ConflictWatcher;
import com.evolveum.midpoint.repo.api.ModificationPrecondition;
import com.evolveum.midpoint.repo.api.PreconditionViolationException;
import com.evolveum.midpoint.repo.common.expression.Expression;
import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext;
import com.evolveum.midpoint.repo.common.expression.ExpressionFactory;
import com.evolveum.midpoint.repo.common.expression.ExpressionUtil;
import com.evolveum.midpoint.repo.common.expression.ExpressionVariables;
import com.evolveum.midpoint.model.api.ModelAuthorizationAction;
import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.api.ProgressInformation;
import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision;
import com.evolveum.midpoint.model.impl.ModelObjectResolver;
import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment;
import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder;
import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor;
import com.evolveum.midpoint.model.impl.lens.projector.focus.FocusConstraintsChecker;
import com.evolveum.midpoint.model.impl.util.ModelImplUtils;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.xnode.PrimitiveXNode;
import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.repo.api.RepoAddOptions;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.repo.api.VersionPrecondition;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ObjectDeltaOperation;
import com.evolveum.midpoint.schema.PointInTimeType;
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.internals.InternalsConfig;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.security.api.MidPointPrincipal;
import com.evolveum.midpoint.security.api.OwnerResolver;
import com.evolveum.midpoint.security.api.SecurityContextManager;
import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters;
import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;
import com.evolveum.prism.xml.ns._public.types_3.RawType;

import org.apache.commons.lang.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

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

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

/**
 * @author semancik
 */
@Component
public class ChangeExecutor {

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

	private static final String OPERATION_EXECUTE_DELTA = ChangeExecutor.class.getName() + ".executeDelta";
	private static final String OPERATION_EXECUTE = ChangeExecutor.class.getName() + ".execute";
	private static final String OPERATION_EXECUTE_FOCUS = OPERATION_EXECUTE + ".focus";
	private static final String OPERATION_EXECUTE_PROJECTION = OPERATION_EXECUTE + ".projection";
	private static final String OPERATION_LINK_ACCOUNT = ChangeExecutor.class.getName() + ".linkShadow";
	private static final String OPERATION_UNLINK_ACCOUNT = ChangeExecutor.class.getName() + ".unlinkShadow";
	private static final String OPERATION_UPDATE_SITUATION_IN_SHADOW = ChangeExecutor.class.getName() + ".updateSituationInShadow";

	@Autowired private transient TaskManager taskManager;
	@Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService;
	@Autowired private ProvisioningService provisioning;
	@Autowired private PrismContext prismContext;
	@Autowired private ExpressionFactory expressionFactory;
	@Autowired private SecurityEnforcer securityEnforcer;
	@Autowired private SecurityContextManager securityContextManager;
	@Autowired private Clock clock;
	@Autowired private ModelObjectResolver objectResolver;
	@Autowired private OperationalDataManager metadataManager;
	@Autowired private CredentialsProcessor credentialsProcessor;

	private PrismObjectDefinition<UserType> userDefinition = null;
	private PrismObjectDefinition<ShadowType> shadowDefinition = null;

	@PostConstruct
	private void locateDefinitions() {
		userDefinition = prismContext.getSchemaRegistry()
				.findObjectDefinitionByCompileTimeClass(UserType.class);
		shadowDefinition = prismContext.getSchemaRegistry()
				.findObjectDefinitionByCompileTimeClass(ShadowType.class);
	}

	// returns true if current operation has to be restarted, see
	// ObjectAlreadyExistsException handling (TODO specify more exactly)
	public <O extends ObjectType> boolean executeChanges(LensContext<O> context, Task task,
			OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException,
					SchemaException, CommunicationException, ConfigurationException,
					SecurityViolationException, ExpressionEvaluationException, PreconditionViolationException, PolicyViolationException {

		OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE);

		try {

			// FOCUS

			context.checkAbortRequested();

			LensFocusContext<O> focusContext = context.getFocusContext();
			if (focusContext != null) {
				ObjectDelta<O> focusDelta = focusContext.getWaveExecutableDelta(context.getExecutionWave());

				focusDelta = applyPendingObjectPolicyStateModifications(focusContext, focusDelta);
				focusDelta = applyPendingAssignmentPolicyStateModifications(focusContext, focusDelta);

				if (focusDelta == null && !context.hasProjectionChange()) {
					LOGGER.trace("Skipping focus change execute, because user delta is null");
				} else {

					if (focusDelta == null) {
						focusDelta = focusContext.getObjectAny().createModifyDelta();
					}

					ObjectPolicyConfigurationType objectPolicyConfigurationType = focusContext.getObjectPolicyConfigurationType();
					applyObjectPolicy(focusContext, focusDelta, objectPolicyConfigurationType);

					OperationResult subResult = result.createSubresult(
							OPERATION_EXECUTE_FOCUS + "." + focusContext.getObjectTypeClass().getSimpleName());

					try {
						// Will remove credential deltas or hash them
						focusDelta = credentialsProcessor.transformFocusExecutionDelta(context, focusDelta);
					} catch (EncryptionException e) {
						recordFatalError(subResult, result, null, e);
						result.computeStatus();
						throw new SystemException(e.getMessage(), e);
					}

					applyLastProvisioningTimestamp(context, focusDelta);

					try {

						context.reportProgress(new ProgressInformation(FOCUS_OPERATION, ENTERING));

						ConflictResolutionType conflictResolution = ModelExecuteOptions
								.getFocusConflictResolution(context.getOptions());

						executeDelta(focusDelta, focusContext, context, null, conflictResolution, null, task, subResult);

						if (focusDelta.isAdd() && focusDelta.getOid() != null) {
							ConflictWatcher watcher = context
									.createAndRegisterConflictWatcher(focusDelta.getOid(), cacheRepositoryService);
							watcher.setExpectedVersion(focusDelta.getObjectToAdd().getVersion());
						}
						subResult.computeStatus();

					} catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | SecurityViolationException | ExpressionEvaluationException | RuntimeException e) {
						recordFatalError(subResult, result, null, e);
						throw e;

					} catch (PreconditionViolationException e) {

						if (LOGGER.isDebugEnabled()) {
							LOGGER.debug("Modification precondition failed for {}: {}", focusContext.getHumanReadableName(),
									e.getMessage());
						}
						//					TODO: fatal error if the conflict resolution is "error" (later)
						result.recordHandledError(e);
						throw e;

					} catch (ObjectAlreadyExistsException e) {
						subResult.computeStatus();
						if (!subResult.isSuccess() && !subResult.isHandledError()) {
							subResult.recordFatalError(e);
						}
						result.computeStatusComposite();
						throw e;
					} finally {
						context.reportProgress(new ProgressInformation(FOCUS_OPERATION, subResult));
					}
				}
			}

			// PROJECTIONS

			context.checkAbortRequested();

			boolean restartRequested = false;

			for (LensProjectionContext projCtx : context.getProjectionContexts()) {
				if (projCtx.getWave() != context.getExecutionWave()) {
					continue;
				}

				if (!projCtx.isCanProject()) {
					continue;
				}

				// we should not get here, but just to be sure
				if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) {
					LOGGER.trace("Skipping ignored projection context {}", projCtx.toHumanReadableString());
					continue;
				}

				OperationResult subResult = result.createSubresult(
						OPERATION_EXECUTE_PROJECTION + "." + projCtx.getObjectTypeClass().getSimpleName());
				subResult.addArbitraryObjectAsContext("discriminator", projCtx.getResourceShadowDiscriminator());
				if (projCtx.getResource() != null) {
					subResult.addParam("resource", projCtx.getResource());
				}

				PrismObject<ShadowType> shadowAfterModification = null;
				try {
					LOGGER.trace("Executing projection context {}", projCtx.toHumanReadableString());

					context.checkAbortRequested();

					context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION,
							projCtx.getResourceShadowDiscriminator(), ENTERING));

					executeReconciliationScript(projCtx, context, BeforeAfterType.BEFORE, task, subResult);

					ObjectDelta<ShadowType> projDelta = projCtx.getExecutableDelta();

					if (shouldBeDeleted(projDelta, projCtx)) {
						projDelta = ObjectDelta.createDeleteDelta(projCtx.getObjectTypeClass(), projCtx.getOid(),
								prismContext);
					}

					if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) {
						if (context.getFocusContext() != null
								&& context.getFocusContext().getDelta() != null
								&& context.getFocusContext().getDelta().isDelete()
								&& context.getOptions() != null
								&& ModelExecuteOptions.isForce(context.getOptions())) {
							if (projDelta == null) {
								projDelta = ObjectDelta.createDeleteDelta(projCtx.getObjectTypeClass(),
										projCtx.getOid(), prismContext);
							}
						}
						if (projDelta != null && projDelta.isDelete()) {

							shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task,
									subResult);

						}
					} else {

						if (projDelta == null || projDelta.isEmpty()) {
							if (LOGGER.isTraceEnabled()) {
								LOGGER.trace("No change for " + projCtx.getResourceShadowDiscriminator());
							}
							shadowAfterModification = projCtx.getObjectCurrent();
							if (focusContext != null) {
								updateLinks(focusContext, projCtx, shadowAfterModification, task, subResult);
							}

							// Make sure post-reconcile delta is always executed,
							// even if there is no change
							executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task,
									subResult);

							subResult.computeStatus();
							subResult.recordNotApplicableIfUnknown();
							continue;

						} else if (projDelta.isDelete() && projCtx.getResourceShadowDiscriminator() != null
								&& projCtx.getResourceShadowDiscriminator().getOrder() > 0) {
							// HACK ... for higher-order context check if this was
							// already deleted
							LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context,
									projCtx);
							if (lowerOrderContext != null && lowerOrderContext.isDelete()) {
								// We assume that this was already executed
								subResult.setStatus(OperationResultStatus.NOT_APPLICABLE);
								continue;
							}
						}

						shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, subResult);

						if (projCtx.isAdd() && shadowAfterModification != null) {
							projCtx.setExists(true);
						}

					}

					subResult.computeStatus();
					if (focusContext != null) {
						updateLinks(focusContext, projCtx, shadowAfterModification, task, subResult);
					}

					executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, subResult);

					subResult.computeStatus();
					subResult.recordNotApplicableIfUnknown();

				} catch (SchemaException | ObjectNotFoundException | PreconditionViolationException | CommunicationException |
						ConfigurationException | SecurityViolationException | PolicyViolationException | ExpressionEvaluationException | RuntimeException | Error e) {
					recordProjectionExecutionException(e, projCtx, subResult, SynchronizationPolicyDecision.BROKEN);

					// We still want to update the links here. E.g. this may be live sync case where we discovered new account
					// try to reconcile, but the reconciliation fails. We still want this shadow linked to user.
					if (focusContext != null) {
						updateLinks(focusContext, projCtx, shadowAfterModification, task, subResult);
					}

					ModelImplUtils.handleConnectorErrorCriticality(projCtx.getResource(), e, subResult);

				} catch (ObjectAlreadyExistsException e) {

					// This exception is quite special. We have to decide how bad this really is.
					// This may be rename conflict. Which would be bad.
					// Or this may be attempt to create account that already exists and just needs
					// to be linked. Which is no big deal and consistency mechanism (discovery) will
					// easily handle that. In that case it is done in "another task" which is
					// quasi-asynchornously executed from provisioning by calling notifyChange.
					// Once that is done then the account is already linked. And all we need to do
					// is to restart this whole operation.

					// check if this is a repeated attempt - OAEE was not handled
					// correctly, e.g. if creating "Users" user in AD, whereas
					// "Users" is SAM Account Name which is used by a built-in group
					// - in such case, mark the context as broken
					if (isRepeatedAlreadyExistsException(projCtx)) {
						// This is the bad case. Currently we do not do anything more intelligent than to look for
						// repeated error. If we get OAEE twice then this is bad and we thow up.
						// TODO: do something smarter here
						LOGGER.debug("Repeated ObjectAlreadyExistsException detected, marking projection {} as broken", projCtx.toHumanReadableString());
						recordProjectionExecutionException(e, projCtx, subResult,
								SynchronizationPolicyDecision.BROKEN);
						continue;
					}

					// in his case we do not need to set account context as
					// broken, instead we need to restart projector for this
					// context to recompute new account or find out if the
					// account was already linked..
					// and also do not set fatal error to the operation result, this
					// is a special case
					// if it is fatal, it will be set later
					// but we need to set some result
					subResult.recordSuccess();
					restartRequested = true;
					LOGGER.debug("ObjectAlreadyExistsException for projection {}, requesting projector restart", projCtx.toHumanReadableString());
					// we will process remaining projections when retrying the wave
					break;

				} finally {
					context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION,
							projCtx.getResourceShadowDiscriminator(), subResult));
				}
			}

			// Result computation here needs to be slightly different
			result.computeStatusComposite();
			return restartRequested;

		} catch (Throwable t) {
			result.recordThrowableIfNeeded(t);      // last resort: to avoid UNKNOWN subresults
			throw t;
		}
	}

	private <O extends ObjectType> ObjectDelta<O> applyPendingObjectPolicyStateModifications(LensFocusContext<O> focusContext,
			ObjectDelta<O> focusDelta) throws SchemaException {
		for (ItemDelta<?, ?> itemDelta : focusContext.getPendingObjectPolicyStateModifications()) {
			focusDelta = focusContext.swallowToDelta(focusDelta, itemDelta);
		}
		focusContext.clearPendingObjectPolicyStateModifications();
		return focusDelta;
	}

	private <O extends ObjectType> ObjectDelta<O> applyPendingAssignmentPolicyStateModifications(LensFocusContext<O> focusContext, ObjectDelta<O> focusDelta)
			throws SchemaException {
		for (Map.Entry<AssignmentSpec, List<ItemDelta<?, ?>>> entry : focusContext
				.getPendingAssignmentPolicyStateModifications().entrySet()) {
			PlusMinusZero mode = entry.getKey().mode;
			if (mode == PlusMinusZero.MINUS) {
				continue;       // this assignment is being thrown out anyway, so let's ignore it (at least for now)
			}
			AssignmentType assignmentToFind = entry.getKey().assignment;
			List<ItemDelta<?, ?>> modifications = entry.getValue();
			if (modifications.isEmpty()) {
				continue;
			}
			LOGGER.trace("Applying policy state modifications for {} ({}):\n{}", assignmentToFind, mode,
					DebugUtil.debugDumpLazily(modifications));
			if (mode == PlusMinusZero.ZERO) {
				if (assignmentToFind.getId() == null) {
					throw new IllegalStateException("Existing assignment with null id: " + assignmentToFind);
				}
				for (ItemDelta<?, ?> modification : modifications) {
					focusDelta = focusContext.swallowToDelta(focusDelta, modification);
				}
			} else {
				assert mode == PlusMinusZero.PLUS;
				if (focusDelta != null && focusDelta.isAdd()) {
					swallowIntoValues(((FocusType) focusDelta.getObjectToAdd().asObjectable()).getAssignment(),
							assignmentToFind, modifications);
				} else {
					ContainerDelta<AssignmentType> assignmentDelta = focusDelta != null ?
							focusDelta.findContainerDelta(FocusType.F_ASSIGNMENT) : null;
					if (assignmentDelta == null) {
						throw new IllegalStateException(
								"We have 'plus' assignment to modify but there's no assignment delta. Assignment="
										+ assignmentToFind + ", objectDelta=" + focusDelta);
					}
					if (assignmentDelta.isReplace()) {
						swallowIntoValues(asContainerables(assignmentDelta.getValuesToReplace()), assignmentToFind,
								modifications);
					} else if (assignmentDelta.isAdd()) {
						swallowIntoValues(asContainerables(assignmentDelta.getValuesToAdd()), assignmentToFind,
								modifications);
					} else {
						throw new IllegalStateException(
								"We have 'plus' assignment to modify but there're no values to add or replace in assignment delta. Assignment="
										+ assignmentToFind + ", objectDelta=" + focusDelta);
					}
				}
			}
		}
		focusContext.clearPendingAssignmentPolicyStateModifications();
		return focusDelta;
	}

	private void swallowIntoValues(Collection<AssignmentType> assignments, AssignmentType assignmentToFind, List<ItemDelta<?, ?>> modifications)
			throws SchemaException {
		for (AssignmentType assignment : assignments) {
			PrismContainerValue<?> pcv = assignment.asPrismContainerValue();
			PrismContainerValue<?> pcvToFind = assignmentToFind.asPrismContainerValue();
			if (pcv.representsSameValue(pcvToFind, false) || pcv.equalsRealValue(pcvToFind)) {
				// TODO what if ID of the assignment being added is changed in repo? Hopefully it will be not.
				for (ItemDelta<?, ?> modification : modifications) {
					ItemPath newParentPath = modification.getParentPath().rest().rest();        // killing assignment + ID
					ItemDelta<?, ?> pathRelativeModification = modification.cloneWithChangedParentPath(newParentPath);
					pathRelativeModification.applyTo(pcv);
				}
				return;
			}
		}
		// TODO change to warning
		throw new IllegalStateException("We have 'plus' assignment to modify but it couldn't be found in assignment delta. Assignment=" + assignmentToFind + ", new assignments=" + assignments);
	}

	private <O extends ObjectType> void applyLastProvisioningTimestamp(LensContext<O> context, ObjectDelta<O> focusDelta) throws SchemaException {
		if (!context.hasProjectionChange()) {
			return;
		}
		if (focusDelta.isAdd()) {

			PrismObject<O> objectToAdd = focusDelta.getObjectToAdd();
			PrismContainer<MetadataType> metadataContainer = objectToAdd.findOrCreateContainer(ObjectType.F_METADATA);
			metadataContainer.getValue().asContainerable().setLastProvisioningTimestamp(clock.currentTimeXMLGregorianCalendar());

		} else if (focusDelta.isModify()) {

			PropertyDelta<XMLGregorianCalendar> provTimestampDelta = PropertyDelta.createModificationReplaceProperty(
					new ItemPath(ObjectType.F_METADATA, MetadataType.F_LAST_PROVISIONING_TIMESTAMP),
					context.getFocusContext().getObjectDefinition(),
					clock.currentTimeXMLGregorianCalendar());
			focusDelta.addModification(provTimestampDelta);

		}
	}

	private boolean shouldBeDeleted(ObjectDelta<ShadowType> accDelta, LensProjectionContext accCtx) {
		return (accDelta == null || accDelta.isEmpty())
		&& (accCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE
				|| accCtx.getSynchronizationIntent() == SynchronizationIntent.DELETE);
	}

	private <O extends ObjectType> boolean isRepeatedAlreadyExistsException(
			LensProjectionContext projContext) {
		int deltas = projContext.getExecutedDeltas().size();
		LOGGER.trace("isRepeatedAlreadyExistsException starting; number of executed deltas = {}", deltas);
		if (deltas < 2) {
			return false;
		}
		LensObjectDeltaOperation<ShadowType> lastDeltaOp = projContext.getExecutedDeltas().get(deltas - 1);
		LensObjectDeltaOperation<ShadowType> previousDeltaOp = projContext.getExecutedDeltas()
				.get(deltas - 2);
		// TODO check also previous execution result to see if it's
		// AlreadyExistException?
		ObjectDelta<ShadowType> lastDelta = lastDeltaOp.getObjectDelta();
		ObjectDelta<ShadowType> previousDelta = previousDeltaOp.getObjectDelta();
		boolean rv;
		if (lastDelta.isAdd() && previousDelta.isAdd()) {
			rv = isEquivalentAddDelta(lastDelta.getObjectToAdd(), previousDelta.getObjectToAdd());
		} else if (lastDelta.isModify() && previousDelta.isModify()) {
			rv = isEquivalentModifyDelta(lastDelta.getModifications(), previousDelta.getModifications());
		} else {
			rv = false;
		}
		LOGGER.trace(
				"isRepeatedAlreadyExistsException returning {}; based of comparison of previousDelta:\n{}\nwith lastDelta:\n{}",
				rv, previousDelta, lastDelta);
		return rv;
	}

	private boolean isEquivalentModifyDelta(Collection<? extends ItemDelta<?, ?>> modifications1,
			Collection<? extends ItemDelta<?, ?>> modifications2) {
		Collection<? extends ItemDelta<?, ?>> attrDeltas1 = ItemDelta.findItemDeltasSubPath(modifications1,
				new ItemPath(ShadowType.F_ATTRIBUTES));
		Collection<? extends ItemDelta<?, ?>> attrDeltas2 = ItemDelta.findItemDeltasSubPath(modifications2,
				new ItemPath(ShadowType.F_ATTRIBUTES));
		return MiscUtil.unorderedCollectionEquals(attrDeltas1, attrDeltas2);
	}

	private boolean isEquivalentAddDelta(PrismObject<ShadowType> object1, PrismObject<ShadowType> object2) {
		PrismContainer attributes1 = object1.findContainer(ShadowType.F_ATTRIBUTES);
		PrismContainer attributes2 = object2.findContainer(ShadowType.F_ATTRIBUTES);
		if (attributes1 == null || attributes2 == null || attributes1.size() != 1
				|| attributes2.size() != 1) { // suspicious cases
			return false;
		}
		return attributes1.getValue().equivalent(attributes2.getValue());
	}

	private <O extends ObjectType> void applyObjectPolicy(LensFocusContext<O> focusContext,
			ObjectDelta<O> focusDelta, ObjectPolicyConfigurationType objectPolicyConfigurationType) {
		if (objectPolicyConfigurationType == null) {
			return;
		}
		PrismObject<O> objectNew = focusContext.getObjectNew();
		if (focusDelta.isAdd() && objectNew.getOid() == null) {

			for (PropertyConstraintType propertyConstraintType : objectPolicyConfigurationType
					.getPropertyConstraint()) {
				if (BooleanUtils.isTrue(propertyConstraintType.isOidBound())) {
					ItemPath itemPath = propertyConstraintType.getPath().getItemPath();
					PrismProperty<Object> prop = objectNew.findProperty(itemPath);
					String stringValue = prop.getRealValue().toString();
					focusContext.setOid(stringValue);
				}
			}

			// deprecated
			if (BooleanUtils.isTrue(objectPolicyConfigurationType.isOidNameBoundMode())) {
				String name = objectNew.asObjectable().getName().getOrig();
				focusContext.setOid(name);
			}
		}
	}

	private <P extends ObjectType> void recordProjectionExecutionException(Throwable e,
			LensProjectionContext accCtx, OperationResult subResult, SynchronizationPolicyDecision decision) {
		subResult.recordFatalError(e);
		LOGGER.error("Error executing changes for {}: {}", accCtx.toHumanReadableString(), e.getMessage(), e);
		if (decision != null) {
			accCtx.setSynchronizationPolicyDecision(decision);
		}
	}

	private void recordFatalError(OperationResult subResult, OperationResult result, String message,
			Throwable e) {
		if (message == null) {
			message = e.getMessage();
		}
		subResult.recordFatalError(e);
		if (result != null) {
			result.computeStatusComposite();
		}
	}

	/**
	 * Make sure that the account is linked (or unlinked) as needed.
	 */
	private <O extends ObjectType, F extends FocusType> void updateLinks(
			LensFocusContext<O> focusObjectContext, LensProjectionContext projCtx,
			PrismObject<ShadowType> shadowAfterModification,
			Task task, OperationResult result) throws ObjectNotFoundException, SchemaException {
		if (focusObjectContext == null) {
			return;
		}
		Class<O> objectTypeClass = focusObjectContext.getObjectTypeClass();
		if (!FocusType.class.isAssignableFrom(objectTypeClass)) {
			return;
		}
		LensFocusContext<F> focusContext = (LensFocusContext<F>) focusObjectContext;

		if (projCtx.getResourceShadowDiscriminator() != null
				&& projCtx.getResourceShadowDiscriminator().getOrder() > 0) {
			// Don't mess with links for higher-order contexts. The link should
			// be dealt with
			// during processing of zero-order context.
			return;
		}

		String projOid = projCtx.getOid();
		if (projOid == null) {
			if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) {
				// This seems to be OK. In quite a strange way, but still OK.
				return;
			}
			LOGGER.error("Projection {} has null OID, this should not happen, context:\n{}", projCtx.toHumanReadableString(), projCtx.debugDump());
			throw new IllegalStateException("Projection "+projCtx.toHumanReadableString()+" has null OID, this should not happen");
		}

		if (linkShouldExist(projCtx, shadowAfterModification, result)) {
			// Link should exist

						PrismObject<F> objectCurrent = focusContext.getObjectCurrent();
						if (objectCurrent != null) {
							for (ObjectReferenceType linkRef : objectCurrent.asObjectable().getLinkRef()) {
								if (projOid.equals(linkRef.getOid())) {
									// Already linked, nothing to do, only be sure, the
									// situation is set with the good value
									LOGGER.trace("Updating situation in already linked shadow.");
									updateSituationInShadow(task, SynchronizationSituationType.LINKED, focusObjectContext,
											projCtx, result);
									return;
								}
							}
						}
						// Not linked, need to link
						linkShadow(focusContext.getOid(), projOid, focusObjectContext, projCtx, task, result);
						// be sure, that the situation is set correctly
						LOGGER.trace("Updating situation after shadow was linked.");
						updateSituationInShadow(task, SynchronizationSituationType.LINKED,  focusObjectContext, projCtx,
								result);
		} else {
			// Link should NOT exist

			if (!focusContext.isDelete()) {
				PrismObject<F> objectCurrent = focusContext.getObjectCurrent();
				// it is possible that objectCurrent is null (and objectNew is
				// non-null), in case of User ADD operation (MID-2176)
				if (objectCurrent != null) {
					PrismReference linkRef = objectCurrent.findReference(FocusType.F_LINK_REF);
					if (linkRef != null) {
						for (PrismReferenceValue linkRefVal : linkRef.getValues()) {
							if (linkRefVal.getOid().equals(projOid)) {
								// Linked, need to unlink
								unlinkShadow(focusContext.getOid(), linkRefVal, focusObjectContext, projCtx,
										task, result);
							}
						}
					}
				}
			}

			// This should NOT be UNLINKED. We just do not know the
			// situation here. Reflect that in the shadow.
			LOGGER.trace("Resource object {} unlinked from the user, updating also situation in shadow.",
					projOid);
			updateSituationInShadow(task, null, focusObjectContext, projCtx, result);
			// Not linked, that's OK
		}
	}

	private boolean linkShouldExist(LensProjectionContext projCtx, PrismObject<ShadowType> shadowAfterModification, OperationResult result) {
		if (!projCtx.isShadowExistsInRepo()) {
			// Nothing to link to
			return false;
		}
		if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.UNLINK) {
			return false;
		}
		if (isEmptyThombstone(projCtx)) {
			return false;
		}
		if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE
				|| projCtx.isDelete()) {
			return shadowAfterModification != null;
		}
		if (projCtx.hasPendingOperations()) {
			return true;
		}
		return true;
	}

	/**
	 * Return true if this projection is just a linkRef that points to no
	 * shadow.
	 */
	private boolean isEmptyThombstone(LensProjectionContext projCtx) {
		return projCtx.getResourceShadowDiscriminator() != null
				&& projCtx.getResourceShadowDiscriminator().isTombstone()
				&& projCtx.getObjectCurrent() == null;
	}

	private <F extends ObjectType> void linkShadow(String userOid, String shadowOid,
			LensElementContext<F> focusContext, LensProjectionContext projCtx, Task task,
			OperationResult parentResult) throws ObjectNotFoundException, SchemaException {

		Class<F> typeClass = focusContext.getObjectTypeClass();
		if (!FocusType.class.isAssignableFrom(typeClass)) {
			return;
		}

		String channel = focusContext.getLensContext().getChannel();

		LOGGER.debug("Linking shadow " + shadowOid + " to focus " + userOid);
		OperationResult result = parentResult.createSubresult(OPERATION_LINK_ACCOUNT);
		PrismReferenceValue linkRef = new PrismReferenceValue();
		linkRef.setOid(shadowOid);
		linkRef.setTargetType(ShadowType.COMPLEX_TYPE);
		Collection<? extends ItemDelta> linkRefDeltas = ReferenceDelta
				.createModificationAddCollection(FocusType.F_LINK_REF, getUserDefinition(), linkRef);

		try {
			cacheRepositoryService.modifyObject(typeClass, userOid, linkRefDeltas, result);
			task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid,
					ChangeType.MODIFY, channel, null);
		} catch (ObjectAlreadyExistsException ex) {
			task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid,
					ChangeType.MODIFY, channel, ex);
			throw new SystemException(ex);
		} catch (Throwable t) {
			task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid,
					ChangeType.MODIFY, channel, t);
			throw t;
		} finally {
			result.computeStatus();
			ObjectDelta<F> userDelta = ObjectDelta.createModifyDelta(userOid, linkRefDeltas, typeClass,
					prismContext);
			LensObjectDeltaOperation<F> userDeltaOp = LensUtil.createObjectDeltaOperation(userDelta, result,
					focusContext, projCtx);
			focusContext.addToExecutedDeltas(userDeltaOp);
		}

	}

	private PrismObjectDefinition<UserType> getUserDefinition() {
		return userDefinition;
	}

	private <F extends ObjectType> void unlinkShadow(String focusOid, PrismReferenceValue accountRef,
			LensElementContext<F> focusContext, LensProjectionContext projCtx, Task task,
			OperationResult parentResult) throws ObjectNotFoundException, SchemaException {

		Class<F> typeClass = focusContext.getObjectTypeClass();
		if (!FocusType.class.isAssignableFrom(typeClass)) {
			return;
		}

		String channel = focusContext.getLensContext().getChannel();

		LOGGER.debug("Unlinking shadow " + accountRef.getOid() + " from focus " + focusOid);
		OperationResult result = parentResult.createSubresult(OPERATION_UNLINK_ACCOUNT);
		Collection<? extends ItemDelta> accountRefDeltas = ReferenceDelta.createModificationDeleteCollection(
				FocusType.F_LINK_REF, getUserDefinition(), accountRef.clone());

		try {
			cacheRepositoryService.modifyObject(typeClass, focusOid, accountRefDeltas, result);
			task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid,
					ChangeType.MODIFY, channel, null);
		} catch (ObjectAlreadyExistsException ex) {
			task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid,
					ChangeType.MODIFY, channel, ex);
			result.recordFatalError(ex);
			throw new SystemException(ex);
		} catch (Throwable t) {
			task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid,
					ChangeType.MODIFY, channel, t);
			throw t;
		} finally {
			result.computeStatus();
			ObjectDelta<F> userDelta = ObjectDelta.createModifyDelta(focusOid, accountRefDeltas, typeClass,
					prismContext);
			LensObjectDeltaOperation<F> userDeltaOp = LensUtil.createObjectDeltaOperation(userDelta, result,
					focusContext, projCtx);
			focusContext.addToExecutedDeltas(userDeltaOp);
		}

	}

	private <F extends ObjectType> void updateSituationInShadow(Task task,
			SynchronizationSituationType newSituation, LensFocusContext<F> focusContext,
			LensProjectionContext projectionCtx, OperationResult parentResult)
					throws ObjectNotFoundException, SchemaException {

		String projectionOid = projectionCtx.getOid();

		OperationResult result = parentResult.createMinorSubresult(OPERATION_UPDATE_SITUATION_IN_SHADOW);
		result.addArbitraryObjectAsParam("situation", newSituation);
		result.addParam("accountRef", projectionOid);

		PrismObject<ShadowType> currentShadow = null;
		GetOperationOptions getOptions = GetOperationOptions.createNoFetch();
		getOptions.setAllowNotFound(true);
		try {
			currentShadow = provisioning.getObject(ShadowType.class, projectionOid,
					SelectorOptions.createCollection(getOptions), task, result);
		} catch (ObjectNotFoundException ex) {
			LOGGER.trace("Shadow is gone, skipping modifying situation in shadow.");
			result.recordSuccess();
			return;
		} catch (Exception ex) {
			LOGGER.trace("Problem with getting shadow, skipping modifying situation in shadow.");
			result.recordPartialError(ex);
			return;
		}

		SynchronizationSituationType currentSynchronizationSituation = currentShadow.asObjectable().getSynchronizationSituation();
		if (SynchronizationSituationType.DELETED.equals(currentSynchronizationSituation) && ShadowUtil.isDead(currentShadow.asObjectable())) {
			LOGGER.trace("Skipping update of synchronization situation for deleted dead shadow");
			result.recordSuccess();
			return;
		}

		// TODO: can we skip the update or cannot we? Do we always need to update sync timestamp?
//		if (newSituation.equals(currentSynchronizationSituation)) {
//			LOGGER.trace("Skipping update of synchronization situation because there is no change ({})", currentSynchronizationSituation);
//			result.recordSuccess();
//			return;
//		}
//		LOGGER.trace("Updating synchronization situation {} -> {}", currentSynchronizationSituation, newSituation);

		XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar();
		List<PropertyDelta<?>> syncSituationDeltas = SynchronizationUtils
				.createSynchronizationSituationAndDescriptionDelta(currentShadow, newSituation, task.getChannel(),
						projectionCtx.hasFullShadow(), now);

		try {
			ModelImplUtils.setRequestee(task, focusContext);
			ProvisioningOperationOptions options = ProvisioningOperationOptions.createCompletePostponed(false);
			options.setDoNotDiscovery(true);
			provisioning.modifyObject(ShadowType.class, projectionOid, syncSituationDeltas, null, options, task, result);
			projectionCtx.setSynchronizationSituationResolved(newSituation);
			LOGGER.trace("Situation in projection {} was updated to {}.", projectionCtx, newSituation);
		} catch (ObjectNotFoundException ex) {
			// if the object not found exception is thrown, it's ok..probably
			// the account was deleted by previous execution of changes..just
			// log in the trace the message for the user..
			LOGGER.debug(
					"Situation in account could not be updated. Account not found on the resource. Skipping modifying situation in account");
			return;
		} catch (Exception ex) {
			result.recordFatalError(ex);
			throw new SystemException(ex.getMessage(), ex);
		} finally {
			ModelImplUtils.clearRequestee(task);
		}
		// if everything is OK, add result of the situation modification to the
		// parent result
		result.recordSuccess();

	}

	/**
	 * @return Returns estimate of the object after modification. Or null if the object was deleted.
	 *         NOTE: this is only partially implemented (only for shadow delete).
	 */
	private <T extends ObjectType, F extends ObjectType> PrismObject<T> executeDelta(ObjectDelta<T> objectDelta,
			LensElementContext<T> objectContext, LensContext<F> context, ModelExecuteOptions options,
			ConflictResolutionType conflictResolution, ResourceType resource, Task task, OperationResult parentResult)
					throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException,
					CommunicationException, ConfigurationException, SecurityViolationException,
					PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException {

		if (objectDelta == null) {
			throw new IllegalArgumentException("Null change");
		}

		if (objectDelta.getOid() == null) {
			objectDelta.setOid(objectContext.getOid());
		}

		objectDelta = computeDeltaToExecute(objectDelta, objectContext);
		if (LOGGER.isTraceEnabled()) {
			LOGGER.trace("computeDeltaToExecute returned:\n{}",
					objectDelta != null ? objectDelta.debugDump(1) : "(null)");
		}

		if (objectDelta == null || objectDelta.isEmpty()) {
			LOGGER.debug("Skipping execution of delta because it was already executed: {}", objectContext);
			return objectContext.getObjectCurrent();
		}

		if (InternalsConfig.consistencyChecks) {
			objectDelta.checkConsistence(ConsistencyCheckScope.fromBoolean(consistencyChecks));
		}

		// Other types than focus types may not be definition-complete (e.g.
		// accounts and resources are completed in provisioning)
		if (FocusType.class.isAssignableFrom(objectDelta.getObjectTypeClass())) {
			objectDelta.assertDefinitions();
		}

		LensUtil.setDeltaOldValue(objectContext, objectDelta);

		if (LOGGER.isTraceEnabled()) {
			logDeltaExecution(objectDelta, context, resource, null, task);
		}

		OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE_DELTA);
		PrismObject<T> objectAfterModification = null;

		try {

			if (objectDelta.getChangeType() == ChangeType.ADD) {
				objectAfterModification = executeAddition(objectDelta, context, objectContext, options, resource, task, result);
			} else if (objectDelta.getChangeType() == ChangeType.MODIFY) {
				executeModification(objectDelta, context, objectContext, options, conflictResolution, resource, task, result);
			} else if (objectDelta.getChangeType() == ChangeType.DELETE) {
				objectAfterModification = executeDeletion(objectDelta, context, objectContext, options, resource, task, result);
			}

			// To make sure that the OID is set (e.g. after ADD operation)
			LensUtil.setContextOid(context, objectContext, objectDelta.getOid());

		} finally {

			result.computeStatus();
			if (objectContext != null) {
				if (!objectDelta.hasCompleteDefinition()) {
					throw new SchemaException("object delta does not have complete definition");
				}
				LensObjectDeltaOperation<T> objectDeltaOp = LensUtil.createObjectDeltaOperation(
						objectDelta.clone(), result, objectContext, null, resource);
				if (LOGGER.isTraceEnabled()) {
					LOGGER.trace("Recording executed delta:\n{}", objectDeltaOp.shorterDebugDump(1));
				}
				objectContext.addToExecutedDeltas(objectDeltaOp);
			}

			if (LOGGER.isDebugEnabled()) {
				if (LOGGER.isTraceEnabled()) {
					LOGGER.trace("EXECUTION result {}", result.getLastSubresult());
				} else {
					// Execution of deltas was not logged yet
					logDeltaExecution(objectDelta, context, resource, result.getLastSubresult(), task);
				}
			}
		}

		return objectAfterModification;
	}

	private <T extends ObjectType, F extends FocusType> void removeExecutedItemDeltas(
			ObjectDelta<T> objectDelta, LensElementContext<T> objectContext) {
		if (objectContext == null) {
			return;
		}

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

		if (objectDelta.getModifications() == null || objectDelta.getModifications().isEmpty()) {
			return;
		}

		List<LensObjectDeltaOperation<T>> executedDeltas = objectContext.getExecutedDeltas();
		for (LensObjectDeltaOperation<T> executedDelta : executedDeltas) {
			ObjectDelta<T> executed = executedDelta.getObjectDelta();
			Iterator<? extends ItemDelta> objectDeltaIterator = objectDelta.getModifications().iterator();
			while (objectDeltaIterator.hasNext()) {
				ItemDelta d = objectDeltaIterator.next();
				if (executed.containsModification(d, true, true) || d.isEmpty()) {
					objectDeltaIterator.remove();
				}
			}
		}
	}

	// TODO beware - what if the delta was executed but not successfully?
	private <T extends ObjectType, F extends FocusType> boolean alreadyExecuted(ObjectDelta<T> objectDelta,
			LensElementContext<T> objectContext) {
		if (objectContext == null) {
			return false;
		}
		if (LOGGER.isTraceEnabled()) {
			LOGGER.trace("Checking for already executed delta:\n{}\nIn deltas:\n{}", objectDelta.debugDump(),
					DebugUtil.debugDump(objectContext.getExecutedDeltas()));
		}
		return ObjectDeltaOperation.containsDelta(objectContext.getExecutedDeltas(), objectDelta);
	}

	/**
	 * Was this object already added? (temporary method, should be removed soon)
	 */
	private <T extends ObjectType> boolean wasAdded(List<LensObjectDeltaOperation<T>> executedOperations,
			String oid) {
		for (LensObjectDeltaOperation operation : executedOperations) {
			if (operation.getObjectDelta().isAdd() && oid.equals(operation.getObjectDelta().getOid())
					&& !operation.getExecutionResult().isFatalError()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Computes delta to execute, given a list of already executes deltas. See
	 * below.
	 */
	private <T extends ObjectType> ObjectDelta<T> computeDeltaToExecute(ObjectDelta<T> objectDelta,
			LensElementContext<T> objectContext) {
		if (objectContext == null) {
			return objectDelta;
		}
		if (LOGGER.isTraceEnabled()) {
			LOGGER.trace("Computing delta to execute from delta:\n{}\nGiven these executed deltas:\n{}",
					objectDelta.debugDump(1), LensObjectDeltaOperation.shorterDebugDump(objectContext.getExecutedDeltas(),1));
		}
		List<LensObjectDeltaOperation<T>> executedDeltas = objectContext.getExecutedDeltas();
		return computeDiffDelta(executedDeltas, objectDelta);
	}

	/**
	 * Compute a "difference delta" - given that executedDeltas were executed,
	 * and objectDelta is about to be executed; eliminates parts that have
	 * already been done. It is meant as a kind of optimization (for MODIFY
	 * deltas) and error avoidance (for ADD deltas).
	 *
	 * Explanation for ADD deltas: there are situations where an execution wave
	 * is restarted - when unexpected AlreadyExistsException is reported from
	 * provisioning. However, in such cases, duplicate ADD Focus deltas were
	 * generated. So we (TEMPORARILY!) decided to filter them out here.
	 *
	 * Unfortunately, this mechanism is not well-defined, and seems to work more
	 * "by accident" than "by design". It should be replaced with something more
	 * serious. Perhaps by re-reading current focus state when repeating a wave?
	 * Actually, it is a supplement for rewriting ADD->MODIFY deltas in
	 * LensElementContext.getFixedPrimaryDelta. That method converts primary
	 * deltas (and as far as I know, that is the only place where this problem
	 * should occur). Nevertheless, for historical and safety reasons I keep
	 * also the processing in this method.
	 *
	 * Anyway, currently it treats only two cases: 1) if the objectDelta is
	 * present in the list of executed deltas 2) if the objectDelta is ADD, and
	 * another ADD delta is there (then the difference is computed)
	 *
	 */
	private <T extends ObjectType> ObjectDelta<T> computeDiffDelta(
			List<? extends ObjectDeltaOperation<T>> executedDeltas, ObjectDelta<T> objectDelta) {
		if (executedDeltas == null || executedDeltas.isEmpty()) {
			return objectDelta;
		}

		// any delta related to our OID, not ending with fatal error
		ObjectDeltaOperation<T> lastRelated = findLastRelatedDelta(executedDeltas, objectDelta);
		if (LOGGER.isTraceEnabled()) {
			LOGGER.trace("findLastRelatedDelta returned:\n{}",
					lastRelated != null ? lastRelated.shorterDebugDump(1) : "  (null)");
		}
		if (lastRelated == null) {
			return objectDelta; // nothing found, let us apply our delta
		}
		if (lastRelated.getExecutionResult().isSuccess() && lastRelated.containsDelta(objectDelta)) {
			return null; // case 1 - exact match found with SUCCESS result,
							// let's skip the processing of our delta
		}
		if (!objectDelta.isAdd()) {
			return objectDelta; // MODIFY or DELETE delta - we may safely apply
								// it (not 100% sure about DELETE case)
		}
		// determine if we got case 2
		if (lastRelated.getObjectDelta().isDelete()) {
			return objectDelta; // we can (and should) apply the ADD delta as a
								// whole, because the object was deleted
		}
		// let us treat the most simple case here - meaning we have existing ADD
		// delta and nothing more
		// TODO add more sophistication if needed
		if (!lastRelated.getObjectDelta().isAdd()) {
			return objectDelta; // this will probably fail, but ...
		}
		// at this point we know that ADD was more-or-less successfully
		// executed, so let's compute the difference, creating a MODIFY delta
		PrismObject<T> alreadyAdded = lastRelated.getObjectDelta().getObjectToAdd();
		PrismObject<T> toBeAddedNow = objectDelta.getObjectToAdd();
		return alreadyAdded.diff(toBeAddedNow);
	}

	private <T extends ObjectType> ObjectDeltaOperation<T> findLastRelatedDelta(
			List<? extends ObjectDeltaOperation<T>> executedDeltas, ObjectDelta<T> objectDelta) {
		for (int i = executedDeltas.size() - 1; i >= 0; i--) {
			ObjectDeltaOperation<T> currentOdo = executedDeltas.get(i);
			if (currentOdo.getExecutionResult().isFatalError()) {
				continue;
			}
			ObjectDelta<T> current = currentOdo.getObjectDelta();

			if (current.equals(objectDelta)) {
				return currentOdo;
			}

			String oid1 = current.isAdd() ? current.getObjectToAdd().getOid() : current.getOid();
			String oid2 = objectDelta.isAdd() ? objectDelta.getObjectToAdd().getOid()
					: objectDelta.getOid();
			if (oid1 != null && oid2 != null) {
				if (oid1.equals(oid2)) {
					return currentOdo;
				}
				continue;
			}
			// ADD-MODIFY and ADD-DELETE combinations lead to applying whole
			// delta (as a result of computeDiffDelta)
			// so we can be lazy and check only ADD-ADD combinations here...
			if (!current.isAdd() || !objectDelta.isAdd()) {
				continue;
			}
			// we simply check the type (for focus objects) and
			// resource+kind+intent (for shadows)
			PrismObject<T> currentObject = current.getObjectToAdd();
			PrismObject<T> objectTypeToAdd = objectDelta.getObjectToAdd();
			Class currentObjectClass = currentObject.getCompileTimeClass();
			Class objectTypeToAddClass = objectTypeToAdd.getCompileTimeClass();
			if (currentObjectClass == null || !currentObjectClass.equals(objectTypeToAddClass)) {
				continue;
			}
			if (FocusType.class.isAssignableFrom(currentObjectClass)) {
				return currentOdo; // we suppose there is only one delta of
									// Focus class
			}
		}
		return null;
	}

	private ProvisioningOperationOptions copyFromModelOptions(ModelExecuteOptions options) {
		ProvisioningOperationOptions provisioningOptions = new ProvisioningOperationOptions();
		if (options == null) {
			return provisioningOptions;
		}

		provisioningOptions.setForce(options.getForce());
		provisioningOptions.setOverwrite(options.getOverwrite());
		return provisioningOptions;
	}

	private <F extends ObjectType> ProvisioningOperationOptions getProvisioningOptions(LensContext<F> context,
			ModelExecuteOptions modelOptions, PrismObject<ShadowType> existingShadow, ObjectDelta<ShadowType> delta) throws SecurityViolationException {
		if (modelOptions == null && context != null) {
			modelOptions = context.getOptions();
		}
		ProvisioningOperationOptions provisioningOptions = copyFromModelOptions(modelOptions);

		if (executeAsSelf(context, modelOptions, existingShadow, delta)) {
			LOGGER.trace("Setting 'execute as self' provisioning option for {}", existingShadow);
			provisioningOptions.setRunAsAccountOid(existingShadow.getOid());
		}

		if (context != null && context.getChannel() != null) {

			if (context.getChannel().equals(QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_RECON))) {
				// TODO: this is probably wrong. We should not have special case
				// for recon channel! This should be handled by the provisioning task
				// setting the right options there.
				provisioningOptions.setCompletePostponed(false);
			}

			if (context.getChannel().equals(SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI)) {
				// We want to avoid endless loops in error handling.
				provisioningOptions.setDoNotDiscovery(true);
			}
		}

		return provisioningOptions;
	}

	// This is a bit of black magic. We only want to execute as self if there a user is changing its own password
	// and we also have old password value.
	// Later, this should be improved. Maybe we need special model operation option for this? Or maybe it should be somehow
	// automatically detected based on resource capabilities? We do not know yet. Therefore let's do the simplest possible
	// thing. Otherwise we might do something that we will later regret.
	private <F extends ObjectType> boolean executeAsSelf(LensContext<F> context,
			ModelExecuteOptions modelOptions, PrismObject<ShadowType> existingShadow, ObjectDelta<ShadowType> delta) throws SecurityViolationException {
		if (existingShadow == null) {
			return false;
		}

		if (!SchemaConstants.CHANNEL_GUI_SELF_SERVICE_URI.equals(context.getChannel())) {
			return false;
		}

		if (delta == null) {
			return false;
		}
		if (!delta.isModify()) {
			return false;
		}
		PropertyDelta<ProtectedStringType> passwordDelta = delta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE);
		if (passwordDelta == null) {
			return false;
		}
		if (passwordDelta.getEstimatedOldValues() == null || passwordDelta.getEstimatedOldValues().isEmpty()) {
			return false;
		}
		ProtectedStringType oldPassword = passwordDelta.getEstimatedOldValues().iterator().next().getValue();
		if (!oldPassword.canGetCleartext()) {
			return false;
		}

		LensFocusContext<F> focusContext = context.getFocusContext();
		if (focusContext == null) {
			return false;
		}
		if (!focusContext.canRepresent(UserType.class)) {
			return false;
		}

		MidPointPrincipal principal = securityContextManager.getPrincipal();
		if (principal == null) {
			return false;
		}
		UserType loggedInUser = principal.getUser();
		if (loggedInUser == null) {
			return false;
		}

		if (!loggedInUser.getOid().equals(focusContext.getOid())) {
			return false;
		}
		return true;
	}

	private <T extends ObjectType, F extends ObjectType> void logDeltaExecution(ObjectDelta<T> objectDelta,
			LensContext<F> context, ResourceType resource, OperationResult result, Task task) {
		StringBuilder sb = new StringBuilder();
		sb.append("---[ ");
		if (result == null) {
			sb.append("Going to EXECUTE");
		} else {
			sb.append("EXECUTED");
		}
		sb.append(" delta of ").append(objectDelta.getObjectTypeClass().getSimpleName());
		sb.append(" ]---------------------\n");
		DebugUtil.debugDumpLabel(sb, "Channel", 0);
		sb.append(" ").append(LensUtil.getChannel(context, task)).append("\n");
		if (context != null) {
			DebugUtil.debugDumpLabel(sb, "Wave", 0);
			sb.append(" ").append(context.getExecutionWave()).append("\n");
		}
		if (resource != null) {
			sb.append("Resource: ").append(resource.toString()).append("\n");
		}
		sb.append(objectDelta.debugDump());
		sb.append("\n");
		if (result != null) {
			DebugUtil.debugDumpLabel(sb, "Result", 0);
			sb.append(" ").append(result.getStatus()).append(": ").append(result.getMessage());
		}
		sb.append("\n--------------------------------------------------");

		LOGGER.debug("\n{}", sb);
	}

	private <F extends ObjectType> OwnerResolver createOwnerResolver(final LensContext<F> context, Task task,
			OperationResult result) {
		return new LensOwnerResolver<>(context, objectResolver, task, result);
	}

	private <T extends ObjectType, F extends ObjectType> PrismObject<T> executeAddition(ObjectDelta<T> change,
			final LensContext<F> context, LensElementContext<T> objectContext, ModelExecuteOptions options,
			ResourceType resource, Task task, OperationResult result) throws ObjectAlreadyExistsException,
					ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException,
					SecurityViolationException, PolicyViolationException, ExpressionEvaluationException {

		PrismObject<T> objectToAdd = change.getObjectToAdd();

		if (change.getModifications() != null) {
			for (ItemDelta delta : change.getModifications()) {
				delta.applyTo(objectToAdd);
			}
			change.getModifications().clear();
		}

		OwnerResolver ownerResolver = createOwnerResolver(context, task, result);
		try {
			securityEnforcer.authorize(ModelAuthorizationAction.ADD.getUrl(),
					AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectAdd(objectToAdd), ownerResolver, task, result);

			T objectTypeToAdd = objectToAdd.asObjectable();

			metadataManager.applyMetadataAdd(context, objectToAdd, clock.currentTimeXMLGregorianCalendar(), task, result);

			if (options == null && context != null) {
				options = context.getOptions();
			}

			RepoAddOptions addOpt = new RepoAddOptions();
			if (ModelExecuteOptions.isOverwrite(options)) {
				addOpt.setOverwrite(true);
			}
			if (ModelExecuteOptions.isNoCrypt(options)) {
				addOpt.setAllowUnencryptedValues(true);
			}

			String oid;
			if (objectTypeToAdd instanceof TaskType) {
				oid = addTask((TaskType) objectTypeToAdd, addOpt, result);
			} else if (objectTypeToAdd instanceof NodeType) {
				throw new UnsupportedOperationException("NodeType cannot be added using model interface");
			} else if (ObjectTypes.isManagedByProvisioning(objectTypeToAdd)) {

				ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options,
						(PrismObject<ShadowType>)objectContext.getObjectCurrent(), (ObjectDelta<ShadowType>)change);

				oid = addProvisioningObject(objectToAdd, context, objectContext, provisioningOptions,
						resource, task, result);
				if (oid == null) {
					throw new SystemException(
							"Provisioning addObject returned null OID while adding " + objectToAdd);
				}
				result.addReturn("createdAccountOid", oid);
			} else {
				FocusConstraintsChecker.clearCacheFor(objectToAdd.asObjectable().getName());

				oid = cacheRepositoryService.addObject(objectToAdd, addOpt, result);
				if (oid == null) {
					throw new SystemException(
							"Repository addObject returned null OID while adding " + objectToAdd);
				}
			}
			change.setOid(oid);
			objectToAdd.setOid(oid);
			task.recordObjectActionExecuted(objectToAdd, objectToAdd.getCompileTimeClass(), oid,
					ChangeType.ADD, context.getChannel(), null);
			return objectToAdd;
		} catch (Throwable t) {
			task.recordObjectActionExecuted(objectToAdd, objectToAdd.getCompileTimeClass(), null,
					ChangeType.ADD, context.getChannel(), t);
			throw t;
		}
	}

	private <T extends ObjectType, F extends ObjectType> PrismObject<T> executeDeletion(ObjectDelta<T> change,
			LensContext<F> context, LensElementContext<T> objectContext, ModelExecuteOptions options,
			ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException,
					ObjectAlreadyExistsException, SchemaException, CommunicationException,
					ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException {

		String oid = change.getOid();
		Class<T> objectTypeClass = change.getObjectTypeClass();

		PrismObject<T> objectOld = objectContext.getObjectOld();
		OwnerResolver ownerResolver = createOwnerResolver(context, task, result);
		PrismObject<T> objectAfterModification = null;
		try {
			securityEnforcer.authorize(ModelAuthorizationAction.DELETE.getUrl(),
					AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectDelete(objectOld), ownerResolver, task, result);

			if (TaskType.class.isAssignableFrom(objectTypeClass)) {
				taskManager.deleteTask(oid, result);
			} else if (NodeType.class.isAssignableFrom(objectTypeClass)) {
				taskManager.deleteNode(oid, result);
			} else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) {
				ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options,
						(PrismObject<ShadowType>)objectContext.getObjectCurrent(), (ObjectDelta<ShadowType>)change);
				try {
					objectAfterModification = deleteProvisioningObject(objectTypeClass, oid, context, objectContext,
							provisioningOptions, resource, task, result);
				} catch (ObjectNotFoundException e) {
					// Object that we wanted to delete is already gone. This can
					// happen in some race conditions.
					// As the resulting state is the same as we wanted it to be
					// we will not complain and we will go on.
					LOGGER.trace("Attempt to delete object {} ({}) that is already gone", oid,
							objectTypeClass);
					result.muteLastSubresultError();
				}
			} else {
				try {
					cacheRepositoryService.deleteObject(objectTypeClass, oid, result);
				} catch (ObjectNotFoundException e) {
					// Object that we wanted to delete is already gone. This can
					// happen in some race conditions.
					// As the resulting state is the same as we wanted it to be
					// we will not complain and we will go on.
					LOGGER.trace("Attempt to delete object {} ({}) that is already gone", oid,
							objectTypeClass);
					result.muteLastSubresultError();
				}
			}
			task.recordObjectActionExecuted(objectOld, objectTypeClass, oid, ChangeType.DELETE,
					context.getChannel(), null);
		} catch (Throwable t) {
			task.recordObjectActionExecuted(objectOld, objectTypeClass, oid, ChangeType.DELETE,
					context.getChannel(), t);
			throw t;
		}

		return objectAfterModification;
	}

	private <T extends ObjectType, F extends ObjectType> void executeModification(ObjectDelta<T> delta,
			LensContext<F> context, LensElementContext<T> objectContext, ModelExecuteOptions options,
			ConflictResolutionType conflictResolution, ResourceType resource, Task task, OperationResult result)
					throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException, CommunicationException,
					ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, PreconditionViolationException {
		Class<T> objectTypeClass = delta.getObjectTypeClass();

		// We need current object here. The current object is used to get data for id-only container delete deltas,
		// replace deltas and so on. The authorization code can figure out new object if needed, but it needs
		// current object to start from.
		// We cannot use old object here. That would fail in multi-wave executions. We want object that has all the previous
		// wave changes already applied.
		PrismObject<T> baseObject = objectContext.getObjectCurrent();
		OwnerResolver ownerResolver = createOwnerResolver(context, task, result);
		try {
			securityEnforcer.authorize(ModelAuthorizationAction.MODIFY.getUrl(),
					AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObjectDelta(baseObject, delta), ownerResolver, task, result);

			metadataManager.applyMetadataModify(delta, objectContext, objectTypeClass,
					clock.currentTimeXMLGregorianCalendar(), task, context, result);

			if (delta.isEmpty()) {
				// Nothing to do
				return;
			}

			if (TaskType.class.isAssignableFrom(objectTypeClass)) {
				taskManager.modifyTask(delta.getOid(), delta.getModifications(), result);
			} else if (NodeType.class.isAssignableFrom(objectTypeClass)) {
				throw new UnsupportedOperationException("NodeType is not modifiable using model interface");
			} else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) {
				ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options,
						(PrismObject<ShadowType>)objectContext.getObjectCurrent(), (ObjectDelta<ShadowType>)delta);
				String oid = modifyProvisioningObject(objectTypeClass, delta.getOid(),
						delta.getModifications(), context, objectContext, provisioningOptions, resource,
						task, result);
				if (!oid.equals(delta.getOid())) {
					delta.setOid(oid);
				}
			} else {
				FocusConstraintsChecker.clearCacheForDelta(delta.getModifications());
				ModificationPrecondition<T> precondition = null;
				if (conflictResolution != null) {
					String readVersion = objectContext.getObjectReadVersion();
					if (readVersion != null) {
						LOGGER.trace("Modification with precondition, readVersion={}", readVersion);
						precondition = new VersionPrecondition<>(readVersion);
					} else {
						LOGGER.warn("Requested careful modification of {}, but there is no read version", objectContext.getHumanReadableName());
					}
				}
				cacheRepositoryService.modifyObject(objectTypeClass, delta.getOid(),
						delta.getModifications(), precondition, null, result);
			}
			task.recordObjectActionExecuted(baseObject, objectTypeClass, delta.getOid(), ChangeType.MODIFY,
					context.getChannel(), null);
		} catch (Throwable t) {
			task.recordObjectActionExecuted(baseObject, objectTypeClass, delta.getOid(), ChangeType.MODIFY,
					context.getChannel(), t);
			throw t;
		}
	}

	private String addTask(TaskType task, RepoAddOptions addOpt, OperationResult result)
			throws ObjectAlreadyExistsException {
		try {
			return taskManager.addTask(task.asPrismObject(), addOpt, result);
		} catch (ObjectAlreadyExistsException ex) {
			throw ex;
		} catch (Exception ex) {
			LoggingUtils.logException(LOGGER, "Couldn't add object {} to task manager", ex, task.getName());
			throw new SystemException(ex.getMessage(), ex);
		}
	}

	private <F extends ObjectType, T extends ObjectType> String addProvisioningObject(PrismObject<T> object,
			LensContext<F> context, LensElementContext<T> objectContext, ProvisioningOperationOptions options,
			ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException,
					ObjectAlreadyExistsException, SchemaException, CommunicationException,
					ConfigurationException, SecurityViolationException, ExpressionEvaluationException, PolicyViolationException {

		if (object.canRepresent(ShadowType.class)) {
			ShadowType shadow = (ShadowType) object.asObjectable();
			String resourceOid = ShadowUtil.getResourceOid(shadow);
			if (resourceOid == null) {
				throw new IllegalArgumentException("Resource OID is null in shadow");
			}
		}

		OperationProvisioningScriptsType scripts = null;
		if (object.canRepresent(ShadowType.class)) {
			scripts = prepareScripts(object, context, objectContext, ProvisioningOperationTypeType.ADD,
					resource, task, result);
		}
		ModelImplUtils.setRequestee(task, context);
		String oid = provisioning.addObject(object, scripts, options, task, result);
		ModelImplUtils.clearRequestee(task);
		return oid;
	}

	private <F extends ObjectType, T extends ObjectType> PrismObject<T> deleteProvisioningObject(
			Class<T> objectTypeClass, String oid, LensContext<F> context, LensElementContext<T> objectContext,
			ProvisioningOperationOptions options, ResourceType resource, Task task, OperationResult result)
					throws ObjectNotFoundException, ObjectAlreadyExistsException, SchemaException,
					CommunicationException, ConfigurationException, SecurityViolationException,
					ExpressionEvaluationException, PolicyViolationException {

		PrismObject<T> shadowToModify = null;
		OperationProvisioningScriptsType scripts = null;
		try {
			GetOperationOptions rootOpts = GetOperationOptions.createNoFetch();
			rootOpts.setPointInTimeType(PointInTimeType.FUTURE);
			shadowToModify = provisioning.getObject(objectTypeClass, oid,
					SelectorOptions.createCollection(rootOpts), task, result);
		} catch (ObjectNotFoundException ex) {
			// this is almost OK, mute the error and try to delete account (it
			// will fail if something is wrong)
			result.muteLastSubresultError();
		}
		if (ShadowType.class.isAssignableFrom(objectTypeClass)) {
			scripts = prepareScripts(shadowToModify, context, objectContext,
					ProvisioningOperationTypeType.DELETE, resource, task, result);
		}
		ModelImplUtils.setRequestee(task, context);
		PrismObject<T> objectAfterModification = provisioning.deleteObject(objectTypeClass, oid, options, scripts, task, result);
		ModelImplUtils.clearRequestee(task);
		return objectAfterModification;
	}

	private <F extends ObjectType, T extends ObjectType> String modifyProvisioningObject(
			Class<T> objectTypeClass, String oid, Collection<? extends ItemDelta> modifications,
			LensContext<F> context, LensElementContext<T> objectContext, ProvisioningOperationOptions options,
			ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException,
					CommunicationException, SchemaException, ConfigurationException,
					SecurityViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException {

		PrismObject<T> shadowToModify = null;
		OperationProvisioningScriptsType scripts = null;
		try {
			GetOperationOptions rootOpts = GetOperationOptions.createNoFetch();
			rootOpts.setPointInTimeType(PointInTimeType.FUTURE);
			shadowToModify = provisioning.getObject(objectTypeClass, oid,
					SelectorOptions.createCollection(rootOpts), task, result);
		} catch (ObjectNotFoundException e) {
			// We do not want the operation to fail here. The object might have
			// been re-created on the resource
			// or discovery might re-create it. So simply ignore this error and
			// give provisioning a chance to fail
			// properly.
			result.muteLastSubresultError();
			LOGGER.warn("Repository object {}: {} is gone. But trying to modify resource object anyway",
					objectTypeClass, oid);
		}
		if (ShadowType.class.isAssignableFrom(objectTypeClass)) {
			scripts = prepareScripts(shadowToModify, context, objectContext,
					ProvisioningOperationTypeType.MODIFY, resource, task, result);
		}
		ModelImplUtils.setRequestee(task, context);
		String changedOid = provisioning.modifyObject(objectTypeClass, oid, modifications, scripts, options,
				task, result);
		ModelImplUtils.clearRequestee(task);
		return changedOid;
	}

	private <F extends ObjectType, T extends ObjectType> OperationProvisioningScriptsType prepareScripts(
			PrismObject<T> changedObject, LensContext<F> context, LensElementContext<T> objectContext,
			ProvisioningOperationTypeType operation, ResourceType resource, Task task, OperationResult result)
					throws ObjectNotFoundException, SchemaException, CommunicationException,
					ConfigurationException, SecurityViolationException, ExpressionEvaluationException {

		if (resource == null) {
			LOGGER.warn("Resource does not exist. Skipping processing scripts.");
			return null;
		}
		OperationProvisioningScriptsType resourceScripts = resource.getScripts();
		PrismObject<ShadowType> resourceObject = (PrismObject<ShadowType>) changedObject;

		PrismObject<F> user = null;
		if (context.getFocusContext() != null) {
			if (context.getFocusContext().getObjectNew() != null) {
				user = context.getFocusContext().getObjectNew();
			} else if (context.getFocusContext().getObjectCurrent() != null) {
				user = context.getFocusContext().getObjectCurrent();
			} else if (context.getFocusContext().getObjectOld() != null) {
				user = context.getFocusContext().getObjectOld();
			}
		}

		LensProjectionContext projectionCtx = (LensProjectionContext) objectContext;
		PrismObject<ShadowType> shadow = null;
		if (projectionCtx.getObjectNew() != null) {
			shadow = projectionCtx.getObjectNew();
		} else if (projectionCtx.getObjectCurrent() != null) {
			shadow = projectionCtx.getObjectCurrent();
		} else {
			shadow = projectionCtx.getObjectOld();
		}

		if (shadow == null) {
			//put at least something
			shadow = resourceObject.clone();
		}

		ResourceShadowDiscriminator discr = ((LensProjectionContext) objectContext)
				.getResourceShadowDiscriminator();

		ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(user, shadow, discr,
				resource.asPrismObject(), context.getSystemConfiguration(), objectContext);
		ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context, (LensProjectionContext) objectContext, task, result));
		try {
			return evaluateScript(resourceScripts, discr, operation, null, variables, context, objectContext, task, result);
		} finally {
			ModelExpressionThreadLocalHolder.popExpressionEnvironment();
		}

	}

	private OperationProvisioningScriptsType evaluateScript(OperationProvisioningScriptsType resourceScripts,
			ResourceShadowDiscriminator discr, ProvisioningOperationTypeType operation, BeforeAfterType order,
			ExpressionVariables variables, LensContext<?> context,
			LensElementContext<?> objectContext, Task task,
			OperationResult result)
					throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException {
		OperationProvisioningScriptsType outScripts = new OperationProvisioningScriptsType();

		if (resourceScripts != null) {
			OperationProvisioningScriptsType scripts = resourceScripts.clone();
			for (OperationProvisioningScriptType script : scripts.getScript()) {
				if (discr != null) {
					if (script.getKind() != null && !script.getKind().isEmpty()
							&& !script.getKind().contains(discr.getKind())) {
						continue;
					}
					if (script.getIntent() != null && !script.getIntent().isEmpty()
							&& !script.getIntent().contains(discr.getIntent()) && discr.getIntent() != null) {
						continue;
					}
				}
				if (operation != null) {
					if (!script.getOperation().contains(operation)) {
						continue;
					}
				}
				if (order != null) {
					if (order != null && order != script.getOrder()) {
						continue;
					}
				}
				// Let's do the most expensive evaluation last
				if (!evaluateScriptCondition(script, variables, task, result)){
					continue;
				}
				for (ProvisioningScriptArgumentType argument : script.getArgument()) {
					evaluateScriptArgument(argument, variables, context, objectContext, task, result);
				}
				outScripts.getScript().add(script);
			}
		}

		return outScripts;
	}

	private boolean evaluateScriptCondition(OperationProvisioningScriptType script,
			ExpressionVariables variables, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
		ExpressionType condition = script.getCondition();
		if (condition == null) {
			return true;
		}

		PrismPropertyValue<Boolean> conditionOutput = ExpressionUtil.evaluateCondition(variables, condition, expressionFactory, " condition for provisioning script ", task, result);
		if (conditionOutput == null) {
			return true;
		}

		Boolean conditionOutputValue = conditionOutput.getValue();

		return BooleanUtils.isNotFalse(conditionOutputValue);

	}

	private void evaluateScriptArgument(ProvisioningScriptArgumentType argument,
			ExpressionVariables variables, LensContext<?> context,
			LensElementContext<?> objectContext, Task task,
			OperationResult result)
					throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException {

		QName FAKE_SCRIPT_ARGUMENT_NAME = new QName(SchemaConstants.NS_C, "arg");

		PrismPropertyDefinition<String> scriptArgumentDefinition = new PrismPropertyDefinitionImpl<>(
				FAKE_SCRIPT_ARGUMENT_NAME, DOMUtil.XSD_STRING, prismContext);

		String shortDesc = "Provisioning script argument expression";
		Expression<PrismPropertyValue<String>, PrismPropertyDefinition<String>> expression = expressionFactory
				.makeExpression(argument, scriptArgumentDefinition, shortDesc, task, result);

		ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task,
				result);
		ExpressionEnvironment<?> env = new ExpressionEnvironment<>(context,
				objectContext instanceof LensProjectionContext ? (LensProjectionContext) objectContext : null, task, result);
		PrismValueDeltaSetTriple<PrismPropertyValue<String>> outputTriple = ModelExpressionThreadLocalHolder
				.evaluateExpressionInContext(expression, params, env);

		Collection<PrismPropertyValue<String>> nonNegativeValues = null;
		if (outputTriple != null) {
			nonNegativeValues = outputTriple.getNonNegativeValues();
		}

		// replace dynamic script with static value..
		argument.getExpressionEvaluator().clear();
		if (nonNegativeValues == null || nonNegativeValues.isEmpty()) {
			// We need to create at least one evaluator. Otherwise the
			// expression code will complain
			// Element value = DOMUtil.createElement(SchemaConstants.C_VALUE);
			// DOMUtil.setNill(value);
			JAXBElement<RawType> el = new JAXBElement(SchemaConstants.C_VALUE, RawType.class,
					new RawType(prismContext));
			argument.getExpressionEvaluator().add(el);

		} else {
			for (PrismPropertyValue<String> val : nonNegativeValues) {
				// Element value =
				// DOMUtil.createElement(SchemaConstants.C_VALUE);
				// value.setTextContent(val.getValue());
				PrimitiveXNode<String> prim = new PrimitiveXNode<>();
				prim.setValue(val.getValue(), DOMUtil.XSD_STRING);
				JAXBElement<RawType> el = new JAXBElement(SchemaConstants.C_VALUE, RawType.class,
						new RawType(prim, prismContext));
				argument.getExpressionEvaluator().add(el);
			}
		}
	}

	private <T extends ObjectType, F extends ObjectType> void executeReconciliationScript(
			LensProjectionContext projContext, LensContext<F> context, BeforeAfterType order, Task task,
			OperationResult parentResult) throws SchemaException, ObjectNotFoundException,
					ExpressionEvaluationException, CommunicationException, ConfigurationException,
					SecurityViolationException, ObjectAlreadyExistsException {

		if (!projContext.isDoReconciliation()) {
			return;
		}

		ResourceType resource = projContext.getResource();
		if (resource == null) {
			LOGGER.warn("Resource does not exist. Skipping processing reconciliation scripts.");
			return;
		}

		OperationProvisioningScriptsType resourceScripts = resource.getScripts();
		if (resourceScripts == null) {
			return;
		}

		executeProvisioningScripts(context, projContext, resourceScripts, ProvisioningOperationTypeType.RECONCILE, order, task, parentResult);
	}

	private <T extends ObjectType, F extends ObjectType> Object executeProvisioningScripts(LensContext<F> context, LensProjectionContext projContext,
			OperationProvisioningScriptsType scripts, ProvisioningOperationTypeType operation, BeforeAfterType order, Task task, OperationResult parentResult)
					throws SchemaException, ObjectNotFoundException,
					ExpressionEvaluationException, CommunicationException, ConfigurationException,
					SecurityViolationException, ObjectAlreadyExistsException {

		ResourceType resource = projContext.getResource();
		if (resource == null) {
			LOGGER.warn("Resource does not exist. Skipping processing reconciliation scripts.");
			return null;
		}

		PrismObject<F> user = null;
		PrismObject<ShadowType> shadow = null;

		if (context.getFocusContext() != null) {
			if (context.getFocusContext().getObjectNew() != null) {
				user = context.getFocusContext().getObjectNew();
			} else if (context.getFocusContext().getObjectOld() != null) {
				user = context.getFocusContext().getObjectOld();
			}
			// if (order == ProvisioningScriptOrderType.BEFORE) {
			// user = context.getFocusContext().getObjectOld();
			// } else if (order == ProvisioningScriptOrderType.AFTER) {
			// user = context.getFocusContext().getObjectNew();
			// } else {
			// throw new IllegalArgumentException("Unknown order "+order);
			// }
		}

		if (order == BeforeAfterType.BEFORE) {
			shadow = (PrismObject<ShadowType>) projContext.getObjectOld();
		} else if (order == BeforeAfterType.AFTER) {
			shadow = (PrismObject<ShadowType>) projContext.getObjectNew();
		} else {
			shadow = (PrismObject<ShadowType>) projContext.getObjectCurrent();
		}

		ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(user, shadow,
				projContext.getResourceShadowDiscriminator(), resource.asPrismObject(),
				context.getSystemConfiguration(), projContext);
		Object scriptResult = null;
		ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context, projContext, task, parentResult));
		try {
			OperationProvisioningScriptsType evaluatedScript = evaluateScript(scripts,
					projContext.getResourceShadowDiscriminator(), operation, order,
					variables, context, projContext, task, parentResult);
			for (OperationProvisioningScriptType script : evaluatedScript.getScript()) {
				ModelImplUtils.setRequestee(task, context);
				scriptResult = provisioning.executeScript(resource.getOid(), script, task, parentResult);
				ModelImplUtils.clearRequestee(task);
			}
		} finally {
			ModelExpressionThreadLocalHolder.popExpressionEnvironment();
		}

		return scriptResult;
	}

}


text:
Framework supporting easy yet powerful implementation of both very basic agents<br/>
as well as agents with advanced mechanisms for reasoning and representation of knowledge.

redpajama_set_name: RedPajamaGithub


text:
define([ "jquery", "lib/mixins/page_state" ], function($, withPageState) {

  "use strict";

  var SocialToolbar = function() {
    this.$listener = $("#js-row--content");
    this.el = ".js-social-toolbar";
    this.shares = [
      { name: "google-plus", separator: "url",  encodeURI: true },
      { name: "facebook",    separator: "u",    encodeURI: true },
      { name: "stumbleupon", separator: "url",  encodeURI: true },
      { name: "tumblr",      separator: "url",  encodeURI: true },
      { name: "pinterest",   separator: "url",  encodeURI: true },
      { name: "twitter",     separator: "text", hasMeta: true },
      { name: "mailto",      separator: "body", hasMeta: true }
    ];

    this.listen();
  };

  withPageState.call(SocialToolbar.prototype);

  // ---------------------------------------------------------------------------
  // Subscribe to Events
  // ---------------------------------------------------------------------------

  SocialToolbar.prototype.listen = function() {
    this.$listener.on(":page/received", function(e, data) {
      this.$el = this.$listener.find(this.el);
      if (!this.$el.length) return;

      if (data && data.displaySocial) {
        this._updateLinks(data);
        this.$el.removeClass("is-hidden");
      } else {
        this.$el.addClass("is-hidden");
      }
    }.bind(this));
  };

  // ---------------------------------------------------------------------------
  // Private Functions
  // ---------------------------------------------------------------------------

  SocialToolbar.prototype._updateLinks = function(data) {
    var url = this.getUrl(), i = 0, share;
    for (i; i < this.shares.length; i++) {
      share = this.shares[i];
      this._updateAnalytics(share, url);
      if (!share.hasMeta) {
        this._updateLink(share, url);
      }
    }
    if (data.copy && data.copy.title) {
      this._updateTweetMeta(url, data);
      this._updateMailtoMeta(url, data);
    }
  };

  SocialToolbar.prototype._updateAnalytics = function(share, url) {
    this.$el.find(".js-" + share.name + "-share").attr("data-lpa-label", url);
  };

  SocialToolbar.prototype._updateTweetMeta = function(url, data) {
    var $tw = this.$el.find(".js-twitter-share");

    $tw.length && $tw.attr("href",
      $tw.attr("href")
        .replace(/(text=).+?(?=via)/, "text=" + (data.copy.tweet || data.copy.title) + " " + url + " ")
    );
  };

  SocialToolbar.prototype._updateMailtoMeta = function(url, data) {
    var $mail = this.$el.find(".js-mailto-share");

    $mail.length && $mail.attr("href",
      $mail.attr("href")
        .replace(/(subject=)[^&]+/, "subject=" + data.copy.title)
        .replace(/(body=)[^&]+/, "body=" + url)
    );
  };

  SocialToolbar.prototype._updateLink = function(share, url) {
    var $link = this.$el.find(".js-" + share.name + "-share"),
        pattern = new RegExp(share.separator + "=[^&]+", "g"),
        shareUrl = share.encodeURI ? encodeURIComponent(url) : url;

    $link.length && $link.attr("href",
      $link.attr("href")
        .replace(pattern, share.separator + "=" + shareUrl)
    );
  };

  $(document).ready(function() {
    new SocialToolbar;
  });

  return SocialToolbar;

});


text:
const should = require('should');
const supertest = require('supertest');
const _ = require('lodash');
const url = require('url');
const cheerio = require('cheerio');
const moment = require('moment');
const testUtils = require('../../utils');
const configUtils = require('../../utils/configUtils');
const config = require('../../../core/shared/config');
const localUtils = require('./utils');

describe('Posts Content API', function () {
    let request;

    before(async function () {
        await localUtils.startGhost();
        request = supertest.agent(config.get('url'));
        await testUtils.initFixtures('owner:post', 'users:no-owner', 'user:inactive', 'posts', 'tags:extra', 'api_keys');
    });

    afterEach(function () {
        configUtils.restore();
    });

    const validKey = localUtils.getValidKey();

    it('Can request posts', async function () {
        const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}`))
            .set('Origin', testUtils.API.getURL())
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        res.headers.vary.should.eql('Accept-Encoding');
        should.exist(res.headers['access-control-allow-origin']);
        should.not.exist(res.headers['x-cache-invalidate']);

        const jsonResponse = res.body;
        should.exist(jsonResponse.posts);
        localUtils.API.checkResponse(jsonResponse, 'posts');
        jsonResponse.posts.should.have.length(11);
        localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
        localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
        _.isBoolean(jsonResponse.posts[0].featured).should.eql(true);

        // Default order 'published_at desc' check
        jsonResponse.posts[0].slug.should.eql('welcome');
        jsonResponse.posts[6].slug.should.eql('integrations');

        // check meta response for this test
        jsonResponse.meta.pagination.page.should.eql(1);
        jsonResponse.meta.pagination.limit.should.eql(15);
        jsonResponse.meta.pagination.pages.should.eql(1);
        jsonResponse.meta.pagination.total.should.eql(11);
        jsonResponse.meta.pagination.hasOwnProperty('next').should.be.true();
        jsonResponse.meta.pagination.hasOwnProperty('prev').should.be.true();
        should.not.exist(jsonResponse.meta.pagination.next);
        should.not.exist(jsonResponse.meta.pagination.prev);

        // kitchen sink
        res.body.posts[9].slug.should.eql(testUtils.DataGenerator.Content.posts[1].slug);

        let urlParts = url.parse(res.body.posts[9].feature_image);
        should.exist(urlParts.protocol);
        should.exist(urlParts.host);

        urlParts = url.parse(res.body.posts[9].url);
        should.exist(urlParts.protocol);
        should.exist(urlParts.host);

        const $ = cheerio.load(res.body.posts[9].html);
        urlParts = url.parse($('img').attr('src'));
        should.exist(urlParts.protocol);
        should.exist(urlParts.host);

        res.body.posts[7].slug.should.eql('not-so-short-bit-complex');
        res.body.posts[7].html.should.match(/<a href="http:\/\/127.0.0.1:2369\/about#nowhere" title="Relative URL/);
        res.body.posts[9].slug.should.eql('ghostly-kitchen-sink');
        res.body.posts[9].html.should.match(/<img src="http:\/\/127.0.0.1:2369\/content\/images\/lol.jpg"/);
    });

    it('Can filter posts by tag', async function () {
        const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=tag:kitchen-sink,featured:true&include=tags`))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        const jsonResponse = res.body;
        const ids = _.map(jsonResponse.posts, 'id');

        should.not.exist(res.headers['x-cache-invalidate']);
        should.exist(jsonResponse.posts);
        localUtils.API.checkResponse(jsonResponse, 'posts');
        localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');

        // should content filtered data and order
        jsonResponse.posts.should.have.length(4);
        ids.should.eql([
            testUtils.DataGenerator.Content.posts[4].id,
            testUtils.DataGenerator.Content.posts[2].id,
            testUtils.DataGenerator.Content.posts[1].id,
            testUtils.DataGenerator.Content.posts[0].id
        ]);

        // Each post must either be featured or have the tag 'kitchen-sink'
        _.each(jsonResponse.posts, (post) => {
            if (post.featured) {
                post.featured.should.equal(true);
            } else {
                const tags = _.map(post.tags, 'slug');
                tags.should.containEql('kitchen-sink');
            }
        });

        // The meta object should contain the right detail
        jsonResponse.meta.should.have.property('pagination');
        jsonResponse.meta.pagination.should.be.an.Object().with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
        jsonResponse.meta.pagination.page.should.eql(1);
        jsonResponse.meta.pagination.limit.should.eql(15);
        jsonResponse.meta.pagination.pages.should.eql(1);
        jsonResponse.meta.pagination.total.should.eql(4);
        should.equal(jsonResponse.meta.pagination.next, null);
        should.equal(jsonResponse.meta.pagination.prev, null);
    });

    it('Can filter posts by authors', async function () {
        const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=authors:[joe-bloggs,pat,ghost,slimer-mcectoplasm]&include=authors`))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        const jsonResponse = res.body;
        const ids = _.map(jsonResponse.posts, 'id');

        should.not.exist(res.headers['x-cache-invalidate']);
        should.exist(jsonResponse.posts);
        localUtils.API.checkResponse(jsonResponse, 'posts');
        localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');

        // 2. The data part of the response should be correct
        // We should have 2 matching items
        jsonResponse.posts.should.be.an.Array().with.lengthOf(11);

        // The API orders by number of matched authors.
        jsonResponse.posts[0].slug.should.eql('not-so-short-bit-complex');

        // Each post must either have the author 'joe-bloggs' or 'ghost', 'pat' is non existing author
        const primaryAuthors = _.map(jsonResponse.posts, function (post) {
            return post.primary_author.slug;
        });

        primaryAuthors.should.matchAny(/joe-bloggs|ghost'/);
        _.filter(primaryAuthors, (value) => {
            return value === 'ghost';
        }).length.should.eql(7);

        _.filter(primaryAuthors, (value) => {
            return value === 'joe-bloggs';
        }).length.should.eql(4);
    });

    it('Can request fields of posts', async function () {
        const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&fields=url`))
            .set('Origin', testUtils.API.getURL())
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        const jsonResponse = res.body;

        should.exist(jsonResponse.posts);

        localUtils.API.checkResponse(jsonResponse.posts[0], 'post', false, false, ['url']);
        res.body.posts[0].url.should.eql('http://127.0.0.1:2369/welcome/');
    });

    it('Can include relations', async function () {
        const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&include=tags,authors`))
            .set('Origin', testUtils.API.getURL())
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.exist(res.body.posts);

        // kitchen sink
        res.body.posts[9].slug.should.eql(testUtils.DataGenerator.Content.posts[1].slug);

        should.exist(res.body.posts[9].tags);
        should.exist(res.body.posts[9].tags[0].url);
        should.exist(url.parse(res.body.posts[9].tags[0].url).protocol);
        should.exist(url.parse(res.body.posts[9].tags[0].url).host);

        should.exist(res.body.posts[9].primary_tag);
        should.exist(res.body.posts[9].primary_tag.url);
        should.exist(url.parse(res.body.posts[9].primary_tag.url).protocol);
        should.exist(url.parse(res.body.posts[9].primary_tag.url).host);

        should.exist(res.body.posts[9].authors);
        should.exist(res.body.posts[9].authors[0].url);
        should.exist(url.parse(res.body.posts[9].authors[0].url).protocol);
        should.exist(url.parse(res.body.posts[9].authors[0].url).host);

        should.exist(res.body.posts[9].primary_author);
        should.exist(res.body.posts[9].primary_author.url);
        should.exist(url.parse(res.body.posts[9].primary_author.url).protocol);
        should.exist(url.parse(res.body.posts[9].primary_author.url).host);
    });

    it('Can request posts from different origin', async function () {
        const res = await request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}`))
            .set('Origin', 'https://example.com')
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        res.headers.vary.should.eql('Accept-Encoding');
        should.exist(res.headers['access-control-allow-origin']);
        should.not.exist(res.headers['x-cache-invalidate']);

        const jsonResponse = res.body;
        should.exist(jsonResponse.posts);
        localUtils.API.checkResponse(jsonResponse, 'posts');
        jsonResponse.posts.should.have.length(11);
        localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
        localUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
        _.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
    });

    it('Can filter by published date', async function () {
        function createFilter(publishedAt, op) {
            // This line deliberately uses double quotes because GQL cannot handle either double quotes
            // or escaped singles, see TryGhost/GQL#34
            return encodeURIComponent('published_at:' + op + '\'' + publishedAt + '\'');
        }

        const res = await request
            .get(localUtils.API.getApiQuery(`posts/?key=${validKey}&limit=1`))
            .set('Origin', testUtils.API.getURL())
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.exist(res.body.posts[0]);
        const post = res.body.posts[0];
        const publishedAt = moment(post.published_at).format('YYYY-MM-DD HH:mm:ss');

        post.title.should.eql('Start here for a quick overview of everything you need to know');

        const res2 = await request
            .get(localUtils.API.getApiQuery(`posts/?key=${validKey}&limit=1&filter=${createFilter(publishedAt, `<`)}`))
            .set('Origin', testUtils.API.getURL())
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.exist(res2.body.posts[0]);
        const post2 = res2.body.posts[0];
        const publishedAt2 = moment(post2.published_at).format('YYYY-MM-DD HH:mm:ss');

        post2.title.should.eql('Customizing your brand and design settings');

        const res3 = await request
            .get(localUtils.API.getApiQuery(`posts/?key=${validKey}&limit=1&filter=${createFilter(publishedAt2, `>`)}`))
            .set('Origin', testUtils.API.getURL())
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.exist(res3.body.posts[0]);
        const post3 = res3.body.posts[0];

        post3.title.should.eql('Start here for a quick overview of everything you need to know');
    });

    it('Can request a single post', async function () {
        const res = await request
            .get(localUtils.API.getApiQuery(`posts/${testUtils.DataGenerator.Content.posts[0].id}/?key=${validKey}`))
            .set('Origin', testUtils.API.getURL())
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.not.exist(res.headers['x-cache-invalidate']);
        const jsonResponse = res.body;
        should.exist(jsonResponse.posts);
        should.not.exist(jsonResponse.meta);
        jsonResponse.posts.should.have.length(1);
        localUtils.API.checkResponse(jsonResponse.posts[0], 'post');
    });
});


text:
<form name="userForm" ng-submit="profileEditNetworks.updateUserProfile(userForm.$valid)" autocomplete="off">

    Elsewhere

Connect to

            {{::providerName}}

Already connected

            {{::providerName}}
            (disconnect)

            Disconnect from {{::providerName}}?
            Yes
            Cancel

Other hospitality networks

            bewelcome.org/members/
            BeWelcome:

          <input id="extSitesBW" type="text" class="form-control" ng-model="profileEditNetworks.user.extSitesBW" ng-change="profileEdit.unsavedModifications=true" placeholder="your-username" aria-label="BeWelcome username">

            Test

            couchsurfing.com/people/
            Couchsurfing:

          <input id="extSitesCS" type="text" class="form-control" ng-model="profileEditNetworks.user.extSitesCS" ng-change="profileEdit.unsavedModifications=true" placeholder="your-username" aria-label="Couchsurfing username">

            Test

            warmshowers.org/user/
            Warmshowers:

          <input id="extSitesWS" type="text" class="form-control" ng-model="profileEditNetworks.user.extSitesWS" ng-change="profileEdit.unsavedModifications=true" placeholder="your-user-id" aria-label="Warmshowers user id">

            Test

This should be your numeric user id, not username.

<button type="submit" class="btn btn-lg btn-primary profile-editor-save" ng-disabled="!profileEdit.unsavedModifications">Save</button>

      Connecting your profile to these networks is absolutely optional.

      If you do, other members in Trustroots will be able to see a link to your other profile and some basic info about you and content you like there.
      In future you will be able to use e.g. your contact list here in Trustroots.
      We will never send any of your profile data to them.

</form>