syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ilgro...@apache.org
Subject [04/10] syncope git commit: [SYNCOPE-709] Refactoring completed
Date Thu, 22 Oct 2015 12:26:58 GMT
http://git-wip-us.apache.org/repos/asf/syncope/blob/9cd92305/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java
index 3d60089..5763449 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/VirAttrHandlerImpl.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.core.provisioning.java;
 
 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -27,26 +26,12 @@ 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.collections4.ListUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.patch.AttrPatch;
-import org.apache.syncope.common.lib.to.AttrTO;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.common.lib.types.IntMappingType;
-import org.apache.syncope.common.lib.types.PropagationByResource;
-import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.misc.MappingUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
-import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
-import org.apache.syncope.core.persistence.api.dao.VirAttrDAO;
-import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
-import org.apache.syncope.core.persistence.api.entity.AnyType;
-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.VirAttr;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
@@ -68,30 +53,17 @@ import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
 @Component
-@Transactional(rollbackFor = { Throwable.class })
 public class VirAttrHandlerImpl implements VirAttrHandler {
 
     private static final Logger LOG = LoggerFactory.getLogger(VirAttrHandler.class);
 
     @Autowired
-    private VirSchemaDAO virSchemaDAO;
-
-    @Autowired
-    private VirAttrDAO virAttrDAO;
-
-    @Autowired
     private AnyObjectDAO anyObjectDAO;
 
     @Autowired
     private UserDAO userDAO;
 
     @Autowired
-    private GroupDAO groupDAO;
-
-    @Autowired
-    private AnyUtilsFactory anyUtilsFactory;
-
-    @Autowired
     private ConnectorFactory connFactory;
 
     /**
@@ -103,290 +75,102 @@ public class VirAttrHandlerImpl implements VirAttrHandler {
     @Autowired
     private MappingUtils mappingUtils;
 
-    @Override
-    public VirSchema getVirSchema(final String virSchemaName) {
-        VirSchema virtualSchema = null;
-        if (StringUtils.isNotBlank(virSchemaName)) {
-            virtualSchema = virSchemaDAO.find(virSchemaName);
-
-            if (virtualSchema == null) {
-                LOG.debug("Ignoring invalid virtual schema {}", virSchemaName);
-            }
+    private Map<VirSchema, List<String>> getValues(final Any<?, ?> any, final Set<VirSchema> schemas) {
+        Collection<? extends ExternalResource> ownedResources;
+        if (any instanceof User) {
+            ownedResources = userDAO.findAllResources((User) any);
+        } else if (any instanceof AnyObject) {
+            ownedResources = anyObjectDAO.findAllResources((AnyObject) any);
+        } else {
+            ownedResources = ((Group) any).getResources();
         }
 
-        return virtualSchema;
-    }
-
-    @Override
-    public void updateOnResourcesIfMappingMatches(final Any<?, ?, ?> any, final String schemaKey,
-            final Iterable<? extends ExternalResource> resources, final IntMappingType mappingType,
-            final PropagationByResource propByRes) {
+        Map<VirSchema, List<String>> result = new HashMap<>();
 
-        for (ExternalResource resource : resources) {
-            for (MappingItem mapItem
-                    : MappingUtils.getPropagationMappingItems(resource.getProvision(any.getType()))) {
+        Map<Provision, Set<VirSchema>> toRead = new HashMap<>();
 
-                if (schemaKey.equals(mapItem.getIntAttrName()) && mapItem.getIntMappingType() == mappingType) {
-                    propByRes.add(ResourceOperation.UPDATE, resource.getKey());
-                }
-            }
-        }
-    }
-
-    private Iterable<? extends ExternalResource> getAllResources(final Any<?, ?, ?> any) {
-        return any instanceof User
-                ? userDAO.findAllResources((User) any)
-                : any instanceof AnyObject
-                        ? anyObjectDAO.findAllResources((AnyObject) any)
-                        : any instanceof Group
-                                ? ((Group) any).getResources()
-                                : Collections.<ExternalResource>emptySet();
-    }
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @Override
-    public void createVirtual(final Any any, final Collection<AttrTO> vAttrs) {
-        AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
+        for (VirSchema schema : schemas) {
+            if (ownedResources.contains(schema.getProvision().getResource())) {
+                VirAttrCacheValue virAttrCacheValue =
+                        virAttrCache.get(any.getType().getKey(), any.getKey(), schema.getKey());
 
-        for (AttrTO attrTO : vAttrs) {
-            VirAttr virAttr = any.getVirAttr(attrTO.getSchema());
-            if (virAttr == null) {
-                VirSchema virSchema = getVirSchema(attrTO.getSchema());
-                if (virSchema != null) {
-                    virAttr = anyUtils.newVirAttr();
-                    virAttr.setSchema(virSchema);
-                    if (virAttr.getSchema() == null) {
-                        LOG.debug("Ignoring {} because no valid schema was found", attrTO);
-                    } else {
-                        virAttr.setOwner(any);
-                        any.add(virAttr);
-                        virAttr.getValues().clear();
-                        virAttr.getValues().addAll(attrTO.getValues());
+                if (virAttrCache.isValidEntry(virAttrCacheValue)) {
+                    LOG.debug("Values for {} found in cache: {}", schema, virAttrCacheValue);
+                    result.put(schema, virAttrCacheValue.getValues());
+                } else {
+                    Set<VirSchema> schemasToRead = toRead.get(schema.getProvision());
+                    if (schemasToRead == null) {
+                        schemasToRead = new HashSet<>();
+                        toRead.put(schema.getProvision(), schemasToRead);
                     }
+                    schemasToRead.add(schema);
                 }
             } else {
-                virAttr.getValues().clear();
-                virAttr.getValues().addAll(attrTO.getValues());
+                LOG.debug("Not considering {} since {} is not assigned to {}",
+                        schema, any, schema.getProvision().getResource());
             }
         }
-    }
-
-    private Any<?, ?, ?> find(final Long key, final AnyTypeKind anyTypeKind) {
-        Any<?, ?, ?> result;
-
-        switch (anyTypeKind) {
-            case USER:
-                result = userDAO.authFind(key);
-                break;
-
-            case GROUP:
-                result = groupDAO.authFind(key);
-                break;
-
-            case ANY_OBJECT:
-            default:
-                result = anyObjectDAO.authFind(key);
-        }
-
-        return result;
-    }
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    @Override
-    public PropagationByResource updateVirtual(final Any any, final Collection<AttrPatch> vAttrs) {
-        AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
-
-        PropagationByResource propByRes = new PropagationByResource();
-
-        Iterable<? extends ExternalResource> externalResources = getAllResources(any);
-
-        for (AttrPatch patch : vAttrs) {
-            VirSchema virSchema = getVirSchema(patch.getAttrTO().getSchema());
-            if (virSchema != null) {
-                VirAttr virAttr = any.getVirAttr(virSchema.getKey());
-                switch (patch.getOperation()) {
-                    case ADD_REPLACE:
-                        if (virAttr == null) {
-                            virAttr = anyUtils.newVirAttr();
-                            virAttr.setOwner(any);
-                            virAttr.setSchema(virSchema);
-
-                            any.add(virAttr);
-                        }
-
-                        updateOnResourcesIfMappingMatches(
-                                any, virSchema.getKey(), externalResources, anyUtils.virIntMappingType(), propByRes);
-
-                        if (!virAttr.getValues().equals(patch.getAttrTO().getValues())) {
-                            virAttr.getValues().clear();
-                            virAttr.getValues().addAll(patch.getAttrTO().getValues());
-                        }
-                        break;
-
-                    case DELETE:
-                    default:
-                        if (virAttr == null) {
-                            LOG.debug("No virtual attribute found for schema {}", virSchema.getKey());
-                        } else {
-                            any.remove(virAttr);
-                            virAttrDAO.delete(virAttr);
-                        }
-
-                        for (ExternalResource resource : externalResources) {
-                            for (MappingItem mapItem
-                                    : MappingUtils.getPropagationMappingItems(resource.getProvision(any.getType()))) {
 
-                                if (virSchema.getKey().equals(mapItem.getIntAttrName())
-                                        && mapItem.getIntMappingType() == anyUtils.virIntMappingType()) {
+        for (Map.Entry<Provision, Set<VirSchema>> entry : toRead.entrySet()) {
+            LOG.debug("About to read from {}: {}", entry.getKey(), entry.getValue());
 
-                                    propByRes.add(ResourceOperation.UPDATE, resource.getKey());
-
-                                    // Using virtual attribute as ConnObjectKey must be avoided
-                                    if (mapItem.isConnObjectKey()
-                                            && virAttr != null && !virAttr.getValues().isEmpty()) {
-
-                                        propByRes.addOldConnObjectKey(
-                                                resource.getKey(), virAttr.getValues().get(0).toString());
-                                    }
-                                }
-                            }
-                        }
+            String connObjectKey = MappingUtils.getConnObjectKeyItem(entry.getKey()) == null
+                    ? null
+                    : mappingUtils.getConnObjectKeyValue(any, entry.getKey());
+            if (StringUtils.isBlank(connObjectKey)) {
+                LOG.error("No ConnObjectKey found for {}, ignoring...", entry.getKey());
+            } else {
+                Set<MappingItem> linkingMappingItems = new HashSet<>();
+                for (VirSchema schema : entry.getValue()) {
+                    linkingMappingItems.add(schema.asLinkingMappingItem());
                 }
-            }
-        }
-
-        return propByRes;
-    }
-
-    @Transactional
-    @Override
-    public PropagationByResource updateVirtual(
-            final Long key, final AnyTypeKind anyTypeKind, final Collection<AttrPatch> vAttrs) {
-
-        return updateVirtual(find(key, anyTypeKind), vAttrs);
-    }
-
-    @Override
-    public void retrieveVirAttrValues(final Any<?, ?, ?> any) {
-        IntMappingType type = any.getType().getKind() == AnyTypeKind.USER
-                ? IntMappingType.UserVirtualSchema
-                : any.getType().getKind() == AnyTypeKind.GROUP
-                        ? IntMappingType.GroupVirtualSchema
-                        : IntMappingType.AnyObjectVirtualSchema;
-
-        Map<String, ConnectorObject> resources = new HashMap<>();
-
-        // -----------------------
-        // Retrieve virtual attribute values if and only if they have not been retrieved yet
-        // -----------------------
-        for (VirAttr<?> virAttr : any.getVirAttrs()) {
-            // reset value set
-            if (virAttr.getValues().isEmpty()) {
-                retrieveVirAttrValue(any, virAttr, type, resources);
-            }
-        }
-        // -----------------------
-    }
-
-    private void retrieveVirAttrValue(
-            final Any<?, ?, ?> any,
-            final VirAttr<?> virAttr,
-            final IntMappingType type,
-            final Map<String, ConnectorObject> externalResources) {
-
-        String schemaName = virAttr.getSchema().getKey();
-        VirAttrCacheValue virAttrCacheValue = virAttrCache.get(any.getType().getKey(), any.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");
-
-            VirAttrCacheValue toBeCached = new VirAttrCacheValue();
-
-            for (ExternalResource resource : getTargetResources(virAttr, type, any.getType())) {
-                Provision provision = resource.getProvision(any.getType());
-                LOG.debug("Search values into {},{}", resource, provision);
 
+                Connector connector = connFactory.getConnector(entry.getKey().getResource());
                 try {
-                    List<MappingItem> mapItems = MappingUtils.getBothMappingItems(provision);
+                    ConnectorObject connectorObject = connector.getObject(
+                            entry.getKey().getObjectClass(),
+                            new Uid(connObjectKey),
+                            connector.getOperationOptions(linkingMappingItems.iterator()));
 
-                    ConnectorObject connectorObject;
-                    if (externalResources.containsKey(resource.getKey())) {
-                        connectorObject = externalResources.get(resource.getKey());
+                    if (connectorObject == null) {
+                        LOG.debug("No read from {} about {}", entry.getKey(), connObjectKey);
                     } else {
-                        LOG.debug("Perform connection to {}", resource.getKey());
-                        String connObjectKey = MappingUtils.getConnObjectKeyItem(provision) == null
-                                ? null
-                                : mappingUtils.getConnObjectKeyValue(any, provision);
-
-                        if (StringUtils.isBlank(connObjectKey)) {
-                            throw new IllegalArgumentException("No ConnObjectKey found for " + resource.getKey());
-                        }
-
-                        Connector connector = connFactory.getConnector(resource);
-                        connectorObject = connector.getObject(
-                                provision.getObjectClass(),
-                                new Uid(connObjectKey),
-                                connector.getOperationOptions(MappingUtils.getMatchingMappingItems(mapItems, type)));
-                        externalResources.put(resource.getKey(), connectorObject);
-                    }
-
-                    if (connectorObject != null) {
-                        // the same virtual attribute could be mapped with one or more external attributes
-                        for (MappingItem mapItem : MappingUtils.getMatchingMappingItems(mapItems, schemaName, type)) {
-                            Attribute attr = connectorObject.getAttributeByName(mapItem.getExtAttrName());
-                            if (attr != null && attr.getValue() != null) {
-                                for (Object obj : attr.getValue()) {
-                                    if (obj != null) {
-                                        virAttr.getValues().add(obj.toString());
-                                    }
-                                }
+                        for (VirSchema schema : entry.getValue()) {
+                            Attribute attr = connectorObject.getAttributeByName(schema.getExtAttrName());
+                            if (attr != null) {
+                                VirAttrCacheValue virAttrCacheValue = new VirAttrCacheValue();
+                                virAttrCacheValue.setValues(attr.getValue());
+                                virAttrCache.put(any.getType().getKey(), any.getKey(), schema.getKey(),
+                                        virAttrCacheValue);
+                                LOG.debug("Values for {} set in cache: {}", schema, virAttrCacheValue);
+
+                                result.put(schema, virAttrCacheValue.getValues());
                             }
                         }
-
-                        toBeCached.setResourceValues(resource.getKey(), new HashSet<>(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));
-                        }
-                    }
+                    LOG.error("Error reading from {}", entry.getKey(), e);
                 }
             }
-
-            virAttrCache.put(any.getType().getKey(), any.getKey(), schemaName, toBeCached);
         }
+
+        return result;
     }
 
-    private Collection<ExternalResource> getTargetResources(
-            final VirAttr<?> attr, final IntMappingType type, final AnyType anyType) {
+    @Transactional(readOnly = true)
+    @Override
+    public List<String> getValues(final Any<?, ?> any, final VirSchema schema) {
+        if (!any.getAllowedVirSchemas().contains(schema)) {
+            LOG.debug("{} not allowed for {}", schema, any);
+            return Collections.emptyList();
+        }
 
-        return CollectionUtils.select(getAllResources(attr.getOwner()), new Predicate<ExternalResource>() {
+        return ListUtils.emptyIfNull(getValues(any, Collections.singleton(schema)).get(schema));
+    }
 
-            @Override
-            public boolean evaluate(final ExternalResource resource) {
-                return resource.getProvision(anyType) != null
-                        && !MappingUtils.getMatchingMappingItems(
-                                MappingUtils.getBothMappingItems(resource.getProvision(anyType)),
-                                attr.getSchema().getKey(), type).isEmpty();
-            }
-        });
+    @Transactional(readOnly = true)
+    @Override
+    public Map<VirSchema, List<String>> getValues(final Any<?, ?> any) {
+        return getValues(any, any.getAllowedVirSchemas());
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/9cd92305/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
index 4a203f9..93a0b31 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
@@ -42,6 +42,7 @@ import org.apache.syncope.common.lib.to.RelationshipTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.IntMappingType;
 import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.dao.DerAttrDAO;
@@ -53,7 +54,6 @@ import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
-import org.apache.syncope.core.persistence.api.dao.VirAttrDAO;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.DerAttr;
 import org.apache.syncope.core.persistence.api.entity.DerSchema;
@@ -61,8 +61,6 @@ import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 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.Group;
 import org.apache.syncope.common.lib.types.PropagationByResource;
 import org.apache.syncope.core.misc.ConnObjectUtils;
@@ -80,6 +78,7 @@ import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.Membership;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.Relationship;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
@@ -130,9 +129,6 @@ abstract class AbstractAnyDataBinder {
     protected DerAttrDAO derAttrDAO;
 
     @Autowired
-    protected VirAttrDAO virAttrDAO;
-
-    @Autowired
     protected PlainAttrValueDAO plainAttrValueDAO;
 
     @Autowired
@@ -159,7 +155,7 @@ abstract class AbstractAnyDataBinder {
     @Autowired
     protected MappingUtils mappingUtils;
 
-    protected void setRealm(final Any<?, ?, ?> any, final AnyPatch anyPatch) {
+    protected void setRealm(final Any<?, ?> any, final AnyPatch anyPatch) {
         if (anyPatch.getRealm() != null && StringUtils.isNotBlank(anyPatch.getRealm().getValue())) {
             Realm newRealm = realmDAO.find(anyPatch.getRealm().getValue());
             if (newRealm == null) {
@@ -225,7 +221,7 @@ abstract class AbstractAnyDataBinder {
         }
     }
 
-    private List<String> evaluateMandatoryCondition(final Provision provision, final Any<?, ?, ?> any) {
+    private List<String> evaluateMandatoryCondition(final Provision provision, final Any<?, ?> any) {
         List<String> missingAttrNames = new ArrayList<>();
 
         if (provision != null) {
@@ -235,7 +231,7 @@ abstract class AbstractAnyDataBinder {
                         || item.getPurpose() == MappingPurpose.BOTH)) {
 
                     List<PlainAttrValue> values = mappingUtils.getIntValues(
-                            provision, item, Collections.<Any<?, ?, ?>>singletonList(any), null);
+                            provision, item, Collections.<Any<?, ?>>singletonList(any));
                     if (values.isEmpty() && JexlUtils.evaluateMandatoryCondition(item.getMandatoryCondition(), any)) {
                         missingAttrNames.add(item.getIntAttrName());
                     }
@@ -247,7 +243,7 @@ abstract class AbstractAnyDataBinder {
     }
 
     private SyncopeClientException checkMandatoryOnResources(
-            final Any<?, ?, ?> any, final Set<ExternalResource> resources) {
+            final Any<?, ?> any, final Set<ExternalResource> resources) {
 
         SyncopeClientException reqValMissing = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
 
@@ -266,7 +262,7 @@ abstract class AbstractAnyDataBinder {
         return reqValMissing;
     }
 
-    private SyncopeClientException checkMandatory(final Any<?, ?, ?> any) {
+    private SyncopeClientException checkMandatory(final Any<?, ?> any) {
         SyncopeClientException reqValMissing = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
 
         // Check if there is some mandatory schema defined for which no value has been provided
@@ -284,7 +280,7 @@ abstract class AbstractAnyDataBinder {
         return reqValMissing;
     }
 
-    private Set<ExternalResource> getAllResources(final Any<?, ?, ?> any) {
+    private Set<ExternalResource> getAllResources(final Any<?, ?> any) {
         Set<ExternalResource> resources = new HashSet<>();
 
         if (any instanceof User) {
@@ -323,21 +319,26 @@ abstract class AbstractAnyDataBinder {
 
         switch (patch.getOperation()) {
             case ADD_REPLACE:
-                virAttrHander.updateOnResourcesIfMappingMatches(
-                        any, schema.getKey(), resources, anyUtils.plainIntMappingType(), propByRes);
-
                 // 1.1 remove values
-                Collection<Long> valuesToBeRemoved = attr.getSchema().isUniqueConstraint()
-                        ? Collections.singleton(attr.getUniqueValue().getKey())
-                        : CollectionUtils.collect(attr.getValues(), new Transformer<PlainAttrValue, Long>() {
-
-                            @Override
-                            public Long transform(final PlainAttrValue input) {
-                                return input.getKey();
-                            }
-                        });
-                for (Long attrValueKey : valuesToBeRemoved) {
-                    plainAttrValueDAO.delete(attrValueKey, anyUtils.plainAttrValueClass());
+                if (attr.getSchema().isUniqueConstraint()) {
+                    if (attr.getUniqueValue() != null
+                            && !patch.getAttrTO().getValues().isEmpty()
+                            && !patch.getAttrTO().getValues().get(0).equals(attr.getUniqueValue().getValueAsString())) {
+
+                        plainAttrValueDAO.delete(attr.getUniqueValue().getKey(), anyUtils.plainAttrUniqueValueClass());
+                    }
+                } else {
+                    Collection<Long> valuesToBeRemoved = CollectionUtils.collect(attr.getValues(),
+                            new Transformer<PlainAttrValue, Long>() {
+
+                                @Override
+                                public Long transform(final PlainAttrValue input) {
+                                    return input.getKey();
+                                }
+                            });
+                    for (Long attrValueKey : valuesToBeRemoved) {
+                        plainAttrValueDAO.delete(attrValueKey, anyUtils.plainAttrValueClass());
+                    }
                 }
 
                 // 1.2 add values
@@ -359,22 +360,20 @@ abstract class AbstractAnyDataBinder {
             default:
                 any.remove(attr);
                 plainAttrDAO.delete(attr.getKey(), anyUtils.plainAttrClass());
+        }
 
-                for (ExternalResource resource : resources) {
-                    for (MappingItem mapItem
-                            : MappingUtils.getPropagationMappingItems(resource.getProvision(any.getType()))) {
-
-                        if (schema.getKey().equals(mapItem.getIntAttrName())
-                                && mapItem.getIntMappingType() == anyUtils.plainIntMappingType()) {
+        for (ExternalResource resource : resources) {
+            for (MappingItem mapItem : MappingUtils.getPropagationMappingItems(resource.getProvision(any.getType()))) {
+                if (schema.getKey().equals(mapItem.getIntAttrName())
+                        && mapItem.getIntMappingType() == anyUtils.plainIntMappingType()) {
 
-                            propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+                    propByRes.add(ResourceOperation.UPDATE, resource.getKey());
 
-                            if (mapItem.isConnObjectKey() && !attr.getValuesAsStrings().isEmpty()) {
-                                propByRes.addOldConnObjectKey(resource.getKey(), attr.getValuesAsStrings().get(0));
-                            }
-                        }
+                    if (mapItem.isConnObjectKey() && !attr.getValuesAsStrings().isEmpty()) {
+                        propByRes.addOldConnObjectKey(resource.getKey(), attr.getValuesAsStrings().get(0));
                     }
                 }
+            }
         }
     }
 
@@ -400,36 +399,26 @@ abstract class AbstractAnyDataBinder {
             }
         }
 
-        switch (patch.getOperation()) {
-            case ADD_REPLACE:
-                virAttrHander.updateOnResourcesIfMappingMatches(
-                        any, schema.getKey(), resources, anyUtils.derIntMappingType(), propByRes);
-                break;
-
-            case DELETE:
-            default:
-                derAttrDAO.delete(attr);
-
-                for (ExternalResource resource : resources) {
-                    for (MappingItem mapItem
-                            : MappingUtils.getPropagationMappingItems(resource.getProvision(any.getType()))) {
+        if (patch.getOperation() == PatchOperation.DELETE) {
+            derAttrDAO.delete(attr);
+        }
 
-                        if (schema.getKey().equals(mapItem.getIntAttrName())
-                                && mapItem.getIntMappingType() == anyUtils.derIntMappingType()) {
+        for (ExternalResource resource : resources) {
+            for (MappingItem mapItem : MappingUtils.getPropagationMappingItems(resource.getProvision(any.getType()))) {
+                if (schema.getKey().equals(mapItem.getIntAttrName())
+                        && mapItem.getIntMappingType() == anyUtils.derIntMappingType()) {
 
-                            propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+                    propByRes.add(ResourceOperation.UPDATE, resource.getKey());
 
-                            if (mapItem.isConnObjectKey() && !attr.getValue(any.getPlainAttrs()).isEmpty()) {
-                                propByRes.addOldConnObjectKey(resource.getKey(), attr.getValue(any.getPlainAttrs()));
-                            }
-                        }
+                    if (mapItem.isConnObjectKey() && !attr.getValue(any.getPlainAttrs()).isEmpty()) {
+                        propByRes.addOldConnObjectKey(resource.getKey(), attr.getValue(any.getPlainAttrs()));
                     }
                 }
+            }
         }
     }
 
-    @SuppressWarnings("rawtypes")
-    protected PropagationByResource fill(final Any any, final AnyPatch anyPatch, final AnyUtils anyUtils,
+    protected PropagationByResource fill(final Any<?, ?> any, final AnyPatch anyPatch, final AnyUtils anyUtils,
             final SyncopeClientCompositeException scce) {
 
         PropagationByResource propByRes = new PropagationByResource();
@@ -576,25 +565,12 @@ abstract class AbstractAnyDataBinder {
             }
         }
 
-        // 3. virtual attributes
-        for (AttrTO vattrTO : anyTO.getVirAttrs()) {
-            VirSchema virSchema = virAttrHander.getVirSchema(vattrTO.getSchema());
-            if (virSchema != null) {
-                VirAttr virAttr = anyUtils.newVirAttr();
-                virAttr.setOwner(any);
-                virAttr.setSchema(virSchema);
-                any.add(virAttr);
-            }
-        }
-
         SyncopeClientException requiredValuesMissing = checkMandatory(any);
         if (!requiredValuesMissing.isEmpty()) {
             scce.addException(requiredValuesMissing);
         }
 
-        virAttrHander.createVirtual(any, anyTO.getVirAttrs());
-
-        // 4. realm & resources
+        // 3. realm & resources
         Realm realm = realmDAO.find(anyTO.getRealm());
         if (realm == null) {
             SyncopeClientException noRealm = SyncopeClientException.build(ClientExceptionType.InvalidRealm);
@@ -628,7 +604,7 @@ abstract class AbstractAnyDataBinder {
             final Collection<? extends AnyTypeClass> auxClasses,
             final Collection<? extends PlainAttr<?>> attrs,
             final Collection<? extends DerAttr<?>> derAttrs,
-            final Collection<? extends VirAttr<?>> virAttrs,
+            final Map<VirSchema, List<String>> virAttrs,
             final Collection<? extends ExternalResource> resources) {
 
         anyTO.setRealm(realmFullPath);
@@ -659,11 +635,11 @@ abstract class AbstractAnyDataBinder {
             anyTO.getDerAttrs().add(attrTO);
         }
 
-        for (VirAttr<?> virAttr : virAttrs) {
+        for (Map.Entry<VirSchema, List<String>> entry : virAttrs.entrySet()) {
             AttrTO attrTO = new AttrTO();
-            attrTO.setSchema(virAttr.getSchema().getKey());
-            attrTO.getValues().addAll(virAttr.getValues());
-            attrTO.setReadonly(virAttr.getSchema().isReadonly());
+            attrTO.setSchema(entry.getKey().getKey());
+            attrTO.getValues().addAll(entry.getValue());
+            attrTO.setReadonly(entry.getKey().isReadonly());
 
             anyTO.getVirAttrs().add(attrTO);
         }
@@ -673,21 +649,21 @@ abstract class AbstractAnyDataBinder {
         }
     }
 
-    protected RelationshipTO getRelationshipTO(final Relationship<? extends Any<?, ?, ?>, AnyObject> relationship) {
+    protected RelationshipTO getRelationshipTO(final Relationship<? extends Any<?, ?>, AnyObject> relationship) {
         return new RelationshipTO.Builder().
                 left(relationship.getLeftEnd().getType().getKey(), relationship.getLeftEnd().getKey()).
                 right(relationship.getRightEnd().getType().getKey(), relationship.getRightEnd().getKey()).
                 build();
     }
 
-    protected MembershipTO getMembershipTO(final Membership<? extends Any<?, ?, ?>> membership) {
+    protected MembershipTO getMembershipTO(final Membership<? extends Any<?, ?>> membership) {
         return new MembershipTO.Builder().
                 left(membership.getLeftEnd().getType().getKey(), membership.getLeftEnd().getKey()).
                 group(membership.getRightEnd().getKey(), membership.getRightEnd().getName()).
                 build();
     }
 
-    protected Map<String, String> getConnObjectKeys(final Any<?, ?, ?> any) {
+    protected Map<String, String> getConnObjectKeys(final Any<?, ?> any) {
         Map<String, String> connObjectKeys = new HashMap<>();
 
         Iterable<? extends ExternalResource> iterable = any instanceof User

http://git-wip-us.apache.org/repos/asf/syncope/blob/9cd92305/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index 91edd7e..2dca6e4 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -19,7 +19,9 @@
 package org.apache.syncope.core.provisioning.java.data;
 
 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;
@@ -41,6 +43,7 @@ import org.apache.syncope.core.misc.spring.BeanUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.RelationshipType;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership;
 import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
@@ -75,12 +78,12 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
 
         BeanUtils.copyProperties(anyObject, anyObjectTO, IGNORE_PROPERTIES);
 
-        if (details) {
-            virAttrHander.retrieveVirAttrValues(anyObject);
-        }
+        Map<VirSchema, List<String>> virAttrValues = details
+                ? virAttrHander.getValues(anyObject)
+                : Collections.<VirSchema, List<String>>emptyMap();
 
         fillTO(anyObjectTO, anyObject.getRealm().getFullPath(), anyObject.getAuxClasses(),
-                anyObject.getPlainAttrs(), anyObject.getDerAttrs(), anyObject.getVirAttrs(),
+                anyObject.getPlainAttrs(), anyObject.getDerAttrs(), virAttrValues,
                 anyObjectDAO.findAllResources(anyObject));
 
         if (details) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/9cd92305/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
index 155034c..620cdf8 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
@@ -18,6 +18,8 @@
  */
 package org.apache.syncope.core.provisioning.java.data;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
@@ -34,6 +36,7 @@ import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
 import org.apache.syncope.core.misc.search.SearchCondConverter;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.DynGroupMembership;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
 import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership;
 import org.springframework.stereotype.Component;
@@ -203,12 +206,12 @@ public class GroupDataBinderImpl extends AbstractAnyDataBinder implements GroupD
             groupTO.setGroupOwner(group.getGroupOwner().getKey());
         }
 
-        if (details) {
-            virAttrHander.retrieveVirAttrValues(group);
-        }
+        Map<VirSchema, List<String>> virAttrValues = details
+                ? virAttrHander.getValues(group)
+                : Collections.<VirSchema, List<String>>emptyMap();
 
         fillTO(groupTO, group.getRealm().getFullPath(), group.getAuxClasses(),
-                group.getPlainAttrs(), group.getDerAttrs(), group.getVirAttrs(), group.getResources());
+                group.getPlainAttrs(), group.getDerAttrs(), virAttrValues, group.getResources());
 
         if (group.getADynMembership() != null) {
             groupTO.setADynMembershipCond(group.getADynMembership().getFIQLCond());

http://git-wip-us.apache.org/repos/asf/syncope/blob/9cd92305/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
index e5dfadf..d04dfd3 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
@@ -21,9 +21,8 @@ package org.apache.syncope.core.provisioning.java.data;
 import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Set;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.collections4.Predicate;
 import org.apache.commons.lang3.SerializationUtils;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
 import org.apache.syncope.common.lib.SyncopeClientException;
@@ -50,7 +49,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.syncope.core.misc.spring.BeanUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
+import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -76,6 +77,9 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
     private PolicyDAO policyDAO;
 
     @Autowired
+    private VirSchemaDAO virSchemaDAO;
+
+    @Autowired
     private EntityFactory entityFactory;
 
     @Override
@@ -112,7 +116,8 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
         for (ProvisionTO provisionTO : resourceTO.getProvisions()) {
             AnyType anyType = anyTypeDAO.find(provisionTO.getAnyType());
             if (anyType == null) {
-                LOG.debug("Invalid AnyType specified {}, ignoring...", provisionTO.getAnyType());
+                LOG.debug("Invalid {} specified {}, ignoring...",
+                        AnyType.class.getSimpleName(), provisionTO.getAnyType());
             } else {
                 Provision provision = resource.getProvision(anyType);
                 if (provision == null) {
@@ -124,7 +129,7 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
 
                 if (provisionTO.getObjectClass() == null) {
                     SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidProvision);
-                    sce.getElements().add("Null ObjectClass");
+                    sce.getElements().add("Null " + ObjectClass.class.getSimpleName());
                     throw sce;
                 }
                 provision.setObjectClass(new ObjectClass(provisionTO.getObjectClass()));
@@ -146,17 +151,36 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
                     }
                     populateMapping(provisionTO.getMapping(), mapping, entityFactory.newEntity(MappingItem.class));
                 }
+
+                if (provisionTO.getVirSchemas().isEmpty()) {
+                    for (VirSchema schema : virSchemaDAO.findByProvision(provision)) {
+                        virSchemaDAO.delete(schema.getKey());
+                    }
+                } else {
+                    for (String schemaName : provisionTO.getVirSchemas()) {
+                        VirSchema schema = virSchemaDAO.find(schemaName);
+                        if (schema == null) {
+                            LOG.debug("Invalid {} specified: {}, ignoring...",
+                                    VirSchema.class.getSimpleName(), schemaName);
+                        } else {
+                            schema.setProvision(provision);
+                        }
+                    }
+                }
             }
         }
 
-        // 2. remove all abouts not contained in the TO
-        CollectionUtils.filter(resource.getProvisions(), new Predicate<Provision>() {
+        // 2. remove all provisions not contained in the TO
+        for (Iterator<? extends Provision> itor = resource.getProvisions().iterator(); itor.hasNext();) {
+            Provision provision = itor.next();
+            if (resourceTO.getProvision(provision.getAnyType().getKey()) == null) {
+                for (VirSchema schema : virSchemaDAO.findByProvision(provision)) {
+                    virSchemaDAO.delete(schema.getKey());
+                }
 
-            @Override
-            public boolean evaluate(final Provision provision) {
-                return resourceTO.getProvision(provision.getAnyType().getKey()) != null;
+                itor.remove();
             }
-        });
+        }
 
         resource.setCreateTraceLevel(resourceTO.getCreateTraceLevel());
         resource.setUpdateTraceLevel(resourceTO.getUpdateTraceLevel());
@@ -310,6 +334,10 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
                 populateMappingTO(provision.getMapping(), mappingTO);
             }
 
+            for (VirSchema virSchema : virSchemaDAO.findByProvision(provision)) {
+                provisionTO.getVirSchemas().add(virSchema.getKey());
+            }
+
             resourceTO.getProvisions().add(provisionTO);
         }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/9cd92305/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SchemaDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SchemaDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SchemaDataBinderImpl.java
index 0794f8c..006a998 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SchemaDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SchemaDataBinderImpl.java
@@ -35,11 +35,13 @@ import org.apache.syncope.core.misc.spring.BeanUtils;
 import org.apache.syncope.core.misc.jexl.JexlUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 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.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -50,7 +52,7 @@ public class SchemaDataBinderImpl implements SchemaDataBinder {
 
     private static final Logger LOG = LoggerFactory.getLogger(SchemaDataBinder.class);
 
-    private static final String[] IGNORE_PROPERTIES = { "anyTypeClass" };
+    private static final String[] IGNORE_PROPERTIES = { "anyTypeClass", "provision" };
 
     @Autowired
     private AnyTypeClassDAO anyTypeClassDAO;
@@ -65,6 +67,9 @@ public class SchemaDataBinderImpl implements SchemaDataBinder {
     private VirSchemaDAO virSchemaDAO;
 
     @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
     private EntityFactory entityFactory;
 
     @Autowired
@@ -235,6 +240,14 @@ public class SchemaDataBinderImpl implements SchemaDataBinder {
             merged.setAnyTypeClass(null);
         }
 
+        Provision provision = resourceDAO.findProvision(schemaTO.getProvision());
+        if (provision == null) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSchemaDefinition);
+            sce.getElements().add("Provision " + schemaTO.getProvision() + " not found");
+            throw sce;
+        }
+        merged.setProvision(provision);
+
         return merged;
     }
 
@@ -253,6 +266,7 @@ public class SchemaDataBinderImpl implements SchemaDataBinder {
         VirSchemaTO schemaTO = new VirSchemaTO();
         BeanUtils.copyProperties(schema, schemaTO, IGNORE_PROPERTIES);
         schemaTO.setAnyTypeClass(schema.getAnyTypeClass() == null ? null : schema.getAnyTypeClass().getKey());
+        schemaTO.setProvision(schema.getProvision().getKey());
 
         return schemaTO;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/9cd92305/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index 3420d27..4bb7bdf 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -19,8 +19,10 @@
 package org.apache.syncope.core.provisioning.java.data;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.Resource;
@@ -57,6 +59,7 @@ import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.entity.RelationshipType;
 import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
@@ -431,12 +434,12 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
             userTO.setSecurityQuestion(user.getSecurityQuestion().getKey());
         }
 
-        if (details) {
-            virAttrHander.retrieveVirAttrValues(user);
-        }
+        Map<VirSchema, List<String>> virAttrValues = details
+                ? virAttrHander.getValues(user)
+                : Collections.<VirSchema, List<String>>emptyMap();
 
         fillTO(userTO, user.getRealm().getFullPath(), user.getAuxClasses(),
-                user.getPlainAttrs(), user.getDerAttrs(), user.getVirAttrs(), userDAO.findAllResources(user));
+                user.getPlainAttrs(), user.getDerAttrs(), virAttrValues, userDAO.findAllResources(user));
 
         if (details) {
             // roles

http://git-wip-us.apache.org/repos/asf/syncope/blob/9cd92305/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
index dbfeca5..7c6d34c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/TaskJob.java
@@ -21,7 +21,7 @@ package org.apache.syncope.core.provisioning.java.job;
 import java.util.Date;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.lang3.ClassUtils;
-import org.apache.syncope.core.misc.DataFormat;
+import org.apache.syncope.core.misc.FormatUtils;
 import org.apache.syncope.core.misc.security.AuthContextUtils;
 import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
 import org.apache.syncope.core.provisioning.api.job.JobInstanceLoader;
@@ -114,7 +114,7 @@ public class TaskJob implements InterruptableJob {
         if (thread == null) {
             LOG.warn("Unable to retrieve the thread of the current job execution");
         } else {
-            LOG.info("Interrupting job from thread {} at {} ", thread.getId(), DataFormat.format(new Date()));
+            LOG.info("Interrupting job from thread {} at {} ", thread.getId(), FormatUtils.format(new Date()));
 
             if (interruptMaxRetries < 1) {
                 interruptMaxRetries = 1;

http://git-wip-us.apache.org/repos/asf/syncope/blob/9cd92305/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java
index 412b978..fd4af1b 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java
@@ -50,16 +50,17 @@ import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 import org.apache.syncope.core.persistence.api.entity.user.UDerAttr;
 import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr;
-import org.apache.syncope.core.persistence.api.entity.user.UVirAttr;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 import org.apache.syncope.core.misc.search.SearchCondConverter;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyAbout;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.app.VelocityEngine;
@@ -76,9 +77,6 @@ import org.springframework.transaction.annotation.Transactional;
 @Transactional(rollbackFor = { Throwable.class })
 public class NotificationManagerImpl implements NotificationManager {
 
-    /**
-     * Logger.
-     */
     private static final Logger LOG = LoggerFactory.getLogger(NotificationManager.class);
 
     public static final String MAIL_TEMPLATES = "mailTemplates/";
@@ -87,6 +85,9 @@ public class NotificationManagerImpl implements NotificationManager {
 
     public static final String MAIL_TEMPLATE_TEXT_SUFFIX = ".txt.vm";
 
+    @Autowired
+    private VirSchemaDAO virSchemaDAO;
+
     /**
      * Notification DAO.
      */
@@ -169,11 +170,11 @@ public class NotificationManagerImpl implements NotificationManager {
      */
     private NotificationTask getNotificationTask(
             final Notification notification,
-            final Any<?, ?, ?> any,
+            final Any<?, ?> any,
             final Map<String, Object> model) {
 
         if (any != null) {
-            virAttrHander.retrieveVirAttrValues(any);
+            virAttrHander.getValues(any);
         }
 
         List<User> recipients = new ArrayList<>();
@@ -191,7 +192,7 @@ public class NotificationManagerImpl implements NotificationManager {
         Set<String> recipientEmails = new HashSet<>();
         List<UserTO> recipientTOs = new ArrayList<>(recipients.size());
         for (User recipient : recipients) {
-            virAttrHander.retrieveVirAttrValues(recipient);
+            virAttrHander.getValues(recipient);
 
             String email = getRecipientEmail(notification.getRecipientAttrType(),
                     notification.getRecipientAttrName(), recipient);
@@ -267,7 +268,7 @@ public class NotificationManagerImpl implements NotificationManager {
             final Object output,
             final Object... input) {
 
-        Any<?, ?, ?> any = null;
+        Any<?, ?> any = null;
 
         if (before instanceof UserTO) {
             any = userDAO.find(((UserTO) before).getKey());
@@ -357,9 +358,12 @@ public class NotificationManagerImpl implements NotificationManager {
                 break;
 
             case UserVirtualSchema:
-                UVirAttr virAttr = user.getVirAttr(recipientAttrName);
-                if (virAttr != null) {
-                    email = virAttr.getValues().isEmpty() ? null : virAttr.getValues().get(0);
+                VirSchema schema = virSchemaDAO.find(recipientAttrName);
+                if (schema == null) {
+                    LOG.warn("Ignoring non existing {} {}", VirSchema.class.getSimpleName(), recipientAttrName);
+                } else {
+                    List<String> virAttrValues = virAttrHander.getValues(user, schema);
+                    email = virAttrValues.isEmpty() ? null : virAttrValues.get(0);
                 }
                 break;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/9cd92305/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index 69a2e9d..316f91a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -27,6 +27,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import org.apache.commons.collections4.IteratorUtils;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.Result;
 import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
@@ -49,12 +50,17 @@ import org.apache.syncope.core.misc.ConnObjectUtils;
 import org.apache.syncope.core.misc.ExceptionUtils2;
 import org.apache.syncope.core.misc.MappingUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
+import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheValue;
 import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
 import org.identityconnectors.framework.common.exceptions.ConnectorException;
 import org.identityconnectors.framework.common.objects.Attribute;
@@ -110,6 +116,9 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
     @Autowired
     protected TaskDAO taskDAO;
 
+    @Autowired
+    protected VirSchemaDAO virSchemaDAO;
+
     /**
      * Notification Manager.
      */
@@ -125,6 +134,9 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
     @Autowired
     protected EntityFactory entityFactory;
 
+    @Autowired
+    protected VirAttrCache virAttrCache;
+
     @Override
     public TaskExec execute(final PropagationTask task) {
         return execute(task, null);
@@ -149,12 +161,15 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
     }
 
     /**
-     * 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>
+     * Transform a {@link Collection} of {@link Attribute} instances into a {@link Map}.
+     * The key to each element in the map is the {@code name} of an {@link Attribute}.
+     * The value of each element in the map is the {@link Attribute} 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.
@@ -173,7 +188,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             final PropagationTask task,
             final ConnectorObject beforeObj,
             final Connector connector,
-            final Set<String> propagationAttempted) {
+            final Boolean[] propagationAttempted) {
 
         // set of attributes to be propagated
         Set<Attribute> attributes = new HashSet<>(task.getAttributes());
@@ -201,14 +216,10 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
 
         if (beforeObj == null) {
             LOG.debug("Create {} on {}", attributes, task.getResource().getKey());
-            connector.create(
-                    new ObjectClass(task.getObjectClassName()),
-                    attributes,
-                    null,
-                    propagationAttempted);
+            connector.create(new ObjectClass(task.getObjectClassName()), attributes, null, propagationAttempted);
         } else {
             // 1. check if rename is really required
-            final Name newName = (Name) AttributeUtil.find(Name.NAME, attributes);
+            Name newName = (Name) AttributeUtil.find(Name.NAME, attributes);
 
             LOG.debug("Rename required with value {}", newName);
 
@@ -254,8 +265,8 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         }
     }
 
-    protected Any<?, ?, ?> getAny(final PropagationTask task) {
-        Any<?, ?, ?> any = null;
+    protected Any<?, ?> getAny(final PropagationTask task) {
+        Any<?, ?> any = null;
 
         if (task.getAnyKey() != null) {
             switch (task.getAnyTypeKind()) {
@@ -289,8 +300,11 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         return any;
     }
 
-    protected void delete(final PropagationTask task, final ConnectorObject beforeObj,
-            final Connector connector, final Set<String> propagationAttempted) {
+    protected void delete(
+            final PropagationTask task,
+            final ConnectorObject beforeObj,
+            final Connector connector,
+            final Boolean[] propagationAttempted) {
 
         if (beforeObj == null) {
             LOG.debug("{} not found on external resource: ignoring delete", task.getConnObjectKey());
@@ -310,7 +324,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
              * update, this user / group used to have the current resource assigned by more than one mean (for example,
              * two different memberships with the same resource).
              */
-            Any<?, ?, ?> any = getAny(task);
+            Any<?, ?> any = getAny(task);
             Collection<String> resources = any instanceof User
                     ? userDAO.findAllResourceNames((User) any)
                     : any instanceof AnyObject
@@ -321,11 +335,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             if (!resources.contains(task.getResource().getKey())) {
                 LOG.debug("Delete {} on {}", beforeObj.getUid(), task.getResource().getKey());
 
-                connector.delete(
-                        beforeObj.getObjectClass(),
-                        beforeObj.getUid(),
-                        null,
-                        propagationAttempted);
+                connector.delete(beforeObj.getObjectClass(), beforeObj.getUid(), null, propagationAttempted);
             } else {
                 createOrUpdate(task, beforeObj, connector, propagationAttempted);
             }
@@ -345,7 +355,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         String failureReason = null;
 
         // Flag to state whether any propagation has been attempted
-        Set<String> propagationAttempted = new HashSet<>();
+        Boolean[] propagationAttempted = new Boolean[] { false };
 
         ConnectorObject beforeObj = null;
         ConnectorObject afterObj = null;
@@ -411,7 +421,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                 LOG.error("While executing KO action on {}", execution, wft);
             }
 
-            propagationAttempted.add(task.getOperation().name().toLowerCase());
+            propagationAttempted[0] = true;
 
             for (PropagationActions action : actions) {
                 action.onError(task, execution, e);
@@ -434,7 +444,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             execution.setEndDate(new Date());
 
             if (hasToBeregistered(task, execution)) {
-                if (propagationAttempted.isEmpty()) {
+                if (!propagationAttempted[0]) {
                     LOG.debug("No propagation attempted for {}", execution);
                 } else {
                     execution.setTask(task);
@@ -535,20 +545,41 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
      * @param latest 'FALSE' to retrieve object using old connObjectKey if not null.
      * @return remote connector object.
      */
-    protected ConnectorObject getRemoteObject(final PropagationTask task, final Connector connector,
-            final Provision provision, final boolean latest) {
+    protected ConnectorObject getRemoteObject(
+            final PropagationTask task,
+            final Connector connector,
+            final Provision provision,
+            final boolean latest) {
 
         String connObjectKey = latest || task.getOldConnObjectKey() == null
                 ? task.getConnObjectKey()
                 : task.getOldConnObjectKey();
 
+        List<MappingItem> linkingMappingItems = new ArrayList<>();
+        for (VirSchema schema : virSchemaDAO.findByProvision(provision)) {
+            linkingMappingItems.add(schema.asLinkingMappingItem());
+        }
+
         ConnectorObject obj = null;
         try {
             obj = connector.getObject(
                     task.getOperation(),
                     new ObjectClass(task.getObjectClassName()),
                     new Uid(connObjectKey),
-                    connector.getOperationOptions(MappingUtils.getPropagationMappingItems(provision)));
+                    connector.getOperationOptions(IteratorUtils.chainedIterator(
+                                    MappingUtils.getPropagationMappingItems(provision).iterator(),
+                                    linkingMappingItems.iterator())));
+
+            for (MappingItem item : linkingMappingItems) {
+                Attribute attr = obj.getAttributeByName(item.getExtAttrName());
+                if (attr == null) {
+                    virAttrCache.expire(task.getAnyType(), task.getAnyKey(), item.getIntAttrName());
+                } else {
+                    VirAttrCacheValue cacheValue = new VirAttrCacheValue();
+                    cacheValue.setValues(attr.getValue());
+                    virAttrCache.put(task.getAnyType(), task.getAnyKey(), item.getIntAttrName(), cacheValue);
+                }
+            }
         } catch (TimeoutException toe) {
             LOG.debug("Request timeout", toe);
             throw toe;


Mime
View raw message