org.apache.syncope.core.misc.ConnObjectUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.syncope.core.misc.ConnObjectUtil.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.misc;

import org.apache.syncope.core.misc.policy.InvalidPasswordPolicySpecException;
import org.apache.syncope.core.misc.security.PasswordGenerator;
import org.apache.syncope.core.misc.security.SecureRandomUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.AttributableOperations;
import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
import org.apache.syncope.common.lib.to.AbstractAttributableTO;
import org.apache.syncope.common.lib.to.AbstractSubjectTO;
import org.apache.syncope.common.lib.to.AttrTO;
import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.MembershipTO;
import org.apache.syncope.common.lib.to.RoleTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AttrSchemaType;
import org.apache.syncope.common.lib.types.AttributableType;
import org.apache.syncope.common.lib.types.IntMappingType;
import org.apache.syncope.common.lib.types.MappingPurpose;
import org.apache.syncope.common.lib.types.PasswordPolicySpec;
import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
import org.apache.syncope.core.persistence.api.dao.RoleDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.Attributable;
import org.apache.syncope.core.persistence.api.entity.AttributableUtil;
import org.apache.syncope.core.persistence.api.entity.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.MappingItem;
import org.apache.syncope.core.persistence.api.entity.PasswordPolicy;
import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
import org.apache.syncope.core.persistence.api.entity.PlainSchema;
import org.apache.syncope.core.persistence.api.entity.Subject;
import org.apache.syncope.core.persistence.api.entity.VirAttr;
import org.apache.syncope.core.persistence.api.entity.membership.Membership;
import org.apache.syncope.core.persistence.api.entity.role.Role;
import org.apache.syncope.core.persistence.api.entity.task.SyncTask;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.Connector;
import org.apache.syncope.core.provisioning.api.ConnectorFactory;
import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheValue;
import org.apache.syncope.core.misc.security.Encryptor;
import org.apache.syncope.core.misc.security.UnauthorizedRoleException;
import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
import org.apache.syncope.core.misc.jexl.JexlUtil;
import org.identityconnectors.common.Base64;
import org.identityconnectors.common.security.GuardedByteArray;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class ConnObjectUtil {

    /**
     * Logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(ConnObjectUtil.class);

    @Autowired
    private PolicyDAO policyDAO;

    @Autowired
    private UserDAO userDAO;

    @Autowired
    private RoleDAO roleDAO;

    @Autowired
    private ExternalResourceDAO resourceDAO;

    @Autowired
    private PlainSchemaDAO plainSchemaDAO;

    @Autowired
    private PasswordGenerator pwdGen;

    private final Encryptor encryptor = Encryptor.getInstance();

    /**
     * Virtual attribute cache.
     */
    @Autowired
    private VirAttrCache virAttrCache;

    public ObjectClass fromSubject(final Subject<?, ?, ?> subject) {
        if (subject == null) {
            throw new IllegalArgumentException("No ObjectClass could be provided for " + subject);
        }

        ObjectClass result = null;
        if (subject instanceof User) {
            result = ObjectClass.ACCOUNT;
        }
        if (subject instanceof Role) {
            result = ObjectClass.GROUP;
        }

        return result;
    }

    /**
     * Build a UserTO / RoleTO out of connector object attributes and schema mapping.
     *
     * @param obj connector object
     * @param syncTask synchronization task
     * @param attrUtil AttributableUtil
     * @param <T> user/role
     * @return UserTO for the user to be created
     */
    @Transactional(readOnly = true)
    public <T extends AbstractSubjectTO> T getSubjectTO(final ConnectorObject obj, final SyncTask syncTask,
            final AttributableUtil attrUtil) {

        T subjectTO = getSubjectTOFromConnObject(obj, syncTask, attrUtil);

        // (for users) if password was not set above, generate
        if (subjectTO instanceof UserTO && StringUtils.isBlank(((UserTO) subjectTO).getPassword())) {
            final UserTO userTO = (UserTO) subjectTO;

            List<PasswordPolicySpec> ppSpecs = new ArrayList<>();

            PasswordPolicy globalPP = policyDAO.getGlobalPasswordPolicy();
            if (globalPP != null && globalPP.getSpecification(PasswordPolicySpec.class) != null) {
                ppSpecs.add(globalPP.getSpecification(PasswordPolicySpec.class));
            }

            for (MembershipTO memb : userTO.getMemberships()) {
                Role role = roleDAO.find(memb.getRoleId());
                if (role != null && role.getPasswordPolicy() != null
                        && role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) {

                    ppSpecs.add(role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class));
                }
            }

            for (String resName : userTO.getResources()) {
                ExternalResource resource = resourceDAO.find(resName);
                if (resource != null && resource.getPasswordPolicy() != null
                        && resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) {

                    ppSpecs.add(resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class));
                }
            }

            String password;
            try {
                password = pwdGen.generate(ppSpecs);
            } catch (InvalidPasswordPolicySpecException e) {
                LOG.error("Could not generate policy-compliant random password for {}", userTO, e);

                password = SecureRandomUtil.generateRandomPassword(16);
            }
            userTO.setPassword(password);
        }

        return subjectTO;
    }

    /**
     * Build an UserMod out of connector object attributes and schema mapping.
     *
     * @param key user to be updated
     * @param obj connector object
     * @param original subject to get diff from
     * @param syncTask synchronization task
     * @param attrUtil AttributableUtil
     * @param <T> user/role
     * @return modifications for the user/role to be updated
     * @throws NotFoundException if given id does not correspond to a T instance
     * @throws UnauthorizedRoleException if there are no enough entitlements to access the T instance
     */
    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true)
    public <T extends AbstractAttributableMod> T getAttributableMod(final Long key, final ConnectorObject obj,
            final AbstractAttributableTO original, final SyncTask syncTask, final AttributableUtil attrUtil)
            throws NotFoundException, UnauthorizedRoleException {

        final AbstractAttributableTO updated = getSubjectTOFromConnObject(obj, syncTask, attrUtil);
        updated.setKey(key);

        if (AttributableType.USER == attrUtil.getType()) {
            // update password if and only if password is really changed
            final User user = userDAO.authFetch(key);
            if (StringUtils.isBlank(((UserTO) updated).getPassword()) || encryptor
                    .verify(((UserTO) updated).getPassword(), user.getCipherAlgorithm(), user.getPassword())) {

                ((UserTO) updated).setPassword(null);
            }

            for (MembershipTO membTO : ((UserTO) updated).getMemberships()) {
                Membership memb = user.getMembership(membTO.getRoleId());
                if (memb != null) {
                    membTO.setKey(memb.getKey());
                }
            }

            return (T) AttributableOperations.diff(((UserTO) updated), ((UserTO) original), true);
        }
        if (AttributableType.ROLE == attrUtil.getType()) {
            // reading from connector object cannot change entitlements
            ((RoleTO) updated).getEntitlements().addAll(((RoleTO) original).getEntitlements());
            return (T) AttributableOperations.diff(((RoleTO) updated), ((RoleTO) original), true);
        }

        return null;
    }

    private <T extends AbstractSubjectTO> T getSubjectTOFromConnObject(final ConnectorObject obj,
            final SyncTask syncTask, final AttributableUtil attrUtil) {

        final T subjectTO = attrUtil.newSubjectTO();

        // 1. fill with data from connector object
        for (MappingItem item : attrUtil.getUidToMappingItems(syncTask.getResource(),
                MappingPurpose.SYNCHRONIZATION)) {

            Attribute attribute = obj.getAttributeByName(item.getExtAttrName());

            AttrTO attributeTO;
            switch (item.getIntMappingType()) {
            case UserId:
            case RoleId:
                break;

            case Password:
                if (subjectTO instanceof UserTO && attribute != null && attribute.getValue() != null
                        && !attribute.getValue().isEmpty()) {

                    ((UserTO) subjectTO).setPassword(getPassword(attribute.getValue().get(0)));
                }
                break;

            case Username:
                if (subjectTO instanceof UserTO) {
                    ((UserTO) subjectTO).setUsername(attribute == null || attribute.getValue().isEmpty()
                            || attribute.getValue().get(0) == null ? null : attribute.getValue().get(0).toString());
                }
                break;

            case RoleName:
                if (subjectTO instanceof RoleTO) {
                    ((RoleTO) subjectTO).setName(attribute == null || attribute.getValue().isEmpty()
                            || attribute.getValue().get(0) == null ? null : attribute.getValue().get(0).toString());
                }
                break;

            case RoleOwnerSchema:
                if (subjectTO instanceof RoleTO && attribute != null) {
                    // using a special attribute (with schema "", that will be ignored) for carrying the
                    // RoleOwnerSchema value
                    attributeTO = new AttrTO();
                    attributeTO.setSchema(StringUtils.EMPTY);
                    if (attribute.getValue().isEmpty() || attribute.getValue().get(0) == null) {
                        attributeTO.getValues().add(StringUtils.EMPTY);
                    } else {
                        attributeTO.getValues().add(attribute.getValue().get(0).toString());
                    }

                    ((RoleTO) subjectTO).getPlainAttrs().add(attributeTO);
                }
                break;

            case UserPlainSchema:
            case RolePlainSchema:
                attributeTO = new AttrTO();
                attributeTO.setSchema(item.getIntAttrName());

                PlainSchema schema = plainSchemaDAO.find(item.getIntAttrName(), attrUtil.plainSchemaClass());

                for (Object value : attribute == null || attribute.getValue() == null ? Collections.emptyList()
                        : attribute.getValue()) {

                    AttrSchemaType schemaType = schema == null ? AttrSchemaType.String : schema.getType();
                    if (value != null) {
                        final PlainAttrValue attrValue = attrUtil.newPlainAttrValue();
                        switch (schemaType) {
                        case String:
                            attrValue.setStringValue(value.toString());
                            break;

                        case Binary:
                            attrValue.setBinaryValue((byte[]) value);
                            break;

                        default:
                            try {
                                attrValue.parseValue(schema, value.toString());
                            } catch (ParsingValidationException e) {
                                LOG.error("While parsing provided value {}", value, e);
                                attrValue.setStringValue(value.toString());
                                schemaType = AttrSchemaType.String;
                            }
                        }
                        attributeTO.getValues().add(attrValue.getValueAsString(schemaType));
                    }
                }

                subjectTO.getPlainAttrs().add(attributeTO);
                break;

            case UserDerivedSchema:
            case RoleDerivedSchema:
                attributeTO = new AttrTO();
                attributeTO.setSchema(item.getIntAttrName());
                subjectTO.getDerAttrs().add(attributeTO);
                break;

            case UserVirtualSchema:
            case RoleVirtualSchema:
                attributeTO = new AttrTO();
                attributeTO.setSchema(item.getIntAttrName());

                for (Object value : attribute == null || attribute.getValue() == null ? Collections.emptyList()
                        : attribute.getValue()) {

                    if (value != null) {
                        attributeTO.getValues().add(value.toString());
                    }
                }

                subjectTO.getVirAttrs().add(attributeTO);
                break;

            default:
            }
        }

        // 2. add data from defined template (if any)
        AbstractSubjectTO template = AttributableType.USER == attrUtil.getType() ? syncTask.getUserTemplate()
                : syncTask.getRoleTemplate();

        if (template != null) {
            if (template instanceof UserTO) {
                if (StringUtils.isNotBlank(((UserTO) template).getUsername())) {
                    String evaluated = JexlUtil.evaluate(((UserTO) template).getUsername(), subjectTO);
                    if (StringUtils.isNotBlank(evaluated)) {
                        ((UserTO) subjectTO).setUsername(evaluated);
                    }
                }

                if (StringUtils.isNotBlank(((UserTO) template).getPassword())) {
                    String evaluated = JexlUtil.evaluate(((UserTO) template).getPassword(), subjectTO);
                    if (StringUtils.isNotBlank(evaluated)) {
                        ((UserTO) subjectTO).setPassword(evaluated);
                    }
                }

                Map<Long, MembershipTO> currentMembs = ((UserTO) subjectTO).getMembershipMap();
                for (MembershipTO membTO : ((UserTO) template).getMemberships()) {
                    MembershipTO membTBU;
                    if (currentMembs.containsKey(membTO.getRoleId())) {
                        membTBU = currentMembs.get(membTO.getRoleId());
                    } else {
                        membTBU = new MembershipTO();
                        membTBU.setRoleId(membTO.getRoleId());
                        ((UserTO) subjectTO).getMemberships().add(membTBU);
                    }
                    fillFromTemplate(membTBU, membTO);
                }
            }
            if (template instanceof RoleTO) {
                if (StringUtils.isNotBlank(((RoleTO) template).getName())) {
                    String evaluated = JexlUtil.evaluate(((RoleTO) template).getName(), subjectTO);
                    if (StringUtils.isNotBlank(evaluated)) {
                        ((RoleTO) subjectTO).setName(evaluated);
                    }
                }

                if (((RoleTO) template).getParent() != 0) {
                    final Role parentRole = roleDAO.find(((RoleTO) template).getParent());
                    if (parentRole != null) {
                        ((RoleTO) subjectTO).setParent(parentRole.getKey());
                    }
                }

                if (((RoleTO) template).getUserOwner() != null) {
                    final User userOwner = userDAO.find(((RoleTO) template).getUserOwner());
                    if (userOwner != null) {
                        ((RoleTO) subjectTO).setUserOwner(userOwner.getKey());
                    }
                }
                if (((RoleTO) template).getRoleOwner() != null) {
                    final Role roleOwner = roleDAO.find(((RoleTO) template).getRoleOwner());
                    if (roleOwner != null) {
                        ((RoleTO) subjectTO).setRoleOwner(roleOwner.getKey());
                    }
                }

                ((RoleTO) subjectTO).getEntitlements().addAll(((RoleTO) template).getEntitlements());

                ((RoleTO) subjectTO).getRPlainAttrTemplates().addAll(((RoleTO) template).getRPlainAttrTemplates());
                ((RoleTO) subjectTO).getRDerAttrTemplates().addAll(((RoleTO) template).getRDerAttrTemplates());
                ((RoleTO) subjectTO).getRVirAttrTemplates().addAll(((RoleTO) template).getRVirAttrTemplates());
                ((RoleTO) subjectTO).getMPlainAttrTemplates().addAll(((RoleTO) template).getMPlainAttrTemplates());
                ((RoleTO) subjectTO).getMDerAttrTemplates().addAll(((RoleTO) template).getMDerAttrTemplates());
                ((RoleTO) subjectTO).getMVirAttrTemplates().addAll(((RoleTO) template).getMVirAttrTemplates());

                ((RoleTO) subjectTO).setAccountPolicy(((RoleTO) template).getAccountPolicy());
                ((RoleTO) subjectTO).setPasswordPolicy(((RoleTO) template).getPasswordPolicy());

                ((RoleTO) subjectTO).setInheritOwner(((RoleTO) template).isInheritOwner());
                ((RoleTO) subjectTO).setInheritTemplates(((RoleTO) template).isInheritTemplates());
                ((RoleTO) subjectTO).setInheritPlainAttrs(((RoleTO) template).isInheritPlainAttrs());
                ((RoleTO) subjectTO).setInheritDerAttrs(((RoleTO) template).isInheritDerAttrs());
                ((RoleTO) subjectTO).setInheritVirAttrs(((RoleTO) template).isInheritVirAttrs());
                ((RoleTO) subjectTO).setInheritPasswordPolicy(((RoleTO) template).isInheritPasswordPolicy());
                ((RoleTO) subjectTO).setInheritAccountPolicy(((RoleTO) template).isInheritAccountPolicy());
            }

            fillFromTemplate(subjectTO, template);

            for (String resource : template.getResources()) {
                subjectTO.getResources().add(resource);
            }
        }

        return subjectTO;
    }

    /**
     * Extract password value from passed value (if instance of GuardedString or GuardedByteArray).
     *
     * @param pwd received from the underlying connector
     * @return password value
     */
    public String getPassword(final Object pwd) {
        final StringBuilder result = new StringBuilder();

        if (pwd instanceof GuardedString) {
            ((GuardedString) pwd).access(new GuardedString.Accessor() {

                @Override
                public void access(final char[] clearChars) {
                    result.append(clearChars);
                }
            });
        } else if (pwd instanceof GuardedByteArray) {
            ((GuardedByteArray) pwd).access(new GuardedByteArray.Accessor() {

                @Override
                public void access(final byte[] clearBytes) {
                    result.append(new String(clearBytes));
                }
            });
        } else if (pwd instanceof String) {
            result.append((String) pwd);
        } else {
            result.append(pwd.toString());
        }

        return result.toString();
    }

    /**
     * Get connector object TO from a connector object.
     *
     * @param connObject connector object.
     * @return connector object TO.
     */
    public ConnObjectTO getConnObjectTO(final ConnectorObject connObject) {
        final ConnObjectTO connObjectTO = new ConnObjectTO();

        for (Attribute attr : connObject.getAttributes()) {
            AttrTO attrTO = new AttrTO();
            attrTO.setSchema(attr.getName());

            if (attr.getValue() != null) {
                for (Object value : attr.getValue()) {
                    if (value != null) {
                        if (value instanceof GuardedString || value instanceof GuardedByteArray) {
                            attrTO.getValues().add(getPassword(value));
                        } else if (value instanceof byte[]) {
                            attrTO.getValues().add(Base64.encode((byte[]) value));
                        } else {
                            attrTO.getValues().add(value.toString());
                        }
                    }
                }
            }

            connObjectTO.getPlainAttrs().add(attrTO);
        }

        return connObjectTO;
    }

    /**
     * Query connected external resources for values to populated virtual attributes associated with the given owner.
     *
     * @param owner user or role
     * @param attrUtil attributable util
     */
    public void retrieveVirAttrValues(final Attributable<?, ?, ?> owner, final AttributableUtil attrUtil) {
        final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext();
        final ConnectorFactory connFactory = context.getBean(ConnectorFactory.class);

        final IntMappingType type = attrUtil.getType() == AttributableType.USER ? IntMappingType.UserVirtualSchema
                : attrUtil.getType() == AttributableType.ROLE ? IntMappingType.RoleVirtualSchema
                        : IntMappingType.MembershipVirtualSchema;

        final Map<String, ConnectorObject> externalResources = new HashMap<>();

        // -----------------------
        // Retrieve virtual attribute values if and only if they have not been retrieved yet
        // -----------------------
        for (VirAttr virAttr : owner.getVirAttrs()) {
            // reset value set
            if (virAttr.getValues().isEmpty()) {
                retrieveVirAttrValue(owner, virAttr, attrUtil, type, externalResources, connFactory);
            }
        }
        // -----------------------
    }

    private void retrieveVirAttrValue(final Attributable<?, ?, ?> owner, final VirAttr virAttr,
            final AttributableUtil attrUtil, final IntMappingType type,
            final Map<String, ConnectorObject> externalResources, final ConnectorFactory connFactory) {

        final String schemaName = virAttr.getSchema().getKey();
        final VirAttrCacheValue virAttrCacheValue = virAttrCache.get(attrUtil.getType(), owner.getKey(),
                schemaName);

        LOG.debug("Retrieve values for virtual attribute {} ({})", schemaName, type);

        if (virAttrCache.isValidEntry(virAttrCacheValue)) {
            // cached ...
            LOG.debug("Values found in cache {}", virAttrCacheValue);
            virAttr.getValues().clear();
            virAttr.getValues().addAll(new ArrayList<>(virAttrCacheValue.getValues()));
        } else {
            // not cached ...
            LOG.debug("Need one or more remote connections");

            final VirAttrCacheValue toBeCached = new VirAttrCacheValue();

            // SYNCOPE-458 if virattr owner is a Membership, owner must become user involved in membership because 
            // membership mapping is contained in user mapping
            final Subject<?, ?, ?> realOwner = owner instanceof Membership ? ((Membership) owner).getUser()
                    : (Subject) owner;

            final Set<ExternalResource> targetResources = owner instanceof Membership
                    ? getTargetResource(virAttr, type, attrUtil, realOwner.getResources())
                    : getTargetResource(virAttr, type, attrUtil);

            for (ExternalResource resource : targetResources) {
                LOG.debug("Search values into {}", resource.getKey());
                try {
                    final List<MappingItem> mappings = attrUtil.getMappingItems(resource, MappingPurpose.BOTH);

                    final ConnectorObject connectorObject;

                    if (externalResources.containsKey(resource.getKey())) {
                        connectorObject = externalResources.get(resource.getKey());
                    } else {
                        LOG.debug("Perform connection to {}", resource.getKey());
                        final String accountId = attrUtil.getAccountIdItem(resource) == null ? null
                                : MappingUtil.getAccountIdValue(realOwner, resource,
                                        attrUtil.getAccountIdItem(resource));

                        if (StringUtils.isBlank(accountId)) {
                            throw new IllegalArgumentException("No AccountId found for " + resource.getKey());
                        }

                        final Connector connector = connFactory.getConnector(resource);

                        final OperationOptions oo = connector
                                .getOperationOptions(MappingUtil.getMatchingMappingItems(mappings, type));

                        connectorObject = connector.getObject(fromSubject(realOwner), new Uid(accountId), oo);
                        externalResources.put(resource.getKey(), connectorObject);
                    }

                    if (connectorObject != null) {
                        // ask for searched virtual attribute value
                        final List<MappingItem> virAttrMappings = MappingUtil.getMatchingMappingItems(mappings,
                                schemaName, type);

                        // the same virtual attribute could be mapped with one or more external attribute 
                        for (MappingItem mapping : virAttrMappings) {
                            final Attribute attribute = connectorObject
                                    .getAttributeByName(mapping.getExtAttrName());

                            if (attribute != null && attribute.getValue() != null) {
                                for (Object obj : attribute.getValue()) {
                                    if (obj != null) {
                                        virAttr.getValues().add(obj.toString());
                                    }
                                }
                            }
                        }

                        toBeCached.setResourceValues(resource.getKey(), new HashSet<String>(virAttr.getValues()));

                        LOG.debug("Retrieved values {}", virAttr.getValues());
                    }
                } catch (Exception e) {
                    LOG.error("Error reading connector object from {}", resource.getKey(), e);

                    if (virAttrCacheValue != null) {
                        toBeCached.forceExpiring();
                        LOG.debug("Search for a cached value (even expired!) ...");
                        final Set<String> cachedValues = virAttrCacheValue.getValues(resource.getKey());
                        if (cachedValues != null) {
                            LOG.debug("Use cached value {}", cachedValues);
                            virAttr.getValues().addAll(cachedValues);
                            toBeCached.setResourceValues(resource.getKey(), new HashSet<>(cachedValues));
                        }
                    }
                }
            }

            virAttrCache.put(attrUtil.getType(), owner.getKey(), schemaName, toBeCached);
        }
    }

    private Set<ExternalResource> getTargetResource(final VirAttr attr, final IntMappingType type,
            final AttributableUtil attrUtil) {

        final Set<ExternalResource> resources = new HashSet<>();

        if (attr.getOwner() instanceof Subject) {
            for (ExternalResource res : ((Subject<?, ?, ?>) attr.getOwner()).getResources()) {
                if (!MappingUtil.getMatchingMappingItems(attrUtil.getMappingItems(res, MappingPurpose.BOTH),
                        attr.getSchema().getKey(), type).isEmpty()) {

                    resources.add(res);
                }
            }
        }

        return resources;
    }

    private Set<ExternalResource> getTargetResource(final VirAttr attr, final IntMappingType type,
            final AttributableUtil attrUtil, final Set<? extends ExternalResource> ownerResources) {

        final Set<ExternalResource> resources = new HashSet<>();

        for (ExternalResource res : ownerResources) {
            if (!MappingUtil.getMatchingMappingItems(attrUtil.getMappingItems(res, MappingPurpose.BOTH),
                    attr.getSchema().getKey(), type).isEmpty()) {

                resources.add(res);
            }
        }

        return resources;
    }

    private void fillFromTemplate(final AbstractAttributableTO attributableTO,
            final AbstractAttributableTO template) {
        Map<String, AttrTO> currentAttrMap = attributableTO.getPlainAttrMap();
        for (AttrTO templateAttr : template.getPlainAttrs()) {
            if (templateAttr.getValues() != null && !templateAttr.getValues().isEmpty()
                    && (!currentAttrMap.containsKey(templateAttr.getSchema())
                            || currentAttrMap.get(templateAttr.getSchema()).getValues().isEmpty())) {

                attributableTO.getPlainAttrs().add(evaluateAttrTemplate(attributableTO, templateAttr));
            }
        }

        currentAttrMap = attributableTO.getDerAttrMap();
        for (AttrTO templateDerAttr : template.getDerAttrs()) {
            if (!currentAttrMap.containsKey(templateDerAttr.getSchema())) {
                attributableTO.getDerAttrs().add(templateDerAttr);
            }
        }

        currentAttrMap = attributableTO.getVirAttrMap();
        for (AttrTO templateVirAttr : template.getVirAttrs()) {
            if (templateVirAttr.getValues() != null && !templateVirAttr.getValues().isEmpty()
                    && (!currentAttrMap.containsKey(templateVirAttr.getSchema())
                            || currentAttrMap.get(templateVirAttr.getSchema()).getValues().isEmpty())) {

                attributableTO.getVirAttrs().add(evaluateAttrTemplate(attributableTO, templateVirAttr));
            }
        }
    }

    private AttrTO evaluateAttrTemplate(final AbstractAttributableTO attributableTO, final AttrTO template) {
        AttrTO result = new AttrTO();
        result.setSchema(template.getSchema());

        if (template.getValues() != null && !template.getValues().isEmpty()) {
            for (String value : template.getValues()) {
                String evaluated = JexlUtil.evaluate(value, attributableTO);
                if (StringUtils.isNotBlank(evaluated)) {
                    result.getValues().add(evaluated);
                }
            }
        }

        return result;
    }

    /**
     * Transform a
     * <code>Collection</code> of {@link Attribute} instances into a {@link Map}. The key to each element in the map is
     * the <i>name</i> of an
     * <code>Attribute</code>. The value of each element in the map is the
     * <code>Attribute</code> instance with that name. <br/> Different from the original because: <ul> <li>map keys are
     * transformed toUpperCase()</li> <li>returned map is mutable</li> </ul>
     *
     * @param attributes set of attribute to transform to a map.
     * @return a map of string and attribute.
     *
     * @see org.identityconnectors.framework.common.objects.AttributeUtil#toMap(java.util.Collection)
     */
    public Map<String, Attribute> toMap(final Collection<? extends Attribute> attributes) {
        final Map<String, Attribute> map = new HashMap<>();
        for (Attribute attr : attributes) {
            map.put(attr.getName().toUpperCase(), attr);
        }
        return map;
    }
}