org.apache.syncope.core.rest.data.AbstractAttributableDataBinder.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.syncope.core.rest.data.AbstractAttributableDataBinder.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.syncope.core.rest.data;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.SyncopeClientCompositeException;
import org.apache.syncope.common.SyncopeClientException;
import org.apache.syncope.common.mod.AbstractAttributableMod;
import org.apache.syncope.common.mod.AbstractSubjectMod;
import org.apache.syncope.common.mod.AttributeMod;
import org.apache.syncope.common.to.AbstractAttributableTO;
import org.apache.syncope.common.to.AbstractSubjectTO;
import org.apache.syncope.common.to.AttributeTO;
import org.apache.syncope.common.types.AttributableType;
import org.apache.syncope.common.types.ClientExceptionType;
import org.apache.syncope.common.types.IntMappingType;
import org.apache.syncope.common.types.MappingPurpose;
import org.apache.syncope.common.types.ResourceOperation;
import org.apache.syncope.core.persistence.beans.AbstractAttr;
import org.apache.syncope.core.persistence.beans.AbstractAttrValue;
import org.apache.syncope.core.persistence.beans.AbstractAttributable;
import org.apache.syncope.core.persistence.beans.AbstractDerAttr;
import org.apache.syncope.core.persistence.beans.AbstractDerSchema;
import org.apache.syncope.core.persistence.beans.AbstractMappingItem;
import org.apache.syncope.core.persistence.beans.AbstractNormalSchema;
import org.apache.syncope.core.persistence.beans.AbstractSchema;
import org.apache.syncope.core.persistence.beans.AbstractSubject;
import org.apache.syncope.core.persistence.beans.AbstractVirAttr;
import org.apache.syncope.core.persistence.beans.AbstractVirSchema;
import org.apache.syncope.core.persistence.beans.ExternalResource;
import org.apache.syncope.core.persistence.beans.membership.MAttr;
import org.apache.syncope.core.persistence.beans.membership.MAttrTemplate;
import org.apache.syncope.core.persistence.beans.membership.MDerAttr;
import org.apache.syncope.core.persistence.beans.membership.MDerAttrTemplate;
import org.apache.syncope.core.persistence.beans.membership.MVirAttr;
import org.apache.syncope.core.persistence.beans.membership.MVirAttrTemplate;
import org.apache.syncope.core.persistence.beans.membership.Membership;
import org.apache.syncope.core.persistence.beans.role.RAttr;
import org.apache.syncope.core.persistence.beans.role.RAttrTemplate;
import org.apache.syncope.core.persistence.beans.role.RDerAttr;
import org.apache.syncope.core.persistence.beans.role.RDerAttrTemplate;
import org.apache.syncope.core.persistence.beans.role.RVirAttr;
import org.apache.syncope.core.persistence.beans.role.RVirAttrTemplate;
import org.apache.syncope.core.persistence.beans.role.SyncopeRole;
import org.apache.syncope.core.persistence.beans.user.UAttr;
import org.apache.syncope.core.persistence.beans.user.UDerAttr;
import org.apache.syncope.core.persistence.beans.user.UDerSchema;
import org.apache.syncope.core.persistence.beans.user.USchema;
import org.apache.syncope.core.persistence.beans.user.UVirAttr;
import org.apache.syncope.core.persistence.beans.user.UVirSchema;
import org.apache.syncope.core.persistence.dao.AttrDAO;
import org.apache.syncope.core.persistence.dao.AttrValueDAO;
import org.apache.syncope.core.persistence.dao.ConfDAO;
import org.apache.syncope.core.persistence.dao.DerAttrDAO;
import org.apache.syncope.core.persistence.dao.DerSchemaDAO;
import org.apache.syncope.core.persistence.dao.MembershipDAO;
import org.apache.syncope.core.persistence.dao.PolicyDAO;
import org.apache.syncope.core.persistence.dao.ResourceDAO;
import org.apache.syncope.core.persistence.dao.RoleDAO;
import org.apache.syncope.core.persistence.dao.SchemaDAO;
import org.apache.syncope.core.persistence.dao.UserDAO;
import org.apache.syncope.core.persistence.dao.VirAttrDAO;
import org.apache.syncope.core.persistence.dao.VirSchemaDAO;
import org.apache.syncope.core.persistence.validation.attrvalue.InvalidAttrValueException;
import org.apache.syncope.core.propagation.PropagationByResource;
import org.apache.syncope.core.util.AttributableUtil;
import org.apache.syncope.core.util.MappingUtil;
import org.apache.syncope.core.util.jexl.JexlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

public abstract class AbstractAttributableDataBinder {

    /**
     * Logger.
     */
    protected static final Logger LOG = LoggerFactory.getLogger(AbstractAttributableDataBinder.class);

    @Autowired
    protected ConfDAO confDAO;

    @Autowired
    protected RoleDAO roleDAO;

    @Autowired
    protected SchemaDAO schemaDAO;

    @Autowired
    protected DerSchemaDAO derSchemaDAO;

    @Autowired
    protected VirSchemaDAO virSchemaDAO;

    @Autowired
    protected AttrDAO attrDAO;

    @Autowired
    protected DerAttrDAO derAttrDAO;

    @Autowired
    protected VirAttrDAO virAttrDAO;

    @Autowired
    protected AttrValueDAO attributeValueDAO;

    @Autowired
    protected UserDAO userDAO;

    @Autowired
    protected ResourceDAO resourceDAO;

    @Autowired
    protected MembershipDAO membershipDAO;

    @Autowired
    protected PolicyDAO policyDAO;

    @SuppressWarnings("unchecked")
    protected <T extends AbstractSchema> T getSchema(final String schemaName, final Class<T> reference) {
        T result = null;

        if (AbstractNormalSchema.class.isAssignableFrom(reference)) {
            result = (T) getNormalSchema(schemaName, (Class<? extends AbstractNormalSchema>) reference);
        } else if (AbstractDerSchema.class.isAssignableFrom(reference)) {
            result = (T) getDerSchema(schemaName, (Class<? extends AbstractDerSchema>) reference);
        } else if (AbstractVirSchema.class.isAssignableFrom(reference)) {
            result = (T) getVirSchema(schemaName, (Class<? extends AbstractVirSchema>) reference);
        }

        return result;
    }

    protected <T extends AbstractNormalSchema> T getNormalSchema(final String schemaName,
            final Class<T> reference) {
        T schema = null;
        if (StringUtils.isNotBlank(schemaName)) {
            schema = schemaDAO.find(schemaName, reference);

            // safely ignore invalid schemas from AttributeTO
            // see http://code.google.com/p/syncope/issues/detail?id=17
            if (schema == null) {
                LOG.debug("Ignoring invalid schema {}", schemaName);
            } else if (schema.isReadonly()) {
                schema = null;

                LOG.debug("Ignoring readonly schema {}", schemaName);
            }
        }

        return schema;
    }

    private <T extends AbstractDerSchema> T getDerSchema(final String derSchemaName, final Class<T> reference) {
        T derivedSchema = null;
        if (StringUtils.isNotBlank(derSchemaName)) {
            derivedSchema = derSchemaDAO.find(derSchemaName, reference);
            if (derivedSchema == null) {
                LOG.debug("Ignoring invalid derived schema {}", derSchemaName);
            }
        }

        return derivedSchema;
    }

    private <T extends AbstractVirSchema> T getVirSchema(final String virSchemaName, final Class<T> reference) {
        T virtualSchema = null;
        if (StringUtils.isNotBlank(virSchemaName)) {
            virtualSchema = virSchemaDAO.find(virSchemaName, reference);

            if (virtualSchema == null) {
                LOG.debug("Ignoring invalid virtual schema {}", virSchemaName);
            }
        }

        return virtualSchema;
    }

    private ExternalResource getResource(final String resourceName) {
        ExternalResource resource = resourceDAO.find(resourceName);
        if (resource == null) {
            LOG.debug("Ignoring invalid resource {} ", resourceName);
        }

        return resource;
    }

    protected void fillAttribute(final List<String> values, final AttributableUtil attributableUtil,
            final AbstractNormalSchema schema, final AbstractAttr attr,
            final SyncopeClientException invalidValues) {

        // if schema is multivalue, all values are considered for addition;
        // otherwise only the fist one - if provided - is considered
        List<String> valuesProvided = schema.isMultivalue() ? values
                : (values.isEmpty() ? Collections.<String>emptyList()
                        : Collections.singletonList(values.iterator().next()));

        for (String value : valuesProvided) {
            if (value == null || value.isEmpty()) {
                LOG.debug("Null value for {}, ignoring", schema.getName());
            } else {
                try {
                    attr.addValue(value, attributableUtil);
                } catch (InvalidAttrValueException e) {
                    LOG.error("Invalid value for attribute " + schema.getName() + ": " + value, e);

                    invalidValues.getElements().add(schema.getName() + ": " + value + " - " + e.getMessage());
                }
            }
        }
    }

    private boolean evaluateMandatoryCondition(final AttributableUtil attrUtil, final ExternalResource resource,
            final AbstractAttributable attributable, final String intAttrName,
            final IntMappingType intMappingType) {

        boolean result = false;

        final List<AbstractMappingItem> mappings = MappingUtil.getMatchingMappingItems(
                attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION), intAttrName, intMappingType);
        for (Iterator<AbstractMappingItem> itor = mappings.iterator(); itor.hasNext() && !result;) {
            final AbstractMappingItem mapping = itor.next();
            result |= JexlUtil.evaluateMandatoryCondition(mapping.getMandatoryCondition(), attributable);
        }

        return result;
    }

    private boolean evaluateMandatoryCondition(final AttributableUtil attrUtil,
            final AbstractAttributable attributable, final String intAttrName,
            final IntMappingType intMappingType) {

        boolean result = false;

        if (attributable instanceof AbstractSubject) {
            for (Iterator<ExternalResource> itor = ((AbstractSubject) attributable).getResources().iterator(); itor
                    .hasNext() && !result;) {

                final ExternalResource resource = itor.next();
                if (resource.isEnforceMandatoryCondition()) {
                    result |= evaluateMandatoryCondition(attrUtil, resource, attributable, intAttrName,
                            intMappingType);
                }
            }
        }

        return result;
    }

    private SyncopeClientException checkMandatory(final AttributableUtil attrUtil,
            final AbstractAttributable attributable) {

        SyncopeClientException reqValMissing = SyncopeClientException
                .build(ClientExceptionType.RequiredValuesMissing);

        // Check if there is some mandatory schema defined for which no value has been provided
        List<? extends AbstractNormalSchema> normalSchemas;
        switch (attrUtil.getType()) {
        case ROLE:
            normalSchemas = ((SyncopeRole) attributable).getAttrTemplateSchemas(RAttrTemplate.class);
            break;

        case MEMBERSHIP:
            normalSchemas = ((Membership) attributable).getSyncopeRole()
                    .getAttrTemplateSchemas(MAttrTemplate.class);
            break;

        case USER:
        default:
            normalSchemas = schemaDAO.findAll(attrUtil.schemaClass());
        }
        for (AbstractNormalSchema schema : normalSchemas) {
            if (attributable.getAttr(schema.getName()) == null && !schema.isReadonly()
                    && (JexlUtil.evaluateMandatoryCondition(schema.getMandatoryCondition(), attributable)
                            || evaluateMandatoryCondition(attrUtil, attributable, schema.getName(),
                                    attrUtil.intMappingType()))) {

                LOG.error("Mandatory schema " + schema.getName() + " not provided with values");

                reqValMissing.getElements().add(schema.getName());
            }
        }

        List<? extends AbstractDerSchema> derSchemas;
        switch (attrUtil.getType()) {
        case ROLE:
            derSchemas = ((SyncopeRole) attributable).getAttrTemplateSchemas(RDerAttrTemplate.class);
            break;

        case MEMBERSHIP:
            derSchemas = ((Membership) attributable).getSyncopeRole()
                    .getAttrTemplateSchemas(MDerAttrTemplate.class);
            break;

        case USER:
        default:
            derSchemas = derSchemaDAO.findAll(attrUtil.derSchemaClass());
        }
        for (AbstractDerSchema derSchema : derSchemas) {
            if (attributable.getDerAttr(derSchema.getName()) == null && evaluateMandatoryCondition(attrUtil,
                    attributable, derSchema.getName(), attrUtil.derIntMappingType())) {

                LOG.error("Mandatory derived schema " + derSchema.getName() + " does not evaluate to any value");

                reqValMissing.getElements().add(derSchema.getName());
            }
        }

        List<? extends AbstractVirSchema> virSchemas;
        switch (attrUtil.getType()) {
        case ROLE:
            virSchemas = ((SyncopeRole) attributable).getAttrTemplateSchemas(RVirAttrTemplate.class);
            break;

        case MEMBERSHIP:
            virSchemas = ((Membership) attributable).getSyncopeRole()
                    .getAttrTemplateSchemas(MVirAttrTemplate.class);
            break;

        case USER:
        default:
            virSchemas = virSchemaDAO.findAll(attrUtil.virSchemaClass());
        }
        for (AbstractVirSchema virSchema : virSchemas) {
            if (attributable.getVirAttr(virSchema.getName()) == null && !virSchema.isReadonly()
                    && evaluateMandatoryCondition(attrUtil, attributable, virSchema.getName(),
                            attrUtil.virIntMappingType())) {

                LOG.error("Mandatory virtual schema " + virSchema.getName() + " not provided with values");

                reqValMissing.getElements().add(virSchema.getName());
            }
        }

        return reqValMissing;
    }

    private void setAttrSchema(final AbstractAttributable attributable, final AbstractAttr attr,
            final AbstractNormalSchema schema) {

        if (attr instanceof UAttr) {
            ((UAttr) attr).setSchema((USchema) schema);
        } else if (attr instanceof RAttr) {
            RAttrTemplate template = ((SyncopeRole) attributable).getAttrTemplate(RAttrTemplate.class,
                    schema.getName());
            if (template != null) {
                ((RAttr) attr).setTemplate(template);
            }
        } else if (attr instanceof MAttr) {
            MAttrTemplate template = ((Membership) attributable).getSyncopeRole()
                    .getAttrTemplate(MAttrTemplate.class, schema.getName());
            if (template != null) {
                ((MAttr) attr).setTemplate(template);
            }
        }
    }

    private void setDerAttrSchema(final AbstractAttributable attributable, final AbstractDerAttr derAttr,
            final AbstractDerSchema derSchema) {

        if (derAttr instanceof UDerAttr) {
            ((UDerAttr) derAttr).setSchema((UDerSchema) derSchema);
        } else if (derAttr instanceof RDerAttr) {
            RDerAttrTemplate template = ((SyncopeRole) attributable).getAttrTemplate(RDerAttrTemplate.class,
                    derSchema.getName());
            if (template != null) {
                ((RDerAttr) derAttr).setTemplate(template);
            }
        } else if (derAttr instanceof MDerAttr) {
            MDerAttrTemplate template = ((Membership) attributable).getSyncopeRole()
                    .getAttrTemplate(MDerAttrTemplate.class, derSchema.getName());
            if (template != null) {
                ((MDerAttr) derAttr).setTemplate(template);
            }
        }
    }

    private void setVirAttrSchema(final AbstractAttributable attributable, final AbstractVirAttr virAttr,
            final AbstractVirSchema virSchema) {

        if (virAttr instanceof UVirAttr) {
            ((UVirAttr) virAttr).setSchema((UVirSchema) virSchema);
        } else if (virAttr instanceof RVirAttr) {
            RVirAttrTemplate template = ((SyncopeRole) attributable).getAttrTemplate(RVirAttrTemplate.class,
                    virSchema.getName());
            if (template != null) {
                ((RVirAttr) virAttr).setTemplate(template);
            }
        } else if (virAttr instanceof MVirAttr) {
            MVirAttrTemplate template = ((Membership) attributable).getSyncopeRole()
                    .getAttrTemplate(MVirAttrTemplate.class, virSchema.getName());
            if (template != null) {
                ((MVirAttr) virAttr).setTemplate(template);
            }
        }
    }

    public PropagationByResource fillVirtual(final AbstractAttributable attributable,
            final Set<String> vAttrsToBeRemoved, final Set<AttributeMod> vAttrsToBeUpdated,
            final AttributableUtil attrUtil) {

        PropagationByResource propByRes = new PropagationByResource();

        final Set<ExternalResource> externalResources = new HashSet<ExternalResource>();
        if (attributable instanceof AbstractSubject) {
            externalResources.addAll(((AbstractSubject) attributable).getResources());
        }

        if (attributable instanceof Membership) {
            externalResources.clear();
            externalResources.addAll(((Membership) attributable).getSyncopeUser().getResources());
        }

        // 1. virtual attributes to be removed
        for (String vAttrToBeRemoved : vAttrsToBeRemoved) {
            AbstractVirSchema virSchema = getVirSchema(vAttrToBeRemoved, attrUtil.virSchemaClass());
            if (virSchema != null) {
                AbstractVirAttr virAttr = attributable.getVirAttr(virSchema.getName());
                if (virAttr == null) {
                    LOG.debug("No virtual attribute found for schema {}", virSchema.getName());
                } else {
                    attributable.removeVirAttr(virAttr);
                    virAttrDAO.delete(virAttr);
                }

                for (ExternalResource resource : resourceDAO.findAll()) {
                    for (AbstractMappingItem mapItem : attrUtil.getMappingItems(resource,
                            MappingPurpose.PROPAGATION)) {
                        if (virSchema.getName().equals(mapItem.getIntAttrName())
                                && mapItem.getIntMappingType() == attrUtil.virIntMappingType()
                                && externalResources.contains(resource)) {

                            propByRes.add(ResourceOperation.UPDATE, resource.getName());

                            // Using virtual attribute as AccountId must be avoided
                            if (mapItem.isAccountid() && virAttr != null && !virAttr.getValues().isEmpty()) {
                                propByRes.addOldAccountId(resource.getName(), virAttr.getValues().get(0));
                            }
                        }
                    }
                }
            }
        }

        LOG.debug("Virtual attributes to be removed:\n{}", propByRes);

        // 2. virtual attributes to be updated
        for (AttributeMod vAttrToBeUpdated : vAttrsToBeUpdated) {
            AbstractVirSchema virSchema = getVirSchema(vAttrToBeUpdated.getSchema(), attrUtil.virSchemaClass());
            AbstractVirAttr virAttr = null;
            if (virSchema != null) {
                virAttr = attributable.getVirAttr(virSchema.getName());
                if (virAttr == null) {
                    virAttr = attrUtil.newVirAttr();
                    setVirAttrSchema(attributable, virAttr, virSchema);
                    if (virAttr.getSchema() == null) {
                        LOG.debug("Ignoring {} because no valid schema or template was found", vAttrToBeUpdated);
                    } else {
                        attributable.addVirAttr(virAttr);
                    }
                }
            }

            if (virSchema != null && virAttr != null && virAttr.getSchema() != null) {
                for (ExternalResource resource : resourceDAO.findAll()) {
                    for (AbstractMappingItem mapItem : attrUtil.getMappingItems(resource,
                            MappingPurpose.PROPAGATION)) {
                        if (virSchema.getName().equals(mapItem.getIntAttrName())
                                && mapItem.getIntMappingType() == attrUtil.virIntMappingType()
                                && externalResources.contains(resource)) {

                            propByRes.add(ResourceOperation.UPDATE, resource.getName());
                        }
                    }
                }

                final List<String> values = new ArrayList<String>(virAttr.getValues());
                values.removeAll(vAttrToBeUpdated.getValuesToBeRemoved());
                values.addAll(vAttrToBeUpdated.getValuesToBeAdded());

                virAttr.setValues(values);

                // Owner cannot be specified before otherwise a virtual attribute remove will be invalidated.
                virAttr.setOwner(attributable);
            }
        }

        LOG.debug("Virtual attributes to be added:\n{}", propByRes);

        return propByRes;
    }

    protected PropagationByResource fill(final AbstractAttributable attributable,
            final AbstractAttributableMod attributableMod, final AttributableUtil attrUtil,
            final SyncopeClientCompositeException scce) {

        PropagationByResource propByRes = new PropagationByResource();

        SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);

        if (attributable instanceof AbstractSubject && attributableMod instanceof AbstractSubjectMod) {
            // 1. resources to be removed
            for (String resourceToBeRemoved : ((AbstractSubjectMod) attributableMod).getResourcesToRemove()) {
                ExternalResource resource = getResource(resourceToBeRemoved);
                if (resource != null) {
                    propByRes.add(ResourceOperation.DELETE, resource.getName());
                    ((AbstractSubject) attributable).removeResource(resource);
                }
            }

            LOG.debug("Resources to be removed:\n{}", propByRes);

            // 2. resources to be added
            for (String resourceToBeAdded : ((AbstractSubjectMod) attributableMod).getResourcesToAdd()) {
                ExternalResource resource = getResource(resourceToBeAdded);
                if (resource != null) {
                    propByRes.add(ResourceOperation.CREATE, resource.getName());
                    ((AbstractSubject) attributable).addResource(resource);
                }
            }

            LOG.debug("Resources to be added:\n{}", propByRes);
        }

        // 3. attributes to be removed
        for (String attributeToBeRemoved : attributableMod.getAttrsToRemove()) {
            AbstractNormalSchema schema = getNormalSchema(attributeToBeRemoved, attrUtil.schemaClass());
            if (schema != null) {
                AbstractAttr attr = attributable.getAttr(schema.getName());
                if (attr == null) {
                    LOG.debug("No attribute found for schema {}", schema);
                } else {
                    String newValue = null;
                    for (AttributeMod mod : attributableMod.getAttrsToUpdate()) {
                        if (schema.getName().equals(mod.getSchema())) {
                            newValue = mod.getValuesToBeAdded().get(0);
                        }
                    }

                    if (!schema.isUniqueConstraint()
                            || (!attr.getUniqueValue().getStringValue().equals(newValue))) {

                        attributable.removeAttr(attr);
                        attrDAO.delete(attr.getId(), attrUtil.attrClass());
                    }
                }

                if (attributable instanceof AbstractSubject) {
                    for (ExternalResource resource : resourceDAO.findAll()) {
                        for (AbstractMappingItem mapItem : attrUtil.getMappingItems(resource,
                                MappingPurpose.PROPAGATION)) {
                            if (schema.getName().equals(mapItem.getIntAttrName())
                                    && mapItem.getIntMappingType() == attrUtil.intMappingType()
                                    && ((AbstractSubject) attributable).getResources().contains(resource)) {

                                propByRes.add(ResourceOperation.UPDATE, resource.getName());

                                if (mapItem.isAccountid() && attr != null && !attr.getValuesAsStrings().isEmpty()) {

                                    propByRes.addOldAccountId(resource.getName(),
                                            attr.getValuesAsStrings().iterator().next());
                                }
                            }
                        }
                    }
                }
            }
        }

        LOG.debug("Attributes to be removed:\n{}", propByRes);

        // 4. attributes to be updated
        for (AttributeMod attributeMod : attributableMod.getAttrsToUpdate()) {
            AbstractNormalSchema schema = getNormalSchema(attributeMod.getSchema(), attrUtil.schemaClass());
            AbstractAttr attr = null;
            if (schema != null) {
                attr = attributable.getAttr(schema.getName());
                if (attr == null) {
                    attr = attrUtil.newAttr();
                    setAttrSchema(attributable, attr, schema);
                    if (attr.getSchema() == null) {
                        LOG.debug("Ignoring {} because no valid schema or template was found", attributeMod);
                    } else {
                        attr.setOwner(attributable);
                        attributable.addAttr(attr);
                    }
                }
            }

            if (schema != null && attr != null && attr.getSchema() != null) {
                if (attributable instanceof AbstractSubject) {
                    for (ExternalResource resource : resourceDAO.findAll()) {
                        for (AbstractMappingItem mapItem : attrUtil.getMappingItems(resource,
                                MappingPurpose.PROPAGATION)) {
                            if (schema.getName().equals(mapItem.getIntAttrName())
                                    && mapItem.getIntMappingType() == attrUtil.intMappingType()
                                    && ((AbstractSubject) attributable).getResources().contains(resource)) {

                                propByRes.add(ResourceOperation.UPDATE, resource.getName());
                            }
                        }
                    }
                }

                // 1.1 remove values
                Set<Long> valuesToBeRemoved = new HashSet<Long>();
                for (String valueToBeRemoved : attributeMod.getValuesToBeRemoved()) {
                    if (attr.getSchema().isUniqueConstraint()) {
                        if (attr.getUniqueValue() != null
                                && valueToBeRemoved.equals(attr.getUniqueValue().getValueAsString())) {

                            valuesToBeRemoved.add(attr.getUniqueValue().getId());
                        }
                    } else {
                        for (AbstractAttrValue mav : attr.getValues()) {
                            if (valueToBeRemoved.equals(mav.getValueAsString())) {
                                valuesToBeRemoved.add(mav.getId());
                            }
                        }
                    }
                }
                for (Long attributeValueId : valuesToBeRemoved) {
                    attributeValueDAO.delete(attributeValueId, attrUtil.attrValueClass());
                }

                // 1.2 add values
                List<String> valuesToBeAdded = attributeMod.getValuesToBeAdded();
                if (valuesToBeAdded != null && !valuesToBeAdded.isEmpty() && (!schema.isUniqueConstraint()
                        || attr.getUniqueValue() == null
                        || !valuesToBeAdded.iterator().next().equals(attr.getUniqueValue().getValueAsString()))) {

                    fillAttribute(attributeMod.getValuesToBeAdded(), attrUtil, schema, attr, invalidValues);
                }

                // if no values are in, the attribute can be safely removed
                if (attr.getValuesAsStrings().isEmpty()) {
                    attrDAO.delete(attr);
                }
            }
        }

        if (!invalidValues.isEmpty()) {
            scce.addException(invalidValues);
        }

        LOG.debug("Attributes to be updated:\n{}", propByRes);

        // 5. derived attributes to be removed
        for (String derAttrToBeRemoved : attributableMod.getDerAttrsToRemove()) {
            AbstractDerSchema derSchema = getDerSchema(derAttrToBeRemoved, attrUtil.derSchemaClass());
            if (derSchema != null) {
                AbstractDerAttr derAttr = attributable.getDerAttr(derSchema.getName());
                if (derAttr == null) {
                    LOG.debug("No derived attribute found for schema {}", derSchema.getName());
                } else {
                    derAttrDAO.delete(derAttr);
                }

                if (attributable instanceof AbstractSubject) {
                    for (ExternalResource resource : resourceDAO.findAll()) {
                        for (AbstractMappingItem mapItem : attrUtil.getMappingItems(resource,
                                MappingPurpose.PROPAGATION)) {
                            if (derSchema.getName().equals(mapItem.getIntAttrName())
                                    && mapItem.getIntMappingType() == attrUtil.derIntMappingType()
                                    && ((AbstractSubject) attributable).getResources().contains(resource)) {

                                propByRes.add(ResourceOperation.UPDATE, resource.getName());

                                if (mapItem.isAccountid() && derAttr != null
                                        && !derAttr.getValue(attributable.getAttrs()).isEmpty()) {

                                    propByRes.addOldAccountId(resource.getName(),
                                            derAttr.getValue(attributable.getAttrs()));
                                }
                            }
                        }
                    }
                }
            }
        }

        LOG.debug("Derived attributes to be removed:\n{}", propByRes);

        // 6. derived attributes to be added
        for (String derAttrToBeAdded : attributableMod.getDerAttrsToAdd()) {
            AbstractDerSchema derSchema = getDerSchema(derAttrToBeAdded, attrUtil.derSchemaClass());
            if (derSchema != null) {
                if (attributable instanceof AbstractSubject) {
                    for (ExternalResource resource : resourceDAO.findAll()) {
                        for (AbstractMappingItem mapItem : attrUtil.getMappingItems(resource,
                                MappingPurpose.PROPAGATION)) {
                            if (derSchema.getName().equals(mapItem.getIntAttrName())
                                    && mapItem.getIntMappingType() == attrUtil.derIntMappingType()
                                    && ((AbstractSubject) attributable).getResources().contains(resource)) {

                                propByRes.add(ResourceOperation.UPDATE, resource.getName());
                            }
                        }
                    }
                }

                AbstractDerAttr derAttr = attrUtil.newDerAttr();
                setDerAttrSchema(attributable, derAttr, derSchema);
                if (derAttr.getSchema() == null) {
                    LOG.debug("Ignoring {} because no valid schema or template was found", derAttrToBeAdded);
                } else {
                    derAttr.setOwner(attributable);
                    attributable.addDerAttr(derAttr);
                }
            }
        }

        LOG.debug("Derived attributes to be added:\n{}", propByRes);

        // 7. virtual attributes: for users and roles this is delegated to PropagationManager
        if (AttributableType.USER != attrUtil.getType() && AttributableType.ROLE != attrUtil.getType()) {
            fillVirtual(attributable, attributableMod.getVirAttrsToRemove(), attributableMod.getVirAttrsToUpdate(),
                    attrUtil);
        }

        // Finally, check if mandatory values are missing
        SyncopeClientException requiredValuesMissing = checkMandatory(attrUtil, attributable);
        if (!requiredValuesMissing.isEmpty()) {
            scce.addException(requiredValuesMissing);
        }

        // Throw composite exception if there is at least one element set in the composing exceptions
        if (scce.hasExceptions()) {
            throw scce;
        }

        return propByRes;
    }

    /**
     * Add virtual attributes and specify values to be propagated.
     *
     * @param attributable attributable.
     * @param vAttrs virtual attributes to be added.
     * @param attrUtil attributable util.
     */
    public void fillVirtual(final AbstractAttributable attributable, final Collection<AttributeTO> vAttrs,
            final AttributableUtil attrUtil) {

        for (AttributeTO attributeTO : vAttrs) {
            AbstractVirAttr virAttr = attributable.getVirAttr(attributeTO.getSchema());
            if (virAttr == null) {
                AbstractVirSchema virSchema = getVirSchema(attributeTO.getSchema(), attrUtil.virSchemaClass());
                if (virSchema != null) {
                    virAttr = attrUtil.newVirAttr();
                    setVirAttrSchema(attributable, virAttr, virSchema);
                    if (virAttr.getSchema() == null) {
                        LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
                    } else {
                        virAttr.setOwner(attributable);
                        attributable.addVirAttr(virAttr);
                        virAttr.setValues(attributeTO.getValues());
                    }
                }
            } else {
                virAttr.setValues(attributeTO.getValues());
            }
        }
    }

    protected void fill(final AbstractAttributable attributable, final AbstractAttributableTO attributableTO,
            final AttributableUtil attributableUtil, final SyncopeClientCompositeException scce) {

        // 1. attributes
        SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);

        // Only consider attributeTO with values
        for (AttributeTO attributeTO : attributableTO.getAttrs()) {
            if (attributeTO.getValues() != null && !attributeTO.getValues().isEmpty()) {
                AbstractNormalSchema schema = getNormalSchema(attributeTO.getSchema(),
                        attributableUtil.schemaClass());

                if (schema != null) {
                    AbstractAttr attr = attributable.getAttr(schema.getName());
                    if (attr == null) {
                        attr = attributableUtil.newAttr();
                        setAttrSchema(attributable, attr, schema);
                    }
                    if (attr.getSchema() == null) {
                        LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
                    } else {
                        fillAttribute(attributeTO.getValues(), attributableUtil, schema, attr, invalidValues);

                        if (!attr.getValuesAsStrings().isEmpty()) {
                            attributable.addAttr(attr);
                            attr.setOwner(attributable);
                        }
                    }
                }
            }
        }

        if (!invalidValues.isEmpty()) {
            scce.addException(invalidValues);
        }

        // 2. derived attributes
        for (AttributeTO attributeTO : attributableTO.getDerAttrs()) {
            AbstractDerSchema derSchema = getDerSchema(attributeTO.getSchema(), attributableUtil.derSchemaClass());

            if (derSchema != null) {
                AbstractDerAttr derAttr = attributableUtil.newDerAttr();
                setDerAttrSchema(attributable, derAttr, derSchema);
                if (derAttr.getSchema() == null) {
                    LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
                } else {
                    derAttr.setOwner(attributable);
                    attributable.addDerAttr(derAttr);
                }
            }
        }

        // 3. user and role virtual attributes will be evaluated by the propagation manager only (if needed).
        if (AttributableType.USER == attributableUtil.getType()
                || AttributableType.ROLE == attributableUtil.getType()) {

            for (AttributeTO vattrTO : attributableTO.getVirAttrs()) {
                AbstractVirSchema virSchema = getVirSchema(vattrTO.getSchema(), attributableUtil.virSchemaClass());

                if (virSchema != null) {
                    AbstractVirAttr virAttr = attributableUtil.newVirAttr();
                    setVirAttrSchema(attributable, virAttr, virSchema);
                    if (virAttr.getSchema() == null) {
                        LOG.debug("Ignoring {} because no valid schema or template was found", vattrTO);
                    } else {
                        virAttr.setOwner(attributable);
                        attributable.addVirAttr(virAttr);
                    }
                }
            }
        }

        fillVirtual(attributable, attributableTO.getVirAttrs(), attributableUtil);

        // 4. resources
        if (attributable instanceof AbstractSubject && attributableTO instanceof AbstractSubjectTO) {
            for (String resourceName : ((AbstractSubjectTO) attributableTO).getResources()) {
                ExternalResource resource = getResource(resourceName);

                if (resource != null) {
                    ((AbstractSubject) attributable).addResource(resource);
                }
            }
        }

        SyncopeClientException requiredValuesMissing = checkMandatory(attributableUtil, attributable);
        if (!requiredValuesMissing.isEmpty()) {
            scce.addException(requiredValuesMissing);
        }

        // Throw composite exception if there is at least one element set in the composing exceptions
        if (scce.hasExceptions()) {
            throw scce;
        }
    }

    protected void fillTO(final AbstractAttributableTO attributableTO,
            final Collection<? extends AbstractAttr> attrs, final Collection<? extends AbstractDerAttr> derAttrs,
            final Collection<? extends AbstractVirAttr> virAttrs, final Collection<ExternalResource> resources) {

        AttributeTO attributeTO;
        for (AbstractAttr attr : attrs) {
            attributeTO = new AttributeTO();
            attributeTO.setSchema(attr.getSchema().getName());
            attributeTO.getValues().addAll(attr.getValuesAsStrings());
            attributeTO.setReadonly(attr.getSchema().isReadonly());

            attributableTO.getAttrs().add(attributeTO);
        }

        for (AbstractDerAttr derAttr : derAttrs) {
            attributeTO = new AttributeTO();
            attributeTO.setSchema(derAttr.getSchema().getName());
            attributeTO.getValues().add(derAttr.getValue(attrs));
            attributeTO.setReadonly(true);

            attributableTO.getDerAttrs().add(attributeTO);
        }

        for (AbstractVirAttr virAttr : virAttrs) {
            attributeTO = new AttributeTO();
            attributeTO.setSchema(virAttr.getSchema().getName());
            attributeTO.getValues().addAll(virAttr.getValues());
            attributeTO.setReadonly(virAttr.getSchema().isReadonly());

            attributableTO.getVirAttrs().add(attributeTO);
        }

        if (attributableTO instanceof AbstractSubjectTO) {
            for (ExternalResource resource : resources) {
                ((AbstractSubjectTO) attributableTO).getResources().add(resource.getName());
            }
        }
    }
}