com.evolveum.midpoint.provisioning.impl.ShadowCache.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.provisioning.impl.ShadowCache.java

Source

/*
 * Copyright (c) 2010-2015 Evolveum
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.evolveum.midpoint.provisioning.impl;

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

import javax.xml.namespace.QName;

import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.schema.util.ObjectQueryUtil;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import com.evolveum.midpoint.common.InternalsConfig;
import com.evolveum.midpoint.common.monitor.InternalMonitor;
import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.ShadowDiscriminatorObjectDelta;
import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerDefinition;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.Visitable;
import com.evolveum.midpoint.prism.Visitor;
import com.evolveum.midpoint.prism.delta.ChangeType;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.delta.PropertyDelta;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.query.AndFilter;
import com.evolveum.midpoint.prism.query.EqualFilter;
import com.evolveum.midpoint.prism.query.NaryLogicalFilter;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectPaging;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.OrFilter;
import com.evolveum.midpoint.prism.query.SubstringFilter;
import com.evolveum.midpoint.prism.query.ValueFilter;
import com.evolveum.midpoint.prism.util.PrismUtil;
import com.evolveum.midpoint.provisioning.api.ChangeNotificationDispatcher;
import com.evolveum.midpoint.provisioning.api.GenericConnectorException;
import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions;
import com.evolveum.midpoint.provisioning.api.ResourceObjectShadowChangeDescription;
import com.evolveum.midpoint.provisioning.api.ResourceOperationDescription;
import com.evolveum.midpoint.provisioning.consistency.api.ErrorHandler;
import com.evolveum.midpoint.provisioning.consistency.api.ErrorHandler.FailedOperation;
import com.evolveum.midpoint.provisioning.consistency.impl.ErrorHandlerFactory;
import com.evolveum.midpoint.provisioning.ucf.api.Change;
import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance;
import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException;
import com.evolveum.midpoint.provisioning.ucf.api.PropertyModificationOperation;
import com.evolveum.midpoint.provisioning.ucf.api.ResultHandler;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.DeltaConvertor;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.schema.RetrieveOption;
import com.evolveum.midpoint.schema.SearchResultMetadata;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition;
import com.evolveum.midpoint.schema.processor.ResourceAttribute;
import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer;
import com.evolveum.midpoint.schema.processor.ResourceAttributeContainerDefinition;
import com.evolveum.midpoint.schema.processor.ResourceAttributeDefinition;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.SchemaDebugUtil;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.Holder;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.exception.TunnelException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AvailabilityStatusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FailedOperationTypeType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationProvisioningScriptsType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAssociationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAttributesType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CountObjectsCapabilityType;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CountObjectsSimulateType;
import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;

/**
 * Shadow cache is a facade that covers all the operations with shadows.
 * It takes care of splitting the operations between repository and resource, merging the data back,
 * handling the errors and generally controlling the process.
 * 
 * The two principal classes that do the operations are:
 *   ResourceObjectConvertor: executes operations on resource
 *   ShadowManager: executes operations in the repository
 *   
 * Note: These three classes were refactored recently. But it will need more refactoring.
 * It is not a very pretty OO code. But it is better than before.
 * 
 * @author Radovan Semancik
 * @author Katarina Valalikova
 *
 */
public abstract class ShadowCache {

    @Autowired(required = true)
    @Qualifier("cacheRepositoryService")
    private RepositoryService repositoryService;

    @Autowired(required = true)
    private ErrorHandlerFactory errorHandlerFactory;

    @Autowired(required = true)
    private ResourceManager resourceManager;

    @Autowired(required = true)
    private PrismContext prismContext;

    @Autowired(required = true)
    private ResourceObjectConverter resouceObjectConverter;

    @Autowired(required = true)
    protected ShadowManager shadowManager;

    @Autowired(required = true)
    private ChangeNotificationDispatcher operationListener;

    @Autowired(required = true)
    private AccessChecker accessChecker;

    @Autowired(required = true)
    private TaskManager taskManager;

    @Autowired(required = true)
    private ChangeNotificationDispatcher changeNotificationDispatcher;

    @Autowired(required = true)
    private ProvisioningContextFactory ctxFactory;

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

    public ShadowCache() {
        repositoryService = null;
    }

    /**
     * Get the value of repositoryService.
     * 
     * @return the value of repositoryService
     */
    public RepositoryService getRepositoryService() {
        return repositoryService;
    }

    public PrismContext getPrismContext() {
        return prismContext;
    }

    public PrismObject<ShadowType> getShadow(String oid, PrismObject<ShadowType> repositoryShadow,
            Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult)
            throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException,
            SecurityViolationException {

        Validate.notNull(oid, "Object id must not be null.");

        LOGGER.trace("Start getting object with oid {}", oid);

        GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options);

        // We are using parent result directly, not creating subresult.
        // We want to hide the existence of shadow cache from the user.

        // Get the shadow from repository. There are identifiers that we need
        // for accessing the object by UCF.
        // Later, the repository object may have a fully cached object from the resource.
        if (repositoryShadow == null) {
            repositoryShadow = repositoryService.getObject(ShadowType.class, oid, null, parentResult);
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Got repository shadow object:\n{}", repositoryShadow.debugDump());
            }
        }

        // Sanity check
        if (!oid.equals(repositoryShadow.getOid())) {
            parentResult.recordFatalError("Provided OID is not equal to OID of repository shadow");
            throw new IllegalArgumentException("Provided OID is not equal to OID of repository shadow");
        }

        ProvisioningContext ctx = ctxFactory.create(repositoryShadow, task, parentResult);
        try {
            ctx.assertDefinition();
            applyAttributesDefinition(ctx, repositoryShadow);
        } catch (ObjectNotFoundException | SchemaException | CommunicationException | ConfigurationException e) {
            throw e;
            //         String msg = e.getMessage()+" (returning repository shadow)";
            //         LOGGER.error("{}", msg, e);
            //         parentResult.recordPartialError(msg,  e);
            //         return repositoryShadow;
        }
        ResourceType resource = ctx.getResource();

        PrismObject<ShadowType> resourceShadow = null;
        try {

            // Let's get all the identifiers from the Shadow <attributes> part
            Collection<? extends ResourceAttribute<?>> identifiers = ShadowUtil.getIdentifiers(repositoryShadow);

            if (identifiers == null || identifiers.isEmpty()) {
                //check if the account is not only partially created (exist only in repo so far)
                if (repositoryShadow.asObjectable().getFailedOperationType() != null) {
                    throw new GenericConnectorException(
                            "Unable to get object from the resource. Probably it has not been created yet because of previous unavailability of the resource.");
                }
                // No identifiers found
                SchemaException ex = new SchemaException("No identifiers found in the repository shadow "
                        + repositoryShadow + " with respect to " + resource);
                parentResult.recordFatalError("No identifiers found in the repository shadow " + repositoryShadow,
                        ex);
                throw ex;
            }

            // We need to record the fetch down here. Now it is certain that we are going to fetch from resource
            // (we do not have raw/noFetch option)
            InternalMonitor.recordShadowFetchOperation();

            resourceShadow = resouceObjectConverter.getResourceObject(ctx, identifiers, parentResult);

            resourceManager.modifyResourceAvailabilityStatus(resource.asPrismObject(), AvailabilityStatusType.UP,
                    parentResult);
            //try to apply changes to the account only if the resource if UP
            if (isCompensate(rootOptions) && repositoryShadow.asObjectable().getObjectChange() != null
                    && repositoryShadow.asObjectable().getFailedOperationType() != null
                    && resource.getOperationalState() != null
                    && resource.getOperationalState().getLastAvailabilityStatus() == AvailabilityStatusType.UP) {
                throw new GenericConnectorException(
                        "Found changes that have been not applied to the resource object yet. Trying to apply them now.");
            }

            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Shadow from repository:\n{}", repositoryShadow.debugDump());
                LOGGER.trace("Resource object fetched from resource:\n{}", resourceShadow.debugDump());
            }

            forceRenameIfNeeded(ctx, resourceShadow.asObjectable(), repositoryShadow.asObjectable(), parentResult);
            // Complete the shadow by adding attributes from the resource object
            PrismObject<ShadowType> resultShadow = completeShadow(ctx, resourceShadow, repositoryShadow,
                    parentResult);

            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Shadow when assembled:\n{}", resultShadow.debugDump());
            }

            parentResult.recordSuccess();
            return resultShadow;

        } catch (Exception ex) {
            try {

                resourceShadow = handleError(ctx, ex, repositoryShadow, FailedOperation.GET, null,
                        isCompensate(rootOptions), parentResult);
                if (parentResult.getStatus() == OperationResultStatus.FATAL_ERROR) {
                    // We are going to return an object. Therefore this cannot be fatal error, as at least some information
                    // is returned
                    parentResult.setStatus(OperationResultStatus.PARTIAL_ERROR);
                }
                return resourceShadow;

            } catch (GenericFrameworkException e) {
                throw new SystemException(e);
            } catch (ObjectAlreadyExistsException e) {
                throw new SystemException(e);
            }
        }

    }

    private boolean isCompensate(GetOperationOptions rootOptions) {
        return GetOperationOptions.isDoNotDiscovery(rootOptions) ? false : true;
    }

    public abstract String afterAddOnResource(ProvisioningContext ctx, PrismObject<ShadowType> shadow,
            OperationResult parentResult) throws SchemaException, ObjectAlreadyExistsException,
            ObjectNotFoundException, ConfigurationException, CommunicationException;

    public String addShadow(PrismObject<ShadowType> shadow, OperationProvisioningScriptsType scripts,
            ResourceType resource, ProvisioningOperationOptions options, Task task, OperationResult parentResult)
            throws CommunicationException, GenericFrameworkException, ObjectAlreadyExistsException, SchemaException,
            ObjectNotFoundException, ConfigurationException, SecurityViolationException {
        Validate.notNull(shadow, "Object to add must not be null.");

        InternalMonitor.recordShadowChangeOperation();

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Start adding shadow object:\n{}", shadow.debugDump());
        }

        ProvisioningContext ctx = ctxFactory.create(shadow, task, parentResult);
        try {
            ctx.assertDefinition();
        } catch (SchemaException e) {
            handleError(ctx, e, shadow, FailedOperation.ADD, null, true, parentResult);
            return null;
        }

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Definition:\n{}", ctx.getObjectClassDefinition().debugDump());
        }

        PrismContainer<?> attributesContainer = shadow.findContainer(ShadowType.F_ATTRIBUTES);
        if (attributesContainer == null || attributesContainer.isEmpty()) {
            SchemaException e = new SchemaException("Attempt to add shadow without any attributes: " + shadow);
            parentResult.recordFatalError(e);
            handleError(ctx, e, shadow, FailedOperation.ADD, null, true, parentResult);
            return null;
        }

        try {
            preprocessEntitlements(ctx, shadow, parentResult);

            applyAttributesDefinition(ctx, shadow);
            shadowManager.setKindIfNecessary(shadow.asObjectable(), ctx.getObjectClassDefinition());
            accessChecker.checkAdd(ctx, shadow, parentResult);
            shadow = resouceObjectConverter.addResourceObject(ctx, shadow, scripts, parentResult);

        } catch (Exception ex) {
            shadow = handleError(ctx, ex, shadow, FailedOperation.ADD, null,
                    ProvisioningOperationOptions.isCompletePostponed(options), parentResult);
            return shadow.getOid();
        }

        // This is where the repo shadow is created (if needed) 
        String oid = afterAddOnResource(ctx, shadow, parentResult);
        shadow.setOid(oid);

        ObjectDelta<ShadowType> delta = ObjectDelta.createAddDelta(shadow);
        ResourceOperationDescription operationDescription = createSuccessOperationDescription(ctx, shadow, delta,
                parentResult);
        operationListener.notifySuccess(operationDescription, task, parentResult);
        return oid;
    }

    private ResourceOperationDescription createSuccessOperationDescription(ProvisioningContext ctx,
            PrismObject<ShadowType> shadowType, ObjectDelta delta, OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException {
        ResourceOperationDescription operationDescription = new ResourceOperationDescription();
        operationDescription.setCurrentShadow(shadowType);
        operationDescription.setResource(ctx.getResource().asPrismObject());
        if (ctx.getTask() != null) {
            operationDescription.setSourceChannel(ctx.getTask().getChannel());
        }
        operationDescription.setObjectDelta(delta);
        operationDescription.setResult(parentResult);
        return operationDescription;
    }

    public abstract void afterModifyOnResource(ProvisioningContext ctx, PrismObject<ShadowType> shadow,
            Collection<? extends ItemDelta> modifications, OperationResult parentResult)
            throws SchemaException, ObjectNotFoundException, ConfigurationException, CommunicationException;

    public abstract Collection<? extends ItemDelta> beforeModifyOnResource(PrismObject<ShadowType> shadow,
            ProvisioningOperationOptions options, Collection<? extends ItemDelta> modifications)
            throws SchemaException;

    public String modifyShadow(PrismObject<ShadowType> shadow, String oid,
            Collection<? extends ItemDelta> modifications, OperationProvisioningScriptsType scripts,
            ProvisioningOperationOptions options, Task task, OperationResult parentResult)
            throws CommunicationException, GenericFrameworkException, ObjectNotFoundException, SchemaException,
            ConfigurationException, SecurityViolationException {

        Validate.notNull(shadow, "Object to modify must not be null.");
        Validate.notNull(oid, "OID must not be null.");
        Validate.notNull(modifications, "Object modification must not be null.");

        InternalMonitor.recordShadowChangeOperation();

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Modifying resource shadow:\n{}", shadow.debugDump());
        }

        Collection<QName> additionalAuxiliaryObjectClassQNames = new ArrayList<>();
        ItemPath auxPath = new ItemPath(ShadowType.F_AUXILIARY_OBJECT_CLASS);
        for (ItemDelta modification : modifications) {
            if (auxPath.equals(modification.getPath())) {
                PropertyDelta<QName> auxDelta = (PropertyDelta<QName>) modification;
                for (PrismPropertyValue<QName> pval : auxDelta.getValues(QName.class)) {
                    additionalAuxiliaryObjectClassQNames.add(pval.getValue());
                }
            }
        }

        ProvisioningContext ctx = ctxFactory.create(shadow, additionalAuxiliaryObjectClassQNames, task,
                parentResult);
        Collection<PropertyModificationOperation> sideEffectChanges;
        try {
            ctx.assertDefinition();

            applyAttributesDefinition(ctx, shadow);

            accessChecker.checkModify(ctx.getResource(), shadow, modifications, ctx.getObjectClassDefinition(),
                    parentResult);

            preprocessEntitlements(ctx, modifications, "delta for shadow " + oid, parentResult);

            modifications = beforeModifyOnResource(shadow, options, modifications);

            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Applying change: {}", DebugUtil.debugDump(modifications));
            }

            sideEffectChanges = resouceObjectConverter.modifyResourceObject(ctx, shadow, scripts, modifications,
                    parentResult);
        } catch (Exception ex) {
            LOGGER.debug("Provisioning exception: {}:{}, attempting to handle it",
                    new Object[] { ex.getClass(), ex.getMessage(), ex });
            try {
                shadow = handleError(ctx, ex, shadow, FailedOperation.MODIFY, modifications,
                        ProvisioningOperationOptions.isCompletePostponed(options), parentResult);
                parentResult.computeStatus();
            } catch (ObjectAlreadyExistsException e) {
                parentResult.recordFatalError(
                        "While compensating communication problem for modify operation got: " + ex.getMessage(),
                        ex);
                throw new SystemException(e);
            }

            return shadow.getOid();
        }

        Collection<PropertyDelta<PrismPropertyValue>> sideEffectDeltas = convertToPropertyDelta(sideEffectChanges);
        if (!sideEffectDeltas.isEmpty()) {
            PrismUtil.setDeltaOldValue(shadow, sideEffectDeltas);
            ItemDelta.addAll(modifications, (Collection) sideEffectDeltas);
        }

        afterModifyOnResource(ctx, shadow, modifications, parentResult);

        ObjectDelta<ShadowType> delta = ObjectDelta.createModifyDelta(shadow.getOid(), modifications,
                shadow.getCompileTimeClass(), prismContext);
        ResourceOperationDescription operationDescription = createSuccessOperationDescription(ctx, shadow, delta,
                parentResult);
        operationListener.notifySuccess(operationDescription, task, parentResult);
        parentResult.recordSuccess();
        return oid;
    }

    private Collection<PropertyDelta<PrismPropertyValue>> convertToPropertyDelta(
            Collection<PropertyModificationOperation> sideEffectChanges) {
        Collection<PropertyDelta<PrismPropertyValue>> sideEffectDelta = new ArrayList<PropertyDelta<PrismPropertyValue>>();
        if (sideEffectChanges != null) {
            for (PropertyModificationOperation mod : sideEffectChanges) {
                sideEffectDelta.add(mod.getPropertyDelta());
            }
        }

        return sideEffectDelta;
    }

    public void deleteShadow(PrismObject<ShadowType> shadow, ProvisioningOperationOptions options,
            OperationProvisioningScriptsType scripts, Task task, OperationResult parentResult)
            throws CommunicationException, GenericFrameworkException, ObjectNotFoundException, SchemaException,
            ConfigurationException, SecurityViolationException {

        Validate.notNull(shadow, "Object to delete must not be null.");
        Validate.notNull(parentResult, "Operation result must not be null.");

        InternalMonitor.recordShadowChangeOperation();

        ProvisioningContext ctx = ctxFactory.create(shadow, task, parentResult);
        try {
            ctx.assertDefinition();
        } catch (ObjectNotFoundException ex) {
            // if the force option is set, delete shadow from the repo
            // although the resource does not exists..
            if (ProvisioningOperationOptions.isForce(options)) {
                parentResult.muteLastSubresultError();
                getRepositoryService().deleteObject(ShadowType.class, shadow.getOid(), parentResult);
                parentResult.recordHandledError(
                        "Resource defined in shadow does not exists. Shadow was deleted from the repository.");
                return;
            } else {
                throw ex;
            }
        }

        applyAttributesDefinition(ctx, shadow);

        LOGGER.trace("Deleting object {} from the resource {}.", shadow, ctx.getResource());

        if (shadow.asObjectable().getFailedOperationType() == null
                || (shadow.asObjectable().getFailedOperationType() != null
                        && FailedOperationTypeType.ADD != shadow.asObjectable().getFailedOperationType())) {
            try {
                resouceObjectConverter.deleteResourceObject(ctx, shadow, scripts, parentResult);
            } catch (Exception ex) {
                try {
                    handleError(ctx, ex, shadow, FailedOperation.DELETE, null,
                            ProvisioningOperationOptions.isCompletePostponed(options), parentResult);
                } catch (ObjectAlreadyExistsException e) {
                    e.printStackTrace();
                }
                return;
            }
        }

        LOGGER.trace("Detele object with oid {} form repository.", shadow.getOid());
        try {
            getRepositoryService().deleteObject(ShadowType.class, shadow.getOid(), parentResult);
            ObjectDelta<ShadowType> delta = ObjectDelta.createDeleteDelta(shadow.getCompileTimeClass(),
                    shadow.getOid(), prismContext);
            ResourceOperationDescription operationDescription = createSuccessOperationDescription(ctx, shadow,
                    delta, parentResult);
            operationListener.notifySuccess(operationDescription, task, parentResult);
        } catch (ObjectNotFoundException ex) {
            parentResult.recordFatalError("Can't delete object " + shadow + ". Reason: " + ex.getMessage(), ex);
            throw new ObjectNotFoundException("An error occured while deleting resource object " + shadow
                    + "whith identifiers " + shadow + ": " + ex.getMessage(), ex);
        }
        LOGGER.trace("Object deleted from repository successfully.");
        parentResult.recordSuccess();
        resourceManager.modifyResourceAvailabilityStatus(ctx.getResource().asPrismObject(),
                AvailabilityStatusType.UP, parentResult);
    }

    public void applyDefinition(ObjectDelta<ShadowType> delta, ShadowType shadowTypeWhenNoOid,
            OperationResult parentResult)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
        PrismObject<ShadowType> shadow = null;
        ResourceShadowDiscriminator discriminator = null;
        if (delta.isAdd()) {
            shadow = delta.getObjectToAdd();
        } else if (delta.isModify()) {
            if (delta instanceof ShadowDiscriminatorObjectDelta) {
                // This one does not have OID, it has to be specially processed
                discriminator = ((ShadowDiscriminatorObjectDelta) delta).getDiscriminator();
            } else {
                String shadowOid = delta.getOid();
                if (shadowOid == null) {
                    if (shadowTypeWhenNoOid == null) {
                        throw new IllegalArgumentException("No OID in object delta " + delta
                                + " and no externally-supplied shadow is present as well.");
                    }
                    shadow = shadowTypeWhenNoOid.asPrismObject();
                } else {
                    shadow = repositoryService.getObject(delta.getObjectTypeClass(), shadowOid, null, parentResult); // TODO consider fetching only when really necessary
                }
            }
        } else {
            // Delete delta, nothing to do at all
            return;
        }
        ProvisioningContext ctx;
        if (shadow == null) {
            ctx = ctxFactory.create(discriminator, null, parentResult);
            ctx.assertDefinition();
        } else {
            ctx = ctxFactory.create(shadow, null, parentResult);
            ctx.assertDefinition();
        }
        applyAttributesDefinition(ctx, delta);
    }

    public void applyDefinition(PrismObject<ShadowType> shadow, OperationResult parentResult)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
        ProvisioningContext ctx = ctxFactory.create(shadow, null, parentResult);
        ctx.assertDefinition();
        applyAttributesDefinition(ctx, shadow);
    }

    public void applyDefinition(final ObjectQuery query, OperationResult result)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
        ResourceShadowDiscriminator coordinates = ObjectQueryUtil.getCoordinates(query.getFilter());
        ProvisioningContext ctx = ctxFactory.create(coordinates, null, result);
        ctx.assertDefinition();
        applyDefinition(ctx, query);
    }

    private void applyDefinition(final ProvisioningContext ctx, final ObjectQuery query)
            throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
        if (query == null) {
            return;
        }
        ObjectFilter filter = query.getFilter();
        if (filter == null) {
            return;
        }
        final RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition();
        final ItemPath attributesPath = new ItemPath(ShadowType.F_ATTRIBUTES);
        com.evolveum.midpoint.prism.query.Visitor visitor = new com.evolveum.midpoint.prism.query.Visitor() {
            @Override
            public void visit(ObjectFilter filter) {
                if (filter instanceof ValueFilter) {
                    ValueFilter<?> valueFilter = (ValueFilter<?>) filter;
                    ItemDefinition definition = valueFilter.getDefinition();
                    if (definition == null) {
                        ItemPath itemPath = valueFilter.getFullPath();
                        if (attributesPath.equivalent(valueFilter.getParentPath())) {
                            QName attributeName = valueFilter.getElementName();
                            ResourceAttributeDefinition attributeDefinition = objectClassDefinition
                                    .findAttributeDefinition(attributeName);
                            if (attributeDefinition == null) {
                                throw new TunnelException(new SchemaException(
                                        "No definition for attribute " + attributeName + " in query " + query));
                            }
                            valueFilter.setDefinition(attributeDefinition);
                        }
                    }
                }
            }
        };
        try {
            filter.accept(visitor);
        } catch (TunnelException te) {
            SchemaException e = (SchemaException) te.getCause();
            throw e;
        }
    }

    protected ResourceType getResource(ResourceShadowDiscriminator coords, OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException {
        String resourceOid = coords.getResourceOid();
        if (resourceOid == null) {
            throw new IllegalArgumentException("No resource OID in " + coords);
        }
        return resourceManager.getResource(resourceOid, null, parentResult).asObjectable();
    }

    @SuppressWarnings("rawtypes")
    protected PrismObject<ShadowType> handleError(ProvisioningContext ctx, Exception ex,
            PrismObject<ShadowType> shadow, FailedOperation op, Collection<? extends ItemDelta> modifications,
            boolean compensate, OperationResult parentResult)
            throws SchemaException, GenericFrameworkException, CommunicationException, ObjectNotFoundException,
            ObjectAlreadyExistsException, ConfigurationException, SecurityViolationException {

        // do not set result in the shadow in case of get operation, it will
        // resilted to misleading information
        // by get operation we do not modify the result in the shadow, so only
        // fetch result in this case needs to be set
        if (FailedOperation.GET != op) {
            shadow = extendShadow(shadow, parentResult, ctx.getResource(), modifications);
        } else {
            shadow.asObjectable().setResource(ctx.getResource());
        }
        ErrorHandler handler = errorHandlerFactory.createErrorHandler(ex);

        if (handler == null) {
            parentResult.recordFatalError("Error without a handler. Reason: " + ex.getMessage(), ex);
            throw new SystemException(ex.getMessage(), ex);
        }

        LOGGER.debug("Handling provisioning exception {}:{}", new Object[] { ex.getClass(), ex.getMessage() });
        LOGGER.trace("Handling provisioning exception {}:{}", new Object[] { ex.getClass(), ex.getMessage(), ex });

        return handler.handleError(shadow.asObjectable(), op, ex, compensate, ctx.getTask(), parentResult)
                .asPrismObject();

    }

    private PrismObject<ShadowType> extendShadow(PrismObject<ShadowType> shadow, OperationResult shadowResult,
            ResourceType resource, Collection<? extends ItemDelta> modifications) throws SchemaException {

        ShadowType shadowType = shadow.asObjectable();
        shadowType.setResult(shadowResult.createOperationResultType());
        shadowType.setResource(resource);

        if (modifications != null) {
            ObjectDelta<? extends ObjectType> objectDelta = ObjectDelta.createModifyDelta(shadow.getOid(),
                    modifications, shadowType.getClass(), prismContext);
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Storing delta to shadow:\n{}", objectDelta.debugDump());
            }
            ObjectDeltaType objectDeltaType = DeltaConvertor.toObjectDeltaType(objectDelta);

            shadowType.setObjectChange(objectDeltaType);
        }
        return shadow;
    }

    ////////////////////////////////////////////////////////////////////////////
    // SEARCH
    ////////////////////////////////////////////////////////////////////////////

    public SearchResultMetadata searchObjectsIterative(ObjectQuery query,
            Collection<SelectorOptions<GetOperationOptions>> options, final ShadowHandler<ShadowType> handler,
            final boolean readFromRepository, Task task, final OperationResult parentResult) throws SchemaException,
            ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {

        ResourceShadowDiscriminator coordinates = ObjectQueryUtil.getCoordinates(query.getFilter());
        final ProvisioningContext ctx = ctxFactory.create(coordinates, task, parentResult);
        ctx.assertDefinition();
        applyDefinition(ctx, query);

        GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options);
        if (GetOperationOptions.isNoFetch(rootOptions)) {
            return searchObjectsIterativeRepository(ctx, query, options, handler, parentResult);
        }

        // We need to record the fetch down here. Now it is certain that we are going to fetch from resource
        // (we do not have raw/noFetch option)
        InternalMonitor.recordShadowFetchOperation();

        ObjectQuery attributeQuery = createAttributeQuery(query);

        ResultHandler<ShadowType> resultHandler = new ResultHandler<ShadowType>() {

            @Override
            public boolean handle(PrismObject<ShadowType> resourceShadow) {
                LOGGER.trace("Found resource object {}", SchemaDebugUtil.prettyPrint(resourceShadow));
                PrismObject<ShadowType> resultShadow;
                try {
                    ProvisioningContext shadowCtx = reapplyDefinitions(ctx, resourceShadow);
                    // Try to find shadow that corresponds to the resource object
                    if (readFromRepository) {
                        PrismObject<ShadowType> repoShadow = lookupOrCreateShadowInRepository(shadowCtx,
                                resourceShadow, parentResult);

                        applyAttributesDefinition(shadowCtx, repoShadow);

                        forceRenameIfNeeded(shadowCtx, resourceShadow.asObjectable(), repoShadow.asObjectable(),
                                parentResult);

                        resultShadow = completeShadow(shadowCtx, resourceShadow, repoShadow, parentResult);

                    } else {
                        resultShadow = resourceShadow;
                    }

                    // TODO: better error handling
                } catch (SchemaException e) {
                    parentResult.recordFatalError("Schema error: " + e.getMessage(), e);
                    LOGGER.error("Schema error: {}", e.getMessage(), e);
                    return false;
                } catch (ConfigurationException e) {
                    parentResult.recordFatalError("Configuration error: " + e.getMessage(), e);
                    LOGGER.error("Configuration error: {}", e.getMessage(), e);
                    return false;
                } catch (ObjectNotFoundException | ObjectAlreadyExistsException | CommunicationException
                        | SecurityViolationException | GenericConnectorException e) {
                    parentResult.recordFatalError(e.getMessage(), e);
                    LOGGER.error("{}", e.getMessage(), e);
                    return false;
                }

                return handler.handle(resultShadow.asObjectable());
            }

        };

        boolean fetchAssociations = SelectorOptions.hasToLoadPath(ShadowType.F_ASSOCIATION, options);

        return resouceObjectConverter.searchResourceObjects(ctx, resultHandler, attributeQuery, fetchAssociations,
                parentResult);

    }

    ObjectQuery createAttributeQuery(ObjectQuery query) throws SchemaException {
        ObjectFilter filter = null;
        if (query != null) {
            filter = query.getFilter();
        }

        ObjectQuery attributeQuery = null;

        if (filter instanceof AndFilter) {
            List<? extends ObjectFilter> conditions = ((AndFilter) filter).getConditions();
            List<ObjectFilter> attributeFilter = createAttributeQueryInternal(conditions);
            if (attributeFilter.size() > 1) {
                attributeQuery = ObjectQuery.createObjectQuery(AndFilter.createAnd(attributeFilter));
            } else if (attributeFilter.size() < 1) {
                LOGGER.trace("No attribute filter defined in the query.");
            } else if (attributeFilter.size() == 1) {
                attributeQuery = ObjectQuery.createObjectQuery(attributeFilter.iterator().next());
            }

        }

        if (query != null && query.getPaging() != null) {
            if (attributeQuery == null) {
                attributeQuery = new ObjectQuery();
            }
            attributeQuery.setPaging(query.getPaging());
        }
        if (query != null && query.isAllowPartialResults()) {
            if (attributeQuery == null) {
                attributeQuery = new ObjectQuery();
            }
            attributeQuery.setAllowPartialResults(true);
        }

        if (InternalsConfig.consistencyChecks && attributeQuery != null && attributeQuery.getFilter() != null) {
            attributeQuery.getFilter().checkConsistence();
        }
        return attributeQuery;
    }

    private List<ObjectFilter> createAttributeQueryInternal(List<? extends ObjectFilter> conditions)
            throws SchemaException {
        List<ObjectFilter> attributeFilter = new ArrayList<>();
        ItemPath objectClassPath = new ItemPath(ShadowType.F_OBJECT_CLASS);
        ItemPath resourceRefPath = new ItemPath(ShadowType.F_RESOURCE_REF);
        for (ObjectFilter f : conditions) {
            if (f instanceof EqualFilter) {
                if (objectClassPath.equivalent(((EqualFilter) f).getFullPath())) {
                    continue;
                }
                if (resourceRefPath.equivalent(((EqualFilter) f).getFullPath())) {
                    continue;
                }

                attributeFilter.add(f);
            } else if (f instanceof NaryLogicalFilter) {
                List<ObjectFilter> subFilters = createAttributeQueryInternal(
                        ((NaryLogicalFilter) f).getConditions());
                if (subFilters.size() > 1) {
                    if (f instanceof OrFilter) {
                        attributeFilter.add(OrFilter.createOr(subFilters));
                    } else if (f instanceof AndFilter) {
                        attributeFilter.add(AndFilter.createAnd(subFilters));
                    } else {
                        throw new IllegalArgumentException("Could not translate query filter. Unknown type: " + f);
                    }
                } else if (subFilters.size() < 1) {
                    continue;
                } else if (subFilters.size() == 1) {
                    attributeFilter.add(subFilters.iterator().next());
                }

            } else if (f instanceof SubstringFilter) {
                attributeFilter.add(f);
            }

        }

        return attributeFilter;
    }

    private SearchResultMetadata searchObjectsIterativeRepository(final ProvisioningContext ctx, ObjectQuery query,
            Collection<SelectorOptions<GetOperationOptions>> options, final ShadowHandler<ShadowType> shadowHandler,
            OperationResult parentResult)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {

        com.evolveum.midpoint.schema.ResultHandler<ShadowType> repoHandler = new com.evolveum.midpoint.schema.ResultHandler<ShadowType>() {
            @Override
            public boolean handle(PrismObject<ShadowType> object, OperationResult parentResult) {
                try {
                    applyAttributesDefinition(ctx, object);
                    resouceObjectConverter.setProtectedFlag(ctx, object); // fixing MID-1640; hoping that the protected object filter uses only identifiers (that are stored in repo)
                    boolean cont = shadowHandler.handle(object.asObjectable());
                    parentResult.recordSuccess();
                    return cont;
                } catch (RuntimeException e) {
                    parentResult.recordFatalError(e);
                    throw e;
                } catch (SchemaException | ConfigurationException | ObjectNotFoundException
                        | CommunicationException e) {
                    parentResult.recordFatalError(e);
                    throw new SystemException(e);
                }
            }
        };

        return shadowManager.searchObjectsIterativeRepository(ctx, query, options, repoHandler, parentResult);
    }

    private PrismObject<ShadowType> lookupOrCreateShadowInRepository(ProvisioningContext ctx,
            PrismObject<ShadowType> resourceShadow, OperationResult parentResult)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException,
            SecurityViolationException, GenericConnectorException {
        PrismObject<ShadowType> repoShadow = shadowManager.lookupShadowInRepository(ctx, resourceShadow,
                parentResult);

        if (repoShadow == null) {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace(
                        "Shadow object (in repo) corresponding to the resource object (on the resource) was not found. The repo shadow will be created. The resource object:\n{}",
                        SchemaDebugUtil.prettyPrint(resourceShadow));
            }

            repoShadow = createShadowInRepository(ctx, resourceShadow, parentResult);
        } else {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Found shadow object in the repository {}", SchemaDebugUtil.prettyPrint(repoShadow));
            }
        }

        return repoShadow;
    }

    private PrismObject<ShadowType> createShadowInRepository(ProvisioningContext ctx,
            PrismObject<ShadowType> resourceShadow, OperationResult parentResult)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException,
            SecurityViolationException, GenericConnectorException {

        PrismObject<ShadowType> repoShadow;
        PrismObject<ShadowType> conflictingShadow = shadowManager.lookupShadowBySecondaryIdentifiers(ctx,
                resourceShadow, parentResult);
        if (conflictingShadow != null) {
            applyAttributesDefinition(ctx, conflictingShadow);
            conflictingShadow = completeShadow(ctx, resourceShadow, conflictingShadow, parentResult);
            Task task = taskManager.createTaskInstance();
            ResourceOperationDescription failureDescription = shadowManager
                    .createResourceFailureDescription(conflictingShadow, ctx.getResource(), parentResult);
            changeNotificationDispatcher.notifyFailure(failureDescription, task, parentResult);
            shadowManager.deleteConflictedShadowFromRepo(conflictingShadow, parentResult);
        }
        // TODO: make sure that the resource object has appropriate definition (use objectClass and schema)
        // The resource object obviously exists on the resource, but appropriate shadow does not exist in the
        // repository we need to create the shadow to align repo state to the reality (resource)

        try {

            repoShadow = shadowManager.addRepositoryShadow(ctx, resourceShadow, parentResult);

        } catch (ObjectAlreadyExistsException e) {
            // This should not happen. We haven't supplied an OID so is should not conflict
            LOGGER.error("Unexpected repository behavior: Object already exists: {}", e.getMessage(), e);
            throw new SystemException("Unexpected repository behavior: Object already exists: " + e.getMessage(),
                    e);
        }

        return repoShadow;
    }

    public Integer countObjects(ObjectQuery query, Task task, final OperationResult result) throws SchemaException,
            ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {

        ResourceShadowDiscriminator coordinates = ObjectQueryUtil.getCoordinates(query.getFilter());
        final ProvisioningContext ctx = ctxFactory.create(coordinates, null, result);
        ctx.assertDefinition();
        applyDefinition(ctx, query);

        RefinedObjectClassDefinition objectClassDef = ctx.getObjectClassDefinition();
        ResourceType resourceType = ctx.getResource();
        CountObjectsCapabilityType countObjectsCapabilityType = objectClassDef
                .getEffectiveCapability(CountObjectsCapabilityType.class);
        if (countObjectsCapabilityType == null) {
            // Unable to count. Return null which means "I do not know"
            result.recordNotApplicableIfUnknown();
            return null;
        } else {
            CountObjectsSimulateType simulate = countObjectsCapabilityType.getSimulate();
            if (simulate == null) {
                // We have native capability

                ConnectorInstance connector = ctx.getConnector(result);
                try {
                    ObjectQuery attributeQuery = createAttributeQuery(query);
                    int count;
                    try {
                        count = connector.count(objectClassDef.getObjectClassDefinition(), attributeQuery,
                                objectClassDef.getPagedSearches(), ctx, result);
                    } catch (CommunicationException | GenericFrameworkException | SchemaException
                            | UnsupportedOperationException e) {
                        result.recordFatalError(e);
                        throw e;
                    }
                    result.computeStatus();
                    result.cleanupResult();
                    return count;
                } catch (GenericFrameworkException | UnsupportedOperationException e) {
                    SystemException ex = new SystemException(
                            "Couldn't count objects on resource " + resourceType + ": " + e.getMessage(), e);
                    result.recordFatalError(ex);
                    throw ex;
                }

            } else if (simulate == CountObjectsSimulateType.PAGED_SEARCH_ESTIMATE) {

                if (!objectClassDef.isPagedSearchEnabled()) {
                    throw new ConfigurationException(
                            "Configured count object capability to be simulated using a paged search but paged search capability is not present");
                }

                final Holder<Integer> countHolder = new Holder<Integer>(0);

                final ShadowHandler<ShadowType> handler = new ShadowHandler<ShadowType>() {
                    @Override
                    public boolean handle(ShadowType object) {
                        int count = countHolder.getValue();
                        count++;
                        countHolder.setValue(count);
                        return true;
                    }
                };

                query = query.clone();
                ObjectPaging paging = ObjectPaging.createEmptyPaging();
                paging.setMaxSize(1);
                query.setPaging(paging);
                Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(
                        new ItemPath(ShadowType.F_ASSOCIATION),
                        GetOperationOptions.createRetrieve(RetrieveOption.EXCLUDE));
                SearchResultMetadata resultMetadata;
                try {
                    resultMetadata = searchObjectsIterative(query, options, handler, false, task, result);
                } catch (SchemaException | ObjectNotFoundException | ConfigurationException
                        | SecurityViolationException e) {
                    result.recordFatalError(e);
                    throw e;
                }
                result.computeStatus();
                result.cleanupResult();

                return resultMetadata.getApproxNumberOfAllResults();

            } else if (simulate == CountObjectsSimulateType.SEQUENTIAL_SEARCH) {

                // traditional way of counting objects (i.e. counting them one by one)
                final Holder<Integer> countHolder = new Holder<Integer>(0);

                final ShadowHandler<ShadowType> handler = new ShadowHandler<ShadowType>() {
                    @Override
                    public boolean handle(ShadowType object) {
                        int count = countHolder.getValue();
                        count++;
                        countHolder.setValue(count);
                        return true;
                    }
                };

                Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(
                        new ItemPath(ShadowType.F_ASSOCIATION),
                        GetOperationOptions.createRetrieve(RetrieveOption.EXCLUDE));
                searchObjectsIterative(query, options, handler, false, task, result);
                // TODO: better error handling
                result.computeStatus();
                result.cleanupResult();
                return countHolder.getValue();

            } else {
                throw new IllegalArgumentException("Unknown count capability simulate type " + simulate);
            }
        }

    }

    ///////////////////////////////////////////////////////////////////////////
    // TODO: maybe split this to a separate class
    ///////////////////////////////////////////////////////////////////////////

    public int synchronize(ResourceShadowDiscriminator shadowCoordinates, PrismProperty<?> lastToken, Task task,
            OperationResult parentResult)
            throws ObjectNotFoundException, CommunicationException, GenericFrameworkException, SchemaException,
            ConfigurationException, SecurityViolationException, ObjectAlreadyExistsException {

        InternalMonitor.recordShadowOtherOperation();

        final ProvisioningContext ctx = ctxFactory.create(shadowCoordinates, task, parentResult);

        List<Change<ShadowType>> changes = null;
        try {

            changes = resouceObjectConverter.fetchChanges(ctx, lastToken, parentResult);

            LOGGER.trace("Found {} change(s). Start processing it (them).", changes.size());

            int processedChanges = 0;

            for (Change<ShadowType> change : changes) {

                if (change.isTokenOnly()) {
                    LOGGER.trace("Found token-only change: {}", change);
                    task.setExtensionProperty(change.getToken());
                    continue;
                }

                ObjectClassComplexTypeDefinition changeObjectClassDefinition = change.getObjectClassDefinition();

                ProvisioningContext shadowCtx;
                PrismObject<ShadowType> oldShadow = null;
                if (changeObjectClassDefinition == null) {
                    if (change.getObjectDelta() != null && change.getObjectDelta().isDelete()) {
                        oldShadow = change.getOldShadow();
                        if (oldShadow == null) {
                            oldShadow = shadowManager.findOrAddShadowFromChangeGlobalContext(ctx, change,
                                    parentResult);
                        }
                        if (oldShadow == null) {
                            LOGGER.debug(
                                    "No old shadow for delete synchronization event {}, we probably did not know about that object anyway, so well be ignoring this event",
                                    change);
                            continue;
                        }
                        shadowCtx = ctx.spawn(oldShadow);
                    } else {
                        throw new SchemaException("No object class definition in change " + change);
                    }
                } else {
                    shadowCtx = ctx.spawn(changeObjectClassDefinition.getTypeName());
                }

                processChange(shadowCtx, change, oldShadow, parentResult);

                // this is the case,when we want to skip processing of change,
                // because the shadow was not created or found to the resource
                // object
                // it may be caused with the fact, that the object which was
                // created in the resource was deleted before the sync run
                // such a change should be skipped to process consistent changes
                if (change.getOldShadow() == null) {
                    PrismProperty<?> newToken = change.getToken();
                    task.setExtensionProperty(newToken);
                    processedChanges++;
                    task.setProgress(task.getProgress() + 1); // because processedChanges are reflected into task only at task run finish
                    LOGGER.debug(
                            "Skipping processing change. Can't find appropriate shadow (e.g. the object was deleted on the resource meantime).");
                    continue;
                }
                boolean isSuccess = processSynchronization(shadowCtx, change, parentResult);

                if (isSuccess) {
                    //               // get updated token from change,
                    //               // create property modification from new token
                    //               // and replace old token with the new one
                    PrismProperty<?> newToken = change.getToken();
                    task.setExtensionProperty(newToken);
                    processedChanges++;
                    task.setProgress(task.getProgress() + 1); // because processedChanges are reflected into task only at task run finish
                }
            }

            // also if no changes was detected, update token
            if (changes.isEmpty() && lastToken != null) {
                LOGGER.trace("No changes to synchronize on " + ctx.getResource());
                task.setExtensionProperty(lastToken);
            }
            task.savePendingModifications(parentResult);
            return processedChanges;

        } catch (SchemaException ex) {
            parentResult.recordFatalError("Schema error: " + ex.getMessage(), ex);
            throw ex;
        } catch (CommunicationException ex) {
            parentResult.recordFatalError("Communication error: " + ex.getMessage(), ex);
            throw ex;
        } catch (GenericFrameworkException ex) {
            parentResult.recordFatalError("Generic error: " + ex.getMessage(), ex);
            throw ex;
        } catch (ConfigurationException ex) {
            parentResult.recordFatalError("Configuration error: " + ex.getMessage(), ex);
            throw ex;
        } catch (ObjectNotFoundException ex) {
            parentResult.recordFatalError("Object not found error: " + ex.getMessage(), ex);
            throw ex;
        } catch (ObjectAlreadyExistsException ex) {
            parentResult.recordFatalError("Already exists error: " + ex.getMessage(), ex);
            throw ex;
        }
    }

    @SuppressWarnings("rawtypes")
    boolean processSynchronization(ProvisioningContext ctx, Change<ShadowType> change, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException,
            ConfigurationException {
        ResourceObjectShadowChangeDescription shadowChangeDescription = createResourceShadowChangeDescription(
                change, ctx.getResource(), ctx.getChannel());

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("**PROVISIONING: Created resource object shadow change description {}",
                    SchemaDebugUtil.prettyPrint(shadowChangeDescription));
        }
        OperationResult notifyChangeResult = new OperationResult(ShadowCache.class.getName() + "notifyChange");
        notifyChangeResult.addParam("resourceObjectShadowChangeDescription", shadowChangeDescription);

        try {
            notifyResourceObjectChangeListeners(shadowChangeDescription, ctx.getTask(), notifyChangeResult);
            notifyChangeResult.recordSuccess();
        } catch (RuntimeException ex) {
            //            recordFatalError(LOGGER, notifyChangeResult, "Synchronization error: " + ex.getMessage(), ex);
            saveAccountResult(shadowChangeDescription, change, notifyChangeResult, result);
            throw new SystemException("Synchronization error: " + ex.getMessage(), ex);
        }

        notifyChangeResult.computeStatus("Error in notify change operation.");

        boolean successfull = false;
        if (notifyChangeResult.isSuccess() || notifyChangeResult.isHandledError()) {
            deleteShadowFromRepo(change, result);
            successfull = true;
            //            // get updated token from change,
            //            // create property modification from new token
            //            // and replace old token with the new one
            //            PrismProperty<?> newToken = change.getToken();
            //            task.setExtensionProperty(newToken);
            //            processedChanges++;

        } else {
            successfull = false;
            saveAccountResult(shadowChangeDescription, change, notifyChangeResult, result);
        }

        return successfull;
        //      }
        //      // also if no changes was detected, update token
        //      if (changes.isEmpty() && tokenProperty != null) {
        //         LOGGER.trace("No changes to synchronize on " + ObjectTypeUtil.toShortString(resourceType));
        //         task.setExtensionProperty(tokenProperty);
        //      }
        //      task.savePendingModifications(result);
        //      return processedChanges;
    }

    private void notifyResourceObjectChangeListeners(ResourceObjectShadowChangeDescription change, Task task,
            OperationResult parentResult) {
        changeNotificationDispatcher.notifyChange(change, task, parentResult);
    }

    @SuppressWarnings("unchecked")
    private ResourceObjectShadowChangeDescription createResourceShadowChangeDescription(Change<ShadowType> change,
            ResourceType resourceType, String channel) {
        ResourceObjectShadowChangeDescription shadowChangeDescription = new ResourceObjectShadowChangeDescription();
        shadowChangeDescription.setObjectDelta(change.getObjectDelta());
        shadowChangeDescription.setResource(resourceType.asPrismObject());
        shadowChangeDescription.setOldShadow(change.getOldShadow());
        shadowChangeDescription.setCurrentShadow(change.getCurrentShadow());
        if (null == channel) {
            shadowChangeDescription
                    .setSourceChannel(QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC));
        } else {
            shadowChangeDescription.setSourceChannel(channel);
        }
        return shadowChangeDescription;
    }

    @SuppressWarnings("rawtypes")
    private void saveAccountResult(ResourceObjectShadowChangeDescription shadowChangeDescription, Change change,
            OperationResult notifyChangeResult, OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException {

        Collection<? extends ItemDelta> shadowModification = createShadowResultModification(change,
                notifyChangeResult);
        String oid = getOidFromChange(change);
        // maybe better error handling is needed
        try {
            ConstraintsChecker.onShadowModifyOperation(shadowModification);
            repositoryService.modifyObject(ShadowType.class, oid, shadowModification, parentResult);
        } catch (SchemaException ex) {
            parentResult.recordPartialError("Couldn't modify object: schema violation: " + ex.getMessage(), ex);
            //         throw ex;
        } catch (ObjectNotFoundException ex) {
            parentResult.recordWarning("Couldn't modify object: object not found: " + ex.getMessage(), ex);
            //         throw ex;
        } catch (ObjectAlreadyExistsException ex) {
            parentResult.recordPartialError("Couldn't modify object: object already exists: " + ex.getMessage(),
                    ex);
            //         throw ex;
        }

    }

    private PrismObjectDefinition<ShadowType> getResourceObjectShadowDefinition() {
        //      if (resourceObjectShadowDefinition == null) {
        return prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class);
        //      }
        //      return resourceObjectShadowDefinition;
    }

    @SuppressWarnings("rawtypes")
    private Collection<? extends ItemDelta> createShadowResultModification(Change change,
            OperationResult shadowResult) {
        PrismObjectDefinition<ShadowType> shadowDefinition = getResourceObjectShadowDefinition();

        Collection<ItemDelta> modifications = new ArrayList<ItemDelta>();
        PropertyDelta resultDelta = PropertyDelta.createModificationReplaceProperty(ShadowType.F_RESULT,
                shadowDefinition, shadowResult.createOperationResultType());
        modifications.add(resultDelta);
        if (change.getObjectDelta() != null && change.getObjectDelta().getChangeType() == ChangeType.DELETE) {
            PropertyDelta failedOperationTypeDelta = PropertyDelta.createModificationReplaceProperty(
                    ShadowType.F_FAILED_OPERATION_TYPE, shadowDefinition, FailedOperationTypeType.DELETE);
            modifications.add(failedOperationTypeDelta);
        }
        return modifications;
    }

    private String getOidFromChange(Change change) {
        String shadowOid = null;
        if (change.getObjectDelta() != null && change.getObjectDelta().getOid() != null) {
            shadowOid = change.getObjectDelta().getOid();
        } else {
            if (change.getCurrentShadow().getOid() != null) {
                shadowOid = change.getCurrentShadow().getOid();
            } else {
                if (change.getOldShadow().getOid() != null) {
                    shadowOid = change.getOldShadow().getOid();
                } else {
                    throw new IllegalArgumentException("No oid value defined for the object to synchronize.");
                }
            }
        }
        return shadowOid;
    }

    private void deleteShadowFromRepo(Change change, OperationResult parentResult) throws ObjectNotFoundException {
        if (change.getObjectDelta() != null && change.getObjectDelta().getChangeType() == ChangeType.DELETE
                && change.getOldShadow() != null) {
            LOGGER.trace("Deleting detected shadow object form repository.");
            try {
                repositoryService.deleteObject(ShadowType.class, change.getOldShadow().getOid(), parentResult);
                LOGGER.debug("Shadow object successfully deleted form repository.");
            } catch (ObjectNotFoundException ex) {
                // What we want to delete is already deleted. Not a big problem.
                LOGGER.debug("Shadow object {} already deleted from repository ({})", change.getOldShadow(), ex);
                parentResult.recordHandledError(
                        "Shadow object " + change.getOldShadow() + " already deleted from repository", ex);
            }

        }
    }

    void processChange(ProvisioningContext ctx, Change<ShadowType> change, PrismObject<ShadowType> oldShadow,
            OperationResult parentResult)
            throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException,
            ObjectNotFoundException, GenericConnectorException, ObjectAlreadyExistsException {

        if (oldShadow == null) {
            oldShadow = shadowManager.findOrAddShadowFromChange(ctx, change, parentResult);
        }

        if (oldShadow != null) {
            applyAttributesDefinition(ctx, oldShadow);
            ShadowType oldShadowType = oldShadow.asObjectable();

            LOGGER.trace("Old shadow: {}", oldShadow);

            // skip setting other attribute when shadow is null
            if (oldShadow == null) {
                change.setOldShadow(null);
                return;
            }

            resouceObjectConverter.setProtectedFlag(ctx, oldShadow);
            change.setOldShadow(oldShadow);

            if (change.getCurrentShadow() != null) {
                PrismObject<ShadowType> currentShadow = completeShadow(ctx, change.getCurrentShadow(), oldShadow,
                        parentResult);
                change.setCurrentShadow(currentShadow);
                ShadowType currentShadowType = currentShadow.asObjectable();
                forceRenameIfNeeded(ctx, currentShadowType, oldShadowType, parentResult);
            }

            // FIXME: hack. the object delta must have oid specified.
            if (change.getObjectDelta() != null && change.getObjectDelta().getOid() == null) {
                //            if (LOGGER.isTraceEnabled()) {
                //               LOGGER.trace("No OID present, assuming delta of type DELETE; change = {}\nobjectDelta: {}", change, change.getObjectDelta().debugDump());
                //            }
                //            ObjectDelta<ShadowType> objDelta = new ObjectDelta<ShadowType>(ShadowType.class, ChangeType.DELETE, prismContext);
                //            change.setObjectDelta(objDelta);
                change.getObjectDelta().setOid(oldShadow.getOid());
            }
        } else {
            LOGGER.debug(
                    "No old shadow for synchronization event {}, the shadow must be gone in the meantime (this is probably harmless)",
                    change);
        }

    }

    private void forceRenameIfNeeded(ProvisioningContext ctx, ShadowType currentShadowType,
            ShadowType oldShadowType, OperationResult parentResult) throws SchemaException, ObjectNotFoundException,
            ObjectAlreadyExistsException, ConfigurationException, CommunicationException {
        Collection<ResourceAttribute<?>> oldSecondaryIdentifiers = ShadowUtil
                .getSecondaryIdentifiers(oldShadowType);
        if (oldSecondaryIdentifiers.isEmpty()) {
            return;
        }
        ResourceAttributeContainer newSecondaryIdentifiers = ShadowUtil.getAttributesContainer(currentShadowType);

        //remember name before normalizing attributes
        PolyString currentShadowName = ShadowUtil.determineShadowName(currentShadowType);
        currentShadowType.setName(new PolyStringType(currentShadowName));

        Iterator<ResourceAttribute<?>> oldSecondaryIterator = oldSecondaryIdentifiers.iterator();
        Collection<PropertyDelta> renameDeltas = new ArrayList<PropertyDelta>();
        while (oldSecondaryIterator.hasNext()) {
            ResourceAttribute<?> oldSecondaryIdentifier = oldSecondaryIterator.next();
            ResourceAttribute newSecondaryIdentifier = newSecondaryIdentifiers
                    .findAttribute(oldSecondaryIdentifier.getElementName());
            Collection newValue = newSecondaryIdentifier.getRealValues();

            if (!shadowManager.compareAttribute(ctx.getObjectClassDefinition(), newSecondaryIdentifier,
                    oldSecondaryIdentifier)) {
                PropertyDelta<?> shadowNameDelta = PropertyDelta.createDelta(
                        new ItemPath(ShadowType.F_ATTRIBUTES, oldSecondaryIdentifier.getElementName()),
                        oldShadowType.asPrismObject().getDefinition());
                shadowNameDelta.addValuesToDelete(
                        PrismPropertyValue.cloneCollection((Collection) oldSecondaryIdentifier.getValues()));
                shadowManager.normalizeAttributes(currentShadowType.asPrismObject(),
                        ctx.getObjectClassDefinition());
                shadowNameDelta.addValuesToAdd(
                        PrismPropertyValue.cloneCollection((Collection) newSecondaryIdentifier.getValues()));
                renameDeltas.add(shadowNameDelta);
            }

        }

        if (!renameDeltas.isEmpty()) {

            PropertyDelta<?> shadowNameDelta = PropertyDelta.createModificationReplaceProperty(ShadowType.F_NAME,
                    oldShadowType.asPrismObject().getDefinition(), currentShadowName);
            renameDeltas.add(shadowNameDelta);
        } else {

            if (!oldShadowType.getName().getOrig().equals(currentShadowType.getName().getOrig())) {
                PropertyDelta<?> shadowNameDelta = PropertyDelta.createModificationReplaceProperty(
                        ShadowType.F_NAME, oldShadowType.asPrismObject().getDefinition(), currentShadowName);
                renameDeltas.add(shadowNameDelta);

            }
        }
        if (!renameDeltas.isEmpty()) {
            ConstraintsChecker.onShadowModifyOperation(renameDeltas);
            repositoryService.modifyObject(ShadowType.class, oldShadowType.getOid(), renameDeltas, parentResult);
            oldShadowType.setName(new PolyStringType(currentShadowName));
        }

    }

    public PrismProperty<?> fetchCurrentToken(ResourceShadowDiscriminator shadowCoordinates,
            OperationResult parentResult)
            throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException {
        Validate.notNull(parentResult, "Operation result must not be null.");

        InternalMonitor.recordShadowOtherOperation();

        ProvisioningContext ctx = ctxFactory.create(shadowCoordinates, null, parentResult);

        LOGGER.trace("Getting last token");
        PrismProperty<?> lastToken = null;
        try {
            lastToken = resouceObjectConverter.fetchCurrentToken(ctx, parentResult);
        } catch (CommunicationException | ConfigurationException e) {
            parentResult.recordFatalError(e.getMessage(), e);
            throw e;
        }

        LOGGER.trace("Got last token: {}", SchemaDebugUtil.prettyPrint(lastToken));
        parentResult.recordSuccess();
        return lastToken;
    }

    //////////////////////////////////////////////////////////////////////////////////////

    //   public ObjectClassComplexTypeDefinition applyAttributesDefinition(ProvisioningContext ctx, ObjectDelta<ShadowType> delta, 
    //         PrismObject<ShadowType> shadow) throws SchemaException, ConfigurationException {
    //      RefinedResourceSchema refinedSchema = ProvisioningUtil.getRefinedSchema(resource);
    //      ObjectClassComplexTypeDefinition objectClassDefinition = refinedSchema.determineCompositeObjectClassDefinition(shadow);
    //      return applyAttributesDefinition(delta, objectClassDefinition, resource, refinedSchema);
    //   }

    private void applyAttributesDefinition(ProvisioningContext ctx, ObjectDelta<ShadowType> delta)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
        if (delta.isAdd()) {
            applyAttributesDefinition(ctx, delta.getObjectToAdd());
        } else if (delta.isModify()) {
            for (ItemDelta<?, ?> itemDelta : delta.getModifications()) {
                if (SchemaConstants.PATH_ATTRIBUTES.equivalent(itemDelta.getParentPath())) {
                    applyAttributeDefinition(ctx, delta, itemDelta);
                } else if (SchemaConstants.PATH_ATTRIBUTES.equivalent(itemDelta.getPath())) {
                    if (itemDelta.isAdd()) {
                        for (PrismValue value : itemDelta.getValuesToAdd()) {
                            applyAttributeDefinition(ctx, value);
                        }
                    }
                    if (itemDelta.isReplace()) {
                        for (PrismValue value : itemDelta.getValuesToReplace()) {
                            applyAttributeDefinition(ctx, value);
                        }
                    }
                }
            }
        }
    }

    // value should be a value of attributes container
    private void applyAttributeDefinition(ProvisioningContext ctx, PrismValue value)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
        if (!(value instanceof PrismContainerValue)) {
            return; // should never occur
        }
        PrismContainerValue<ShadowAttributesType> pcv = (PrismContainerValue<ShadowAttributesType>) value;
        for (Item item : pcv.getItems()) {
            ItemDefinition itemDef = item.getDefinition();
            if (itemDef == null || !(itemDef instanceof ResourceAttributeDefinition)) {
                QName attributeName = item.getElementName();
                ResourceAttributeDefinition attributeDefinition = ctx.getObjectClassDefinition()
                        .findAttributeDefinition(attributeName);
                if (attributeDefinition == null) {
                    throw new SchemaException("No definition for attribute " + attributeName);
                }
                if (itemDef != null) {
                    // We are going to rewrite the definition anyway. Let's just do some basic checks first
                    if (!QNameUtil.match(itemDef.getTypeName(), attributeDefinition.getTypeName())) {
                        throw new SchemaException(
                                "The value of type " + itemDef.getTypeName() + " cannot be applied to attribute "
                                        + attributeName + " which is of type " + attributeDefinition.getTypeName());
                    }
                }
                item.applyDefinition(attributeDefinition);
            }
        }
    }

    private <V extends PrismValue, D extends ItemDefinition> void applyAttributeDefinition(ProvisioningContext ctx,
            ObjectDelta<ShadowType> delta, ItemDelta<V, D> itemDelta)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
        if (!SchemaConstants.PATH_ATTRIBUTES.equivalent(itemDelta.getParentPath())) { // just to be sure
            return;
        }
        D itemDef = itemDelta.getDefinition();
        if (itemDef == null || !(itemDef instanceof ResourceAttributeDefinition)) {
            QName attributeName = itemDelta.getElementName();
            ResourceAttributeDefinition attributeDefinition = ctx.getObjectClassDefinition()
                    .findAttributeDefinition(attributeName);
            if (attributeDefinition == null) {
                throw new SchemaException(
                        "No definition for attribute " + attributeName + " in object delta " + delta);
            }
            if (itemDef != null) {
                // We are going to rewrite the definition anyway. Let's just do some basic checks first
                if (!QNameUtil.match(itemDef.getTypeName(), attributeDefinition.getTypeName())) {
                    throw new SchemaException(
                            "The value of type " + itemDef.getTypeName() + " cannot be applied to attribute "
                                    + attributeName + " which is of type " + attributeDefinition.getTypeName());
                }
            }
            itemDelta.applyDefinition((D) attributeDefinition);
        }
    }

    private RefinedObjectClassDefinition applyAttributesDefinition(ProvisioningContext ctx,
            PrismObject<ShadowType> shadow)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
        RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition();

        PrismContainer<ShadowAttributesType> attributesContainer = shadow.findContainer(ShadowType.F_ATTRIBUTES);
        if (attributesContainer != null) {
            if (attributesContainer instanceof ResourceAttributeContainer) {
                if (attributesContainer.getDefinition() == null) {
                    attributesContainer
                            .applyDefinition(objectClassDefinition.toResourceAttributeContainerDefinition());
                }
            } else {
                // We need to convert <attributes> to ResourceAttributeContainer
                ResourceAttributeContainer convertedContainer = ResourceAttributeContainer
                        .convertFromContainer(attributesContainer, objectClassDefinition);
                shadow.getValue().replace(attributesContainer, convertedContainer);
            }
        }

        // We also need to replace the entire object definition to inject correct object class definition here
        // If we don't do this then the patch (delta.applyTo) will not work correctly because it will not be able to
        // create the attribute container if needed.

        PrismObjectDefinition<ShadowType> objectDefinition = shadow.getDefinition();
        PrismContainerDefinition<ShadowAttributesType> origAttrContainerDef = objectDefinition
                .findContainerDefinition(ShadowType.F_ATTRIBUTES);
        if (origAttrContainerDef == null
                || !(origAttrContainerDef instanceof ResourceAttributeContainerDefinition)) {
            PrismObjectDefinition<ShadowType> clonedDefinition = objectDefinition.cloneWithReplacedDefinition(
                    ShadowType.F_ATTRIBUTES, objectClassDefinition.toResourceAttributeContainerDefinition());
            shadow.setDefinition(clonedDefinition);
        }

        return objectClassDefinition;
    }

    /**
     * Reapplies definition to the shadow if needed. The definition needs to be reapplied e.g.
     * if the shadow has auxiliary object classes, it if subclass of the object class that was originally
     * requested, etc. 
     */
    private ProvisioningContext reapplyDefinitions(ProvisioningContext ctx,
            PrismObject<ShadowType> rawResourceShadow)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
        ShadowType rawResourceShadowType = rawResourceShadow.asObjectable();
        QName objectClassQName = rawResourceShadowType.getObjectClass();
        List<QName> auxiliaryObjectClassQNames = rawResourceShadowType.getAuxiliaryObjectClass();
        if (auxiliaryObjectClassQNames.isEmpty()
                && objectClassQName.equals(ctx.getObjectClassDefinition().getTypeName())) {
            // shortcut, no need to reapply anything
            return ctx;
        }
        ProvisioningContext shadowCtx = ctx.spawn(rawResourceShadow);
        shadowCtx.assertDefinition();
        RefinedObjectClassDefinition shadowDef = shadowCtx.getObjectClassDefinition();
        ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(rawResourceShadow);
        attributesContainer.applyDefinition(shadowDef.toResourceAttributeContainerDefinition());
        return shadowCtx;
    }

    /**
     * Make sure that the shadow is complete, e.g. that all the mandatory fields
     * are filled (e.g name, resourceRef, ...) Also transforms the shadow with
     * respect to simulated capabilities.
     */
    private PrismObject<ShadowType> completeShadow(ProvisioningContext ctx, PrismObject<ShadowType> resourceShadow,
            PrismObject<ShadowType> repoShadow, OperationResult parentResult)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException,
            SecurityViolationException, GenericConnectorException {

        PrismObject<ShadowType> resultShadow = repoShadow.clone();
        boolean resultIsResourceShadowClone = false;
        if (resultShadow == null) { // todo how could this happen (see above)? [mederly]
            resultShadow = resourceShadow.clone();
            resultIsResourceShadowClone = true;
        }

        assert resultShadow.getPrismContext() != null : "No prism context in resultShadow";

        ResourceAttributeContainer resourceAttributesContainer = ShadowUtil.getAttributesContainer(resourceShadow);

        ShadowType resultShadowType = resultShadow.asObjectable();
        ShadowType repoShadowType = repoShadow.asObjectable();
        ShadowType resourceShadowType = resourceShadow.asObjectable();

        if (resultShadowType.getObjectClass() == null) {
            resultShadowType.setObjectClass(resourceAttributesContainer.getDefinition().getTypeName());
        }
        if (resultShadowType.getName() == null) {
            resultShadowType.setName(new PolyStringType(ShadowUtil.determineShadowName(resourceShadow)));
        }
        if (resultShadowType.getResource() == null) {
            resultShadowType.setResourceRef(ObjectTypeUtil.createObjectRef(ctx.getResource()));
        }

        // If the shadows are the same then no copy is needed. This was already copied by clone.
        if (!resultIsResourceShadowClone) {
            // Attributes
            resultShadow.removeContainer(ShadowType.F_ATTRIBUTES);
            ResourceAttributeContainer resultAttibutes = resourceAttributesContainer.clone();
            accessChecker.filterGetAttributes(resultAttibutes, ctx.getObjectClassDefinition(), parentResult);
            resultShadow.add(resultAttibutes);

            //         resultShadowType.setProtectedObject(resourceShadowType.isProtectedObject());
            resultShadowType.setIgnored(resourceShadowType.isIgnored());

            resultShadowType.setActivation(resourceShadowType.getActivation());

            // Credentials
            ShadowType resultAccountShadow = resultShadow.asObjectable();
            ShadowType resourceAccountShadow = resourceShadow.asObjectable();
            resultAccountShadow.setCredentials(resourceAccountShadow.getCredentials());
        }

        //protected
        resouceObjectConverter.setProtectedFlag(ctx, resultShadow);
        //      resultShadowType.setProtectedObject();

        // Activation
        ActivationType resultActivationType = resultShadowType.getActivation();
        ActivationType repoActivation = repoShadowType.getActivation();
        if (repoActivation != null) {
            if (resultActivationType == null) {
                resultActivationType = new ActivationType();
                resultShadowType.setActivation(resultActivationType);
            }
            resultActivationType.setId(repoActivation.getId());
            // .. but we want metadata from repo
            resultActivationType.setDisableReason(repoActivation.getDisableReason());
            resultActivationType.setEnableTimestamp(repoActivation.getEnableTimestamp());
            resultActivationType.setDisableTimestamp(repoActivation.getDisableTimestamp());
            resultActivationType.setArchiveTimestamp(repoActivation.getArchiveTimestamp());
            resultActivationType.setValidityChangeTimestamp(repoActivation.getValidityChangeTimestamp());
        }

        // Associations
        PrismContainer<ShadowAssociationType> resourceAssociationContainer = resourceShadow
                .findContainer(ShadowType.F_ASSOCIATION);
        if (resourceAssociationContainer != null) {
            PrismContainer<ShadowAssociationType> associationContainer = resourceAssociationContainer.clone();
            resultShadow.addReplaceExisting(associationContainer);
            if (associationContainer != null) {
                for (PrismContainerValue<ShadowAssociationType> associationCVal : associationContainer
                        .getValues()) {
                    ResourceAttributeContainer identifierContainer = ShadowUtil
                            .getAttributesContainer(associationCVal, ShadowAssociationType.F_IDENTIFIERS);
                    Collection<ResourceAttribute<?>> entitlementIdentifiers = identifierContainer.getAttributes();
                    if (entitlementIdentifiers == null || entitlementIdentifiers.isEmpty()) {
                        throw new IllegalStateException(
                                "No entitlement identifiers present for association " + associationCVal);
                    }
                    ShadowAssociationType shadowAssociationType = associationCVal.asContainerable();
                    QName associationName = shadowAssociationType.getName();
                    RefinedAssociationDefinition rEntitlementAssociation = ctx.getObjectClassDefinition()
                            .findEntitlementAssociation(associationName);
                    for (String intent : rEntitlementAssociation.getIntents()) {
                        ProvisioningContext ctxEntitlement = ctx.spawn(ShadowKindType.ENTITLEMENT, intent);

                        PrismObject<ShadowType> entitlementRepoShadow;
                        PrismObject<ShadowType> entitlementShadow = (PrismObject<ShadowType>) identifierContainer
                                .getUserData(ResourceObjectConverter.FULL_SHADOW_KEY);
                        if (entitlementShadow == null) {
                            try {
                                entitlementRepoShadow = shadowManager.lookupShadowInRepository(ctxEntitlement,
                                        identifierContainer, parentResult);
                                if (entitlementRepoShadow == null) {
                                    entitlementShadow = resouceObjectConverter.locateResourceObject(ctxEntitlement,
                                            entitlementIdentifiers, parentResult);
                                    entitlementRepoShadow = createShadowInRepository(ctxEntitlement,
                                            entitlementShadow, parentResult);
                                }
                            } catch (ObjectNotFoundException e) {
                                // The entitlement to which we point is not there.
                                // Simply ignore this association value.
                                parentResult.muteLastSubresultError();
                                LOGGER.warn(
                                        "The entitlement identified by {} referenced from {} does not exist. Skipping.",
                                        new Object[] { associationCVal, resourceShadow });
                                continue;
                            } catch (SchemaException e) {
                                // The entitlement to which we point is not bad.
                                // Simply ignore this association value.
                                parentResult.muteLastSubresultError();
                                LOGGER.warn(
                                        "The entitlement identified by {} referenced from {} violates the schema. Skipping. Original error: {}",
                                        new Object[] { associationCVal, resourceShadow, e.getMessage(), e });
                                continue;
                            }
                        } else {
                            entitlementRepoShadow = lookupOrCreateShadowInRepository(ctxEntitlement,
                                    entitlementShadow, parentResult);
                        }
                        ObjectReferenceType shadowRefType = new ObjectReferenceType();
                        shadowRefType.setOid(entitlementRepoShadow.getOid());
                        shadowRefType.setType(ShadowType.COMPLEX_TYPE);
                        shadowAssociationType.setShadowRef(shadowRefType);
                    }
                }
            }
        }

        resultShadowType.setCachingMetadata(resourceShadowType.getCachingMetadata());

        // Sanity asserts to catch some exotic bugs
        PolyStringType resultName = resultShadow.asObjectable().getName();
        assert resultName != null : "No name generated in " + resultShadow;
        assert !StringUtils.isEmpty(resultName.getOrig()) : "No name (orig) in " + resultShadow;
        assert !StringUtils.isEmpty(resultName.getNorm()) : "No name (norm) in " + resultShadow;

        return resultShadow;
    }

    // ENTITLEMENTS

    /**
     * Makes sure that all the entitlements have identifiers in them so this is usable by the
     * ResourceObjectConverter. 
     */
    private void preprocessEntitlements(final ProvisioningContext ctx, final PrismObject<ShadowType> shadow,
            final OperationResult result)
            throws SchemaException, ObjectNotFoundException, ConfigurationException, CommunicationException {
        Visitor visitor = new Visitor() {
            @Override
            public void visit(Visitable visitable) {
                try {
                    preprocessEntitlement(ctx, (PrismContainerValue<ShadowAssociationType>) visitable,
                            shadow.toString(), result);
                } catch (SchemaException | ObjectNotFoundException | ConfigurationException
                        | CommunicationException e) {
                    throw new TunnelException(e);
                }
            }
        };
        try {
            shadow.accept(visitor,
                    new ItemPath(new NameItemPathSegment(ShadowType.F_ASSOCIATION), IdItemPathSegment.WILDCARD),
                    false);
        } catch (TunnelException e) {
            Throwable cause = e.getCause();
            if (cause instanceof SchemaException) {
                throw (SchemaException) cause;
            } else if (cause instanceof ObjectNotFoundException) {
                throw (ObjectNotFoundException) cause;
            } else if (cause instanceof ConfigurationException) {
                throw (ConfigurationException) cause;
            } else if (cause instanceof CommunicationException) {
                throw (CommunicationException) cause;
            } else {
                throw new SystemException("Unexpected exception " + cause, cause);
            }
        }
    }

    /**
     * Makes sure that all the entitlements have identifiers in them so this is usable by the
     * ResourceObjectConverter. 
     */
    private void preprocessEntitlements(final ProvisioningContext ctx,
            Collection<? extends ItemDelta> modifications, final String desc, final OperationResult result)
            throws SchemaException, ObjectNotFoundException, ConfigurationException, CommunicationException {
        Visitor visitor = new Visitor() {
            @Override
            public void visit(Visitable visitable) {
                try {
                    preprocessEntitlement(ctx, (PrismContainerValue<ShadowAssociationType>) visitable, desc,
                            result);
                } catch (SchemaException | ObjectNotFoundException | ConfigurationException
                        | CommunicationException e) {
                    throw new TunnelException(e);
                }
            }
        };
        try {
            ItemDelta.accept(modifications, visitor,
                    new ItemPath(new NameItemPathSegment(ShadowType.F_ASSOCIATION), IdItemPathSegment.WILDCARD),
                    false);
        } catch (TunnelException e) {
            Throwable cause = e.getCause();
            if (cause instanceof SchemaException) {
                throw (SchemaException) cause;
            } else if (cause instanceof ObjectNotFoundException) {
                throw (ObjectNotFoundException) cause;
            } else if (cause instanceof ConfigurationException) {
                throw (ConfigurationException) cause;
            } else if (cause instanceof CommunicationException) {
                throw (CommunicationException) cause;
            } else {
                throw new SystemException("Unexpected exception " + cause, cause);
            }
        }
    }

    private void preprocessEntitlement(ProvisioningContext ctx,
            PrismContainerValue<ShadowAssociationType> association, String desc, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ConfigurationException, CommunicationException {
        PrismContainer<Containerable> identifiersContainer = association
                .findContainer(ShadowAssociationType.F_IDENTIFIERS);
        if (identifiersContainer != null && !identifiersContainer.isEmpty()) {
            // We already have identifiers here
            return;
        }
        ShadowAssociationType associationType = association.asContainerable();
        if (associationType.getShadowRef() == null
                || StringUtils.isEmpty(associationType.getShadowRef().getOid())) {
            throw new SchemaException(
                    "No identifiers and no OID specified in entitlements association " + association);
        }
        PrismObject<ShadowType> repoShadow;
        try {
            repoShadow = repositoryService.getObject(ShadowType.class, associationType.getShadowRef().getOid(),
                    null, result);
        } catch (ObjectNotFoundException e) {
            throw new ObjectNotFoundException(e.getMessage() + " while resolving entitlement association OID in "
                    + association + " in " + desc, e);
        }
        applyAttributesDefinition(ctx, repoShadow);
        transplantIdentifiers(association, repoShadow);
    }

    private void transplantIdentifiers(PrismContainerValue<ShadowAssociationType> association,
            PrismObject<ShadowType> repoShadow) throws SchemaException {
        PrismContainer<Containerable> identifiersContainer = association
                .findContainer(ShadowAssociationType.F_IDENTIFIERS);
        if (identifiersContainer == null) {
            ResourceAttributeContainer origContainer = ShadowUtil.getAttributesContainer(repoShadow);
            identifiersContainer = new ResourceAttributeContainer(ShadowAssociationType.F_IDENTIFIERS,
                    origContainer.getDefinition(), prismContext);
            association.add(identifiersContainer);
        }
        Collection<ResourceAttribute<?>> identifiers = ShadowUtil.getIdentifiers(repoShadow);
        for (ResourceAttribute<?> identifier : identifiers) {
            identifiersContainer.add(identifier.clone());
        }
        Collection<ResourceAttribute<?>> secondaryIdentifiers = ShadowUtil.getSecondaryIdentifiers(repoShadow);
        for (ResourceAttribute<?> identifier : secondaryIdentifiers) {
            identifiersContainer.add(identifier.clone());
        }
    }

}