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

Java tutorial

Introduction

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

Source

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;

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

import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition;
import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchema;
import com.evolveum.midpoint.prism.ModificationType;
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.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.PropertyDelta;
import com.evolveum.midpoint.prism.match.MatchingRule;
import com.evolveum.midpoint.prism.match.MatchingRuleRegistry;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.EqualFilter;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.provisioning.api.GenericConnectorException;
import com.evolveum.midpoint.provisioning.ucf.api.AttributesToReturn;
import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance;
import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException;
import com.evolveum.midpoint.provisioning.ucf.api.Operation;
import com.evolveum.midpoint.provisioning.ucf.api.PropertyModificationOperation;
import com.evolveum.midpoint.provisioning.ucf.api.ResultHandler;
import com.evolveum.midpoint.provisioning.util.ProvisioningUtil;
import com.evolveum.midpoint.schema.processor.ResourceAttribute;
import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.SchemaException;
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.ResourceObjectAssociationDirectionType;
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.ShadowKindType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;

/**
 * Class that collects the entitlement-related methods used by ResourceObjectConverter
 * 
 * Should NOT be used by any class other than ResourceObjectConverter.
 * 
 * @author Radovan Semancik
 *
 */
@Component
class EntitlementConverter {

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

    @Autowired(required = true)
    private PrismContext prismContext;

    @Autowired(required = true)
    private MatchingRuleRegistry matchingRuleRegistry;

    //////////
    // GET
    /////////

    public void postProcessEntitlementsRead(ConnectorInstance connector, ResourceType resourceType,
            PrismObject<ShadowType> resourceObject, RefinedObjectClassDefinition objectClassDefinition,
            OperationResult parentResult)
            throws SchemaException, CommunicationException, GenericFrameworkException {

        Collection<RefinedAssociationDefinition> entitlementAssociationDefs = objectClassDefinition
                .getEntitlementAssociations();
        if (entitlementAssociationDefs != null) {
            ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(resourceObject);
            RefinedResourceSchema refinedSchema = RefinedResourceSchema.getRefinedSchema(resourceType);

            PrismContainerDefinition<ShadowAssociationType> associationDef = resourceObject.getDefinition()
                    .findContainerDefinition(ShadowType.F_ASSOCIATION);
            PrismContainer<ShadowAssociationType> associationContainer = associationDef.instantiate();

            for (RefinedAssociationDefinition assocDefType : entitlementAssociationDefs) {
                RefinedObjectClassDefinition entitlementDef = refinedSchema
                        .getRefinedDefinition(ShadowKindType.ENTITLEMENT, assocDefType.getIntents());
                if (entitlementDef == null) {
                    throw new SchemaException("No definition for entitlement intent(s) '"
                            + assocDefType.getIntents() + "' in " + resourceType);
                }
                ResourceObjectAssociationDirectionType direction = assocDefType.getResourceObjectAssociationType()
                        .getDirection();
                if (direction == ResourceObjectAssociationDirectionType.SUBJECT_TO_OBJECT) {
                    postProcessEntitlementSubjectToEntitlement(resourceType, resourceObject, objectClassDefinition,
                            assocDefType, entitlementDef, attributesContainer, associationContainer, parentResult);
                } else if (direction == ResourceObjectAssociationDirectionType.OBJECT_TO_SUBJECT) {
                    postProcessEntitlementEntitlementToSubject(connector, resourceType, resourceObject,
                            objectClassDefinition, assocDefType, entitlementDef, attributesContainer,
                            associationContainer, parentResult);
                } else {
                    throw new IllegalArgumentException("Unknown entitlement direction " + direction
                            + " in association " + assocDefType + " in " + resourceType);
                }
            }

            if (!associationContainer.isEmpty()) {
                resourceObject.add(associationContainer);
            }
        }
    }

    private <S extends ShadowType, T> void postProcessEntitlementSubjectToEntitlement(ResourceType resourceType,
            PrismObject<S> resourceObject, RefinedObjectClassDefinition objectClassDefinition,
            RefinedAssociationDefinition assocDefType, RefinedObjectClassDefinition entitlementDef,
            ResourceAttributeContainer attributesContainer,
            PrismContainer<ShadowAssociationType> associationContainer, OperationResult parentResult)
            throws SchemaException {
        QName associationName = assocDefType.getName();
        if (associationName == null) {
            throw new SchemaException("No name in entitlement association " + assocDefType + " in " + resourceType);
        }

        QName assocAttrName = assocDefType.getResourceObjectAssociationType().getAssociationAttribute();
        if (assocAttrName == null) {
            throw new SchemaException("No association attribute defined in entitlement association '"
                    + associationName + "' in " + resourceType);
        }
        RefinedAttributeDefinition assocAttrDef = objectClassDefinition.findAttributeDefinition(assocAttrName);
        if (assocAttrDef == null) {
            throw new SchemaException(
                    "Association attribute '" + assocAttrName + "'defined in entitlement association '"
                            + associationName + "' was not found in schema for " + resourceType);
        }
        ResourceAttribute<T> assocAttr = attributesContainer.findAttribute(assocAttrName);
        if (assocAttr == null || assocAttr.isEmpty()) {
            // Nothing to do. No attribute to base the association on.
            return;
        }

        QName valueAttrName = assocDefType.getResourceObjectAssociationType().getValueAttribute();
        if (valueAttrName == null) {
            throw new SchemaException("No value attribute defined in entitlement association '" + associationName
                    + "' in " + resourceType);
        }
        RefinedAttributeDefinition valueAttrDef = entitlementDef.findAttributeDefinition(valueAttrName);
        ResourceAttribute<T> valueAttribute = valueAttrDef.instantiate();

        for (PrismPropertyValue<T> assocAttrPVal : assocAttr.getValues()) {
            valueAttribute.add(assocAttrPVal.clone());
        }

        PrismContainerValue<ShadowAssociationType> associationCVal = associationContainer.createNewValue();
        associationCVal.asContainerable().setName(associationName);
        ResourceAttributeContainer identifiersContainer = new ResourceAttributeContainer(
                ShadowAssociationType.F_IDENTIFIERS, entitlementDef.toResourceAttributeContainerDefinition(),
                prismContext);
        associationCVal.add(identifiersContainer);
        identifiersContainer.add(valueAttribute);
    }

    private <S extends ShadowType, T> void postProcessEntitlementEntitlementToSubject(ConnectorInstance connector,
            ResourceType resourceType, final PrismObject<S> resourceObject,
            RefinedObjectClassDefinition objectClassDefinition, RefinedAssociationDefinition assocDefType,
            final RefinedObjectClassDefinition entitlementDef, ResourceAttributeContainer attributesContainer,
            final PrismContainer<ShadowAssociationType> associationContainer, OperationResult parentResult)
            throws SchemaException, CommunicationException, GenericFrameworkException {
        final QName associationName = assocDefType.getName();
        if (associationName == null) {
            throw new SchemaException("No name in entitlement association " + assocDefType + " in " + resourceType);
        }

        QName assocAttrName = assocDefType.getResourceObjectAssociationType().getAssociationAttribute();
        if (assocAttrName == null) {
            throw new SchemaException("No association attribute defined in entitlement association '"
                    + associationName + "' in " + resourceType);
        }
        RefinedAttributeDefinition assocAttrDef = entitlementDef.findAttributeDefinition(assocAttrName);
        if (assocAttrDef == null) {
            throw new SchemaException(
                    "Association attribute '" + assocAttrName + "'defined in entitlement association '"
                            + associationName + "' was not found in schema for " + resourceType);
        }

        QName valueAttrName = assocDefType.getResourceObjectAssociationType().getValueAttribute();
        if (valueAttrName == null) {
            throw new SchemaException("No value attribute defined in entitlement association '" + associationName
                    + "' in " + resourceType);
        }
        ResourceAttribute<T> valueAttr = attributesContainer.findAttribute(valueAttrName);
        if (valueAttr == null || valueAttr.isEmpty()) {
            throw new SchemaException("Value attribute " + valueAttrName
                    + " has no value; attribute defined in entitlement association '" + associationName + "' in "
                    + resourceType);
        }
        if (valueAttr.size() > 1) {
            throw new SchemaException("Value attribute " + valueAttrName
                    + " has no more than one value; attribute defined in entitlement association '"
                    + associationName + "' in " + resourceType);
        }

        ObjectQuery query = createQuery(assocDefType, assocAttrDef, valueAttr);

        //      MatchingRule matchingRule = matchingRuleRegistry.getMatchingRule(assocDefType.getResourceObjectAssociationType().getMatchingRule(), valueAttr.getDefinition().getTypeName());
        //      PrismPropertyValue normalized = valueAttr.getValue();
        //      if (matchingRule != null) {
        //         Object normalizedRealValue = matchingRule.normalize(valueAttr.getRealValue());
        //         normalized = new PrismPropertyValue(normalizedRealValue);
        //      }
        //      
        //      ObjectFilter filter = EqualsFilter.createEqual(new ItemPath(ShadowType.F_ATTRIBUTES, assocAttrDef.getName()), assocAttrDef, normalized);
        //      ObjectQuery query = ObjectQuery.createObjectQuery(filter);
        //      ObjectQuery query = new ObjectQuery();
        //      query.setFilter(filter);

        AttributesToReturn attributesToReturn = ProvisioningUtil.createAttributesToReturn(entitlementDef,
                resourceType);

        ResultHandler<ShadowType> handler = new ResultHandler<ShadowType>() {
            @Override
            public boolean handle(PrismObject<ShadowType> entitlementShadow) {
                PrismContainerValue<ShadowAssociationType> associationCVal = associationContainer.createNewValue();
                associationCVal.asContainerable().setName(associationName);
                Collection<ResourceAttribute<?>> entitlementIdentifiers = ShadowUtil
                        .getIdentifiers(entitlementShadow);
                try {
                    ResourceAttributeContainer identifiersContainer = new ResourceAttributeContainer(
                            ShadowAssociationType.F_IDENTIFIERS,
                            entitlementDef.toResourceAttributeContainerDefinition(), prismContext);
                    associationCVal.add(identifiersContainer);
                    identifiersContainer.getValue()
                            .addAll(ResourceAttribute.cloneCollection(entitlementIdentifiers));

                    // Remember the full shadow in user data. This is used later as an optimization to create the shadow in repo 
                    identifiersContainer.setUserData(ResourceObjectConverter.FULL_SHADOW_KEY, entitlementShadow);
                    if (LOGGER.isTraceEnabled()) {
                        LOGGER.trace(
                                "Processed entitlement-to-subject association for account {} and entitlement {}",
                                ShadowUtil.getHumanReadableName(resourceObject),
                                ShadowUtil.getHumanReadableName(entitlementShadow));
                    }
                } catch (SchemaException e) {
                    throw new TunnelException(e);
                }
                return true;
            }
        };

        try {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Processed entitlement-to-subject association for account {}: query {}",
                        ShadowUtil.getHumanReadableName(resourceObject), query);
            }
            connector.search(entitlementDef, query, handler, attributesToReturn, parentResult);
        } catch (TunnelException e) {
            throw (SchemaException) e.getCause();
        }

    }

    private <T> ObjectQuery createQuery(RefinedAssociationDefinition assocDefType,
            RefinedAttributeDefinition assocAttrDef, ResourceAttribute<T> valueAttr) throws SchemaException {
        MatchingRule matchingRule = matchingRuleRegistry.getMatchingRule(
                assocDefType.getResourceObjectAssociationType().getMatchingRule(),
                valueAttr.getDefinition().getTypeName());
        PrismPropertyValue normalized = valueAttr.getValue();
        if (matchingRule != null) {
            Object normalizedRealValue = matchingRule.normalize(valueAttr.getRealValue());
            normalized = new PrismPropertyValue(normalizedRealValue);
        }

        ObjectFilter filter = EqualFilter.createEqual(new ItemPath(ShadowType.F_ATTRIBUTES, assocAttrDef.getName()),
                assocAttrDef, normalized);
        ObjectQuery query = ObjectQuery.createObjectQuery(filter);
        return query;
    }

    //////////
    // ADD
    /////////

    public void processEntitlementsAdd(ResourceType resource, PrismObject<ShadowType> shadow,
            RefinedObjectClassDefinition objectClassDefinition) throws SchemaException {
        PrismContainer<ShadowAssociationType> associationContainer = shadow.findContainer(ShadowType.F_ASSOCIATION);
        if (associationContainer == null || associationContainer.isEmpty()) {
            return;
        }
        Map<QName, PropertyDelta<?>> deltaMap = new HashMap<QName, PropertyDelta<?>>();
        collectEntitlementToAttrsDelta(deltaMap, associationContainer.getValues(), objectClassDefinition, resource,
                ModificationType.ADD);
        for (PropertyDelta<?> delta : deltaMap.values()) {
            delta.applyTo(shadow);
        }
    }

    public <T> void collectEntitlementsAsObjectOperationInShadowAdd(
            Map<ResourceObjectDiscriminator, Collection<Operation>> roMap,
            RefinedObjectClassDefinition objectClassDefinition, PrismObject<ShadowType> shadow,
            RefinedResourceSchema rSchema, ResourceType resource) throws SchemaException {
        PrismContainer<ShadowAssociationType> associationContainer = shadow.findContainer(ShadowType.F_ASSOCIATION);
        if (associationContainer == null || associationContainer.isEmpty()) {
            return;
        }
        collectEntitlementsAsObjectOperation(roMap, associationContainer.getValues(), objectClassDefinition, null,
                shadow, rSchema, resource, ModificationType.ADD);
    }

    //////////
    // MODIFY
    /////////

    /**
     * Collects entitlement changes from the shadow to entitlement section into attribute operations.
     * NOTE: only collects  SUBJECT_TO_ENTITLEMENT entitlement direction.
     */
    public void collectEntitlementChange(ContainerDelta<ShadowAssociationType> itemDelta,
            Collection<Operation> operations, RefinedObjectClassDefinition objectClassDefinition,
            ResourceType resource) throws SchemaException {
        Map<QName, PropertyDelta<?>> deltaMap = new HashMap<QName, PropertyDelta<?>>();

        collectEntitlementToAttrsDelta(deltaMap, itemDelta.getValuesToAdd(), objectClassDefinition, resource,
                ModificationType.ADD);
        collectEntitlementToAttrsDelta(deltaMap, itemDelta.getValuesToDelete(), objectClassDefinition, resource,
                ModificationType.DELETE);
        collectEntitlementToAttrsDelta(deltaMap, itemDelta.getValuesToReplace(), objectClassDefinition, resource,
                ModificationType.REPLACE);

        for (PropertyDelta<?> attrDelta : deltaMap.values()) {
            PropertyModificationOperation attributeModification = new PropertyModificationOperation(attrDelta);
            operations.add(attributeModification);
        }
    }

    public <T> void collectEntitlementsAsObjectOperation(
            Map<ResourceObjectDiscriminator, Collection<Operation>> roMap,
            ContainerDelta<ShadowAssociationType> containerDelta,
            RefinedObjectClassDefinition objectClassDefinition, PrismObject<ShadowType> shadowBefore,
            PrismObject<ShadowType> shadowAfter, RefinedResourceSchema rSchema, ResourceType resource)
            throws SchemaException {
        collectEntitlementsAsObjectOperation(roMap, containerDelta.getValuesToAdd(), objectClassDefinition,
                shadowBefore, shadowAfter, rSchema, resource, ModificationType.ADD);
        collectEntitlementsAsObjectOperation(roMap, containerDelta.getValuesToDelete(), objectClassDefinition,
                shadowBefore, shadowAfter, rSchema, resource, ModificationType.DELETE);
        collectEntitlementsAsObjectOperation(roMap, containerDelta.getValuesToReplace(), objectClassDefinition,
                shadowBefore, shadowAfter, rSchema, resource, ModificationType.REPLACE);
    }

    /////////
    // DELETE
    /////////

    /**
     * This is somehow different that all the other methods. We are not following the content of a shadow or delta. We are following
     * the definitions. This is to avoid the need to read the object that is going to be deleted. In fact, the object should not be there
     * any more, but we still want to clean up entitlement membership based on the information from the shadow. 
     * @param parentResult 
     */
    public <T> void collectEntitlementsAsObjectOperationDelete(ConnectorInstance connector,
            final Map<ResourceObjectDiscriminator, Collection<Operation>> roMap,
            RefinedObjectClassDefinition objectClassDefinition, PrismObject<ShadowType> shadow,
            RefinedResourceSchema rSchema, ResourceType resourceType, OperationResult parentResult)
            throws SchemaException, CommunicationException {

        Collection<RefinedAssociationDefinition> entitlementAssociationDefs = objectClassDefinition
                .getEntitlementAssociations();
        if (entitlementAssociationDefs == null || entitlementAssociationDefs.isEmpty()) {
            // Nothing to do
            LOGGER.trace("No associations in deleted shadow");
            return;
        }
        RefinedResourceSchema refinedSchema = RefinedResourceSchema.getRefinedSchema(resourceType);
        ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(shadow);
        for (RefinedAssociationDefinition assocDefType : objectClassDefinition.getEntitlementAssociations()) {
            if (assocDefType.getResourceObjectAssociationType()
                    .getDirection() != ResourceObjectAssociationDirectionType.OBJECT_TO_SUBJECT) {
                // We can ignore these. They will die together with the object. No need to explicitly delete them.
                LOGGER.trace("Ignoring subject-to-object association in deleted shadow");
                continue;
            }
            if (assocDefType.getResourceObjectAssociationType().isExplicitReferentialIntegrity() != null
                    && !assocDefType.getResourceObjectAssociationType().isExplicitReferentialIntegrity()) {
                // Referential integrity not required for this one
                LOGGER.trace(
                        "Ignoring association in deleted shadow because it has explicit referential integrity turned off");
                continue;
            }
            QName associationName = assocDefType.getName();
            if (associationName == null) {
                throw new SchemaException(
                        "No name in entitlement association " + assocDefType + " in " + resourceType);
            }
            final RefinedObjectClassDefinition entitlementOcDef = refinedSchema
                    .getRefinedDefinition(ShadowKindType.ENTITLEMENT, assocDefType.getIntents());
            if (entitlementOcDef == null) {
                throw new SchemaException("No definition for entitlement intent(s) '" + assocDefType.getIntents()
                        + "' defined in entitlement association " + associationName + " in " + resourceType);
            }

            final QName assocAttrName = assocDefType.getResourceObjectAssociationType().getAssociationAttribute();
            if (assocAttrName == null) {
                throw new SchemaException("No association attribute defined in entitlement association '"
                        + associationName + "' in " + resourceType);
            }
            final RefinedAttributeDefinition assocAttrDef = entitlementOcDef.findAttributeDefinition(assocAttrName);
            if (assocAttrDef == null) {
                throw new SchemaException(
                        "Association attribute '" + assocAttrName + "'defined in entitlement association '"
                                + associationName + "' was not found in schema for " + resourceType);
            }

            QName valueAttrName = assocDefType.getResourceObjectAssociationType().getValueAttribute();
            if (valueAttrName == null) {
                throw new SchemaException("No value attribute defined in entitlement association '"
                        + associationName + "' in " + resourceType);
            }
            final ResourceAttribute<T> valueAttr = attributesContainer.findAttribute(valueAttrName);
            if (valueAttr == null || valueAttr.isEmpty()) {
                throw new SchemaException("Value attribute " + valueAttrName
                        + " has no value; attribute defined in entitlement association '" + associationName
                        + "' in " + resourceType);
            }
            if (valueAttr.size() > 1) {
                throw new SchemaException("Value attribute " + valueAttrName
                        + " has no more than one value; attribute defined in entitlement association '"
                        + associationName + "' in " + resourceType);
            }

            ObjectQuery query = createQuery(assocDefType, assocAttrDef, valueAttr);

            //         ObjectFilter filter = EqualsFilter.createEqual(new ItemPath(ShadowType.F_ATTRIBUTES), assocAttrDef, valueAttr.getValue());
            //         ObjectFilter filter = InFilter.createIn(new ItemPath(ShadowType.F_ATTRIBUTES, assocAttrDef.getName()), assocAttrDef, valueAttr.getValue());
            //         ObjectFilter filter = EqualsFilter.createEqual(new ItemPath(ShadowType.F_ATTRIBUTES, assocAttrDef.getName()), assocAttrDef, valueAttr.getValue());
            //         ObjectQuery query = ObjectQuery.createObjectQuery(filter); 
            //               new ObjectQuery();
            //         query.setFilter(filter);

            AttributesToReturn attributesToReturn = ProvisioningUtil.createAttributesToReturn(entitlementOcDef,
                    resourceType);

            ResultHandler<ShadowType> handler = new ResultHandler<ShadowType>() {
                @Override
                public boolean handle(PrismObject<ShadowType> entitlementShadow) {
                    Collection<? extends ResourceAttribute<?>> identifiers = ShadowUtil
                            .getIdentifiers(entitlementShadow);
                    ResourceObjectDiscriminator disc = new ResourceObjectDiscriminator(entitlementOcDef,
                            identifiers);
                    Collection<Operation> operations = roMap.get(disc);
                    if (operations == null) {
                        operations = new ArrayList<Operation>();
                        roMap.put(disc, operations);
                    }

                    PropertyDelta<T> attributeDelta = null;
                    for (Operation operation : operations) {
                        if (operation instanceof PropertyModificationOperation) {
                            PropertyModificationOperation propOp = (PropertyModificationOperation) operation;
                            if (propOp.getPropertyDelta().getElementName().equals(assocAttrName)) {
                                attributeDelta = propOp.getPropertyDelta();
                            }
                        }
                    }
                    if (attributeDelta == null) {
                        attributeDelta = assocAttrDef
                                .createEmptyDelta(new ItemPath(ShadowType.F_ATTRIBUTES, assocAttrName));
                        PropertyModificationOperation attributeModification = new PropertyModificationOperation(
                                attributeDelta);
                        operations.add(attributeModification);
                    }

                    attributeDelta.addValuesToDelete(valueAttr.getClonedValues());
                    LOGGER.trace("Association in deleted shadow delta: {}", attributeDelta);

                    return true;
                }
            };
            try {
                LOGGER.trace("Searching for associations in deleted shadow, query: {}", query);
                connector.search(entitlementOcDef, query, handler, attributesToReturn, parentResult);
            } catch (TunnelException e) {
                throw (SchemaException) e.getCause();
            } catch (GenericFrameworkException e) {
                throw new GenericConnectorException(e.getMessage(), e);
            }

        }

    }

    /////////
    // common
    /////////

    private <T> void collectEntitlementToAttrsDelta(Map<QName, PropertyDelta<?>> deltaMap,
            Collection<PrismContainerValue<ShadowAssociationType>> set,
            RefinedObjectClassDefinition objectClassDefinition, ResourceType resource,
            ModificationType modificationType) throws SchemaException {
        if (set == null) {
            return;
        }
        for (PrismContainerValue<ShadowAssociationType> associationCVal : set) {
            collectEntitlementToAttrDelta(deltaMap, associationCVal, objectClassDefinition, resource,
                    modificationType);
        }
    }

    /**
     *  Collects entitlement changes from the shadow to entitlement section into attribute operations.
     *  Collects a single value.
     *  NOTE: only collects  SUBJECT_TO_ENTITLEMENT entitlement direction.
     */
    private <T> void collectEntitlementToAttrDelta(Map<QName, PropertyDelta<?>> deltaMap,
            PrismContainerValue<ShadowAssociationType> associationCVal,
            RefinedObjectClassDefinition objectClassDefinition, ResourceType resource,
            ModificationType modificationType) throws SchemaException {

        ShadowAssociationType associationType = associationCVal.asContainerable();
        QName associationName = associationType.getName();
        if (associationName == null) {
            throw new SchemaException("No name in entitlement association " + associationCVal);
        }
        RefinedAssociationDefinition assocDefType = objectClassDefinition
                .findEntitlementAssociation(associationName);
        if (assocDefType == null) {
            throw new SchemaException(
                    "No entitlement association with name " + associationName + " in schema of " + resource);
        }

        ResourceObjectAssociationDirectionType direction = assocDefType.getResourceObjectAssociationType()
                .getDirection();
        if (direction != ResourceObjectAssociationDirectionType.SUBJECT_TO_OBJECT) {
            // Process just this one direction. The other direction means modification of another object and
            // therefore will be processed later
            return;
        }

        QName assocAttrName = assocDefType.getResourceObjectAssociationType().getAssociationAttribute();
        if (assocAttrName == null) {
            throw new SchemaException("No association attribute definied in entitlement association '"
                    + associationName + "' in " + resource);
        }
        RefinedAttributeDefinition assocAttrDef = objectClassDefinition.findAttributeDefinition(assocAttrName);
        if (assocAttrDef == null) {
            throw new SchemaException(
                    "Association attribute '" + assocAttrName + "'definied in entitlement association '"
                            + associationName + "' was not found in schema for " + resource);
        }
        PropertyDelta<T> attributeDelta = (PropertyDelta<T>) deltaMap.get(assocAttrName);
        if (attributeDelta == null) {
            attributeDelta = assocAttrDef.createEmptyDelta(new ItemPath(ShadowType.F_ATTRIBUTES, assocAttrName));
            deltaMap.put(assocAttrName, attributeDelta);
        }

        QName valueAttrName = assocDefType.getResourceObjectAssociationType().getValueAttribute();
        if (valueAttrName == null) {
            throw new SchemaException("No value attribute definied in entitlement association '" + associationName
                    + "' in " + resource);
        }
        ResourceAttributeContainer identifiersContainer = ShadowUtil.getAttributesContainer(associationCVal,
                ShadowAssociationType.F_IDENTIFIERS);
        PrismProperty<T> valueAttr = identifiersContainer.findProperty(valueAttrName);
        if (valueAttr == null) {
            throw new SchemaException("No value attribute " + valueAttrName
                    + " present in entitlement association '" + associationName + "' in shadow for " + resource);
        }

        if (modificationType == ModificationType.ADD) {
            attributeDelta.addValuesToAdd(valueAttr.getClonedValues());
        } else if (modificationType == ModificationType.DELETE) {
            attributeDelta.addValuesToDelete(valueAttr.getClonedValues());
        } else if (modificationType == ModificationType.REPLACE) {
            // TODO: check if already exists
            attributeDelta.setValuesToReplace(valueAttr.getClonedValues());
        }
    }

    private <T> void collectEntitlementsAsObjectOperation(
            Map<ResourceObjectDiscriminator, Collection<Operation>> roMap,
            Collection<PrismContainerValue<ShadowAssociationType>> set,
            RefinedObjectClassDefinition objectClassDefinition, PrismObject<ShadowType> shadowBefore,
            PrismObject<ShadowType> shadowAfter, RefinedResourceSchema rSchema, ResourceType resource,
            ModificationType modificationType) throws SchemaException {
        if (set == null) {
            return;
        }
        for (PrismContainerValue<ShadowAssociationType> associationCVal : set) {
            collectEntitlementAsObjectOperation(roMap, associationCVal, objectClassDefinition, shadowBefore,
                    shadowAfter, rSchema, resource, modificationType);
        }
    }

    private <T> void collectEntitlementAsObjectOperation(
            Map<ResourceObjectDiscriminator, Collection<Operation>> roMap,
            PrismContainerValue<ShadowAssociationType> associationCVal,
            RefinedObjectClassDefinition objectClassDefinition, PrismObject<ShadowType> shadowBefore,
            PrismObject<ShadowType> shadowAfter, RefinedResourceSchema rSchema, ResourceType resource,
            ModificationType modificationType) throws SchemaException {

        ShadowAssociationType associationType = associationCVal.asContainerable();
        QName associationName = associationType.getName();
        if (associationName == null) {
            throw new SchemaException("No name in entitlement association " + associationCVal);
        }
        RefinedAssociationDefinition assocDefType = objectClassDefinition
                .findEntitlementAssociation(associationName);
        if (assocDefType == null) {
            throw new SchemaException(
                    "No entitlement association with name " + assocDefType + " in schema of " + resource);
        }

        ResourceObjectAssociationDirectionType direction = assocDefType.getResourceObjectAssociationType()
                .getDirection();
        if (direction != ResourceObjectAssociationDirectionType.OBJECT_TO_SUBJECT) {
            // Process just this one direction. The other direction was processed before
            return;
        }

        Collection<String> entitlementIntents = assocDefType.getIntents();
        if (entitlementIntents == null || entitlementIntents.isEmpty()) {
            throw new SchemaException(
                    "No entitlement intent specified in association " + associationCVal + " in " + resource);
        }
        RefinedObjectClassDefinition entitlementOcDef = rSchema.getRefinedDefinition(ShadowKindType.ENTITLEMENT,
                entitlementIntents);
        if (entitlementOcDef == null) {
            throw new SchemaException("No definition of entitlement intent(s) '" + entitlementIntents
                    + "' specified in association " + associationCVal + " in " + resource);
        }

        QName assocAttrName = assocDefType.getResourceObjectAssociationType().getAssociationAttribute();
        if (assocAttrName == null) {
            throw new SchemaException("No association attribute defined in entitlement association in " + resource);
        }

        RefinedAttributeDefinition assocAttrDef = entitlementOcDef.findAttributeDefinition(assocAttrName);
        if (assocAttrDef == null) {
            throw new SchemaException("Association attribute '" + assocAttrName
                    + "'defined in entitlement association was not found in entitlement intent(s) '"
                    + entitlementIntents + "' in schema for " + resource);
        }

        ResourceAttributeContainer identifiersContainer = ShadowUtil.getAttributesContainer(associationCVal,
                ShadowAssociationType.F_IDENTIFIERS);
        Collection<ResourceAttribute<?>> identifiers = identifiersContainer.getAttributes();

        ResourceObjectDiscriminator disc = new ResourceObjectDiscriminator(entitlementOcDef, identifiers);
        Collection<Operation> operations = roMap.get(disc);
        if (operations == null) {
            operations = new ArrayList<Operation>();
            roMap.put(disc, operations);
        }

        QName valueAttrName = assocDefType.getResourceObjectAssociationType().getValueAttribute();
        if (valueAttrName == null) {
            throw new SchemaException("No value attribute defined in entitlement association in " + resource);
        }

        // Which shadow would we use - shadowBefore or shadowAfter?
        //
        // If the operation is ADD or REPLACE, we use current version of the shadow (shadowAfter), because we want
        // to ensure that we add most-recent data to the subject.
        //
        // If the operation is DELETE, we have two possibilities:
        //  - if the resource provides referential integrity, the subject has already
        //    new data (because the object operation was already carried out), so we use shadowAfter
        //  - if the resource does not provide referential integrity, the subject has OLD data
        //    so we use shadowBefore
        PrismObject<ShadowType> shadow;
        if (modificationType != ModificationType.DELETE) {
            shadow = shadowAfter;
        } else {
            if (BooleanUtils
                    .isFalse(assocDefType.getResourceObjectAssociationType().isExplicitReferentialIntegrity())) {
                // i.e. resource has ref integrity by itself
                shadow = shadowAfter;
            } else {
                shadow = shadowBefore;
            }
        }

        ResourceAttribute<T> valueAttr = ShadowUtil.getAttribute(shadow, valueAttrName);
        if (valueAttr == null) {
            // TODO: check schema and try to fetch full shadow if necessary
            throw new SchemaException("No value attribute " + valueAttrName + " in shadow");
        }

        PropertyDelta<T> attributeDelta = null;
        for (Operation operation : operations) {
            if (operation instanceof PropertyModificationOperation) {
                PropertyModificationOperation propOp = (PropertyModificationOperation) operation;
                if (propOp.getPropertyDelta().getElementName().equals(assocAttrName)) {
                    attributeDelta = propOp.getPropertyDelta();
                }
            }
        }
        if (attributeDelta == null) {
            attributeDelta = assocAttrDef.createEmptyDelta(new ItemPath(ShadowType.F_ATTRIBUTES, assocAttrName));
            PropertyModificationOperation attributeModification = new PropertyModificationOperation(attributeDelta);
            operations.add(attributeModification);
        }

        if (modificationType == ModificationType.ADD) {
            attributeDelta.addValuesToAdd(valueAttr.getClonedValues());
        } else if (modificationType == ModificationType.DELETE) {
            attributeDelta.addValuesToDelete(valueAttr.getClonedValues());
        } else if (modificationType == ModificationType.REPLACE) {
            // TODO: check if already exists
            attributeDelta.setValuesToReplace(valueAttr.getClonedValues());
        }
    }

}