You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by fm...@apache.org on 2015/09/07 16:38:01 UTC

[2/2] syncope git commit: [SYNCOPE-691] merge from 1_2_X

[SYNCOPE-691] merge from 1_2_X


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/76e38c59
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/76e38c59
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/76e38c59

Branch: refs/heads/master
Commit: 76e38c59664f39245e6877c5853f5eae79835af4
Parents: f29fd4e 87e7543
Author: fmartelli <fa...@gmail.com>
Authored: Mon Sep 7 16:37:32 2015 +0200
Committer: fmartelli <fa...@gmail.com>
Committed: Mon Sep 7 16:37:32 2015 +0200

----------------------------------------------------------------------
 .../apache/syncope/core/misc/MappingUtils.java  |  9 +-
 .../fit/core/reference/VirAttrITCase.java       | 98 ++++++++++++++++++++
 2 files changed, 102 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/76e38c59/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtils.java
----------------------------------------------------------------------
diff --cc core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtils.java
index 5cb419d,0000000..3e7a802
mode 100644,000000..100644
--- a/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtils.java
+++ b/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtils.java
@@@ -1,672 -1,0 +1,671 @@@
 +/*
 + * 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.InvalidPasswordRuleConf;
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.HashSet;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Set;
 +import org.apache.commons.collections4.CollectionUtils;
 +import org.apache.commons.collections4.Predicate;
 +import org.apache.commons.jexl2.JexlContext;
 +import org.apache.commons.jexl2.MapContext;
 +import org.apache.commons.lang3.StringUtils;
 +import org.apache.commons.lang3.tuple.ImmutablePair;
 +import org.apache.commons.lang3.tuple.Pair;
 +import org.apache.syncope.common.lib.mod.AttrMod;
 +import org.apache.syncope.common.lib.types.AttrSchemaType;
 +import org.apache.syncope.common.lib.types.IntMappingType;
 +import org.apache.syncope.common.lib.types.MappingPurpose;
 +import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 +import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 +import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 +import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 +import org.apache.syncope.core.persistence.api.entity.DerAttr;
 +import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 +import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 +import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 +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.VirAttr;
- import org.apache.syncope.core.persistence.api.entity.VirSchema;
 +import org.apache.syncope.core.persistence.api.entity.group.GPlainAttrValue;
 +import org.apache.syncope.core.persistence.api.entity.group.Group;
 +import org.apache.syncope.core.persistence.api.entity.user.UPlainAttrValue;
 +import org.apache.syncope.core.persistence.api.entity.user.User;
 +import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
 +import org.apache.syncope.core.misc.security.Encryptor;
 +import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
 +import org.apache.syncope.core.misc.jexl.JexlUtils;
 +import org.apache.syncope.core.misc.security.PasswordGenerator;
 +import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 +import org.apache.syncope.core.persistence.api.dao.UserDAO;
 +import org.apache.syncope.core.persistence.api.entity.Any;
++import org.apache.syncope.core.persistence.api.entity.Schema;
 +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 +import org.apache.syncope.core.persistence.api.entity.resource.Mapping;
 +import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 +import org.apache.syncope.core.provisioning.api.VirAttrHandler;
 +import org.identityconnectors.framework.common.FrameworkUtil;
 +import org.identityconnectors.framework.common.objects.Attribute;
 +import org.identityconnectors.framework.common.objects.AttributeBuilder;
 +import org.identityconnectors.framework.common.objects.AttributeUtil;
 +import org.identityconnectors.framework.common.objects.Name;
 +import org.identityconnectors.framework.common.objects.OperationalAttributes;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +import org.springframework.beans.factory.support.DefaultListableBeanFactory;
 +
 +public final class MappingUtils {
 +
 +    private static final Logger LOG = LoggerFactory.getLogger(MappingUtils.class);
 +
 +    private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
 +
 +    public static <T extends MappingItem> Collection<T> getMatchingMappingItems(
 +            final Collection<T> items, final IntMappingType type) {
 +
 +        return CollectionUtils.select(items, new Predicate<T>() {
 +
 +            @Override
 +            public boolean evaluate(final T item) {
 +                return item.getIntMappingType() == type;
 +            }
 +        });
 +    }
 +
 +    public static <T extends MappingItem> Collection<T> getMatchingMappingItems(
 +            final Collection<T> items, final String intAttrName, final IntMappingType type) {
 +
 +        return CollectionUtils.select(items, new Predicate<T>() {
 +
 +            @Override
 +            public boolean evaluate(final T item) {
 +                return item.getIntMappingType() == type && intAttrName.equals(item.getIntAttrName());
 +            }
 +        });
 +    }
 +
 +    public static <T extends MappingItem> Collection<T> getMatchingMappingItems(
 +            final Collection<T> items, final String intAttrName) {
 +
 +        return CollectionUtils.select(items, new Predicate<T>() {
 +
 +            @Override
 +            public boolean evaluate(final T item) {
 +                return intAttrName.equals(item.getIntAttrName());
 +            }
 +        });
 +    }
 +
 +    /**
 +     * Prepare attributes for sending to a connector instance.
 +     *
 +     * @param any given any object
 +     * @param password clear-text password
 +     * @param changePwd whether password should be included for propagation attributes or not
 +     * @param vAttrsToBeRemoved virtual attributes to be removed
 +     * @param vAttrsToBeUpdated virtual attributes to be added
 +     * @param enable whether any object must be enabled or not
 +     * @param provision provision information
 +     * @return connObjectLink + prepared attributes
 +     */
 +    public static Pair<String, Set<Attribute>> prepareAttrs(
 +            final Any<?, ?, ?> any,
 +            final String password,
 +            final boolean changePwd,
 +            final Set<String> vAttrsToBeRemoved,
 +            final Map<String, AttrMod> vAttrsToBeUpdated,
 +            final Boolean enable,
 +            final Provision provision) {
 +
 +        LOG.debug("Preparing resource attributes for {} with provision {} for attributes {}",
 +                any, provision, any.getPlainAttrs());
 +
 +        DefaultListableBeanFactory beanFactory = ApplicationContextProvider.getBeanFactory();
 +        VirAttrCache virAttrCache = beanFactory.getBean(VirAttrCache.class);
 +        PasswordGenerator passwordGenerator = beanFactory.getBean(PasswordGenerator.class);
 +
 +        Set<Attribute> attributes = new HashSet<>();
 +        String connObjectKey = null;
 +
 +        for (MappingItem mapping : getMappingItems(provision, MappingPurpose.PROPAGATION)) {
 +            LOG.debug("Processing schema {}", mapping.getIntAttrName());
 +
 +            try {
 +                if (mapping.getIntMappingType() == IntMappingType.UserVirtualSchema
 +                        || mapping.getIntMappingType() == IntMappingType.GroupVirtualSchema
 +                        || mapping.getIntMappingType() == IntMappingType.AnyObjectVirtualSchema) {
 +
 +                    LOG.debug("Expire entry cache {}-{}", any.getKey(), mapping.getIntAttrName());
 +                    virAttrCache.expire(any.getType().getKey(), any.getKey(), mapping.getIntAttrName());
 +                }
 +
 +                Pair<String, Attribute> preparedAttr = prepareAttr(
 +                        provision, mapping, any, password, passwordGenerator, vAttrsToBeRemoved, vAttrsToBeUpdated);
 +
 +                if (preparedAttr != null && preparedAttr.getKey() != null) {
 +                    connObjectKey = preparedAttr.getKey();
 +                }
 +
 +                if (preparedAttr != null && preparedAttr.getValue() != null) {
 +                    Attribute alreadyAdded = AttributeUtil.find(preparedAttr.getValue().getName(), attributes);
 +
 +                    if (alreadyAdded == null) {
 +                        attributes.add(preparedAttr.getValue());
 +                    } else {
 +                        attributes.remove(alreadyAdded);
 +
 +                        Set<Object> values = new HashSet<>(alreadyAdded.getValue());
 +                        values.addAll(preparedAttr.getValue().getValue());
 +
 +                        attributes.add(AttributeBuilder.build(preparedAttr.getValue().getName(), values));
 +                    }
 +                }
 +            } catch (Exception e) {
 +                LOG.debug("Attribute '{}' processing failed", mapping.getIntAttrName(), e);
 +            }
 +        }
 +
 +        Attribute connObjectKeyExtAttr =
 +                AttributeUtil.find(getConnObjectKeyItem(provision).getExtAttrName(), attributes);
 +        if (connObjectKeyExtAttr != null) {
 +            attributes.remove(connObjectKeyExtAttr);
 +            attributes.add(AttributeBuilder.build(getConnObjectKeyItem(provision).getExtAttrName(), connObjectKey));
 +        }
 +        attributes.add(evaluateNAME(any, provision, connObjectKey));
 +
 +        if (enable != null) {
 +            attributes.add(AttributeBuilder.buildEnabled(enable));
 +        }
 +        if (!changePwd) {
 +            Attribute pwdAttr = AttributeUtil.find(OperationalAttributes.PASSWORD_NAME, attributes);
 +            if (pwdAttr != null) {
 +                attributes.remove(pwdAttr);
 +            }
 +        }
 +
 +        return new ImmutablePair<>(connObjectKey, attributes);
 +    }
 +
 +    /**
 +     * Prepare an attribute to be sent to a connector instance.
 +     *
 +     * @param provision external resource
 +     * @param mapItem mapping item for the given attribute
 +     * @param any any object
 +     * @param password clear-text password
 +     * @param passwordGenerator password generator
 +     * @param vAttrsToBeRemoved virtual attributes to be removed
 +     * @param vAttrsToBeUpdated virtual attributes to be added
 +     * @return connObjectLink + prepared attribute
 +     */
 +    @SuppressWarnings("unchecked")
 +    private static Pair<String, Attribute> prepareAttr(
 +            final Provision provision, final MappingItem mapItem,
 +            final Any<?, ?, ?> any, final String password, final PasswordGenerator passwordGenerator,
 +            final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated) {
 +
 +        List<Any<?, ?, ?>> anys = new ArrayList<>();
 +
 +        DefaultListableBeanFactory beanFactory = ApplicationContextProvider.getBeanFactory();
 +        AnyUtilsFactory anyUtilsFactory = beanFactory.getBean(AnyUtilsFactory.class);
 +        VirAttrHandler virAttrHandler = beanFactory.getBean(VirAttrHandler.class);
 +
 +        switch (mapItem.getIntMappingType().getAnyTypeKind()) {
 +            case USER:
 +                if (any instanceof User) {
 +                    anys.add(any);
 +                }
 +                break;
 +
 +            case GROUP:
 +                if (any instanceof User) {
 +                    UserDAO userDAO = beanFactory.getBean(UserDAO.class);
 +                    for (Group group : userDAO.findAllGroups((User) any)) {
 +                        virAttrHandler.retrieveVirAttrValues(group);
 +                        anys.add(group);
 +                    }
 +                } else if (any instanceof Group) {
 +                    anys.add(any);
 +                }
 +                break;
 +
 +            case ANY_OBJECT:
 +                if (any instanceof AnyObject) {
 +                    anys.add(any);
 +                }
 +                break;
 +
 +            default:
 +        }
 +
 +        List<PlainAttrValue> values = getIntValues(
 +                provision, mapItem, anys, vAttrsToBeRemoved, vAttrsToBeUpdated);
 +
-         PlainSchema schema = null;
++        Schema schema = null;
 +        boolean readOnlyVirSchema = false;
 +        AttrSchemaType schemaType;
 +        Pair<String, Attribute> result;
 +
 +        switch (mapItem.getIntMappingType()) {
 +            case UserPlainSchema:
 +            case GroupPlainSchema:
 +            case AnyObjectPlainSchema:
 +                PlainSchemaDAO plainSchemaDAO = beanFactory.getBean(PlainSchemaDAO.class);
 +                schema = plainSchemaDAO.find(mapItem.getIntAttrName());
 +                schemaType = schema == null ? AttrSchemaType.String : schema.getType();
 +                break;
 +
 +            case UserVirtualSchema:
 +            case GroupVirtualSchema:
 +            case AnyObjectVirtualSchema:
 +                VirSchemaDAO virSchemaDAO = beanFactory.getBean(VirSchemaDAO.class);
-                 VirSchema virSchema = virSchemaDAO.find(mapItem.getIntAttrName());
-                 readOnlyVirSchema = (virSchema != null && virSchema.isReadonly());
++                schema = virSchemaDAO.find(mapItem.getIntAttrName());
++                readOnlyVirSchema = (schema != null && schema.isReadonly());
 +                schemaType = AttrSchemaType.String;
 +                break;
 +
 +            default:
 +                schemaType = AttrSchemaType.String;
 +        }
 +
 +        String extAttrName = mapItem.getExtAttrName();
 +
 +        LOG.debug("Define mapping for: "
 +                + "\n* ExtAttrName " + extAttrName
 +                + "\n* is connObjectKey " + mapItem.isConnObjectKey()
 +                + "\n* is password " + (mapItem.isPassword() || mapItem.getIntMappingType() == IntMappingType.Password)
 +                + "\n* mandatory condition " + mapItem.getMandatoryCondition()
 +                + "\n* Schema " + mapItem.getIntAttrName()
 +                + "\n* IntMappingType " + mapItem.getIntMappingType().toString()
 +                + "\n* ClassType " + schemaType.getType().getName()
 +                + "\n* Values " + values);
 +
 +        if (readOnlyVirSchema) {
 +            result = null;
 +        } else {
 +            List<Object> objValues = new ArrayList<>();
 +
 +            for (PlainAttrValue value : values) {
 +                if (FrameworkUtil.isSupportedAttributeType(schemaType.getType())) {
 +                    objValues.add(value.getValue());
 +                } else {
 +                    objValues.add(value.getValueAsString());
 +                }
 +            }
 +
 +            if (mapItem.isConnObjectKey()) {
 +                result = new ImmutablePair<>(objValues.iterator().next().toString(), null);
 +            } else if (mapItem.isPassword() && any instanceof User) {
 +                String passwordAttrValue = password;
 +                if (StringUtils.isBlank(passwordAttrValue)) {
 +                    User user = (User) any;
 +                    if (user.canDecodePassword()) {
 +                        try {
 +                            passwordAttrValue = ENCRYPTOR.decode(user.getPassword(), user.getCipherAlgorithm());
 +                        } catch (Exception e) {
 +                            LOG.error("Could not decode password for {}", user, e);
 +                        }
 +                    } else if (provision.getResource().isRandomPwdIfNotProvided()) {
 +                        try {
 +                            passwordAttrValue = passwordGenerator.generate(user);
 +                        } catch (InvalidPasswordRuleConf e) {
 +                            LOG.error("Could not generate policy-compliant random password for {}", user, e);
 +                        }
 +                    }
 +                }
 +
 +                if (passwordAttrValue == null) {
 +                    result = null;
 +                } else {
 +                    result = new ImmutablePair<>(
 +                            null,
 +                            AttributeBuilder.buildPassword(passwordAttrValue.toCharArray()));
 +                }
 +            } else {
 +                if ((schema != null && schema.isMultivalue())
 +                        || anyUtilsFactory.getInstance(any).getAnyTypeKind()
 +                        != mapItem.getIntMappingType().getAnyTypeKind()) {
 +
 +                    result = new ImmutablePair<>(
 +                            null,
 +                            AttributeBuilder.build(extAttrName, objValues));
 +                } else {
 +                    result = new ImmutablePair<>(
 +                            null, objValues.isEmpty()
 +                                    ? AttributeBuilder.build(extAttrName)
 +                                    : AttributeBuilder.build(extAttrName, objValues.iterator().next()));
 +                }
 +            }
 +        }
 +
 +        return result;
 +    }
 +
 +    /**
 +     * Build __NAME__ for propagation. First look if there ia a defined connObjectLink for the given resource (and in
 +     * this case evaluate as JEXL); otherwise, take given connObjectKey.
 +     *
 +     * @param any given any object
 +     * @param provision external resource
 +     * @param connObjectKey connector object key
 +     * @return the value to be propagated as __NAME__
 +     */
 +    public static Name evaluateNAME(final Any<?, ?, ?> any, final Provision provision, final String connObjectKey) {
 +        if (StringUtils.isBlank(connObjectKey)) {
 +            // LOG error but avoid to throw exception: leave it to the external resource
 +            LOG.error("Missing ConnObjectKey for '{}': ", provision.getResource());
 +        }
 +
 +        // Evaluate connObjectKey expression
 +        String connObjectLink = provision == null || provision.getMapping() == null
 +                ? null
 +                : provision.getMapping().getConnObjectLink();
 +        String evalConnObjectLink = null;
 +        if (StringUtils.isNotBlank(connObjectLink)) {
 +            JexlContext jexlContext = new MapContext();
 +            JexlUtils.addFieldsToContext(any, jexlContext);
 +            JexlUtils.addPlainAttrsToContext(any.getPlainAttrs(), jexlContext);
 +            JexlUtils.addDerAttrsToContext(any.getDerAttrs(), any.getPlainAttrs(), jexlContext);
 +            evalConnObjectLink = JexlUtils.evaluate(connObjectLink, jexlContext);
 +        }
 +
 +        // If connObjectLink evaluates to an empty string, just use the provided connObjectKey as Name(),
 +        // otherwise evaluated connObjectLink expression is taken as Name().
 +        Name name;
 +        if (StringUtils.isBlank(evalConnObjectLink)) {
 +            // add connObjectKey as __NAME__ attribute ...
 +            LOG.debug("Add connObjectKey [{}] as __NAME__", connObjectKey);
 +            name = new Name(connObjectKey);
 +        } else {
 +            LOG.debug("Add connObjectLink [{}] as __NAME__", evalConnObjectLink);
 +            name = new Name(evalConnObjectLink);
 +
 +            // connObjectKey not propagated: it will be used to set the value for __UID__ attribute
 +            LOG.debug("connObjectKey will be used just as __UID__ attribute");
 +        }
 +
 +        return name;
 +    }
 +
 +    private static String getGroupOwnerValue(final Provision provision, final Any<?, ?, ?> any) {
 +        Pair<String, Attribute> preparedAttr = prepareAttr(provision, getConnObjectKeyItem(provision),
 +                any, null, null, Collections.<String>emptySet(), Collections.<String, AttrMod>emptyMap());
 +        String connObjectKey = preparedAttr.getKey();
 +
 +        final Name groupOwnerName = evaluateNAME(any, provision, connObjectKey);
 +        return groupOwnerName.getNameValue();
 +    }
 +
 +    /**
 +     * Get attribute values.
 +     *
 +     * @param provision provision information
 +     * @param mappingItem mapping item
 +     * @param anys any objects
 +     * @param vAttrsToBeRemoved virtual attributes to be removed
 +     * @param vAttrsToBeUpdated virtual attributes to be added
 +     * @return attribute values.
 +     */
 +    public static List<PlainAttrValue> getIntValues(final Provision provision,
 +            final MappingItem mappingItem, final List<Any<?, ?, ?>> anys,
 +            final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated) {
 +
 +        LOG.debug("Get attributes for '{}' and mapping type '{}'", anys, mappingItem.getIntMappingType());
 +
 +        EntityFactory entityFactory =
 +                ApplicationContextProvider.getBeanFactory().getBean(EntityFactory.class);
 +        AnyUtilsFactory anyUtilsFactory =
 +                ApplicationContextProvider.getBeanFactory().getBean(AnyUtilsFactory.class);
 +        List<PlainAttrValue> values = new ArrayList<>();
 +        PlainAttrValue attrValue;
 +        switch (mappingItem.getIntMappingType()) {
 +            case UserPlainSchema:
 +            case GroupPlainSchema:
 +            case AnyObjectPlainSchema:
 +                for (Any<?, ?, ?> any : anys) {
 +                    PlainAttr<?> attr = any.getPlainAttr(mappingItem.getIntAttrName());
 +                    if (attr != null) {
 +                        if (attr.getUniqueValue() != null) {
 +                            values.add(attr.getUniqueValue());
 +                        } else if (attr.getValues() != null) {
 +                            values.addAll(attr.getValues());
 +                        }
 +                    }
 +
 +                    LOG.debug("Retrieved attribute {}"
 +                            + "\n* IntAttrName {}"
 +                            + "\n* IntMappingType {}"
 +                            + "\n* Attribute values {}",
 +                            attr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values);
 +                }
 +
 +                break;
 +
 +            case UserVirtualSchema:
 +            case GroupVirtualSchema:
 +            case AnyObjectVirtualSchema:
 +                for (Any<?, ?, ?> any : anys) {
 +                    AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
 +                    VirAttr<?> virAttr = any.getVirAttr(mappingItem.getIntAttrName());
 +                    if (virAttr != null) {
 +                        if (vAttrsToBeRemoved != null && vAttrsToBeUpdated != null) {
 +                            if (vAttrsToBeUpdated.containsKey(mappingItem.getIntAttrName())) {
 +                                virAttr.getValues().clear();
 +                                virAttr.getValues().addAll(
 +                                        vAttrsToBeUpdated.get(mappingItem.getIntAttrName()).getValuesToBeAdded());
 +                            } else if (vAttrsToBeRemoved.contains(mappingItem.getIntAttrName())) {
 +                                virAttr.getValues().clear();
 +                            } else {
 +                                throw new IllegalArgumentException("Don't need to update virtual attribute '"
 +                                        + mappingItem.getIntAttrName() + "'");
 +                            }
 +                        }
 +                        if (virAttr.getValues() != null) {
 +                            for (String value : virAttr.getValues()) {
 +                                attrValue = anyUtils.newPlainAttrValue();
 +                                attrValue.setStringValue(value);
 +                                values.add(attrValue);
 +                            }
 +                        }
 +                    }
 +
 +                    LOG.debug("Retrieved {} virtual attribute {}"
 +                            + "\n* IntAttrName {}"
 +                            + "\n* IntMappingType {}"
 +                            + "\n* Attribute values {}",
 +                            any.getClass().getSimpleName(),
 +                            virAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values);
 +                }
 +                break;
 +
 +            case UserDerivedSchema:
 +            case GroupDerivedSchema:
 +            case AnyObjectDerivedSchema:
 +                for (Any<?, ?, ?> any : anys) {
 +                    AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
 +                    DerAttr<?> derAttr = any.getDerAttr(mappingItem.getIntAttrName());
 +                    if (derAttr != null) {
 +                        attrValue = anyUtils.newPlainAttrValue();
 +                        attrValue.setStringValue(derAttr.getValue(any.getPlainAttrs()));
 +                        values.add(attrValue);
 +                    }
 +
 +                    LOG.debug("Retrieved attribute {}"
 +                            + "\n* IntAttrName {}"
 +                            + "\n* IntMappingType {}"
 +                            + "\n* Attribute values {}",
 +                            derAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values);
 +                }
 +                break;
 +
 +            case UserKey:
 +            case GroupKey:
 +            case AnyObjectKey:
 +                for (Any<?, ?, ?> any : anys) {
 +                    AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
 +                    attrValue = anyUtils.newPlainAttrValue();
 +                    attrValue.setStringValue(any.getKey().toString());
 +                    values.add(attrValue);
 +                }
 +                break;
 +
 +            case Username:
 +                for (Any<?, ?, ?> any : anys) {
 +                    if (any instanceof User) {
 +                        attrValue = entityFactory.newEntity(UPlainAttrValue.class);
 +                        attrValue.setStringValue(((User) any).getUsername());
 +                        values.add(attrValue);
 +                    }
 +                }
 +                break;
 +
 +            case GroupName:
 +                for (Any<?, ?, ?> any : anys) {
 +                    if (any instanceof Group) {
 +                        attrValue = entityFactory.newEntity(GPlainAttrValue.class);
 +                        attrValue.setStringValue(((Group) any).getName());
 +                        values.add(attrValue);
 +                    }
 +                }
 +                break;
 +
 +            case GroupOwnerSchema:
 +                AnyTypeDAO anyTypeDAO = ApplicationContextProvider.getBeanFactory().getBean(AnyTypeDAO.class);
 +                Mapping uMapping = provision.getAnyType().equals(anyTypeDAO.findUser())
 +                        ? null
 +                        : provision.getMapping();
 +                Mapping gMapping = provision.getAnyType().equals(anyTypeDAO.findGroup())
 +                        ? null
 +                        : provision.getMapping();
 +
 +                for (Any<?, ?, ?> any : anys) {
 +                    if (any instanceof Group) {
 +                        Group group = (Group) any;
 +                        String groupOwnerValue = null;
 +                        if (group.getUserOwner() != null && uMapping != null) {
 +                            groupOwnerValue = getGroupOwnerValue(provision, group.getUserOwner());
 +                        }
 +                        if (group.getGroupOwner() != null && gMapping != null) {
 +                            groupOwnerValue = getGroupOwnerValue(provision, group.getGroupOwner());
 +                        }
 +
 +                        if (StringUtils.isNotBlank(groupOwnerValue)) {
 +                            attrValue = entityFactory.newEntity(GPlainAttrValue.class);
 +                            attrValue.setStringValue(groupOwnerValue);
 +                            values.add(attrValue);
 +                        }
 +                    }
 +                }
 +                break;
 +
 +            default:
 +        }
 +
 +        LOG.debug("Retrieved values '{}'", values);
 +
 +        return values;
 +    }
 +
 +    /**
 +     * Get connObjectKey internal value.
 +     *
 +     * @param any any object
 +     * @param provision provision information
 +     * @return connObjectKey internal value
 +     */
 +    public static String getConnObjectKeyValue(final Any<?, ?, ?> any, final Provision provision) {
 +        List<PlainAttrValue> values = getIntValues(provision, provision.getMapping().getConnObjectKeyItem(),
 +                Collections.<Any<?, ?, ?>>singletonList(any), null, null);
 +        return values == null || values.isEmpty()
 +                ? null
 +                : values.get(0).getValueAsString();
 +    }
 +
 +    public static MappingItem getConnObjectKeyItem(final Provision provision) {
 +        Mapping mapping = null;
 +        if (provision != null) {
 +            mapping = provision.getMapping();
 +        }
 +
 +        return mapping == null
 +                ? null
 +                : mapping.getConnObjectKeyItem();
 +    }
 +
 +    public static List<MappingItem> getMappingItems(final Provision provision, final MappingPurpose purpose) {
 +        List<? extends MappingItem> items = Collections.<MappingItem>emptyList();
 +        if (provision != null) {
 +            items = provision.getMapping().getItems();
 +        }
 +
 +        List<MappingItem> result = new ArrayList<>();
 +
 +        switch (purpose) {
 +            case SYNCHRONIZATION:
 +                for (MappingItem item : items) {
 +                    if (MappingPurpose.PROPAGATION != item.getPurpose()
 +                            && MappingPurpose.NONE != item.getPurpose()) {
 +
 +                        result.add(item);
 +                    }
 +                }
 +                break;
 +
 +            case PROPAGATION:
 +                for (MappingItem item : items) {
 +                    if (MappingPurpose.SYNCHRONIZATION != item.getPurpose()
 +                            && MappingPurpose.NONE != item.getPurpose()) {
 +
 +                        result.add(item);
 +                    }
 +                }
 +                break;
 +
 +            case BOTH:
 +                for (MappingItem item : items) {
 +                    if (MappingPurpose.NONE != item.getPurpose()) {
 +                        result.add(item);
 +                    }
 +                }
 +                break;
 +
 +            case NONE:
 +                for (MappingItem item : items) {
 +                    if (MappingPurpose.NONE == item.getPurpose()) {
 +                        result.add(item);
 +                    }
 +                }
 +                break;
 +
 +            default:
 +        }
 +
 +        return result;
 +    }
 +
 +    /**
 +     * Private default constructor, for static-only classes.
 +     */
 +    private MappingUtils() {
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/76e38c59/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/VirAttrITCase.java
----------------------------------------------------------------------
diff --cc fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/VirAttrITCase.java
index cb166a9,0000000..5ab153f
mode 100644,000000..100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/VirAttrITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/VirAttrITCase.java
@@@ -1,616 -1,0 +1,714 @@@
 +/*
 + * 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.fit.core.reference;
 +
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.assertFalse;
 +import static org.junit.Assert.assertNotNull;
 +import static org.junit.Assert.assertTrue;
 +
 +import java.util.Collections;
 +import java.util.Locale;
 +import java.util.Map;
++import org.apache.commons.collections4.CollectionUtils;
++import org.apache.commons.collections4.Predicate;
 +import org.apache.commons.lang3.SerializationUtils;
 +import org.apache.syncope.common.lib.mod.AttrMod;
 +import org.apache.syncope.common.lib.mod.StatusMod;
 +import org.apache.syncope.common.lib.mod.UserMod;
 +import org.apache.syncope.common.lib.to.AttrTO;
 +import org.apache.syncope.common.lib.to.ConnInstanceTO;
 +import org.apache.syncope.common.lib.to.ConnObjectTO;
 +import org.apache.syncope.common.lib.to.MappingItemTO;
 +import org.apache.syncope.common.lib.to.MappingTO;
 +import org.apache.syncope.common.lib.to.MembershipTO;
 +import org.apache.syncope.common.lib.to.ResourceTO;
 +import org.apache.syncope.common.lib.to.GroupTO;
 +import org.apache.syncope.common.lib.to.ProvisionTO;
 +import org.apache.syncope.common.lib.to.UserTO;
 +import org.apache.syncope.common.lib.types.AnyTypeKind;
 +import org.apache.syncope.common.lib.types.ConnConfProperty;
 +import org.apache.syncope.common.lib.types.IntMappingType;
 +import org.apache.syncope.common.lib.types.MappingPurpose;
 +import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
 +import org.apache.syncope.common.rest.api.service.ResourceService;
 +import org.identityconnectors.framework.common.objects.ObjectClass;
 +import org.junit.FixMethodOrder;
 +import org.junit.Test;
 +import org.junit.runners.MethodSorters;
 +import org.springframework.jdbc.core.JdbcTemplate;
 +
 +@FixMethodOrder(MethodSorters.JVM)
 +public class VirAttrITCase extends AbstractITCase {
 +
 +    @Test
 +    public void issueSYNCOPE16() {
 +        UserTO userTO = UserITCase.getUniqueSampleTO("issue16@apache.org");
 +
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRightKey(8L);
 +        userTO.getMemberships().add(membershipTO);
 +
 +        // 1. create user
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +
 +        // 2. check for virtual attribute value
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertEquals("virtualvalue", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(actual.getKey());
 +        userMod.getVirAttrsToRemove().add("virtualdata");
 +        userMod.getVirAttrsToUpdate().add(attrMod("virtualdata", "virtualupdated"));
 +
 +        // 3. update virtual attribute
 +        actual = updateUser(userMod);
 +        assertNotNull(actual);
 +
 +        // 4. check for virtual attribute value
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertEquals("virtualupdated", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE260() {
 +        // ----------------------------------
 +        // create user and check virtual attribute value propagation
 +        // ----------------------------------
 +        UserTO userTO = UserITCase.getUniqueSampleTO("260@a.com");
 +        userTO.getResources().add(RESOURCE_NAME_WS2);
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
 +        assertEquals(RESOURCE_NAME_WS2, userTO.getPropagationStatusTOs().get(0).getResource());
 +        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationStatusTOs().get(0).getStatus());
 +
 +        ConnObjectTO connObjectTO =
 +                resourceService.readConnObject(RESOURCE_NAME_WS2, AnyTypeKind.USER.name(), userTO.getKey());
 +        assertNotNull(connObjectTO);
 +        assertEquals("virtualvalue", connObjectTO.getPlainAttrMap().get("NAME").getValues().get(0));
 +        // ----------------------------------
 +
 +        // ----------------------------------
 +        // update user virtual attribute and check virtual attribute value update propagation
 +        // ----------------------------------
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +
 +        AttrMod attrMod = new AttrMod();
 +        attrMod.setSchema("virtualdata");
 +        attrMod.getValuesToBeRemoved().add("virtualvalue");
 +        attrMod.getValuesToBeAdded().add("virtualvalue2");
 +
 +        userMod.getVirAttrsToUpdate().add(attrMod);
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
 +        assertEquals("ws-target-resource-2", userTO.getPropagationStatusTOs().get(0).getResource());
 +        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationStatusTOs().get(0).getStatus());
 +
 +        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_WS2, AnyTypeKind.USER.name(), userTO.getKey());
 +        assertNotNull(connObjectTO);
 +        assertEquals("virtualvalue2", connObjectTO.getPlainAttrMap().get("NAME").getValues().get(0));
 +        // ----------------------------------
 +
 +        // ----------------------------------
 +        // suspend/reactivate user and check virtual attribute value (unchanged)
 +        // ----------------------------------
 +        StatusMod statusMod = new StatusMod();
 +        statusMod.setKey(userTO.getKey());
 +        statusMod.setType(StatusMod.ModType.SUSPEND);
 +        userTO = userService.status(statusMod).readEntity(UserTO.class);
 +        assertEquals("suspended", userTO.getStatus());
 +
 +        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_WS2, AnyTypeKind.USER.name(), userTO.getKey());
 +        assertNotNull(connObjectTO);
 +        assertFalse(connObjectTO.getPlainAttrMap().get("NAME").getValues().isEmpty());
 +        assertEquals("virtualvalue2", connObjectTO.getPlainAttrMap().get("NAME").getValues().get(0));
 +
 +        statusMod = new StatusMod();
 +        statusMod.setKey(userTO.getKey());
 +        statusMod.setType(StatusMod.ModType.REACTIVATE);
 +        userTO = userService.status(statusMod).readEntity(UserTO.class);
 +        assertEquals("active", userTO.getStatus());
 +
 +        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_WS2, AnyTypeKind.USER.name(), userTO.getKey());
 +        assertNotNull(connObjectTO);
 +        assertFalse(connObjectTO.getPlainAttrMap().get("NAME").getValues().isEmpty());
 +        assertEquals("virtualvalue2", connObjectTO.getPlainAttrMap().get("NAME").getValues().get(0));
 +        // ----------------------------------
 +
 +        // ----------------------------------
 +        // update user attribute and check virtual attribute value (unchanged)
 +        // ----------------------------------
 +        userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +
 +        attrMod = new AttrMod();
 +        attrMod.setSchema("surname");
 +        attrMod.getValuesToBeRemoved().add("Surname");
 +        attrMod.getValuesToBeAdded().add("Surname2");
 +
 +        userMod.getPlainAttrsToUpdate().add(attrMod);
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
 +        assertEquals(RESOURCE_NAME_WS2, userTO.getPropagationStatusTOs().get(0).getResource());
 +        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationStatusTOs().get(0).getStatus());
 +
 +        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_WS2, AnyTypeKind.USER.name(), userTO.getKey());
 +        assertNotNull(connObjectTO);
 +        assertEquals("Surname2", connObjectTO.getPlainAttrMap().get("SURNAME").getValues().get(0));
 +
 +        // attribute "name" mapped on virtual attribute "virtualdata" shouldn't be changed
 +        assertFalse(connObjectTO.getPlainAttrMap().get("NAME").getValues().isEmpty());
 +        assertEquals("virtualvalue2", connObjectTO.getPlainAttrMap().get("NAME").getValues().get(0));
 +        // ----------------------------------
 +
 +        // ----------------------------------
 +        // remove user virtual attribute and check virtual attribute value (reset)
 +        // ----------------------------------
 +        userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +        userMod.getVirAttrsToRemove().add("virtualdata");
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +        assertTrue(userTO.getVirAttrs().isEmpty());
 +        assertFalse(userTO.getPropagationStatusTOs().isEmpty());
 +        assertEquals(RESOURCE_NAME_WS2, userTO.getPropagationStatusTOs().get(0).getResource());
 +        assertEquals(PropagationTaskExecStatus.SUBMITTED, userTO.getPropagationStatusTOs().get(0).getStatus());
 +
 +        connObjectTO = resourceService.readConnObject(RESOURCE_NAME_WS2, AnyTypeKind.USER.name(), userTO.getKey());
 +        assertNotNull(connObjectTO);
 +
 +        // attribute "name" mapped on virtual attribute "virtualdata" should be reset
 +        assertTrue(connObjectTO.getPlainAttrMap().get("NAME").getValues() == null
 +                || connObjectTO.getPlainAttrMap().get("NAME").getValues().isEmpty());
 +        // ----------------------------------
 +    }
 +
 +    @Test
 +    public void virAttrCache() {
 +        UserTO userTO = UserITCase.getUniqueSampleTO("virattrcache@apache.org");
 +        userTO.getVirAttrs().clear();
 +
 +        AttrTO virAttrTO = new AttrTO();
 +        virAttrTO.setSchema("virtualdata");
 +        virAttrTO.getValues().add("virattrcache");
 +        userTO.getVirAttrs().add(virAttrTO);
 +
 +        userTO.getMemberships().clear();
 +        userTO.getResources().clear();
 +        userTO.getResources().add(RESOURCE_NAME_DBVIRATTR);
 +
 +        // 1. create user
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +
 +        // 2. check for virtual attribute value
 +        actual = userService.read(actual.getKey());
 +        assertEquals("virattrcache", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
 +
 +        // 3. update virtual attribute directly
 +        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
 +
 +        String value = jdbcTemplate.queryForObject(
 +                "SELECT USERNAME FROM testsync WHERE ID=?", String.class, actual.getKey());
 +        assertEquals("virattrcache", value);
 +
 +        jdbcTemplate.update("UPDATE testsync set USERNAME='virattrcache2' WHERE ID=?", actual.getKey());
 +
 +        value = jdbcTemplate.queryForObject(
 +                "SELECT USERNAME FROM testsync WHERE ID=?", String.class, actual.getKey());
 +        assertEquals("virattrcache2", value);
 +
 +        // 4. check for cached attribute value
 +        actual = userService.read(actual.getKey());
 +        assertEquals("virattrcache", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(actual.getKey());
 +
 +        AttrMod virtualdata = new AttrMod();
 +        virtualdata.setSchema("virtualdata");
 +        virtualdata.getValuesToBeAdded().add("virtualupdated");
 +
 +        userMod.getVirAttrsToRemove().add("virtualdata");
 +        userMod.getVirAttrsToUpdate().add(virtualdata);
 +
 +        // 5. update virtual attribute
 +        actual = updateUser(userMod);
 +        assertNotNull(actual);
 +
 +        // 6. check for virtual attribute value
 +        actual = userService.read(actual.getKey());
 +        assertNotNull(actual);
 +        assertEquals("virtualupdated", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE397() {
 +        ResourceTO csv = resourceService.read(RESOURCE_NAME_CSV);
 +        MappingTO origMapping = SerializationUtils.clone(csv.getProvisions().get(0).getMapping());
 +        try {
 +            // change mapping of resource-csv
 +            assertNotNull(origMapping);
 +            for (MappingItemTO item : csv.getProvisions().get(0).getMapping().getItems()) {
 +                if ("email".equals(item.getIntAttrName())) {
 +                    // unset internal attribute mail and set virtual attribute virtualdata as mapped to external email
 +                    item.setIntMappingType(IntMappingType.UserVirtualSchema);
 +                    item.setIntAttrName("virtualdata");
 +                    item.setPurpose(MappingPurpose.BOTH);
 +                    item.setExtAttrName("email");
 +                }
 +            }
 +
 +            resourceService.update(csv);
 +            csv = resourceService.read(RESOURCE_NAME_CSV);
 +            assertNotNull(csv.getProvisions().get(0).getMapping());
 +
 +            boolean found = false;
 +            for (MappingItemTO item : csv.getProvisions().get(0).getMapping().getItems()) {
 +                if ("email".equals(item.getExtAttrName()) && "virtualdata".equals(item.getIntAttrName())) {
 +                    found = true;
 +                }
 +            }
 +
 +            assertTrue(found);
 +
 +            // create a new user
 +            UserTO userTO = UserITCase.getUniqueSampleTO("syncope397@syncope.apache.org");
 +            userTO.getAuxClasses().add("csv");
 +            userTO.getResources().clear();
 +            userTO.getMemberships().clear();
 +            userTO.getDerAttrs().clear();
 +            userTO.getVirAttrs().clear();
 +
 +            userTO.getDerAttrs().add(attrTO("csvuserid", null));
 +            userTO.getDerAttrs().add(attrTO("cn", null));
 +            userTO.getVirAttrs().add(attrTO("virtualdata", "test@testone.org"));
 +            // assign resource-csv to user
 +            userTO.getResources().add(RESOURCE_NAME_CSV);
 +            // save user
 +            UserTO created = createUser(userTO);
 +            // make std controls about user
 +            assertNotNull(created);
 +            assertTrue(RESOURCE_NAME_CSV.equals(created.getResources().iterator().next()));
++            assertEquals("test@testone.org", created.getVirAttrs().iterator().next().getValues().get(0));
++            
 +            // update user
 +            UserTO toBeUpdated = userService.read(created.getKey());
 +            UserMod userMod = new UserMod();
 +            userMod.setKey(toBeUpdated.getKey());
 +            userMod.setPassword("password234");
 +            // assign new resource to user
 +            userMod.getResourcesToAdd().add(RESOURCE_NAME_WS2);
 +            //modify virtual attribute
 +            userMod.getVirAttrsToRemove().add("virtualdata");
 +            userMod.getVirAttrsToUpdate().add(attrMod("virtualdata", "test@testoneone.com"));
 +
 +            // check Syncope change password
 +            StatusMod pwdPropRequest = new StatusMod();
 +            pwdPropRequest.setOnSyncope(true);
 +            pwdPropRequest.getResources().add(RESOURCE_NAME_WS2);
 +            userMod.setPwdPropRequest(pwdPropRequest);
 +
 +            toBeUpdated = updateUser(userMod);
 +            assertNotNull(toBeUpdated);
 +            assertEquals("test@testoneone.com", toBeUpdated.getVirAttrs().iterator().next().getValues().get(0));
 +            // check if propagates correctly with assertEquals on size of tasks list
 +            assertEquals(2, toBeUpdated.getPropagationStatusTOs().size());
 +        } finally {
 +            // restore mapping of resource-csv
 +            csv.getProvisions().get(0).setMapping(origMapping);
 +            resourceService.update(csv);
 +        }
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE442() {
 +        UserTO userTO = UserITCase.getUniqueSampleTO("syncope442@apache.org");
 +        userTO.getVirAttrs().clear();
 +
 +        AttrTO virAttrTO = new AttrTO();
 +        virAttrTO.setSchema("virtualdata");
 +        virAttrTO.getValues().add("virattrcache");
 +        userTO.getVirAttrs().add(virAttrTO);
 +
 +        userTO.getMemberships().clear();
 +        userTO.getResources().clear();
 +        userTO.getResources().add(RESOURCE_NAME_DBVIRATTR);
 +
 +        // 1. create user
 +        UserTO actual = createUser(userTO);
 +        assertNotNull(actual);
 +
 +        // 2. check for virtual attribute value
 +        actual = userService.read(actual.getKey());
 +        assertEquals("virattrcache", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
 +
 +        // ----------------------------------------
 +        // 3. force cache expiring without any modification
 +        // ----------------------------------------
 +        String jdbcURL = null;
 +        ConnInstanceTO connInstanceTO = connectorService.readByResource(
 +                RESOURCE_NAME_DBVIRATTR, Locale.ENGLISH.getLanguage());
 +        for (ConnConfProperty prop : connInstanceTO.getConfiguration()) {
 +            if ("jdbcUrlTemplate".equals(prop.getSchema().getName())) {
 +                jdbcURL = prop.getValues().iterator().next().toString();
 +                prop.getValues().clear();
 +                prop.getValues().add("jdbc:h2:tcp://localhost:9092/xxx");
 +            }
 +        }
 +
 +        connectorService.update(connInstanceTO);
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(actual.getKey());
 +
 +        AttrMod virtualdata = new AttrMod();
 +        virtualdata.setSchema("virtualdata");
 +        virtualdata.getValuesToBeAdded().add("virtualupdated");
 +
 +        userMod.getVirAttrsToRemove().add("virtualdata");
 +        userMod.getVirAttrsToUpdate().add(virtualdata);
 +
 +        actual = updateUser(userMod);
 +        assertNotNull(actual);
 +        // ----------------------------------------
 +
 +        // ----------------------------------------
 +        // 4. update virtual attribute
 +        // ----------------------------------------
 +        final JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
 +
 +        String value = jdbcTemplate.queryForObject(
 +                "SELECT USERNAME FROM testsync WHERE ID=?", String.class, actual.getKey());
 +        assertEquals("virattrcache", value);
 +
 +        jdbcTemplate.update("UPDATE testsync set USERNAME='virattrcache2' WHERE ID=?", actual.getKey());
 +
 +        value = jdbcTemplate.queryForObject(
 +                "SELECT USERNAME FROM testsync WHERE ID=?", String.class, actual.getKey());
 +        assertEquals("virattrcache2", value);
 +        // ----------------------------------------
 +
 +        actual = userService.read(actual.getKey());
 +        assertEquals("virattrcache", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
 +
 +        // ----------------------------------------
 +        // 5. restore connector
 +        // ----------------------------------------
 +        for (ConnConfProperty prop : connInstanceTO.getConfiguration()) {
 +            if ("jdbcUrlTemplate".equals(prop.getSchema().getName())) {
 +                prop.getValues().clear();
 +                prop.getValues().add(jdbcURL);
 +            }
 +        }
 +
 +        connectorService.update(connInstanceTO);
 +        // ----------------------------------------
 +
 +        actual = userService.read(actual.getKey());
 +        assertEquals("virattrcache2", actual.getVirAttrMap().get("virtualdata").getValues().get(0));
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE436() {
 +        UserTO userTO = UserITCase.getUniqueSampleTO("syncope436@syncope.apache.org");
 +        userTO.getMemberships().clear();
 +        userTO.getResources().clear();
 +        userTO.getResources().add(RESOURCE_NAME_LDAP);
 +        userTO.getVirAttrs().add(attrTO("virtualReadOnly", "readOnly"));
 +        userTO = createUser(userTO);
 +        //Finding no values because the virtual attribute is readonly 
 +        assertTrue(userTO.getVirAttrMap().get("virtualReadOnly").getValues().isEmpty());
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE453() {
 +        final String resourceName = "issueSYNCOPE453-Res-" + getUUIDString();
 +        final String groupName = "issueSYNCOPE453-Group-" + getUUIDString();
 +
 +        // -------------------------------------------
 +        // Create a resource ad-hoc
 +        // -------------------------------------------
 +        ResourceTO resourceTO = new ResourceTO();
 +
 +        resourceTO.setKey(resourceName);
 +        resourceTO.setConnector(107L);
 +
 +        ProvisionTO provisionTO = new ProvisionTO();
 +        provisionTO.setAnyType(AnyTypeKind.USER.name());
 +        provisionTO.setObjectClass(ObjectClass.ACCOUNT_NAME);
 +        resourceTO.getProvisions().add(provisionTO);
 +
 +        MappingTO mapping = new MappingTO();
 +        provisionTO.setMapping(mapping);
 +
 +        MappingItemTO item = new MappingItemTO();
 +        item.setIntAttrName("aLong");
 +        item.setIntMappingType(IntMappingType.UserPlainSchema);
 +        item.setExtAttrName("ID");
 +        item.setPurpose(MappingPurpose.PROPAGATION);
 +        item.setConnObjectKey(true);
 +        mapping.setConnObjectKeyItem(item);
 +
 +        item = new MappingItemTO();
 +        item.setExtAttrName("USERNAME");
 +        item.setIntAttrName("username");
 +        item.setIntMappingType(IntMappingType.Username);
 +        item.setPurpose(MappingPurpose.PROPAGATION);
 +        mapping.getItems().add(item);
 +
 +        item = new MappingItemTO();
 +        item.setExtAttrName("EMAIL");
 +        item.setIntAttrName("rvirtualdata");
 +        item.setIntMappingType(IntMappingType.GroupVirtualSchema);
 +        item.setPurpose(MappingPurpose.PROPAGATION);
 +        mapping.getItems().add(item);
 +
 +        assertNotNull(getObject(
 +                resourceService.create(resourceTO).getLocation(), ResourceService.class, ResourceTO.class));
 +        // -------------------------------------------
 +
 +        // -------------------------------------------
 +        // Create a VirAttrITCase ad-hoc
 +        // -------------------------------------------
 +        GroupTO groupTO = new GroupTO();
 +        groupTO.setName(groupName);
 +        groupTO.setRealm("/");
 +        groupTO.getVirAttrs().add(attrTO("rvirtualdata", "ml@group.it"));
 +        groupTO.getResources().add(RESOURCE_NAME_LDAP);
 +        groupTO = createGroup(groupTO);
 +        assertEquals(1, groupTO.getVirAttrs().size());
 +        assertEquals("ml@group.it", groupTO.getVirAttrs().iterator().next().getValues().get(0));
 +        // -------------------------------------------
 +
 +        // -------------------------------------------
 +        // Create new user
 +        // -------------------------------------------
 +        UserTO userTO = UserITCase.getUniqueSampleTO("syncope453@syncope.apache.org");
 +        userTO.getPlainAttrs().add(attrTO("aLong", "123"));
 +        userTO.getResources().clear();
 +        userTO.getResources().add(resourceName);
 +        userTO.getVirAttrs().clear();
 +        userTO.getDerAttrs().clear();
 +        userTO.getMemberships().clear();
 +
 +        MembershipTO membership = new MembershipTO();
 +        membership.setRightKey(groupTO.getKey());
 +        userTO.getMemberships().add(membership);
 +
 +        userTO = createUser(userTO);
 +        assertEquals(2, userTO.getPropagationStatusTOs().size());
 +        assertTrue(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful());
 +        assertTrue(userTO.getPropagationStatusTOs().get(1).getStatus().isSuccessful());
 +
 +        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
 +
 +        final Map<String, Object> actuals = jdbcTemplate.queryForMap(
 +                "SELECT id, surname, email FROM testsync WHERE id=?",
 +                new Object[] { Integer.parseInt(userTO.getPlainAttrMap().get("aLong").getValues().get(0)) });
 +
 +        assertEquals(userTO.getPlainAttrMap().get("aLong").getValues().get(0), actuals.get("id").toString());
 +        assertEquals("ml@group.it", actuals.get("email"));
 +        // -------------------------------------------
 +
 +        // -------------------------------------------
 +        // Delete resource and group ad-hoc
 +        // -------------------------------------------
 +        resourceService.delete(resourceName);
 +        groupService.delete(groupTO.getKey());
 +        // -------------------------------------------
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE459() {
 +        UserTO userTO = UserITCase.getUniqueSampleTO("syncope459@apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getVirAttrs().clear();
 +
 +        AttrTO virtualReadOnly = attrTO("virtualReadOnly", "");
 +        virtualReadOnly.getValues().clear();
 +
 +        userTO.getVirAttrs().add(virtualReadOnly);
 +
 +        userTO = createUser(userTO);
 +
 +        assertNotNull(userTO.getVirAttrMap().get("virtualReadOnly"));
 +
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +
 +        AttrMod virtualdata = new AttrMod();
 +        virtualdata.setSchema("virtualdata");
 +
 +        userMod.getVirAttrsToUpdate().add(virtualdata);
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO.getVirAttrMap().get("virtualdata"));
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE501() {
 +        // 1. create user and propagate him on resource-db-virattr
 +        UserTO userTO = UserITCase.getUniqueSampleTO("syncope501@apache.org");
 +        userTO.getResources().clear();
 +        userTO.getMemberships().clear();
 +        userTO.getVirAttrs().clear();
 +
 +        userTO.getResources().add(RESOURCE_NAME_DBVIRATTR);
 +
 +        // virtualdata is mapped with username
 +        final AttrTO virtualData = attrTO("virtualdata", "syncope501@apache.org");
 +        userTO.getVirAttrs().add(virtualData);
 +
 +        userTO = createUser(userTO);
 +
 +        assertNotNull(userTO.getVirAttrMap().get("virtualdata"));
 +        assertEquals("syncope501@apache.org", userTO.getVirAttrMap().get("virtualdata").getValues().get(0));
 +
 +        // 2. update virtual attribute
 +        UserMod userMod = new UserMod();
 +        userMod.setKey(userTO.getKey());
 +
 +        final StatusMod statusMod = new StatusMod();
 +        statusMod.getResources().addAll(Collections.<String>emptySet());
 +        statusMod.setOnSyncope(false);
 +
 +        userMod.setPwdPropRequest(statusMod);
 +        // change virtual attribute value
 +        final AttrMod virtualDataMod = new AttrMod();
 +        virtualDataMod.setSchema("virtualdata");
 +        virtualDataMod.getValuesToBeAdded().add("syncope501_updated@apache.org");
 +        virtualDataMod.getValuesToBeRemoved().add("syncope501@apache.org");
 +        userMod.getVirAttrsToUpdate().add(virtualDataMod);
 +        userMod.getVirAttrsToRemove().add("virtualdata");
 +
 +        userTO = updateUser(userMod);
 +        assertNotNull(userTO);
 +
 +        // 3. check that user virtual attribute has really been updated 
 +        assertFalse(userTO.getVirAttrMap().get("virtualdata").getValues().isEmpty());
 +        assertEquals("syncope501_updated@apache.org", userTO.getVirAttrMap().get("virtualdata").getValues().get(0));
 +    }
++
++    @Test
++    public void issueSYNCOPE691() {
++        final String res = RESOURCE_NAME_LDAP + "691";
++
++        ResourceTO ldap = resourceService.read(RESOURCE_NAME_LDAP);
++        ldap.setKey(res);
++
++        try {
++
++            CollectionUtils.filterInverse(ldap.getProvision(AnyTypeKind.USER.name()).getMapping().getItems(),
++                    new Predicate<MappingItemTO>() {
++
++                        @Override
++                        public boolean evaluate(final MappingItemTO item) {
++                            return "mail".equals(item.getExtAttrName());
++                        }
++                    });
++
++            final MappingItemTO mail = new MappingItemTO();
++            // unset internal attribute mail and set virtual attribute virtualdata as mapped to external email
++            mail.setIntMappingType(IntMappingType.UserVirtualSchema);
++            mail.setIntAttrName("virtualdata");
++            mail.setPurpose(MappingPurpose.BOTH);
++            mail.setExtAttrName("mail");
++
++            ldap.getProvision(AnyTypeKind.USER.name()).getMapping().getItems().add(mail);
++
++            resourceService.create(ldap);
++
++            ldap = resourceService.read(res);
++            assertNotNull(ldap.getProvision(AnyTypeKind.USER.name()).getMapping());
++
++            assertTrue(CollectionUtils.exists(ldap.getProvision(AnyTypeKind.USER.name()).getMapping().getItems(),
++                    new Predicate<MappingItemTO>() {
++
++                        @Override
++                        public boolean evaluate(final MappingItemTO item) {
++                            return "mail".equals(item.getExtAttrName()) && "virtualdata".equals(item.getIntAttrName());
++                        }
++                    }));
++
++            // create a new user
++            UserTO userTO = UserITCase.getUniqueSampleTO("syncope691@syncope.apache.org");
++            userTO.getResources().clear();
++            userTO.getMemberships().clear();
++            userTO.getDerAttrs().clear();
++            userTO.getVirAttrs().clear();
++
++            final AttrTO emailTO = new AttrTO();
++            emailTO.setSchema("virtualdata");
++            emailTO.getValues().add("test@issue691.dom1.org");
++            emailTO.getValues().add("test@issue691.dom2.org");
++
++            userTO.getVirAttrs().add(emailTO);
++            // assign resource-ldap691 to user
++            userTO.getResources().add(res);
++            // save user
++            UserTO created = createUser(userTO);
++            // make std controls about user
++            assertNotNull(created);
++            assertTrue(res.equals(created.getResources().iterator().next()));
++
++            assertEquals(2, created.getVirAttrs().iterator().next().getValues().size(), 0);
++            assertTrue(created.getVirAttrs().iterator().next().getValues().contains("test@issue691.dom1.org"));
++            assertTrue(created.getVirAttrs().iterator().next().getValues().contains("test@issue691.dom2.org"));
++
++            // update user
++            UserMod userMod = new UserMod();
++            userMod.setKey(created.getKey());
++            //modify virtual attribute
++            userMod.getVirAttrsToRemove().add("virtualdata");
++
++            final AttrMod emailMod = new AttrMod();
++            emailMod.setSchema("virtualdata");
++            emailMod.getValuesToBeAdded().add("test@issue691.dom3.org");
++            emailMod.getValuesToBeAdded().add("test@issue691.dom4.org");
++
++            userMod.getVirAttrsToUpdate().add(emailMod);
++
++            final UserTO updated = updateUser(userMod);
++            assertNotNull(updated);
++            assertEquals(2, updated.getVirAttrs().iterator().next().getValues().size(), 0);
++            assertTrue(updated.getVirAttrs().iterator().next().getValues().contains("test@issue691.dom3.org"));
++            assertTrue(updated.getVirAttrs().iterator().next().getValues().contains("test@issue691.dom4.org"));
++        } finally {
++            try {
++                resourceService.delete(res);
++            } catch (Exception ignore) {
++                // ignore
++            }
++        }
++    }
++
 +}