You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by an...@apache.org on 2014/06/10 11:45:55 UTC

svn commit: r1601588 - in /syncope/trunk: console/src/main/java/org/apache/syncope/console/rest/ core/src/main/java/org/apache/syncope/core/connid/ core/src/main/java/org/apache/syncope/core/persistence/beans/membership/ core/src/main/java/org/apache/s...

Author: andreapatricelli
Date: Tue Jun 10 09:45:54 2014
New Revision: 1601588

URL: http://svn.apache.org/r1601588
Log:
[SYNCOPE-458] improved membership virtual attribute management

Modified:
    syncope/trunk/console/src/main/java/org/apache/syncope/console/rest/SchemaRestClient.java
    syncope/trunk/core/src/main/java/org/apache/syncope/core/connid/ConnObjectUtil.java
    syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/membership/MVirAttr.java
    syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java
    syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java
    syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/data/UserDataBinder.java
    syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopePushResultHandler.java
    syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopeSyncResultHandler.java
    syncope/trunk/core/src/main/java/org/apache/syncope/core/util/AttributableUtil.java
    syncope/trunk/core/src/main/java/org/apache/syncope/core/util/MappingUtil.java
    syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/VirAttrTestITCase.java

Modified: syncope/trunk/console/src/main/java/org/apache/syncope/console/rest/SchemaRestClient.java
URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/java/org/apache/syncope/console/rest/SchemaRestClient.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/console/src/main/java/org/apache/syncope/console/rest/SchemaRestClient.java (original)
+++ syncope/trunk/console/src/main/java/org/apache/syncope/console/rest/SchemaRestClient.java Tue Jun 10 09:45:54 2014
@@ -140,16 +140,16 @@ public class SchemaRestClient extends Ba
                 userDerSchemasNames.add(schemaTO.getName());
             }
         } catch (SyncopeClientException e) {
-            LOG.error("While getting all user derived schema names", e);
+            LOG.error("While getting all {} derived schema names", type, e);
         }
 
         return userDerSchemasNames;
     }
 
     /**
-     * Get derived schemas.
+     * Get virtual schemas.
      *
-     * @return List of derived schemas.
+     * @return List of virtual schemas.
      */
     @SuppressWarnings("unchecked")
     public List<VirSchemaTO> getVirSchemas(final AttributableType type) {
@@ -158,7 +158,7 @@ public class SchemaRestClient extends Ba
         try {
             userVirSchemas = getService(SchemaService.class).list(type, SchemaType.VIRTUAL);
         } catch (SyncopeClientException e) {
-            LOG.error("While getting all user derived schemas", e);
+            LOG.error("While getting all {} virtual schemas", type, e);
         }
 
         return userVirSchemas;

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/connid/ConnObjectUtil.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/connid/ConnObjectUtil.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/connid/ConnObjectUtil.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/connid/ConnObjectUtil.java Tue Jun 10 09:45:54 2014
@@ -550,7 +550,15 @@ public class ConnObjectUtil {
 
             final VirAttrCacheValue toBeCached = new VirAttrCacheValue();
 
-            for (ExternalResource resource : getTargetResource(virAttr, type, attrUtil)) {
+            // SYNCOPE-458 if virattr owner is a Membership, owner must become user involved in membership because 
+            // membership mapping is contained in user mapping
+            final AbstractAttributable realOwner = owner instanceof Membership ? ((Membership) owner).getSyncopeUser()
+                    : 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.getName());
                 try {
                     final List<AbstractMappingItem> mappings = attrUtil.getMappingItems(resource, MappingPurpose.BOTH);
@@ -564,7 +572,7 @@ public class ConnObjectUtil {
                         final String accountId = attrUtil.getAccountIdItem(resource) == null
                                 ? null
                                 : MappingUtil.getAccountIdValue(
-                                        owner, resource, attrUtil.getAccountIdItem(resource));
+                                        realOwner, resource, attrUtil.getAccountIdItem(resource));
 
                         if (StringUtils.isBlank(accountId)) {
                             throw new IllegalArgumentException("No AccountId found for " + resource.getName());
@@ -575,7 +583,7 @@ public class ConnObjectUtil {
                         final OperationOptions oo =
                                 connector.getOperationOptions(MappingUtil.getMatchingMappingItems(mappings, type));
 
-                        connectorObject = connector.getObject(fromAttributable(owner), new Uid(accountId), oo);
+                        connectorObject = connector.getObject(fromAttributable(realOwner), new Uid(accountId), oo);
                         externalResources.put(resource.getName(), connectorObject);
                     }
 
@@ -617,9 +625,9 @@ public class ConnObjectUtil {
                 }
             }
 
-            virAttrCache.put(attrUtil.getType(), owner.getId(), schemaName, toBeCached);
+                virAttrCache.put(attrUtil.getType(), owner.getId(), schemaName, toBeCached);
+            }
         }
-    }
 
     private Set<ExternalResource> getTargetResource(
             final AbstractVirAttr attr, final IntMappingType type, final AttributableUtil attrUtil) {
@@ -638,6 +646,23 @@ public class ConnObjectUtil {
         return resources;
     }
 
+    private Set<ExternalResource> getTargetResource(final AbstractVirAttr attr, final IntMappingType type,
+            final AttributableUtil attrUtil, final Set<ExternalResource> ownerResources) {
+
+        final Set<ExternalResource> resources = new HashSet<ExternalResource>();
+
+        for (ExternalResource res : ownerResources) {
+            if (!MappingUtil.getMatchingMappingItems(
+                    attrUtil.getMappingItems(res, MappingPurpose.BOTH),
+                    attr.getSchema().getName(), type).isEmpty()) {
+
+                resources.add(res);
+            }
+        }
+
+        return resources;
+    }
+
     private void fillFromTemplate(final AbstractAttributableTO attributableTO, final AbstractAttributableTO template) {
         Map<String, AttributeTO> currentAttrMap = attributableTO.getAttrMap();
         for (AttributeTO templateAttr : template.getAttrs()) {
@@ -657,7 +682,7 @@ public class ConnObjectUtil {
         }
 
         currentAttrMap = attributableTO.getVirAttrMap();
-        for (AttributeTO templateVirAttr : template.getDerAttrs()) {
+        for (AttributeTO templateVirAttr : template.getVirAttrs()) {
             if (templateVirAttr.getValues() != null && !templateVirAttr.getValues().isEmpty()
                     && (!currentAttrMap.containsKey(templateVirAttr.getSchema())
                     || currentAttrMap.get(templateVirAttr.getSchema()).getValues().isEmpty())) {

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/membership/MVirAttr.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/membership/MVirAttr.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/membership/MVirAttr.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/membership/MVirAttr.java Tue Jun 10 09:45:54 2014
@@ -18,8 +18,6 @@
  */
 package org.apache.syncope.core.persistence.beans.membership;
 
-import java.util.Collections;
-import java.util.List;
 import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -69,24 +67,4 @@ public class MVirAttr extends AbstractVi
     public <T extends AbstractVirSchema> T getSchema() {
         return template == null ? null : (T) template.getSchema();
     }
-
-    @Override
-    public List<String> getValues() {
-        return Collections.emptyList();
-    }
-
-    @Override
-    public boolean addValue(final String value) {
-        return false;
-    }
-
-    @Override
-    public boolean removeValue(final String value) {
-        return false;
-    }
-
-    @Override
-    public void setValues(final List<String> values) {
-        // do nothing
-    }
 }

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java Tue Jun 10 09:45:54 2014
@@ -27,8 +27,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.apache.syncope.common.mod.AttributeMod;
+import org.apache.syncope.common.mod.MembershipMod;
 import org.apache.syncope.common.mod.UserMod;
 import org.apache.syncope.common.to.AttributeTO;
+import org.apache.syncope.common.to.MembershipTO;
 import org.apache.syncope.common.types.AttributableType;
 import org.apache.syncope.common.types.MappingPurpose;
 import org.apache.syncope.common.types.ResourceOperation;
@@ -38,6 +40,7 @@ import org.apache.syncope.core.persisten
 import org.apache.syncope.core.persistence.beans.AbstractVirAttr;
 import org.apache.syncope.core.persistence.beans.ExternalResource;
 import org.apache.syncope.core.persistence.beans.PropagationTask;
+import org.apache.syncope.core.persistence.beans.membership.Membership;
 import org.apache.syncope.core.persistence.beans.role.SyncopeRole;
 import org.apache.syncope.core.persistence.beans.user.SyncopeUser;
 import org.apache.syncope.core.persistence.dao.NotFoundException;
@@ -101,15 +104,16 @@ public class PropagationManager {
      * @param wfResult user to be propagated (and info associated), as per result from workflow
      * @param password to be set
      * @param vAttrs virtual attributes to be set
+     * @param membershipTOs user memberships
      * @return list of propagation tasks
      * @throws NotFoundException if user is not found
      * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
      */
     public List<PropagationTask> getUserCreateTaskIds(final WorkflowResult<Map.Entry<Long, Boolean>> wfResult,
-            final String password, final List<AttributeTO> vAttrs)
+            final String password, final List<AttributeTO> vAttrs, final List<MembershipTO> membershipTOs)
             throws NotFoundException, UnauthorizedRoleException {
 
-        return getUserCreateTaskIds(wfResult, password, vAttrs, null);
+        return getUserCreateTaskIds(wfResult, password, vAttrs, null, membershipTOs);
     }
 
     /**
@@ -119,17 +123,30 @@ public class PropagationManager {
      * @param password to be set
      * @param vAttrs virtual attributes to be set
      * @param noPropResourceNames external resources not to be considered for propagation
+     * @param membershipTOs user memberships
      * @return list of propagation tasks
      * @throws NotFoundException if user is not found
      * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
      */
     public List<PropagationTask> getUserCreateTaskIds(final WorkflowResult<Map.Entry<Long, Boolean>> wfResult,
-            final String password, final Collection<AttributeTO> vAttrs, final Set<String> noPropResourceNames)
+            final String password, final Collection<AttributeTO> vAttrs,
+            final Set<String> noPropResourceNames, final List<MembershipTO> membershipTOs)
             throws NotFoundException, UnauthorizedRoleException {
 
         SyncopeUser user = userDataBinder.getUserFromId(wfResult.getResult().getKey());
         if (vAttrs != null && !vAttrs.isEmpty()) {
             userDataBinder.fillVirtual(user, vAttrs, AttributableUtil.getInstance(AttributableType.USER));
+
+        }
+        for (Membership membership : user.getMemberships()) {
+            MembershipTO membershipTO;
+            if (membership.getVirAttrs() != null && !membership.getVirAttrs().isEmpty()) {
+                membershipTO = findMembershipTO(membership, membershipTOs);
+                if (membershipTO != null) {
+                    userDataBinder.fillVirtual(membership, membershipTO.getVirAttrs(), AttributableUtil.getInstance(
+                            AttributableType.MEMBERSHIP));
+                }
+            }
         }
         return getCreateTaskIds(user, password,
                 wfResult.getResult().getValue(), wfResult.getPropByRes(), noPropResourceNames);
@@ -185,7 +202,7 @@ public class PropagationManager {
             propByRes.get(ResourceOperation.CREATE).removeAll(noPropResourceNames);
         }
 
-        return createTasks(attributable, password, true, null, null, enable, false, propByRes);
+        return createTasks(attributable, password, true, null, null, null, null, enable, false, propByRes);
     }
 
     /**
@@ -209,7 +226,8 @@ public class PropagationManager {
                 Collections.<String>emptySet(), // no virtual attributes to be managed
                 Collections.<AttributeMod>emptySet(), // no virtual attributes to be managed
                 null, // no propagation by resources
-                noPropResourceNames);
+                noPropResourceNames,
+                Collections.<MembershipMod>emptySet());
     }
 
     /**
@@ -234,7 +252,8 @@ public class PropagationManager {
                 wfResult.getResult().getKey().getVirAttrsToRemove(),
                 wfResult.getResult().getKey().getVirAttrsToUpdate(),
                 wfResult.getPropByRes(),
-                noPropResourceNames);
+                noPropResourceNames,
+                wfResult.getResult().getKey().getMembershipsToAdd());
     }
 
     public List<PropagationTask> getUserUpdateTaskIds(final WorkflowResult<Map.Entry<UserMod, Boolean>> wfResult) {
@@ -310,13 +329,15 @@ public class PropagationManager {
 
         SyncopeRole role = roleDataBinder.getRoleFromId(wfResult.getResult());
         return getUpdateTaskIds(role, null, false, null,
-                vAttrsToBeRemoved, vAttrsToBeUpdated, wfResult.getPropByRes(), noPropResourceNames);
+                vAttrsToBeRemoved, vAttrsToBeUpdated, wfResult.getPropByRes(), noPropResourceNames,
+                Collections.<MembershipMod>emptySet());
     }
 
     protected List<PropagationTask> getUpdateTaskIds(final AbstractAttributable attributable,
             final String password, final boolean changePwd, final Boolean enable,
             final Set<String> vAttrsToBeRemoved, final Set<AttributeMod> vAttrsToBeUpdated,
-            final PropagationByResource propByRes, final Collection<String> noPropResourceNames)
+            final PropagationByResource propByRes, final Collection<String> noPropResourceNames,
+            final Set<MembershipMod> membershipsToAdd)
             throws NotFoundException {
 
         AbstractAttributableDataBinder binder = attributable instanceof SyncopeUser
@@ -328,6 +349,24 @@ public class PropagationManager {
                 ? Collections.<AttributeMod>emptySet()
                 : vAttrsToBeUpdated, AttributableUtil.getInstance(attributable));
 
+        // SYNCOPE-458 fill membership virtual attributes
+        if (attributable instanceof SyncopeUser) {
+            final SyncopeUser user = (SyncopeUser) attributable;
+            for (Membership membership : user.getMemberships()) {
+                if (membership.getVirAttrs() != null && !membership.getVirAttrs().isEmpty()) {
+                    final MembershipMod membershipMod = findMembershipMod(membership, membershipsToAdd);
+                    if (membershipMod != null) {
+                        binder.fillVirtual(membership, membershipMod.getVirAttrsToRemove() == null
+                                ? Collections.<String>emptySet()
+                                : membershipMod.getVirAttrsToRemove(),
+                                membershipMod.getVirAttrsToUpdate() == null ? Collections.<AttributeMod>emptySet()
+                                : membershipMod.getVirAttrsToUpdate(), AttributableUtil.getInstance(
+                                        AttributableType.MEMBERSHIP));
+                    }
+                }
+            }
+        }
+
         if (propByRes == null || propByRes.isEmpty()) {
             localPropByRes.addAll(ResourceOperation.UPDATE, attributable.getResourceNames());
         } else {
@@ -346,8 +385,23 @@ public class PropagationManager {
             }
         }
 
+        // SYNCOPE-458 fill membership virtual attributes to be updated map
+        Map<String, AttributeMod> membVAttrsToBeUpdatedMap = new HashMap<String, AttributeMod>();
+        for (MembershipMod membershipMod : membershipsToAdd) {
+            for (AttributeMod attrMod : membershipMod.getVirAttrsToUpdate()) {
+                membVAttrsToBeUpdatedMap.put(attrMod.getSchema(), attrMod);
+            }
+        }
+
+        // SYNCOPE-458 fill membership virtual attributes to be removed set
+        final Set<String> membVAttrsToBeRemoved = new HashSet<String>();
+        for (MembershipMod membershipMod : membershipsToAdd) {
+            membVAttrsToBeRemoved.addAll(membershipMod.getVirAttrsToRemove());
+        }
+
         return createTasks(attributable, password, changePwd,
-                vAttrsToBeRemoved, vAttrsToBeUpdatedMap, enable, false, localPropByRes);
+                vAttrsToBeRemoved, vAttrsToBeUpdatedMap, membVAttrsToBeRemoved, membVAttrsToBeUpdatedMap, enable, false,
+                localPropByRes);
     }
 
     /**
@@ -429,7 +483,7 @@ public class PropagationManager {
      */
     public List<PropagationTask> getUserDeleteTaskIds(final WorkflowResult<Long> wfResult) {
         SyncopeUser user = userDataBinder.getUserFromId(wfResult.getResult());
-        return createTasks(user, null, false, null, null, false, true, wfResult.getPropByRes());
+        return createTasks(user, null, false, null, null, null, null, false, true, wfResult.getPropByRes());
     }
 
     /**
@@ -512,7 +566,7 @@ public class PropagationManager {
         if (noPropResourceNames != null && !noPropResourceNames.isEmpty()) {
             propByRes.get(ResourceOperation.DELETE).removeAll(noPropResourceNames);
         }
-        return createTasks(attributable, null, false, null, null, false, true, propByRes);
+        return createTasks(attributable, null, false, null, null, null, null, false, true, propByRes);
     }
 
     /**
@@ -524,6 +578,8 @@ public class PropagationManager {
      * @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 membVAttrsToBeUpdatedMap membership virtual attributes to be added
      * @param enable whether user must be enabled or not
      * @param deleteOnResource whether user / role must be deleted anyway from external resource or not
      * @param propByRes operation to be performed per resource
@@ -532,8 +588,8 @@ public class PropagationManager {
     protected <T extends AbstractAttributable> List<PropagationTask> createTasks(final T subject,
             final String password, final boolean changePwd,
             final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated,
-            final Boolean enable, final boolean deleteOnResource,
-            final PropagationByResource propByRes) {
+            final Set<String> membVAttrsToBeRemoved, final Map<String, AttributeMod> membVAttrsToBeUpdatedMap,
+            final Boolean enable, final boolean deleteOnResource, final PropagationByResource propByRes) {
 
         LOG.debug("Provisioning subject {}:\n{}", subject, propByRes);
 
@@ -583,7 +639,8 @@ public class PropagationManager {
                     task.setOldAccountId(propByRes.getOldAccountId(resource.getName()));
 
                     Map.Entry<String, Set<Attribute>> preparedAttrs = MappingUtil.prepareAttributes(attrUtil, subject,
-                            password, changePwd, vAttrsToBeRemoved, vAttrsToBeUpdated, enable, resource);
+                            password, changePwd, vAttrsToBeRemoved, vAttrsToBeUpdated, membVAttrsToBeRemoved,
+                            membVAttrsToBeUpdatedMap, enable, resource);
                     task.setAccountId(preparedAttrs.getKey());
 
                     // Check if any of mandatory attributes (in the mapping) is missing or not received any value: 
@@ -621,4 +678,24 @@ public class PropagationManager {
 
         return tasks;
     }
+
+    private MembershipTO findMembershipTO(final Membership membership, final List<MembershipTO> memberships) {
+        for (MembershipTO membershipTO : memberships) {
+            if (membershipTO.getRoleId() == membership.getSyncopeRole().getId()) {
+                return membershipTO;
+            }
+        }
+        LOG.error("No MembershipTO found for membership {}", membership);
+        return null;
+    }
+
+    private MembershipMod findMembershipMod(final Membership membership, final Set<MembershipMod> membershipMods) {
+        for (MembershipMod membershipMod : membershipMods) {
+            if (membershipMod.getRole() == membership.getSyncopeRole().getId()) {
+                return membershipMod;
+            }
+        }
+        LOG.error("No MembershipMod found for membership {}", membership);
+        return null;
+    }
 }

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java Tue Jun 10 09:45:54 2014
@@ -38,6 +38,7 @@ import org.apache.syncope.common.to.User
 import org.apache.syncope.common.types.AttributableType;
 import org.apache.syncope.common.types.ClientExceptionType;
 import org.apache.syncope.common.SyncopeClientException;
+import org.apache.syncope.common.mod.MembershipMod;
 import org.apache.syncope.core.persistence.beans.PropagationTask;
 import org.apache.syncope.core.persistence.beans.role.SyncopeRole;
 import org.apache.syncope.core.persistence.beans.user.SyncopeUser;
@@ -207,7 +208,7 @@ public class UserController extends Abst
         WorkflowResult<Map.Entry<Long, Boolean>> created = uwfAdapter.create(actual);
 
         List<PropagationTask> tasks = propagationManager.getUserCreateTaskIds(
-                created, actual.getPassword(), actual.getVirAttrs());
+                created, actual.getPassword(), actual.getVirAttrs(), actual.getMemberships());
         PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().
                 getBean(PropagationReporter.class);
         try {

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/data/UserDataBinder.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/data/UserDataBinder.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/data/UserDataBinder.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/data/UserDataBinder.java Tue Jun 10 09:45:54 2014
@@ -104,7 +104,7 @@ public class UserDataBinder extends Abst
 
         return user;
     }
-
+    
     @Transactional(readOnly = true)
     public Set<String> getResourceNamesForUserId(final Long userId) {
         return getUserFromId(userId).getResourceNames();
@@ -412,6 +412,9 @@ public class UserDataBinder extends Abst
             membershipTO.setRoleId(membership.getSyncopeRole().getId());
             membershipTO.setRoleName(membership.getSyncopeRole().getName());
 
+            // SYNCOPE-458 retrieve also membership virtual attributes
+            connObjectUtil.retrieveVirAttrValues(membership, AttributableUtil.getInstance(AttributableType.MEMBERSHIP));
+            
             fillTO(membershipTO,
                     membership.getAttrs(), membership.getDerAttrs(), membership.getVirAttrs(),
                     membership.getResources());

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopePushResultHandler.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopePushResultHandler.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopePushResultHandler.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopePushResultHandler.java Tue Jun 10 09:45:54 2014
@@ -117,6 +117,8 @@ public class SyncopePushResultHandler ex
                     true, // propagate password (if required)
                     null, // no vir attrs to be removed
                     null, // propagate current vir attr values
+                    null, // no membership vir attrs to be removed
+                    null, // propagate current membership vir attr values
                     enabled, // propagate status (suspended or not) if required
                     getSyncTask().getResource()); // target external resource
 

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopeSyncResultHandler.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopeSyncResultHandler.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopeSyncResultHandler.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopeSyncResultHandler.java Tue Jun 10 09:45:54 2014
@@ -362,7 +362,7 @@ public class SyncopeSyncResultHandler ex
 
         final List<ConnectorObject> found = connector.search(objectClass,
                 new EqualsFilter(new Name(name)), connector.getOperationOptions(
-                attrUtil.getMappingItems(syncTask.getResource(), MappingPurpose.SYNCHRONIZATION)));
+                        attrUtil.getMappingItems(syncTask.getResource(), MappingPurpose.SYNCHRONIZATION)));
 
         if (found.isEmpty()) {
             LOG.debug("No {} found on {} with __NAME__ {}", objectClass, syncTask.getResource(), name);
@@ -441,7 +441,7 @@ public class SyncopeSyncResultHandler ex
 
                     List<PropagationTask> tasks = propagationManager.getUserCreateTaskIds(created,
                             ((UserTO) actual).getPassword(), actual.getVirAttrs(),
-                            Collections.singleton(syncTask.getResource().getName()));
+                            Collections.singleton(syncTask.getResource().getName()), ((UserTO) actual).getMemberships());
 
                     taskExecutor.execute(tasks);
 

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/util/AttributableUtil.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/util/AttributableUtil.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/util/AttributableUtil.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/util/AttributableUtil.java Tue Jun 10 09:45:54 2014
@@ -190,17 +190,17 @@ public final class AttributableUtil {
 
         if (resource != null) {
             switch (type) {
-                case USER:
-                    if (resource.getUmapping() != null) {
-                        result = resource.getUmapping().getAccountIdItem();
-                    }
-                    break;
                 case ROLE:
                     if (resource.getRmapping() != null) {
                         result = resource.getRmapping().getAccountIdItem();
                     }
                     break;
                 case MEMBERSHIP:
+                case USER:
+                    if (resource.getUmapping() != null) {
+                        result = resource.getUmapping().getAccountIdItem();
+                    }
+                    break;
                 default:
             }
         }
@@ -215,17 +215,17 @@ public final class AttributableUtil {
 
         if (resource != null) {
             switch (type) {
-                case USER:
-                    if (resource.getUmapping() != null) {
-                        items = resource.getUmapping().getItems();
-                    }
-                    break;
                 case ROLE:
                     if (resource.getRmapping() != null) {
                         items = resource.getRmapping().getItems();
                     }
                     break;
                 case MEMBERSHIP:
+                case USER:
+                    if (resource.getUmapping() != null) {
+                        items = resource.getUmapping().getItems();
+                    }
+                    break;
                 default:
             }
         }
@@ -265,6 +265,7 @@ public final class AttributableUtil {
                 }
                 break;
             default:
+                LOG.error("You requested not existing purpose {}", purpose);
         }
 
         return result;

Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/util/MappingUtil.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/util/MappingUtil.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/core/src/main/java/org/apache/syncope/core/util/MappingUtil.java (original)
+++ syncope/trunk/core/src/main/java/org/apache/syncope/core/util/MappingUtil.java Tue Jun 10 09:45:54 2014
@@ -49,6 +49,7 @@ import org.apache.syncope.core.persisten
 import org.apache.syncope.core.persistence.beans.membership.MDerSchema;
 import org.apache.syncope.core.persistence.beans.membership.MSchema;
 import org.apache.syncope.core.persistence.beans.membership.MVirSchema;
+import org.apache.syncope.core.persistence.beans.membership.Membership;
 import org.apache.syncope.core.persistence.beans.role.RAttrValue;
 import org.apache.syncope.core.persistence.beans.role.RDerSchema;
 import org.apache.syncope.core.persistence.beans.role.RSchema;
@@ -130,6 +131,8 @@ public final class MappingUtil {
      * @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
@@ -137,6 +140,7 @@ public final class MappingUtil {
     public static <T extends AbstractAttributable> Map.Entry<String, Set<Attribute>> prepareAttributes(
             final AttributableUtil attrUtil, final T subject, final String password, final boolean changePwd,
             final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated,
+            final Set<String> membVAttrsToBeRemoved, final Map<String, AttributeMod> membVAttrsToBeUpdated,
             final Boolean enable, final ExternalResource resource) {
 
         LOG.debug("Preparing resource attributes for {} on resource {} with attributes {}",
@@ -162,8 +166,21 @@ public final class MappingUtil {
                     virAttrCache.expire(attrUtil.getType(), subject.getId(), mapping.getIntAttrName());
                 }
 
+                // SYNCOPE-458 expire cache also for membership virtual schemas
+                if (attrUtil.getType() == AttributableType.USER && mapping.getIntMappingType()
+                        == IntMappingType.MembershipVirtualSchema && (subject instanceof SyncopeUser)) {
+                    final SyncopeUser user = (SyncopeUser) subject;
+                    for (Membership membership : user.getMemberships()) {
+                        LOG.debug("Expire entry cache {}-{} for membership {}", subject.getId(), mapping.
+                                getIntAttrName(), membership);
+                        virAttrCache.expire(AttributableType.MEMBERSHIP, membership.getId(), mapping.
+                                getIntAttrName());
+                    }
+                }
+
                 Map.Entry<String, Attribute> preparedAttribute = prepareAttribute(
-                        resource, mapping, subject, password, passwordGenerator, vAttrsToBeRemoved, vAttrsToBeUpdated);
+                        resource, mapping, subject, password, passwordGenerator, vAttrsToBeRemoved, vAttrsToBeUpdated,
+                        membVAttrsToBeRemoved, membVAttrsToBeUpdated);
 
                 if (preparedAttribute != null && preparedAttribute.getKey() != null) {
                     accountId = preparedAttribute.getKey();
@@ -220,7 +237,8 @@ public final class MappingUtil {
     private static <T extends AbstractAttributable> Map.Entry<String, Attribute> prepareAttribute(
             final ExternalResource resource, final AbstractMappingItem mapItem,
             final T subject, final String password, final PasswordGenerator passwordGenerator,
-            final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated) {
+            final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated,
+            final Set<String> membVAttrsToBeRemoved, final Map<String, AttributeMod> membVAttrsToBeUpdated) {
 
         final List<AbstractAttributable> attributables = new ArrayList<AbstractAttributable>();
 
@@ -256,7 +274,8 @@ public final class MappingUtil {
         }
 
         List<AbstractAttrValue> values = getIntValues(
-                resource, mapItem, attributables, vAttrsToBeRemoved, vAttrsToBeUpdated);
+                resource, mapItem, attributables, vAttrsToBeRemoved, vAttrsToBeUpdated, membVAttrsToBeRemoved,
+                membVAttrsToBeUpdated);
 
         AbstractNormalSchema schema = null;
         boolean readOnlyVirSchema = false;
@@ -415,7 +434,8 @@ public final class MappingUtil {
 
         Map.Entry<String, Attribute> preparedAttr = prepareAttribute(
                 resource, attrUtil.getAccountIdItem(resource), subject, null, null,
-                Collections.<String>emptySet(), Collections.<String, AttributeMod>emptyMap());
+                Collections.<String>emptySet(), Collections.<String, AttributeMod>emptyMap(), Collections.
+                <String>emptySet(), Collections.<String, AttributeMod>emptyMap());
         String accountId = preparedAttr.getKey();
 
         final Name roleOwnerName = evaluateNAME(subject, resource, accountId);
@@ -430,11 +450,14 @@ public final class MappingUtil {
      * @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<AbstractAttrValue> getIntValues(final ExternalResource resource,
             final AbstractMappingItem mappingItem, final List<AbstractAttributable> attributables,
-            final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated) {
+            final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated,
+            final Set<String> membVAttrsToBeRemoved, final Map<String, AttributeMod> membVAttrsToBeUpdated) {
 
         LOG.debug("Get attributes for '{}' and mapping type '{}'", attributables, mappingItem.getIntMappingType());
 
@@ -465,7 +488,6 @@ public final class MappingUtil {
 
             case UserVirtualSchema:
             case RoleVirtualSchema:
-            case MembershipVirtualSchema:
                 for (AbstractAttributable attributable : attributables) {
                     AbstractVirAttr virAttr = attributable.getVirAttr(mappingItem.getIntAttrName());
                     if (virAttr != null) {
@@ -489,10 +511,44 @@ public final class MappingUtil {
                         }
                     }
 
-                    LOG.debug("Retrieved virtual attribute {}"
+                    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 (AbstractAttributable attributable : attributables) {
+                    AbstractVirAttr virAttr = attributable.getVirAttr(mappingItem.getIntAttrName());
+                    if (virAttr != null) {
+                        if (membVAttrsToBeRemoved != null && membVAttrsToBeUpdated != null) {
+                            if (membVAttrsToBeUpdated.containsKey(mappingItem.getIntAttrName())) {
+                                virAttr.setValues(
+                                        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 = new UAttrValue();
+                                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;
@@ -580,6 +636,7 @@ public final class MappingUtil {
      * Get accountId internal value.
      *
      * @param attributable attributable
+     * @param resource external resource
      * @param accountIdItem accountid mapping item
      * @return accountId internal value
      */
@@ -587,7 +644,7 @@ public final class MappingUtil {
             final AbstractMappingItem accountIdItem) {
 
         List<AbstractAttrValue> values = getIntValues(resource, accountIdItem,
-                Collections.<AbstractAttributable>singletonList(attributable), null, null);
+                Collections.<AbstractAttributable>singletonList(attributable), null, null, null, null);
         return values == null || values.isEmpty()
                 ? null
                 : values.get(0).getValueAsString();

Modified: syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/VirAttrTestITCase.java
URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/VirAttrTestITCase.java?rev=1601588&r1=1601587&r2=1601588&view=diff
==============================================================================
--- syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/VirAttrTestITCase.java (original)
+++ syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/VirAttrTestITCase.java Tue Jun 10 09:45:54 2014
@@ -27,6 +27,7 @@ import static org.junit.Assert.assertTru
 import org.apache.commons.lang3.SerializationUtils;
 import java.util.Map;
 import org.apache.syncope.common.mod.AttributeMod;
+import org.apache.syncope.common.mod.MembershipMod;
 import org.apache.syncope.common.mod.StatusMod;
 import org.apache.syncope.common.mod.UserMod;
 import org.apache.syncope.common.services.ResourceService;
@@ -555,4 +556,169 @@ public class VirAttrTestITCase extends A
         userTO = updateUser(userMod);
         assertNotNull(userTO.getVirAttrMap().get("virtualdata"));
     }
+
+    @Test
+    public void issueSYNCOPE458() {
+        // -------------------------------------------
+        // Create a role ad-hoc
+        // -------------------------------------------
+        final String roleName = "issueSYNCOPE458-Role-" + getUUIDString();
+        RoleTO roleTO = new RoleTO();
+        roleTO.setName(roleName);
+        roleTO.setParent(2L);
+        roleTO.setInheritTemplates(true);
+        roleTO = createRole(roleTO);
+        // -------------------------------------------
+
+        // -------------------------------------------
+        // Update resource-db-virattr mapping adding new membership virtual schema mapping
+        // -------------------------------------------
+        ResourceTO resourceDBVirAttr = resourceService.read(RESOURCE_NAME_DBVIRATTR);
+        assertNotNull(resourceDBVirAttr);
+
+        final MappingTO resourceUMapping = resourceDBVirAttr.getUmapping();
+
+        MappingItemTO item = new MappingItemTO();
+        item.setIntAttrName("mvirtualdata");
+        item.setIntMappingType(IntMappingType.MembershipVirtualSchema);
+        item.setExtAttrName("EMAIL");
+        item.setPurpose(MappingPurpose.BOTH);
+
+        resourceUMapping.addItem(item);
+
+        resourceDBVirAttr.setUmapping(resourceUMapping);
+
+        resourceService.update(RESOURCE_NAME_DBVIRATTR, resourceDBVirAttr);
+        // -------------------------------------------
+
+        // -------------------------------------------
+        // Create new user
+        // -------------------------------------------
+        UserTO userTO = getUniqueSampleTO("syncope458@syncope.apache.org");
+        userTO.getResources().clear();
+        userTO.getResources().add(RESOURCE_NAME_DBVIRATTR);
+        userTO.getVirAttrs().clear();
+        userTO.getDerAttrs().clear();
+        userTO.getMemberships().clear();
+
+        // add membership, with virtual attribute populated, to user
+        MembershipTO membership = new MembershipTO();
+        membership.setRoleId(roleTO.getId());
+        membership.getVirAttrs().add(attributeTO("mvirtualdata", "syncope458@syncope.apache.org"));
+        userTO.getMemberships().add(membership);
+
+        //propagate user
+        userTO = createUser(userTO);
+        assertEquals(1, userTO.getPropagationStatusTOs().size());
+        assertTrue(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful());
+       // -------------------------------------------
+
+        // 1. check if membership has virtual attribute populated
+        assertNotNull(userTO.getMemberships().get(0).getVirAttrMap().get("mvirtualdata"));
+        assertEquals("syncope458@syncope.apache.org",
+                userTO.getMemberships().get(0).getVirAttrMap().get("mvirtualdata").getValues().get(0));
+        // -------------------------------------------
+
+        // 2. update membership virtual attribute
+        MembershipMod membershipMod = new MembershipMod();
+        membershipMod.setRole(roleTO.getId());
+        membershipMod.getVirAttrsToUpdate().add(attributeMod("mvirtualdata", "syncope458_NEW@syncope.apache.org"));
+
+        UserMod userMod = new UserMod();
+        userMod.setId(userTO.getId());
+        userMod.getMembershipsToAdd().add(membershipMod);
+        userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getId());
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        // 3. check again after update if membership has virtual attribute populated with new value
+        assertNotNull(userTO.getMemberships().get(0).getVirAttrMap().get("mvirtualdata"));
+        assertEquals("syncope458_NEW@syncope.apache.org", userTO.getMemberships().get(0).getVirAttrMap().get(
+                "mvirtualdata").getValues().get(0));
+
+        // ----------------------------------------
+        // force cache expiring without any modification
+        // ----------------------------------------
+        String jdbcURL = null;
+        ConnInstanceTO connInstanceBean = connectorService.readByResource(RESOURCE_NAME_DBVIRATTR);
+        for (ConnConfProperty prop : connInstanceBean.getConfiguration()) {
+            if ("jdbcUrlTemplate".equals(prop.getSchema().getName())) {
+                jdbcURL = prop.getValues().iterator().next().toString();
+                prop.getValues().clear();
+                prop.getValues().add("jdbc:h2:tcp://localhost:9092/xxx");
+            }
+        }
+
+        connectorService.update(connInstanceBean.getId(), connInstanceBean);
+
+        membershipMod = new MembershipMod();
+        membershipMod.setRole(roleTO.getId());
+        membershipMod.getVirAttrsToUpdate().add(attributeMod("mvirtualdata", "syncope458_updated@syncope.apache.org"));
+
+        userMod = new UserMod();
+        userMod.setId(userTO.getId());
+        userMod.getMembershipsToAdd().add(membershipMod);
+        userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getId());
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        // ----------------------------------
+
+        // change attribute value directly on resource
+        final JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+
+        String value = jdbcTemplate.queryForObject(
+                "SELECT EMAIL FROM testsync WHERE ID=?", String.class, userTO.getId());
+        assertEquals("syncope458_NEW@syncope.apache.org", value);
+
+        jdbcTemplate.update("UPDATE testsync set EMAIL='syncope458_NEW_TWO@syncope.apache.org' WHERE ID=?", userTO.
+                getId());
+
+        value = jdbcTemplate.queryForObject("SELECT EMAIL FROM testsync WHERE ID=?", String.class, userTO.getId());
+        assertEquals("syncope458_NEW_TWO@syncope.apache.org", value);
+        // ----------------------------------------
+
+        // ----------------------------------------
+        // restore connector
+        // ----------------------------------------
+        for (ConnConfProperty prop : connInstanceBean.getConfiguration()) {
+            if ("jdbcUrlTemplate".equals(prop.getSchema().getName())) {
+                prop.getValues().clear();
+                prop.getValues().add(jdbcURL);
+            }
+        }
+        connectorService.update(connInstanceBean.getId(), connInstanceBean);
+        // ----------------------------------------
+
+        userTO = userService.read(userTO.getId());
+        assertNotNull(userTO);
+        // 4. check virtual attribute synchronization after direct update on resource
+        assertEquals("syncope458_NEW_TWO@syncope.apache.org", userTO.getMemberships().get(0).getVirAttrMap().get(
+                "mvirtualdata").getValues().get(0));
+
+        // 5. remove membership virtual attribute
+        membershipMod = new MembershipMod();
+        membershipMod.setRole(roleTO.getId());
+        membershipMod.getVirAttrsToRemove().add("mvirtualdata");
+
+        userMod = new UserMod();
+        userMod.setId(userTO.getId());
+        userMod.getMembershipsToAdd().add(membershipMod);
+        userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getId());
+
+        userTO = updateUser(userMod);
+        assertNotNull(userTO);
+        // check again after update if membership hasn't any virtual attribute
+        assertTrue(userTO.getMemberships().get(0).getVirAttrMap().isEmpty());
+
+        // -------------------------------------------
+        // Delete role ad-hoc and restore resource mapping
+        // -------------------------------------------
+        roleService.delete(roleTO.getId());
+
+        resourceUMapping.removeItem(item);
+        resourceDBVirAttr.setUmapping(resourceUMapping);
+        resourceService.update(RESOURCE_NAME_DBVIRATTR, resourceDBVirAttr);
+        // -------------------------------------------
+    }
 }