You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2015/01/12 17:32:25 UTC

[46/52] [abbrv] [partial] syncope git commit: [SYNCOPE-620] Unit tests all in

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/ConnObjectUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/ConnObjectUtil.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/ConnObjectUtil.java
new file mode 100644
index 0000000..831a981
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/ConnObjectUtil.java
@@ -0,0 +1,767 @@
+/*
+ * 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.server.misc;
+
+import org.apache.syncope.server.misc.policy.InvalidPasswordPolicySpecException;
+import org.apache.syncope.server.misc.security.PasswordGenerator;
+import org.apache.syncope.server.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.server.persistence.api.attrvalue.validation.ParsingValidationException;
+import org.apache.syncope.server.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.server.persistence.api.dao.NotFoundException;
+import org.apache.syncope.server.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.server.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.entity.Attributable;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.MappingItem;
+import org.apache.syncope.server.persistence.api.entity.PasswordPolicy;
+import org.apache.syncope.server.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.server.persistence.api.entity.PlainSchema;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.Membership;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.Connector;
+import org.apache.syncope.server.provisioning.api.ConnectorFactory;
+import org.apache.syncope.server.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.server.provisioning.api.cache.VirAttrCacheValue;
+import org.apache.syncope.server.misc.security.Encryptor;
+import org.apache.syncope.server.misc.security.UnauthorizedRoleException;
+import org.apache.syncope.server.misc.spring.ApplicationContextProvider;
+import org.apache.syncope.server.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 UserSchema:
+                case RoleSchema:
+                    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).getRAttrTemplates().addAll(((RoleTO) template).getRAttrTemplates());
+                ((RoleTO) subjectTO).getRDerAttrTemplates().addAll(((RoleTO) template).getRDerAttrTemplates());
+                ((RoleTO) subjectTO).getRVirAttrTemplates().addAll(((RoleTO) template).getRVirAttrTemplates());
+                ((RoleTO) subjectTO).getMAttrTemplates().addAll(((RoleTO) template).getMAttrTemplates());
+                ((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).setInheritAttrs(((RoleTO) template).isInheritAttrs());
+                ((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.getAttrMap();
+        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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/DataFormat.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/DataFormat.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/DataFormat.java
new file mode 100644
index 0000000..d9af59f
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/DataFormat.java
@@ -0,0 +1,117 @@
+/*
+ * 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.server.misc;
+
+import java.text.DecimalFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import org.apache.commons.lang3.time.DateUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+
+/**
+ * Utility class for parsing / formatting date and numbers.
+ */
+public final class DataFormat {
+
+    private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
+
+        @Override
+        protected SimpleDateFormat initialValue() {
+            SimpleDateFormat sdf = new SimpleDateFormat();
+            sdf.applyPattern(SyncopeConstants.DEFAULT_DATE_PATTERN);
+            return sdf;
+        }
+    };
+
+    private static final ThreadLocal<DecimalFormat> DECIMAL_FORMAT = new ThreadLocal<DecimalFormat>() {
+
+        @Override
+        protected DecimalFormat initialValue() {
+            return new DecimalFormat();
+        }
+    };
+
+    public static String format(final Date date) {
+        return format(date, true);
+    }
+
+    public static String format(final Date date, final boolean lenient) {
+        return format(date, lenient, null);
+    }
+
+    public static String format(final Date date, final boolean lenient, final String conversionPattern) {
+        SimpleDateFormat sdf = DATE_FORMAT.get();
+        if (conversionPattern != null) {
+            sdf.applyPattern(conversionPattern);
+        }
+        sdf.setLenient(lenient);
+        return sdf.format(date);
+    }
+
+    public static String format(final long number) {
+        return format(number, null);
+    }
+
+    public static String format(final long number, final String conversionPattern) {
+        DecimalFormat df = DECIMAL_FORMAT.get();
+        if (conversionPattern != null) {
+            df.applyPattern(conversionPattern);
+        }
+        return df.format(number);
+    }
+
+    public static String format(final double number) {
+        return format(number, null);
+    }
+
+    public static String format(final double number, final String conversionPattern) {
+        DecimalFormat df = DECIMAL_FORMAT.get();
+        if (conversionPattern != null) {
+            df.applyPattern(conversionPattern);
+        }
+        return df.format(number);
+    }
+
+    public static Date parseDate(final String source) throws ParseException {
+        return DateUtils.parseDate(source, SyncopeConstants.DATE_PATTERNS);
+    }
+
+    public static Date parseDate(final String source, final String conversionPattern) throws ParseException {
+        SimpleDateFormat sdf = DATE_FORMAT.get();
+        sdf.applyPattern(conversionPattern);
+        sdf.setLenient(false);
+        return sdf.parse(source);
+    }
+
+    public static Number parseNumber(final String source, final String conversionPattern) throws ParseException {
+        DecimalFormat df = DECIMAL_FORMAT.get();
+        df.applyPattern(conversionPattern);
+        return df.parse(source);
+    }
+
+    public static void clear() {
+        DATE_FORMAT.remove();
+        DECIMAL_FORMAT.remove();
+    }
+
+    private DataFormat() {
+        // private empty constructor
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/ExceptionUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/ExceptionUtil.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/ExceptionUtil.java
new file mode 100644
index 0000000..564469f
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/ExceptionUtil.java
@@ -0,0 +1,47 @@
+/*
+ * 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.server.misc;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+public final class ExceptionUtil {
+
+    /**
+     * Uses commons lang's ExceptionUtils to provide a representation of the full stack trace of the given throwable.
+     *
+     * @param t throwable to build stack trace from
+     * @return a string representation of full stack trace of the given throwable
+     */
+    public static String getFullStackTrace(final Throwable t) {
+        StringBuilder result = new StringBuilder();
+
+        for (Throwable throwable : ExceptionUtils.getThrowableList(t)) {
+            result.append(ExceptionUtils.getMessage(throwable)).append('\n').
+                    append(ExceptionUtils.getStackTrace(throwable)).append("\n\n");
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Private default constructor, for static-only classes.
+     */
+    private ExceptionUtil() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/MappingUtil.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/MappingUtil.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/MappingUtil.java
new file mode 100644
index 0000000..7ae586f
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/MappingUtil.java
@@ -0,0 +1,738 @@
+/*
+ * 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.server.misc;
+
+import org.apache.syncope.server.misc.policy.InvalidPasswordPolicySpecException;
+import org.apache.syncope.server.misc.security.PasswordGenerator;
+import java.util.AbstractMap;
+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.jexl2.JexlContext;
+import org.apache.commons.jexl2.MapContext;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.mod.AttrMod;
+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.server.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.server.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.server.persistence.api.entity.Attributable;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.server.persistence.api.entity.DerAttr;
+import org.apache.syncope.server.persistence.api.entity.EntityFactory;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.MappingItem;
+import org.apache.syncope.server.persistence.api.entity.PlainAttr;
+import org.apache.syncope.server.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.server.persistence.api.entity.PlainSchema;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.persistence.api.entity.VirSchema;
+import org.apache.syncope.server.persistence.api.entity.membership.MDerSchema;
+import org.apache.syncope.server.persistence.api.entity.membership.MPlainSchema;
+import org.apache.syncope.server.persistence.api.entity.membership.MVirSchema;
+import org.apache.syncope.server.persistence.api.entity.membership.Membership;
+import org.apache.syncope.server.persistence.api.entity.role.RDerSchema;
+import org.apache.syncope.server.persistence.api.entity.role.RPlainAttrValue;
+import org.apache.syncope.server.persistence.api.entity.role.RPlainSchema;
+import org.apache.syncope.server.persistence.api.entity.role.RVirSchema;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.user.UDerSchema;
+import org.apache.syncope.server.persistence.api.entity.user.UPlainAttrValue;
+import org.apache.syncope.server.persistence.api.entity.user.UPlainSchema;
+import org.apache.syncope.server.persistence.api.entity.user.UVirSchema;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.server.misc.security.Encryptor;
+import org.apache.syncope.server.misc.spring.ApplicationContextProvider;
+import org.apache.syncope.server.misc.jexl.JexlUtil;
+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.context.ConfigurableApplicationContext;
+
+public final class MappingUtil {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(MappingUtil.class);
+
+    private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
+
+    public static <T extends MappingItem> List<T> getMatchingMappingItems(
+            final Collection<T> items, final IntMappingType type) {
+
+        final List<T> result = new ArrayList<>();
+
+        for (T mapItem : items) {
+            if (mapItem.getIntMappingType() == type) {
+                result.add(mapItem);
+            }
+        }
+
+        return result;
+    }
+
+    public static <T extends MappingItem> List<T> getMatchingMappingItems(final Collection<T> items,
+            final String intAttrName, final IntMappingType type) {
+
+        final List<T> result = new ArrayList<>();
+
+        for (T mapItem : items) {
+            if (mapItem.getIntMappingType() == type && intAttrName.equals(mapItem.getIntAttrName())) {
+                result.add(mapItem);
+            }
+        }
+
+        return result;
+    }
+
+    public static <T extends MappingItem> Set<T> getMatchingMappingItems(final Collection<T> mapItems,
+            final String intAttrName) {
+
+        final Set<T> result = new HashSet<>();
+
+        for (T mapItem : mapItems) {
+            if (intAttrName.equals(mapItem.getIntAttrName())) {
+                result.add(mapItem);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Prepare attributes for sending to a connector instance.
+     *
+     * @param attrUtil user / role
+     * @param subject given user / role
+     * @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 membVAttrsToBeRemoved membership virtual attributes to be removed
+     * @param membVAttrsToBeUpdated membership virtual attributes to be added
+     * @param enable whether user must be enabled or not
+     * @param resource target resource
+     * @return account link + prepared attributes
+     */
+    public static Map.Entry<String, Set<Attribute>> prepareAttributes(
+            final AttributableUtil attrUtil, final Subject<?, ?, ?> subject,
+            final String password,
+            final boolean changePwd,
+            final Set<String> vAttrsToBeRemoved,
+            final Map<String, AttrMod> vAttrsToBeUpdated,
+            final Set<String> membVAttrsToBeRemoved,
+            final Map<String, AttrMod> membVAttrsToBeUpdated,
+            final Boolean enable,
+            final ExternalResource resource) {
+
+        LOG.debug("Preparing resource attributes for {} on resource {} with attributes {}",
+                subject, resource, subject.getPlainAttrs());
+
+        final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext();
+        final VirAttrCache virAttrCache = context.getBean(VirAttrCache.class);
+        final PasswordGenerator passwordGenerator = context.getBean(PasswordGenerator.class);
+
+        Set<Attribute> attributes = new HashSet<>();
+        String accountId = null;
+
+        for (MappingItem mapping : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+            LOG.debug("Processing schema {}", mapping.getIntAttrName());
+
+            try {
+                if ((attrUtil.getType() == AttributableType.USER
+                        && mapping.getIntMappingType() == IntMappingType.UserVirtualSchema)
+                        || (attrUtil.getType() == AttributableType.ROLE
+                        && mapping.getIntMappingType() == IntMappingType.RoleVirtualSchema)) {
+
+                    LOG.debug("Expire entry cache {}-{}", subject.getKey(), mapping.getIntAttrName());
+                    virAttrCache.expire(attrUtil.getType(), subject.getKey(), mapping.getIntAttrName());
+                }
+
+                // SYNCOPE-458 expire cache also for membership virtual schemas
+                if (attrUtil.getType() == AttributableType.USER && mapping.getIntMappingType()
+                        == IntMappingType.MembershipVirtualSchema && (subject instanceof User)) {
+
+                    final User user = (User) subject;
+                    for (Membership membership : user.getMemberships()) {
+                        LOG.debug("Expire entry cache {}-{} for membership {}", subject.getKey(),
+                                mapping.getIntAttrName(), membership);
+                        virAttrCache.expire(AttributableType.MEMBERSHIP, membership.getKey(),
+                                mapping.getIntAttrName());
+                    }
+                }
+
+                Map.Entry<String, Attribute> preparedAttr = prepareAttr(
+                        resource, mapping, subject, password, passwordGenerator, vAttrsToBeRemoved, vAttrsToBeUpdated,
+                        membVAttrsToBeRemoved, membVAttrsToBeUpdated);
+
+                if (preparedAttr != null && preparedAttr.getKey() != null) {
+                    accountId = 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);
+            }
+        }
+
+        final Attribute accountIdExtAttr =
+                AttributeUtil.find(attrUtil.getAccountIdItem(resource).getExtAttrName(), attributes);
+        if (accountIdExtAttr != null) {
+            attributes.remove(accountIdExtAttr);
+            attributes.add(AttributeBuilder.build(attrUtil.getAccountIdItem(resource).getExtAttrName(), accountId));
+        }
+        attributes.add(MappingUtil.evaluateNAME(subject, resource, accountId));
+
+        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 AbstractMap.SimpleEntry<>(accountId, attributes);
+    }
+
+    /**
+     * Prepare an attribute to be sent to a connector instance.
+     *
+     * @param resource target resource
+     * @param mapItem mapping item for the given attribute
+     * @param subject given user
+     * @param password clear-text password
+     * @param passwordGenerator password generator
+     * @param vAttrsToBeRemoved virtual attributes to be removed
+     * @param vAttrsToBeUpdated virtual attributes to be added
+     * @return account link + prepared attribute
+     */
+    @SuppressWarnings("unchecked")
+    private static Map.Entry<String, Attribute> prepareAttr(
+            final ExternalResource resource, final MappingItem mapItem,
+            final Subject<?, ?, ?> subject, final String password, final PasswordGenerator passwordGenerator,
+            final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated,
+            final Set<String> membVAttrsToBeRemoved, final Map<String, AttrMod> membVAttrsToBeUpdated) {
+
+        final List<Attributable<?, ?, ?>> attributables = new ArrayList<>();
+
+        final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext();
+        final AttributableUtilFactory attrUtilFactory = context.getBean(AttributableUtilFactory.class);
+        final ConnObjectUtil connObjectUtil = context.getBean(ConnObjectUtil.class);
+
+        switch (mapItem.getIntMappingType().getAttributableType()) {
+            case USER:
+                if (subject instanceof User) {
+                    attributables.add(subject);
+                }
+                break;
+
+            case ROLE:
+                if (subject instanceof User) {
+                    for (Role role : ((User) subject).getRoles()) {
+                        connObjectUtil.retrieveVirAttrValues(role, attrUtilFactory.getInstance(role));
+                        attributables.add(role);
+                    }
+                }
+                if (subject instanceof Role) {
+                    attributables.add(subject);
+                }
+                break;
+
+            case MEMBERSHIP:
+                if (subject instanceof User) {
+                    attributables.addAll(((User) subject).getMemberships());
+                }
+                break;
+
+            default:
+        }
+
+        List<PlainAttrValue> values = getIntValues(
+                resource, mapItem, attributables, vAttrsToBeRemoved, vAttrsToBeUpdated, membVAttrsToBeRemoved,
+                membVAttrsToBeUpdated);
+
+        PlainSchema schema = null;
+        boolean readOnlyVirSchema = false;
+        AttrSchemaType schemaType;
+        final Map.Entry<String, Attribute> result;
+
+        switch (mapItem.getIntMappingType()) {
+            case UserSchema:
+            case RoleSchema:
+            case MembershipSchema:
+                final PlainSchemaDAO plainSchemaDAO = context.getBean(PlainSchemaDAO.class);
+                schema = plainSchemaDAO.find(mapItem.getIntAttrName(),
+                        MappingUtil.getIntMappingTypeClass(mapItem.getIntMappingType()));
+                schemaType = schema == null ? AttrSchemaType.String : schema.getType();
+                break;
+
+            case UserVirtualSchema:
+            case RoleVirtualSchema:
+            case MembershipVirtualSchema:
+                VirSchemaDAO virSchemaDAO = context.getBean(VirSchemaDAO.class);
+                VirSchema virSchema = virSchemaDAO.find(mapItem.getIntAttrName(),
+                        MappingUtil.getIntMappingTypeClass(mapItem.getIntMappingType()));
+                readOnlyVirSchema = (virSchema != null && virSchema.isReadonly());
+                schemaType = AttrSchemaType.String;
+                break;
+
+            default:
+                schemaType = AttrSchemaType.String;
+        }
+
+        final String extAttrName = mapItem.getExtAttrName();
+
+        LOG.debug("Define mapping for: "
+                + "\n* ExtAttrName " + extAttrName
+                + "\n* is accountId " + mapItem.isAccountid()
+                + "\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 {
+            final 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.isAccountid()) {
+                result = new AbstractMap.SimpleEntry<>(objValues.iterator().next().toString(), null);
+            } else if (mapItem.isPassword() && subject instanceof User) {
+                String passwordAttrValue = password;
+                if (StringUtils.isBlank(passwordAttrValue)) {
+                    User user = (User) subject;
+                    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 (resource.isRandomPwdIfNotProvided()) {
+                        try {
+                            passwordAttrValue = passwordGenerator.generate(user);
+                        } catch (InvalidPasswordPolicySpecException e) {
+                            LOG.error("Could not generate policy-compliant random password for {}", user, e);
+                        }
+                    }
+                }
+
+                if (passwordAttrValue == null) {
+                    result = null;
+                } else {
+                    result = new AbstractMap.SimpleEntry<>(
+                            null,
+                            AttributeBuilder.buildPassword(passwordAttrValue.toCharArray()));
+                }
+            } else {
+                if ((schema != null && schema.isMultivalue()) || attrUtilFactory.getInstance(subject).getType()
+                        != mapItem.getIntMappingType().getAttributableType()) {
+
+                    result = new AbstractMap.SimpleEntry<>(
+                            null,
+                            AttributeBuilder.build(extAttrName, objValues));
+                } else {
+                    result = new AbstractMap.SimpleEntry<>(
+                            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 accountLink for the given resource (and in this
+     * case evaluate as JEXL); otherwise, take given accountId.
+     *
+     * @param subject given user / role
+     * @param resource target resource
+     * @param accountId accountId
+     * @return the value to be propagated as __NAME__
+     */
+    public static Name evaluateNAME(final Subject<?, ?, ?> subject,
+            final ExternalResource resource, final String accountId) {
+
+        final AttributableUtilFactory attrUtilFactory =
+                ApplicationContextProvider.getApplicationContext().getBean(AttributableUtilFactory.class);
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(subject);
+
+        if (StringUtils.isBlank(accountId)) {
+            // LOG error but avoid to throw exception: leave it to the external resource
+            LOG.error("Missing accountId for '{}': ", resource.getKey());
+        }
+
+        // Evaluate AccountLink expression
+        String evalAccountLink = null;
+        if (StringUtils.isNotBlank(attrUtil.getAccountLink(resource))) {
+            final JexlContext jexlContext = new MapContext();
+            JexlUtil.addFieldsToContext(subject, jexlContext);
+            JexlUtil.addAttrsToContext(subject.getPlainAttrs(), jexlContext);
+            JexlUtil.addDerAttrsToContext(subject.getDerAttrs(), subject.getPlainAttrs(), jexlContext);
+            evalAccountLink = JexlUtil.evaluate(attrUtil.getAccountLink(resource), jexlContext);
+        }
+
+        // If AccountLink evaluates to an empty string, just use the provided AccountId as Name(),
+        // otherwise evaluated AccountLink expression is taken as Name().
+        Name name;
+        if (StringUtils.isBlank(evalAccountLink)) {
+            // add AccountId as __NAME__ attribute ...
+            LOG.debug("Add AccountId [{}] as __NAME__", accountId);
+            name = new Name(accountId);
+        } else {
+            LOG.debug("Add AccountLink [{}] as __NAME__", evalAccountLink);
+            name = new Name(evalAccountLink);
+
+            // AccountId not propagated: it will be used to set the value for __UID__ attribute
+            LOG.debug("AccountId will be used just as __UID__ attribute");
+        }
+
+        return name;
+    }
+
+    private static String getRoleOwnerValue(
+            final ExternalResource resource, final Subject<?, ?, ?> subject) {
+
+        AttributableUtilFactory attrUtilFactory =
+                ApplicationContextProvider.getApplicationContext().getBean(AttributableUtilFactory.class);
+
+        Map.Entry<String, Attribute> preparedAttr = prepareAttr(
+                resource, attrUtilFactory.getInstance(subject).getAccountIdItem(resource), subject, null, null,
+                Collections.<String>emptySet(), Collections.<String, AttrMod>emptyMap(),
+                Collections.<String>emptySet(), Collections.<String, AttrMod>emptyMap());
+        String accountId = preparedAttr.getKey();
+
+        final Name roleOwnerName = evaluateNAME(subject, resource, accountId);
+        return roleOwnerName.getNameValue();
+    }
+
+    /**
+     * Get attribute values.
+     *
+     * @param resource target resource
+     * @param mappingItem mapping item
+     * @param attributables list of attributables
+     * @param vAttrsToBeRemoved virtual attributes to be removed
+     * @param vAttrsToBeUpdated virtual attributes to be added
+     * @param membVAttrsToBeRemoved membership virtual attributes to be removed
+     * @param membVAttrsToBeUpdated membership virtual attributes to be added
+     * @return attribute values.
+     */
+    public static List<PlainAttrValue> getIntValues(final ExternalResource resource,
+            final MappingItem mappingItem, final List<Attributable<?, ?, ?>> attributables,
+            final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated,
+            final Set<String> membVAttrsToBeRemoved, final Map<String, AttrMod> membVAttrsToBeUpdated) {
+
+        LOG.debug("Get attributes for '{}' and mapping type '{}'", attributables, mappingItem.getIntMappingType());
+
+        final EntityFactory entityFactory =
+                ApplicationContextProvider.getApplicationContext().getBean(EntityFactory.class);
+        List<PlainAttrValue> values = new ArrayList<>();
+        PlainAttrValue attrValue;
+        switch (mappingItem.getIntMappingType()) {
+            case UserSchema:
+            case RoleSchema:
+            case MembershipSchema:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    final PlainAttr attr = attributable.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 RoleVirtualSchema:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    VirAttr virAttr = attributable.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 = entityFactory.newEntity(UPlainAttrValue.class);
+                                attrValue.setStringValue(value);
+                                values.add(attrValue);
+                            }
+                        }
+                    }
+
+                    LOG.debug("Retrieved {} virtual attribute {}"
+                            + "\n* IntAttrName {}"
+                            + "\n* IntMappingType {}"
+                            + "\n* Attribute values {}",
+                            attributable.getClass().getSimpleName(),
+                            virAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values);
+                }
+                break;
+
+            case MembershipVirtualSchema:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    VirAttr virAttr = attributable.getVirAttr(mappingItem.getIntAttrName());
+                    if (virAttr != null) {
+                        if (membVAttrsToBeRemoved != null && membVAttrsToBeUpdated != null) {
+                            if (membVAttrsToBeUpdated.containsKey(mappingItem.getIntAttrName())) {
+                                virAttr.getValues().clear();
+                                virAttr.getValues().addAll(
+                                        membVAttrsToBeUpdated.get(mappingItem.getIntAttrName()).getValuesToBeAdded());
+                            } else if (membVAttrsToBeRemoved.contains(mappingItem.getIntAttrName())) {
+                                virAttr.getValues().clear();
+                            } else {
+                                throw new IllegalArgumentException("Don't need to update membership virtual attribute '"
+                                        + mappingItem.getIntAttrName() + "'");
+                            }
+                        }
+                        if (virAttr.getValues() != null) {
+                            for (String value : virAttr.getValues()) {
+                                attrValue = entityFactory.newEntity(UPlainAttrValue.class);
+                                attrValue.setStringValue(value);
+                                values.add(attrValue);
+                            }
+                        }
+                    }
+
+                    LOG.debug("Retrieved {} virtual attribute {}"
+                            + "\n* IntAttrName {}"
+                            + "\n* IntMappingType {}"
+                            + "\n* Attribute values {}",
+                            attributable.getClass().getSimpleName(),
+                            virAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values);
+                }
+                break;
+
+            case UserDerivedSchema:
+            case RoleDerivedSchema:
+            case MembershipDerivedSchema:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    DerAttr derAttr = attributable.getDerAttr(mappingItem.getIntAttrName());
+                    if (derAttr != null) {
+                        attrValue = attributable instanceof Role
+                                ? entityFactory.newEntity(RPlainAttrValue.class)
+                                : entityFactory.newEntity(UPlainAttrValue.class);
+                        attrValue.setStringValue(derAttr.getValue(attributable.getPlainAttrs()));
+                        values.add(attrValue);
+                    }
+
+                    LOG.debug("Retrieved attribute {}"
+                            + "\n* IntAttrName {}"
+                            + "\n* IntMappingType {}"
+                            + "\n* Attribute values {}",
+                            derAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values);
+                }
+                break;
+
+            case UserId:
+            case RoleId:
+            case MembershipId:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    attrValue = entityFactory.newEntity(UPlainAttrValue.class);
+                    attrValue.setStringValue(attributable.getKey().toString());
+                    values.add(attrValue);
+                }
+                break;
+
+            case Username:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    if (attributable instanceof User) {
+                        attrValue = entityFactory.newEntity(UPlainAttrValue.class);
+                        attrValue.setStringValue(((User) attributable).getUsername());
+                        values.add(attrValue);
+                    }
+                }
+                break;
+
+            case RoleName:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    if (attributable instanceof Role) {
+                        attrValue = entityFactory.newEntity(RPlainAttrValue.class);
+                        attrValue.setStringValue(((Role) attributable).getName());
+                        values.add(attrValue);
+                    }
+                }
+                break;
+
+            case RoleOwnerSchema:
+                for (Attributable<?, ?, ?> attributable : attributables) {
+                    if (attributable instanceof Role) {
+                        Role role = (Role) attributable;
+                        String roleOwnerValue = null;
+                        if (role.getUserOwner() != null && resource.getUmapping() != null) {
+                            roleOwnerValue = getRoleOwnerValue(resource, role.getUserOwner());
+                        }
+                        if (role.getRoleOwner() != null && resource.getRmapping() != null) {
+                            roleOwnerValue = getRoleOwnerValue(resource, role.getRoleOwner());
+                        }
+
+                        if (StringUtils.isNotBlank(roleOwnerValue)) {
+                            attrValue = entityFactory.newEntity(RPlainAttrValue.class);
+                            attrValue.setStringValue(roleOwnerValue);
+                            values.add(attrValue);
+                        }
+                    }
+                }
+                break;
+
+            default:
+        }
+
+        LOG.debug("Retrieved values '{}'", values);
+
+        return values;
+    }
+
+    /**
+     * Get accountId internal value.
+     *
+     * @param attributable attributable
+     * @param accountIdItem accountId mapping item
+     * @param resource external resource
+     * @return accountId internal value
+     */
+    public static String getAccountIdValue(final Attributable<?, ?, ?> attributable,
+            final ExternalResource resource, final MappingItem accountIdItem) {
+
+        List<PlainAttrValue> values = getIntValues(resource, accountIdItem,
+                Collections.<Attributable<?, ?, ?>>singletonList(attributable), null, null, null, null);
+        return values == null || values.isEmpty()
+                ? null
+                : values.get(0).getValueAsString();
+    }
+
+    /**
+     * For given source mapping type, return the corresponding Class object.
+     *
+     * @param intMappingType source mapping type
+     * @return corresponding Class object, if any (can be null)
+     */
+    @SuppressWarnings("rawtypes")
+    public static Class getIntMappingTypeClass(final IntMappingType intMappingType) {
+        Class result;
+
+        switch (intMappingType) {
+            case UserSchema:
+                result = UPlainSchema.class;
+                break;
+
+            case RoleSchema:
+                result = RPlainSchema.class;
+                break;
+
+            case MembershipSchema:
+                result = MPlainSchema.class;
+                break;
+
+            case UserDerivedSchema:
+                result = UDerSchema.class;
+                break;
+
+            case RoleDerivedSchema:
+                result = RDerSchema.class;
+                break;
+
+            case MembershipDerivedSchema:
+                result = MDerSchema.class;
+                break;
+
+            case UserVirtualSchema:
+                result = UVirSchema.class;
+                break;
+
+            case RoleVirtualSchema:
+                result = RVirSchema.class;
+                break;
+
+            case MembershipVirtualSchema:
+                result = MVirSchema.class;
+                break;
+
+            default:
+                result = null;
+        }
+
+        return result;
+    }
+
+    /**
+     * Private default constructor, for static-only classes.
+     */
+    private MappingUtil() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/ClassFreeUberspectImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/ClassFreeUberspectImpl.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/ClassFreeUberspectImpl.java
new file mode 100644
index 0000000..cd2c5a6
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/ClassFreeUberspectImpl.java
@@ -0,0 +1,43 @@
+/*
+ * 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.server.misc.jexl;
+
+import org.apache.commons.jexl2.JexlInfo;
+import org.apache.commons.jexl2.introspection.JexlMethod;
+import org.apache.commons.jexl2.introspection.JexlPropertyGet;
+import org.apache.commons.jexl2.introspection.UberspectImpl;
+import org.apache.commons.logging.Log;
+
+class ClassFreeUberspectImpl extends UberspectImpl {
+
+    public ClassFreeUberspectImpl(final Log runtimeLogger) {
+        super(runtimeLogger);
+    }
+
+    @Override
+    public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier, final JexlInfo info) {
+        return "class".equals(identifier) ? null : super.getPropertyGet(obj, identifier, info);
+    }
+
+    @Override
+    public JexlMethod getMethod(final Object obj, final String method, final Object[] args, final JexlInfo info) {
+        return "getClass".equals(method) ? null : super.getMethod(obj, method, args, info);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/EmptyClassLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/EmptyClassLoader.java b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/EmptyClassLoader.java
new file mode 100644
index 0000000..317bf7b
--- /dev/null
+++ b/syncope620/server/misc/src/main/java/org/apache/syncope/server/misc/jexl/EmptyClassLoader.java
@@ -0,0 +1,36 @@
+/*
+ * 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.server.misc.jexl;
+
+/**
+ * A class loader that will throw {@link ClassNotFoundException} for every class name.
+ */
+class EmptyClassLoader extends ClassLoader {
+
+    @Override
+    public Class<?> loadClass(final String name) throws ClassNotFoundException {
+        throw new ClassNotFoundException("This classloader won't attemp to load " + name);
+    }
+
+    @Override
+    protected Class<?> loadClass(final String name, boolean resolve) throws ClassNotFoundException {
+        throw new ClassNotFoundException("This classloader won't attemp to load " + name);
+    }
+
+}