You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2015/09/18 10:48:57 UTC

[07/11] syncope git commit: [SYNCOPE-694] Implementation completed

http://git-wip-us.apache.org/repos/asf/syncope/blob/9f73fa43/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 689037b..30852f4 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
@@ -32,8 +32,9 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.mod.AnyMod;
-import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.patch.AttrPatch;
+import org.apache.syncope.common.lib.patch.StringPatchItem;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
@@ -71,6 +72,7 @@ import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
@@ -140,6 +142,9 @@ abstract class AbstractAnyDataBinder {
     protected PolicyDAO policyDAO;
 
     @Autowired
+    protected RelationshipTypeDAO relationshipTypeDAO;
+
+    @Autowired
     protected EntityFactory entityFactory;
 
     @Autowired
@@ -151,11 +156,11 @@ abstract class AbstractAnyDataBinder {
     @Autowired
     protected ConnObjectUtils connObjectUtils;
 
-    protected void setRealm(final Any<?, ?, ?> any, final AnyMod anyMod) {
-        if (StringUtils.isNotBlank(anyMod.getRealm())) {
-            Realm newRealm = realmDAO.find(anyMod.getRealm());
+    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) {
-                LOG.debug("Invalid realm specified: {}, ignoring", anyMod.getRealm());
+                LOG.debug("Invalid realm specified: {}, ignoring", anyPatch.getRealm().getValue());
             } else {
                 any.setRealm(newRealm);
             }
@@ -191,7 +196,7 @@ abstract class AbstractAnyDataBinder {
         return schema;
     }
 
-    private void fillAttribute(final List<String> values, final AnyUtils anyUtils,
+    private void fillAttr(final List<String> values, final AnyUtils anyUtils,
             final PlainSchema schema, final PlainAttr<?> attr, final SyncopeClientException invalidValues) {
 
         // if schema is multivalue, all values are considered for addition;
@@ -227,7 +232,7 @@ abstract class AbstractAnyDataBinder {
                         || item.getPurpose() == MappingPurpose.BOTH)) {
 
                     List<PlainAttrValue> values = MappingUtils.getIntValues(
-                            provision, item, Collections.<Any<?, ?, ?>>singletonList(any), null, null);
+                            provision, item, Collections.<Any<?, ?, ?>>singletonList(any), null);
                     if ((values == null || values.isEmpty())
                             && JexlUtils.evaluateMandatoryCondition(item.getMandatoryCondition(), any)) {
 
@@ -293,79 +298,66 @@ abstract class AbstractAnyDataBinder {
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
-    protected PropagationByResource fill(final Any any, final AnyMod anyMod, final AnyUtils anyUtils,
-            final SyncopeClientCompositeException scce) {
-
-        PropagationByResource propByRes = new PropagationByResource();
-
-        // 1. anyTypeClass to be removed
-        for (String className : anyMod.getAuxClassesToRemove()) {
-            AnyTypeClass auxClass = anyTypeClassDAO.find(className);
-            if (auxClass == null) {
-                LOG.debug("Invalid " + AnyTypeClass.class.getSimpleName() + "{}, ignoring...", auxClass);
-            } else {
-                any.remove(auxClass);
-            }
-        }
+    private void processAttrPatch(final Any any, final AttrPatch patch, final PlainSchema schema,
+            final AnyUtils anyUtils, final Set<ExternalResource> resources, final PropagationByResource propByRes,
+            final SyncopeClientException invalidValues) {
 
-        // 2. anyTypeClass to be added
-        for (String className : anyMod.getAuxClassesToAdd()) {
-            AnyTypeClass auxClass = anyTypeClassDAO.find(className);
-            if (auxClass == null) {
-                LOG.debug("Invalid " + AnyTypeClass.class.getSimpleName() + "{}, ignoring...", auxClass);
-            } else {
-                any.add(auxClass);
-            }
-        }
+        PlainAttr<?> attr = any.getPlainAttr(schema.getKey());
+        if (attr == null) {
+            LOG.debug("No plain attribute found for schema {}", schema);
 
-        SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
+            switch (patch.getOperation()) {
+                case ADD_REPLACE:
+                    attr = anyUtils.newPlainAttr();
+                    ((PlainAttr) attr).setOwner(any);
+                    attr.setSchema(schema);
+                    any.add(attr);
+                    break;
 
-        // 3. resources to be removed
-        for (String resourceToBeRemoved : anyMod.getResourcesToRemove()) {
-            ExternalResource resource = resourceDAO.find(resourceToBeRemoved);
-            if (resource != null) {
-                propByRes.add(ResourceOperation.DELETE, resource.getKey());
-                any.remove(resource);
+                case DELETE:
+                default:
+                    return;
             }
         }
 
-        LOG.debug("Resources to be removed:\n{}", propByRes);
+        switch (patch.getOperation()) {
+            case ADD_REPLACE:
+                virAttrHander.updateOnResourcesIfMappingMatches(
+                        any, schema.getKey(), resources, anyUtils.plainIntMappingType(), propByRes);
 
-        // 4. resources to be added
-        for (String resourceToBeAdded : anyMod.getResourcesToAdd()) {
-            ExternalResource resource = resourceDAO.find(resourceToBeAdded);
-            if (resource != null) {
-                propByRes.add(ResourceOperation.CREATE, resource.getKey());
-                any.add(resource);
-            }
-        }
-
-        LOG.debug("Resources to be added:\n{}", propByRes);
+                // 1.1 remove values
+                Collection<Long> valuesToBeRemoved = attr.getSchema().isUniqueConstraint()
+                        ? Collections.singleton(attr.getUniqueValue().getKey())
+                        : CollectionUtils.collect(attr.getValues(), new Transformer<PlainAttrValue, Long>() {
 
-        Set<ExternalResource> resources = getAllResources(any);
+                            @Override
+                            public Long transform(final PlainAttrValue input) {
+                                return input.getKey();
+                            }
+                        });
+                for (Long attrValueKey : valuesToBeRemoved) {
+                    plainAttrValueDAO.delete(attrValueKey, anyUtils.plainAttrValueClass());
+                }
 
-        // 5. attributes to be removed
-        for (String attrToRemove : anyMod.getPlainAttrsToRemove()) {
-            PlainSchema schema = getPlainSchema(attrToRemove);
-            if (schema != null) {
-                PlainAttr<?> attr = any.getPlainAttr(schema.getKey());
-                if (attr == null) {
-                    LOG.debug("No attribute found for schema {}", schema);
-                } else {
-                    String newValue = null;
-                    for (AttrMod mod : anyMod.getPlainAttrsToUpdate()) {
-                        if (schema.getKey().equals(mod.getSchema())) {
-                            newValue = mod.getValuesToBeAdded().get(0);
-                        }
-                    }
+                // 1.2 add values
+                List<String> valuesToBeAdded = patch.getAttrTO().getValues();
+                if (!valuesToBeAdded.isEmpty()
+                        && (!schema.isUniqueConstraint() || attr.getUniqueValue() == null
+                        || !valuesToBeAdded.iterator().next().equals(attr.getUniqueValue().getValueAsString()))) {
 
-                    if (!schema.isUniqueConstraint()
-                            || (!attr.getUniqueValue().getStringValue().equals(newValue))) {
+                    fillAttr(valuesToBeAdded, anyUtils, schema, attr, invalidValues);
+                }
 
-                        any.remove(attr);
-                        plainAttrDAO.delete(attr.getKey(), anyUtils.plainAttrClass());
-                    }
+                // if no values are in, the attribute can be safely removed
+                if (attr.getValuesAsStrings().isEmpty()) {
+                    plainAttrDAO.delete(attr);
                 }
+                break;
+
+            case DELETE:
+            default:
+                any.remove(attr);
+                plainAttrDAO.delete(attr.getKey(), anyUtils.plainAttrClass());
 
                 for (ExternalResource resource : resources) {
                     for (MappingItem mapItem : MappingUtils.getMappingItems(
@@ -376,130 +368,140 @@ abstract class AbstractAnyDataBinder {
 
                             propByRes.add(ResourceOperation.UPDATE, resource.getKey());
 
-                            if (mapItem.isConnObjectKey() && attr != null && !attr.getValuesAsStrings().isEmpty()) {
+                            if (mapItem.isConnObjectKey() && !attr.getValuesAsStrings().isEmpty()) {
                                 propByRes.addOldConnObjectKey(resource.getKey(), attr.getValuesAsStrings().get(0));
                             }
                         }
                     }
                 }
-            }
         }
+    }
 
-        LOG.debug("Attributes to be removed:\n{}", propByRes);
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private void processAttrPatch(final Any any, final AttrPatch patch, final DerSchema schema,
+            final AnyUtils anyUtils, final Set<ExternalResource> resources, final PropagationByResource propByRes) {
 
-        // 6. attributes to be updated
-        for (AttrMod attrMod : anyMod.getPlainAttrsToUpdate()) {
-            PlainSchema schema = getPlainSchema(attrMod.getSchema());
-            PlainAttr attr = null;
-            if (schema != null) {
-                attr = any.getPlainAttr(schema.getKey());
-                if (attr == null) {
-                    attr = anyUtils.newPlainAttr();
-                    attr.setOwner(any);
+        DerAttr<?> attr = any.getDerAttr(schema.getKey());
+        if (attr == null) {
+            LOG.debug("No plain attribute found for schema {}", schema);
+
+            switch (patch.getOperation()) {
+                case ADD_REPLACE:
+                    attr = anyUtils.newDerAttr();
+                    ((DerAttr) attr).setOwner(any);
                     attr.setSchema(schema);
                     any.add(attr);
-                }
-            }
-
-            if (schema != null && attr != null && attr.getSchema() != null) {
-                virAttrHander.updateOnResourcesIfMappingMatches(any, schema.getKey(),
-                        resources, anyUtils.plainIntMappingType(), propByRes);
+                    break;
 
-                // 1.1 remove values
-                Set<Long> valuesToBeRemoved = new HashSet<>();
-                for (String valueToBeRemoved : attrMod.getValuesToBeRemoved()) {
-                    if (attr.getSchema().isUniqueConstraint()) {
-                        if (attr.getUniqueValue() != null
-                                && valueToBeRemoved.equals(attr.getUniqueValue().getValueAsString())) {
-
-                            valuesToBeRemoved.add(attr.getUniqueValue().getKey());
-                        }
-                    } else {
-                        for (PlainAttrValue mav : ((PlainAttr<?>) attr).getValues()) {
-                            if (valueToBeRemoved.equals(mav.getValueAsString())) {
-                                valuesToBeRemoved.add(mav.getKey());
-                            }
-                        }
-                    }
-                }
-                for (Long attrValueKey : valuesToBeRemoved) {
-                    plainAttrValueDAO.delete(attrValueKey, anyUtils.plainAttrValueClass());
-                }
-
-                // 1.2 add values
-                List<String> valuesToBeAdded = attrMod.getValuesToBeAdded();
-                if (valuesToBeAdded != null && !valuesToBeAdded.isEmpty()
-                        && (!schema.isUniqueConstraint() || attr.getUniqueValue() == null
-                        || !valuesToBeAdded.iterator().next().equals(attr.getUniqueValue().getValueAsString()))) {
-
-                    fillAttribute(attrMod.getValuesToBeAdded(), anyUtils, schema, attr, invalidValues);
-                }
-
-                // if no values are in, the attribute can be safely removed
-                if (attr.getValuesAsStrings().isEmpty()) {
-                    plainAttrDAO.delete(attr);
-                }
+                case DELETE:
+                default:
+                    return;
             }
         }
 
-        if (!invalidValues.isEmpty()) {
-            scce.addException(invalidValues);
-        }
-
-        LOG.debug("Attributes to be updated:\n{}", propByRes);
+        switch (patch.getOperation()) {
+            case ADD_REPLACE:
+                virAttrHander.updateOnResourcesIfMappingMatches(
+                        any, schema.getKey(), resources, anyUtils.derIntMappingType(), propByRes);
+                break;
 
-        // 7. derived attributes to be removed
-        for (String derAttrToBeRemoved : anyMod.getDerAttrsToRemove()) {
-            DerSchema derSchema = getDerSchema(derAttrToBeRemoved);
-            if (derSchema != null) {
-                DerAttr derAttr = any.getDerAttr(derSchema.getKey());
-                if (derAttr == null) {
-                    LOG.debug("No derived attribute found for schema {}", derSchema.getKey());
-                } else {
-                    derAttrDAO.delete(derAttr);
-                }
+            case DELETE:
+            default:
+                derAttrDAO.delete(attr);
 
                 for (ExternalResource resource : resources) {
                     for (MappingItem mapItem : MappingUtils.getMappingItems(
                             resource.getProvision(any.getType()), MappingPurpose.PROPAGATION)) {
 
-                        if (derSchema.getKey().equals(mapItem.getIntAttrName())
+                        if (schema.getKey().equals(mapItem.getIntAttrName())
                                 && mapItem.getIntMappingType() == anyUtils.derIntMappingType()) {
 
                             propByRes.add(ResourceOperation.UPDATE, resource.getKey());
 
-                            if (mapItem.isConnObjectKey() && derAttr != null
-                                    && !derAttr.getValue(any.getPlainAttrs()).isEmpty()) {
-
-                                propByRes.addOldConnObjectKey(resource.getKey(),
-                                        derAttr.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,
+            final SyncopeClientCompositeException scce) {
+
+        PropagationByResource propByRes = new PropagationByResource();
+
+        // 1. anyTypeClasses
+        for (StringPatchItem patch : anyPatch.getAuxClasses()) {
+            AnyTypeClass auxClass = anyTypeClassDAO.find(patch.getValue());
+            if (auxClass == null) {
+                LOG.debug("Invalid " + AnyTypeClass.class.getSimpleName() + "{}, ignoring...", patch.getValue());
+            } else {
+                switch (patch.getOperation()) {
+                    case ADD_REPLACE:
+                        any.add(auxClass);
+                        break;
+
+                    case DELETE:
+                    default:
+                        any.remove(auxClass);
+                }
             }
         }
 
-        LOG.debug("Derived attributes to be removed:\n{}", propByRes);
+        // 2. resources
+        for (StringPatchItem patch : anyPatch.getResources()) {
+            ExternalResource resource = resourceDAO.find(patch.getValue());
+            if (resource == null) {
+                LOG.debug("Invalid " + ExternalResource.class.getSimpleName() + "{}, ignoring...", patch.getValue());
+            } else {
+                switch (patch.getOperation()) {
+                    case ADD_REPLACE:
+                        propByRes.add(ResourceOperation.CREATE, resource.getKey());
+                        any.add(resource);
+                        break;
+
+                    case DELETE:
+                    default:
+                        propByRes.add(ResourceOperation.DELETE, resource.getKey());
+                        any.remove(resource);
+                }
+            }
+        }
 
-        // 8. derived attributes to be added
-        for (String derAttrToBeAdded : anyMod.getDerAttrsToAdd()) {
-            DerSchema derSchema = getDerSchema(derAttrToBeAdded);
-            if (derSchema != null) {
-                virAttrHander.updateOnResourcesIfMappingMatches(any, derSchema.getKey(),
-                        resources, anyUtils.derIntMappingType(), propByRes);
-
-                DerAttr derAttr = any.getDerAttr(derSchema.getKey());
-                if (derAttr == null) {
-                    derAttr = anyUtils.newDerAttr();
-                    derAttr.setOwner(any);
-                    derAttr.setSchema(derSchema);
-                    any.add(derAttr);
+        Set<ExternalResource> resources = getAllResources(any);
+        SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
+
+        // 3. plain attributes
+        for (AttrPatch patch : anyPatch.getPlainAttrs()) {
+            if (patch.getAttrTO() != null) {
+                PlainSchema schema = getPlainSchema(patch.getAttrTO().getSchema());
+                if (schema == null) {
+                    LOG.debug("Invalid " + PlainSchema.class.getSimpleName()
+                            + "{}, ignoring...", patch.getAttrTO().getSchema());
+                } else {
+                    processAttrPatch(any, patch, schema, anyUtils, resources, propByRes, invalidValues);
                 }
             }
         }
+        if (!invalidValues.isEmpty()) {
+            scce.addException(invalidValues);
+        }
 
-        LOG.debug("Derived attributes to be added:\n{}", propByRes);
+        // 4. derived attributes
+        for (AttrPatch patch : anyPatch.getDerAttrs()) {
+            if (patch.getAttrTO() != null) {
+                DerSchema schema = getDerSchema(patch.getAttrTO().getSchema());
+                if (schema == null) {
+                    LOG.debug("Invalid " + DerSchema.class.getSimpleName()
+                            + "{}, ignoring...", patch.getAttrTO().getSchema());
+                } else {
+                    processAttrPatch(any, patch, schema, anyUtils, resources, propByRes);
+                }
+            }
+        }
 
         SyncopeClientException requiredValuesMissing = checkMandatory(any);
         if (!requiredValuesMissing.isEmpty()) {
@@ -519,8 +521,8 @@ abstract class AbstractAnyDataBinder {
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
-    protected void fill(final Any any, final AnyTO anyTO,
-            final AnyUtils anyUtils, final SyncopeClientCompositeException scce) {
+    protected void fill(
+            final Any any, final AnyTO anyTO, final AnyUtils anyUtils, final SyncopeClientCompositeException scce) {
 
         // 0. aux classes
         any.getAuxClasses().clear();
@@ -537,9 +539,9 @@ abstract class AbstractAnyDataBinder {
         SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
 
         // Only consider attributeTO with values
-        for (AttrTO attributeTO : anyTO.getPlainAttrs()) {
-            if (attributeTO.getValues() != null && !attributeTO.getValues().isEmpty()) {
-                PlainSchema schema = getPlainSchema(attributeTO.getSchema());
+        for (AttrTO attrTO : anyTO.getPlainAttrs()) {
+            if (attrTO.getValues() != null && !attrTO.getValues().isEmpty()) {
+                PlainSchema schema = getPlainSchema(attrTO.getSchema());
                 if (schema != null) {
                     PlainAttr attr = any.getPlainAttr(schema.getKey());
                     if (attr == null) {
@@ -547,7 +549,7 @@ abstract class AbstractAnyDataBinder {
                         attr.setOwner(any);
                         attr.setSchema(schema);
                     }
-                    fillAttribute(attributeTO.getValues(), anyUtils, schema, attr, invalidValues);
+                    fillAttr(attrTO.getValues(), anyUtils, schema, attr, invalidValues);
 
                     if (attr.getValuesAsStrings().isEmpty()) {
                         attr.setOwner(null);
@@ -638,32 +640,31 @@ abstract class AbstractAnyDataBinder {
             }
         }, anyTO.getAuxClasses());
 
-        AttrTO attributeTO;
         for (PlainAttr<?> attr : attrs) {
-            attributeTO = new AttrTO();
-            attributeTO.setSchema(attr.getSchema().getKey());
-            attributeTO.getValues().addAll(attr.getValuesAsStrings());
-            attributeTO.setReadonly(attr.getSchema().isReadonly());
+            AttrTO attrTO = new AttrTO();
+            attrTO.setSchema(attr.getSchema().getKey());
+            attrTO.getValues().addAll(attr.getValuesAsStrings());
+            attrTO.setReadonly(attr.getSchema().isReadonly());
 
-            anyTO.getPlainAttrs().add(attributeTO);
+            anyTO.getPlainAttrs().add(attrTO);
         }
 
         for (DerAttr<?> derAttr : derAttrs) {
-            attributeTO = new AttrTO();
-            attributeTO.setSchema(derAttr.getSchema().getKey());
-            attributeTO.getValues().add(derAttr.getValue(attrs));
-            attributeTO.setReadonly(true);
+            AttrTO attrTO = new AttrTO();
+            attrTO.setSchema(derAttr.getSchema().getKey());
+            attrTO.getValues().add(derAttr.getValue(attrs));
+            attrTO.setReadonly(true);
 
-            anyTO.getDerAttrs().add(attributeTO);
+            anyTO.getDerAttrs().add(attrTO);
         }
 
         for (VirAttr<?> virAttr : virAttrs) {
-            attributeTO = new AttrTO();
-            attributeTO.setSchema(virAttr.getSchema().getKey());
-            attributeTO.getValues().addAll(virAttr.getValues());
-            attributeTO.setReadonly(virAttr.getSchema().isReadonly());
+            AttrTO attrTO = new AttrTO();
+            attrTO.setSchema(virAttr.getSchema().getKey());
+            attrTO.getValues().addAll(virAttr.getValues());
+            attrTO.setReadonly(virAttr.getSchema().isReadonly());
 
-            anyTO.getVirAttrs().add(attributeTO);
+            anyTO.getVirAttrs().add(attrTO);
         }
 
         for (ExternalResource resource : resources) {
@@ -672,21 +673,17 @@ abstract class AbstractAnyDataBinder {
     }
 
     protected RelationshipTO getRelationshipTO(final Relationship<? extends Any<?, ?, ?>, AnyObject> relationship) {
-        RelationshipTO relationshipTO = new RelationshipTO();
-        relationshipTO.setLeftKey(relationship.getLeftEnd().getKey());
-        relationshipTO.setLeftType(relationship.getLeftEnd().getType().getKey());
-        relationshipTO.setRightKey(relationship.getRightEnd().getKey());
-        relationshipTO.setRightType(relationship.getRightEnd().getType().getKey());
-        return relationshipTO;
+        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) {
-        MembershipTO membershipTO = new MembershipTO();
-        membershipTO.setLeftKey(membership.getLeftEnd().getKey());
-        membershipTO.setLeftType(membership.getLeftEnd().getType().getKey());
-        membershipTO.setRightKey(membership.getRightEnd().getKey());
-        membershipTO.setGroupName(membership.getRightEnd().getName());
-        return membershipTO;
+        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) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/9f73fa43/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 433b909..91edd7e 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
@@ -26,17 +26,21 @@ import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.Transformer;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.mod.AnyObjectMod;
+import org.apache.syncope.common.lib.patch.AnyObjectPatch;
+import org.apache.syncope.common.lib.patch.MembershipPatch;
+import org.apache.syncope.common.lib.patch.RelationshipPatch;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.RelationshipTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.PropagationByResource;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 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.anyobject.AMembership;
 import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
@@ -131,13 +135,15 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
             if (otherEnd == null) {
                 LOG.debug("Ignoring invalid anyObject " + relationshipTO.getRightKey());
             } else {
+                RelationshipType relationshipType = relationshipTypeDAO.find(relationshipTO.getType());
                 ARelationship relationship = null;
                 if (anyObject.getKey() != null) {
-                    relationship = anyObject.getRelationship(otherEnd.getKey());
+                    relationship = anyObject.getRelationship(relationshipType, anyObject.getKey());
                 }
                 if (relationship == null) {
                     relationship = entityFactory.newEntity(ARelationship.class);
-                    relationship.setRightEnd(otherEnd);
+                    relationship.setType(relationshipType);
+                    relationship.setRightEnd(anyObject);
                     relationship.setLeftEnd(anyObject);
 
                     anyObject.add(relationship);
@@ -171,7 +177,7 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
     }
 
     @Override
-    public PropagationByResource update(final AnyObject toBeUpdated, final AnyObjectMod anyObjectMod) {
+    public PropagationByResource update(final AnyObject toBeUpdated, final AnyObjectPatch anyObjectPatch) {
         // Re-merge any pending change from workflow tasks
         final AnyObject anyObject = anyObjectDAO.save(toBeUpdated);
 
@@ -185,79 +191,63 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
         Map<String, String> oldConnObjectKeys = getConnObjectKeys(anyObject);
 
         // attributes, derived attributes, virtual attributes and resources
-        propByRes.merge(fill(anyObject, anyObjectMod, anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT), scce));
+        propByRes.merge(fill(anyObject, anyObjectPatch, anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT), scce));
 
         Set<String> toBeDeprovisioned = new HashSet<>();
         Set<String> toBeProvisioned = new HashSet<>();
 
-        // relationships to be removed
-        for (Long anyObjectKey : anyObjectMod.getRelationshipsToRemove()) {
-            LOG.debug("Relationship to be removed for any object {}", anyObjectKey);
-
-            ARelationship relationship = anyObject.getRelationship(anyObjectKey);
-            if (relationship == null) {
-                LOG.warn("Invalid anyObject key specified for relationship to be removed: {}", anyObjectKey);
-            } else {
-                if (!anyObjectMod.getRelationshipsToAdd().contains(anyObjectKey)) {
+        // relationships
+        for (RelationshipPatch patch : anyObjectPatch.getRelationships()) {
+            if (patch.getRelationshipTO() != null) {
+                RelationshipType relationshipType = relationshipTypeDAO.find(patch.getRelationshipTO().getType());
+                ARelationship relationship =
+                        anyObject.getRelationship(relationshipType, patch.getRelationshipTO().getRightKey());
+                if (relationship != null) {
                     anyObject.remove(relationship);
                     toBeDeprovisioned.addAll(relationship.getRightEnd().getResourceNames());
                 }
-            }
-        }
 
-        // relationships to be added
-        for (Long anyObjectKey : anyObjectMod.getRelationshipsToAdd()) {
-            LOG.debug("Relationship to be added for any object {}", anyObjectKey);
+                if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
+                    AnyObject otherEnd = anyObjectDAO.find(patch.getRelationshipTO().getRightKey());
+                    if (otherEnd == null) {
+                        LOG.debug("Ignoring invalid any object {}", patch.getRelationshipTO().getRightKey());
+                    } else {
 
-            AnyObject otherEnd = anyObjectDAO.find(anyObjectKey);
-            if (otherEnd == null) {
-                LOG.debug("Ignoring invalid any object {}", anyObjectKey);
-            } else {
-                ARelationship relationship = anyObject.getRelationship(otherEnd.getKey());
-                if (relationship == null) {
-                    relationship = entityFactory.newEntity(ARelationship.class);
-                    relationship.setRightEnd(otherEnd);
-                    relationship.setLeftEnd(anyObject);
+                        relationship = entityFactory.newEntity(ARelationship.class);
+                        relationship.setType(relationshipType);
+                        relationship.setRightEnd(otherEnd);
+                        relationship.setLeftEnd(anyObject);
 
-                    anyObject.add(relationship);
+                        anyObject.add(relationship);
 
-                    toBeProvisioned.addAll(otherEnd.getResourceNames());
+                        toBeProvisioned.addAll(otherEnd.getResourceNames());
+                    }
                 }
             }
         }
 
-        // memberships to be removed
-        for (Long groupKey : anyObjectMod.getMembershipsToRemove()) {
-            LOG.debug("Membership to be removed for group {}", groupKey);
-
-            AMembership membership = anyObject.getMembership(groupKey);
-            if (membership == null) {
-                LOG.warn("Invalid group key specified for membership to be removed: {}", groupKey);
-            } else {
-                if (!anyObjectMod.getMembershipsToAdd().contains(groupKey)) {
+        // memberships
+        for (MembershipPatch patch : anyObjectPatch.getMemberships()) {
+            if (patch.getMembershipTO() != null) {
+                AMembership membership = anyObject.getMembership(patch.getMembershipTO().getRightKey());
+                if (membership != null) {
                     anyObject.remove(membership);
                     toBeDeprovisioned.addAll(membership.getRightEnd().getResourceNames());
                 }
-            }
-        }
-
-        // memberships to be added
-        for (Long groupKey : anyObjectMod.getMembershipsToAdd()) {
-            LOG.debug("Membership to be added for group {}", groupKey);
 
-            Group group = groupDAO.find(groupKey);
-            if (group == null) {
-                LOG.debug("Ignoring invalid group {}", groupKey);
-            } else {
-                AMembership membership = anyObject.getMembership(group.getKey());
-                if (membership == null) {
-                    membership = entityFactory.newEntity(AMembership.class);
-                    membership.setRightEnd(group);
-                    membership.setLeftEnd(anyObject);
+                if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
+                    Group group = groupDAO.find(patch.getMembershipTO().getRightKey());
+                    if (group == null) {
+                        LOG.debug("Ignoring invalid group {}", patch.getMembershipTO().getRightKey());
+                    } else {
+                        membership = entityFactory.newEntity(AMembership.class);
+                        membership.setRightEnd(group);
+                        membership.setLeftEnd(anyObject);
 
-                    anyObject.add(membership);
+                        anyObject.add(membership);
 
-                    toBeProvisioned.addAll(group.getResourceNames());
+                        toBeProvisioned.addAll(group.getResourceNames());
+                    }
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/9f73fa43/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 909b05d..155034c 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
@@ -19,9 +19,10 @@
 package org.apache.syncope.core.provisioning.java.data;
 
 import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.mod.GroupMod;
+import org.apache.syncope.common.lib.patch.GroupPatch;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
@@ -113,7 +114,7 @@ public class GroupDataBinderImpl extends AbstractAnyDataBinder implements GroupD
     }
 
     @Override
-    public PropagationByResource update(final Group toBeUpdated, final GroupMod groupMod) {
+    public PropagationByResource update(final Group toBeUpdated, final GroupPatch groupPatch) {
         // Re-merge any pending change from workflow tasks
         Group group = groupDAO.save(toBeUpdated);
 
@@ -125,28 +126,29 @@ public class GroupDataBinderImpl extends AbstractAnyDataBinder implements GroupD
         Map<String, String> oldConnObjectKeys = getConnObjectKeys(group);
 
         // realm
-        setRealm(group, groupMod);
+        setRealm(group, groupPatch);
+
         // name
-        if (groupMod.getName() != null && !groupMod.getName().equals(group.getName())) {
+        if (groupPatch.getName() != null && StringUtils.isNotBlank(groupPatch.getName().getValue())) {
             propByRes.addAll(ResourceOperation.UPDATE, group.getResourceNames());
 
-            group.setName(groupMod.getName());
+            group.setName(groupPatch.getName().getValue());
         }
 
         // owner
-        if (groupMod.getUserOwner() != null) {
-            group.setUserOwner(groupMod.getUserOwner().getKey() == null
+        if (groupPatch.getUserOwner() != null) {
+            group.setUserOwner(groupPatch.getUserOwner().getValue() == null
                     ? null
-                    : userDAO.find(groupMod.getUserOwner().getKey()));
+                    : userDAO.find(groupPatch.getUserOwner().getValue()));
         }
-        if (groupMod.getGroupOwner() != null) {
-            group.setGroupOwner(groupMod.getGroupOwner().getKey() == null
+        if (groupPatch.getGroupOwner() != null) {
+            group.setGroupOwner(groupPatch.getGroupOwner().getValue() == null
                     ? null
-                    : groupDAO.find(groupMod.getGroupOwner().getKey()));
+                    : groupDAO.find(groupPatch.getGroupOwner().getValue()));
         }
 
         // attributes, derived attributes, virtual attributes and resources
-        propByRes.merge(fill(group, groupMod, anyUtilsFactory.getInstance(AnyTypeKind.GROUP), scce));
+        propByRes.merge(fill(group, groupPatch, anyUtilsFactory.getInstance(AnyTypeKind.GROUP), scce));
 
         // check if some connObjectKey was changed by the update above
         Map<String, String> newConnObjectKeys = getConnObjectKeys(group);
@@ -160,31 +162,26 @@ public class GroupDataBinderImpl extends AbstractAnyDataBinder implements GroupD
         }
 
         // dynamic membership
-        if (group.getADynMembership() != null && groupMod.getADynMembershipCond() == null) {
-            group.setADynMembership(null);
-        } else if (group.getADynMembership() == null && groupMod.getADynMembershipCond() != null) {
-            setDynMembership(group, AnyTypeKind.ANY_OBJECT, groupMod.getADynMembershipCond());
-        } else if (group.getADynMembership() != null && groupMod.getADynMembershipCond() != null
-                && !group.getADynMembership().getFIQLCond().equals(groupMod.getADynMembershipCond())) {
-
-            group.getADynMembership().getMembers().clear();
-            setDynMembership(group, AnyTypeKind.ANY_OBJECT, groupMod.getADynMembershipCond());
+        if (groupPatch.getADynMembershipCond() != null) {
+            if (groupPatch.getADynMembershipCond().getValue() == null) {
+                group.setADynMembership(null);
+            } else {
+                group.getADynMembership().getMembers().clear();
+                setDynMembership(group, AnyTypeKind.ANY_OBJECT, groupPatch.getADynMembershipCond().getValue());
+            }
         }
-        if (group.getUDynMembership() != null && groupMod.getUDynMembershipCond() == null) {
-            group.setUDynMembership(null);
-        } else if (group.getUDynMembership() == null && groupMod.getUDynMembershipCond() != null) {
-            setDynMembership(group, AnyTypeKind.USER, groupMod.getUDynMembershipCond());
-        } else if (group.getUDynMembership() != null && groupMod.getUDynMembershipCond() != null
-                && !group.getUDynMembership().getFIQLCond().equals(groupMod.getUDynMembershipCond())) {
-
-            group.getUDynMembership().getMembers().clear();
-            setDynMembership(group, AnyTypeKind.USER, groupMod.getUDynMembershipCond());
+        if (groupPatch.getUDynMembershipCond() != null) {
+            if (groupPatch.getUDynMembershipCond().getValue() == null) {
+                group.setUDynMembership(null);
+            } else {
+                group.getUDynMembership().getMembers().clear();
+                setDynMembership(group, AnyTypeKind.USER, groupPatch.getUDynMembershipCond().getValue());
+            }
         }
 
         return propByRes;
     }
 
-    @SuppressWarnings("unchecked")
     @Transactional(readOnly = true)
     @Override
     public GroupTO getGroupTO(final Group group, final boolean details) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/9f73fa43/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 ca801f0..82dfbc0 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
@@ -24,21 +24,24 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.Resource;
-import org.apache.commons.collections4.Closure;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.Predicate;
 import org.apache.commons.collections4.Transformer;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.mod.StatusMod;
-import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.patch.LongPatchItem;
+import org.apache.syncope.common.lib.patch.MembershipPatch;
+import org.apache.syncope.common.lib.patch.PasswordPatch;
+import org.apache.syncope.common.lib.patch.RelationshipPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.RelationshipTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.SecurityQuestionDAO;
@@ -52,6 +55,7 @@ import org.apache.syncope.core.misc.security.Encryptor;
 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.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.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
@@ -159,16 +163,17 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
         // relationships
         for (RelationshipTO relationshipTO : userTO.getRelationships()) {
             AnyObject anyObject = anyObjectDAO.find(relationshipTO.getRightKey());
-
             if (anyObject == null) {
                 LOG.debug("Ignoring invalid anyObject " + relationshipTO.getRightKey());
             } else {
+                RelationshipType relationshipType = relationshipTypeDAO.find(relationshipTO.getType());
                 URelationship relationship = null;
                 if (user.getKey() != null) {
-                    relationship = user.getRelationship(anyObject.getKey());
+                    relationship = user.getRelationship(relationshipType, anyObject.getKey());
                 }
                 if (relationship == null) {
                     relationship = entityFactory.newEntity(URelationship.class);
+                    relationship.setType(relationshipType);
                     relationship.setRightEnd(anyObject);
                     relationship.setLeftEnd(user);
 
@@ -241,7 +246,7 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
     }
 
     @Override
-    public PropagationByResource update(final User toBeUpdated, final UserMod userMod) {
+    public PropagationByResource update(final User toBeUpdated, final UserPatch userPatch) {
         // Re-merge any pending change from workflow tasks
         final User user = userDAO.save(toBeUpdated);
 
@@ -255,155 +260,132 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
         Map<String, String> oldConnObjectKeys = getConnObjectKeys(user);
 
         // realm
-        setRealm(user, userMod);
+        setRealm(user, userPatch);
 
         // password
-        if (StringUtils.isNotBlank(userMod.getPassword())) {
-            setPassword(user, userMod.getPassword(), scce);
+        if (userPatch.getPassword() != null && StringUtils.isNotBlank(userPatch.getPassword().getValue())) {
+            setPassword(user, userPatch.getPassword().getValue(), scce);
             user.setChangePwdDate(new Date());
             propByRes.addAll(ResourceOperation.UPDATE, currentResources);
         }
 
         // username
-        if (userMod.getUsername() != null && !userMod.getUsername().equals(user.getUsername())) {
+        if (userPatch.getUsername() != null && StringUtils.isNotBlank(userPatch.getUsername().getValue())) {
             propByRes.addAll(ResourceOperation.UPDATE, currentResources);
 
             String oldUsername = user.getUsername();
-            user.setUsername(userMod.getUsername());
+            user.setUsername(userPatch.getUsername().getValue());
 
             if (oldUsername.equals(AuthContextUtils.getUsername())) {
-                AuthContextUtils.updateUsername(userMod.getUsername());
+                AuthContextUtils.updateUsername(userPatch.getUsername().getValue());
             }
         }
 
         // security question / answer:
-        // userMod.getSecurityQuestion() is null => remove user security question and answer
-        // userMod.getSecurityQuestion() == 0 => don't change anything
-        // userMod.getSecurityQuestion() > 0 => update user security question and answer
-        if (userMod.getSecurityQuestion() == null) {
-            user.setSecurityQuestion(null);
-            user.setSecurityAnswer(null);
-        } else if (userMod.getSecurityQuestion() > 0) {
-            SecurityQuestion securityQuestion = securityQuestionDAO.find(userMod.getSecurityQuestion());
-            if (securityQuestion != null) {
-                user.setSecurityQuestion(securityQuestion);
-                user.setSecurityAnswer(userMod.getSecurityAnswer());
+        if (userPatch.getSecurityQuestion() != null) {
+            if (userPatch.getSecurityQuestion().getValue() == null) {
+                user.setSecurityQuestion(null);
+                user.setSecurityAnswer(null);
+            } else {
+                SecurityQuestion securityQuestion =
+                        securityQuestionDAO.find(userPatch.getSecurityQuestion().getValue());
+                if (securityQuestion != null) {
+                    user.setSecurityQuestion(securityQuestion);
+                    user.setSecurityAnswer(userPatch.getSecurityAnswer().getValue());
+                }
             }
         }
 
-        user.setMustChangePassword(userMod.isMustChangePassword());
+        if (userPatch.getMustChangePassword() != null) {
+            user.setMustChangePassword(userPatch.getMustChangePassword().getValue());
+        }
 
         // roles
-        CollectionUtils.forAllDo(userMod.getRolesToRemove(), new Closure<Long>() {
-
-            @Override
-            public void execute(final Long roleKey) {
-                Role role = roleDAO.find(roleKey);
-                if (role == null) {
-                    LOG.warn("Ignoring unknown role with id {}", roleKey);
-                } else {
-                    user.remove(role);
-                }
-            }
-        });
-        CollectionUtils.forAllDo(userMod.getRolesToAdd(), new Closure<Long>() {
-
-            @Override
-            public void execute(final Long roleKey) {
-                Role role = roleDAO.find(roleKey);
-                if (role == null) {
-                    LOG.warn("Ignoring unknown role with id {}", roleKey);
-                } else {
-                    user.add(role);
+        for (LongPatchItem patch : userPatch.getRoles()) {
+            Role role = roleDAO.find(patch.getValue());
+            if (role == null) {
+                LOG.warn("Ignoring unknown role with key {}", patch.getValue());
+            } else {
+                switch (patch.getOperation()) {
+                    case ADD_REPLACE:
+                        user.add(role);
+                        break;
+
+                    case DELETE:
+                    default:
+                        user.remove(role);
                 }
             }
-        });
+        }
 
         // attributes, derived attributes, virtual attributes and resources
-        propByRes.merge(fill(user, userMod, anyUtilsFactory.getInstance(AnyTypeKind.USER), scce));
+        propByRes.merge(fill(user, userPatch, anyUtilsFactory.getInstance(AnyTypeKind.USER), scce));
 
         Set<String> toBeDeprovisioned = new HashSet<>();
         Set<String> toBeProvisioned = new HashSet<>();
 
-        // relationships to be removed
-        for (Long anyObjectKey : userMod.getRelationshipsToRemove()) {
-            LOG.debug("Relationship to be removed for any object {}", anyObjectKey);
-
-            URelationship relationship = user.getRelationship(anyObjectKey);
-            if (relationship == null) {
-                LOG.warn("Invalid anyObject key specified for relationship to be removed: {}", anyObjectKey);
-            } else {
-                if (!userMod.getRelationshipsToAdd().contains(anyObjectKey)) {
+        // relationships
+        for (RelationshipPatch patch : userPatch.getRelationships()) {
+            if (patch.getRelationshipTO() != null) {
+                RelationshipType relationshipType = relationshipTypeDAO.find(patch.getRelationshipTO().getType());
+                URelationship relationship =
+                        user.getRelationship(relationshipType, patch.getRelationshipTO().getRightKey());
+                if (relationship != null) {
                     user.remove(relationship);
                     toBeDeprovisioned.addAll(relationship.getRightEnd().getResourceNames());
                 }
-            }
-        }
 
-        // relationships to be added
-        for (Long anyObjectKey : userMod.getRelationshipsToAdd()) {
-            LOG.debug("Relationship to be added for any object {}", anyObjectKey);
+                if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
+                    AnyObject otherEnd = anyObjectDAO.find(patch.getRelationshipTO().getRightKey());
+                    if (otherEnd == null) {
+                        LOG.debug("Ignoring invalid any object {}", patch.getRelationshipTO().getRightKey());
+                    } else {
 
-            AnyObject otherEnd = anyObjectDAO.find(anyObjectKey);
-            if (otherEnd == null) {
-                LOG.debug("Ignoring invalid any object {}", anyObjectKey);
-            } else {
-                URelationship relationship = user.getRelationship(otherEnd.getKey());
-                if (relationship == null) {
-                    relationship = entityFactory.newEntity(URelationship.class);
-                    relationship.setRightEnd(otherEnd);
-                    relationship.setLeftEnd(user);
+                        relationship = entityFactory.newEntity(URelationship.class);
+                        relationship.setType(relationshipType);
+                        relationship.setRightEnd(otherEnd);
+                        relationship.setLeftEnd(user);
 
-                    user.add(relationship);
+                        user.add(relationship);
 
-                    toBeProvisioned.addAll(otherEnd.getResourceNames());
+                        toBeProvisioned.addAll(otherEnd.getResourceNames());
+                    }
                 }
             }
         }
 
-        // memberships to be removed
-        for (Long groupKey : userMod.getMembershipsToRemove()) {
-            LOG.debug("Membership to be removed for group {}", groupKey);
-
-            UMembership membership = user.getMembership(groupKey);
-            if (membership == null) {
-                LOG.debug("Invalid group key specified for membership to be removed: {}", groupKey);
-            } else {
-                if (!userMod.getMembershipsToAdd().contains(groupKey)) {
+        // memberships
+        for (MembershipPatch patch : userPatch.getMemberships()) {
+            if (patch.getMembershipTO() != null) {
+                UMembership membership = user.getMembership(patch.getMembershipTO().getRightKey());
+                if (membership != null) {
                     user.remove(membership);
                     toBeDeprovisioned.addAll(membership.getRightEnd().getResourceNames());
                 }
-            }
-        }
-
-        // memberships to be added
-        for (Long groupKey : userMod.getMembershipsToAdd()) {
-            LOG.debug("Membership to be added for group {}", groupKey);
 
-            Group group = groupDAO.find(groupKey);
-            if (group == null) {
-                LOG.debug("Ignoring invalid group {}", groupKey);
-            } else {
-                UMembership membership = user.getMembership(group.getKey());
-                if (membership == null) {
-                    membership = entityFactory.newEntity(UMembership.class);
-                    membership.setRightEnd(group);
-                    membership.setLeftEnd(user);
+                if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
+                    Group group = groupDAO.find(patch.getMembershipTO().getRightKey());
+                    if (group == null) {
+                        LOG.debug("Ignoring invalid group {}", patch.getMembershipTO().getRightKey());
+                    } else {
+                        membership = entityFactory.newEntity(UMembership.class);
+                        membership.setRightEnd(group);
+                        membership.setLeftEnd(user);
 
-                    user.add(membership);
+                        user.add(membership);
 
-                    toBeProvisioned.addAll(group.getResourceNames());
+                        toBeProvisioned.addAll(group.getResourceNames());
 
-                    // SYNCOPE-686: if password is invertible and we are adding resources with password mapping,
-                    // ensure that they are counted for password propagation
-                    if (toBeUpdated.canDecodePassword()) {
-                        for (ExternalResource resource : group.getResources()) {
-                            if (isPasswordMapped(resource)) {
-                                if (userMod.getPwdPropRequest() == null) {
-                                    userMod.setPwdPropRequest(new StatusMod());
+                        // SYNCOPE-686: if password is invertible and we are adding resources with password mapping,
+                        // ensure that they are counted for password propagation
+                        if (toBeUpdated.canDecodePassword()) {
+                            if (userPatch.getPassword() == null) {
+                                userPatch.setPassword(new PasswordPatch());
+                            }
+                            for (ExternalResource resource : group.getResources()) {
+                                if (isPasswordMapped(resource)) {
+                                    userPatch.getPassword().getResources().add(resource.getKey());
                                 }
-
-                                userMod.getPwdPropRequest().getResources().add(resource.getKey());
                             }
                         }
                     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/9f73fa43/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
index 631cbe3..cd2db88 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
@@ -26,9 +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.Transformer;
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.syncope.common.lib.mod.AttrMod;
-import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.patch.AttrPatch;
+import org.apache.syncope.common.lib.patch.StringPatchItem;
+import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.types.MappingPurpose;
 import org.apache.syncope.common.lib.types.PropagationByResource;
@@ -180,18 +183,18 @@ public class PropagationManagerImpl implements PropagationManager {
             propByRes.get(ResourceOperation.CREATE).removeAll(noPropResourceNames);
         }
 
-        return createTasks(any, password, true, null, null, enable, false, propByRes);
+        return createTasks(any, password, true, null, enable, false, propByRes);
     }
 
     @Override
     public List<PropagationTask> getAnyObjectUpdateTasks(
             final WorkflowResult<Long> wfResult,
-            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated,
+            final Set<AttrPatch> vAttrs,
             final Set<String> noPropResourceNames) {
 
         AnyObject anyObject = anyObjectDAO.authFind(wfResult.getResult());
         return getUpdateTasks(anyObject, null, false, null,
-                vAttrsToBeRemoved, vAttrsToBeUpdated, wfResult.getPropByRes(), noPropResourceNames);
+                vAttrs, wfResult.getPropByRes(), noPropResourceNames);
     }
 
     @Override
@@ -203,34 +206,34 @@ public class PropagationManagerImpl implements PropagationManager {
                 null, // no password
                 false,
                 enable, // status to be propagated
-                Collections.<String>emptySet(), // no virtual attributes to be managed
-                Collections.<AttrMod>emptySet(), // no virtual attributes to be managed
+                Collections.<AttrPatch>emptySet(), // no virtual attributes to be managed
                 null, // no propagation by resources
                 noPropResourceNames);
     }
 
     @Override
-    public List<PropagationTask> getUserUpdateTasks(final WorkflowResult<Pair<UserMod, Boolean>> wfResult,
+    public List<PropagationTask> getUserUpdateTasks(final WorkflowResult<Pair<UserPatch, Boolean>> wfResult,
             final boolean changePwd, final Collection<String> noPropResourceNames) {
 
         User user = userDAO.authFind(wfResult.getResult().getKey().getKey());
         return getUpdateTasks(user,
-                wfResult.getResult().getKey().getPassword(),
+                wfResult.getResult().getKey().getPassword() == null
+                        ? null
+                        : wfResult.getResult().getKey().getPassword().getValue(),
                 changePwd,
                 wfResult.getResult().getValue(),
-                wfResult.getResult().getKey().getVirAttrsToRemove(),
-                wfResult.getResult().getKey().getVirAttrsToUpdate(),
+                wfResult.getResult().getKey().getVirAttrs(),
                 wfResult.getPropByRes(),
                 noPropResourceNames);
     }
 
     @Override
-    public List<PropagationTask> getUserUpdateTasks(final WorkflowResult<Pair<UserMod, Boolean>> wfResult) {
-        UserMod userMod = wfResult.getResult().getKey();
+    public List<PropagationTask> getUserUpdateTasks(final WorkflowResult<Pair<UserPatch, Boolean>> wfResult) {
+        UserPatch userPatch = wfResult.getResult().getKey();
 
         // Propagate password update only to requested resources
         List<PropagationTask> tasks = new ArrayList<>();
-        if (userMod.getPwdPropRequest() == null) {
+        if (userPatch.getPassword() == null) {
             // a. no specific password propagation request: generate propagation tasks for any resource associated
             tasks = getUserUpdateTasks(wfResult, false, null);
         } else {
@@ -239,14 +242,22 @@ public class PropagationManagerImpl implements PropagationManager {
             PropagationByResource origPropByRes = new PropagationByResource();
             origPropByRes.merge(wfResult.getPropByRes());
 
-            Set<String> pwdResourceNames = new HashSet<>(userMod.getPwdPropRequest().getResources());
-            Collection<String> currentResourceNames = userDAO.findAllResourceNames(userDAO.authFind(userMod.getKey()));
+            Set<String> pwdResourceNames = new HashSet<>(userPatch.getPassword().getResources());
+            Collection<String> currentResourceNames =
+                    userDAO.findAllResourceNames(userDAO.authFind(userPatch.getKey()));
             pwdResourceNames.retainAll(currentResourceNames);
             PropagationByResource pwdPropByRes = new PropagationByResource();
             pwdPropByRes.addAll(ResourceOperation.UPDATE, pwdResourceNames);
             if (!pwdPropByRes.isEmpty()) {
                 Set<String> toBeExcluded = new HashSet<>(currentResourceNames);
-                toBeExcluded.addAll(userMod.getResourcesToAdd());
+                toBeExcluded.addAll(CollectionUtils.collect(userPatch.getResources(),
+                        new Transformer<StringPatchItem, String>() {
+
+                            @Override
+                            public String transform(final StringPatchItem input) {
+                                return input.getValue();
+                            }
+                        }));
                 toBeExcluded.removeAll(pwdResourceNames);
                 tasks.addAll(getUserUpdateTasks(wfResult, true, toBeExcluded));
             }
@@ -265,27 +276,22 @@ public class PropagationManagerImpl implements PropagationManager {
 
     @Override
     public List<PropagationTask> getGroupUpdateTasks(final WorkflowResult<Long> wfResult,
-            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated,
-            final Set<String> noPropResourceNames) {
+            final Set<AttrPatch> vAttrs, final Set<String> noPropResourceNames) {
 
         Group group = groupDAO.authFind(wfResult.getResult());
         return getUpdateTasks(group, null, false, null,
-                vAttrsToBeRemoved, vAttrsToBeUpdated, wfResult.getPropByRes(), noPropResourceNames);
+                vAttrs, wfResult.getPropByRes(), noPropResourceNames);
     }
 
     @Override
     public List<PropagationTask> getUpdateTasks(final Any<?, ?, ?> any,
-            final String password, final boolean changePwd, final Boolean enable,
-            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated,
+            final String password, final boolean changePwd, final Boolean enable, final Set<AttrPatch> vAttrs,
             final PropagationByResource propByRes, final Collection<String> noPropResourceNames) {
 
         PropagationByResource localPropByRes = virAttrHandler.fillVirtual(
-                any,
-                vAttrsToBeRemoved == null
-                        ? Collections.<String>emptySet()
-                        : vAttrsToBeRemoved, vAttrsToBeUpdated == null
-                        ? Collections.<AttrMod>emptySet()
-                        : vAttrsToBeUpdated);
+                any, vAttrs == null
+                        ? Collections.<AttrPatch>emptySet()
+                        : vAttrs);
 
         if (propByRes == null || propByRes.isEmpty()) {
             localPropByRes.addAll(ResourceOperation.UPDATE, any.getResourceNames());
@@ -297,16 +303,15 @@ public class PropagationManagerImpl implements PropagationManager {
             localPropByRes.removeAll(noPropResourceNames);
         }
 
-        Map<String, AttrMod> vAttrsToBeUpdatedMap = null;
-        if (vAttrsToBeUpdated != null) {
-            vAttrsToBeUpdatedMap = new HashMap<>();
-            for (AttrMod attrMod : vAttrsToBeUpdated) {
-                vAttrsToBeUpdatedMap.put(attrMod.getSchema(), attrMod);
+        Map<String, AttrPatch> vAttrsMap = null;
+        if (vAttrs != null) {
+            vAttrsMap = new HashMap<>();
+            for (AttrPatch attrPatch : vAttrs) {
+                vAttrsMap.put(attrPatch.getAttrTO().getSchema(), attrPatch);
             }
         }
 
-        return createTasks(
-                any, password, changePwd, vAttrsToBeRemoved, vAttrsToBeUpdatedMap, enable, false, localPropByRes);
+        return createTasks(any, password, changePwd, vAttrsMap, enable, false, localPropByRes);
     }
 
     @Override
@@ -385,7 +390,7 @@ public class PropagationManagerImpl implements PropagationManager {
         if (noPropResourceNames != null && !noPropResourceNames.isEmpty()) {
             propByRes.get(ResourceOperation.DELETE).removeAll(noPropResourceNames);
         }
-        return createTasks(any, null, false, null, null, false, true, propByRes);
+        return createTasks(any, null, false, null, false, true, propByRes);
     }
 
     /**
@@ -394,8 +399,7 @@ public class PropagationManagerImpl implements PropagationManager {
      * @param any user / group to be provisioned
      * @param password cleartext password to be provisioned
      * @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 vAttrs virtual attributes to be maaged
      * @param enable whether user must be enabled or not
      * @param deleteOnResource whether user / group must be deleted anyway from external resource or not
      * @param propByRes operation to be performed per resource
@@ -403,25 +407,21 @@ public class PropagationManagerImpl implements PropagationManager {
      */
     protected List<PropagationTask> createTasks(final Any<?, ?, ?> any,
             final String password, final boolean changePwd,
-            final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated,
+            final Map<String, AttrPatch> vAttrs,
             final Boolean enable, final boolean deleteOnResource, final PropagationByResource propByRes) {
 
         LOG.debug("Provisioning any {}:\n{}", any, propByRes);
 
-        if (!propByRes.get(ResourceOperation.CREATE).isEmpty()
-                && vAttrsToBeRemoved != null && vAttrsToBeUpdated != null) {
-
+        if (!propByRes.get(ResourceOperation.CREATE).isEmpty() && vAttrs != null) {
             virAttrHandler.retrieveVirAttrValues(any);
 
             // update vAttrsToBeUpdated as well
             for (VirAttr<?> virAttr : any.getVirAttrs()) {
                 String schema = virAttr.getSchema().getKey();
 
-                AttrMod attributeMod = new AttrMod();
-                attributeMod.setSchema(schema);
-                attributeMod.getValuesToBeAdded().addAll(virAttr.getValues());
-
-                vAttrsToBeUpdated.put(schema, attributeMod);
+                vAttrs.put(schema, new AttrPatch.Builder().
+                        attrTO(new AttrTO.Builder().schema(schema).values(virAttr.getValues()).build()).
+                        build());
             }
         }
 
@@ -457,7 +457,7 @@ public class PropagationManagerImpl implements PropagationManager {
                     task.setOldConnObjectKey(propByRes.getOldConnObjectKey(resource.getKey()));
 
                     Pair<String, Set<Attribute>> preparedAttrs = MappingUtils.prepareAttrs(
-                            any, password, changePwd, vAttrsToBeRemoved, vAttrsToBeUpdated, enable, provision);
+                            any, password, changePwd, vAttrs, enable, provision);
                     task.setConnObjectKey(preparedAttrs.getKey());
 
                     // Check if any of mandatory attributes (in the mapping) is missing or not received any value: 

http://git-wip-us.apache.org/repos/asf/syncope/blob/9f73fa43/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java
index 6be2167..6c7f39e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java
@@ -24,11 +24,13 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import org.apache.commons.lang3.exception.ExceptionUtils;
-import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.patch.AttrPatch;
+import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.Result;
 import org.apache.syncope.common.lib.types.IntMappingType;
 import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.PropagationByResource;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
@@ -96,9 +98,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
     }
 
-    protected final void doHandle(final Any<?, ?, ?> any)
-            throws JobExecutionException {
-
+    protected final void doHandle(final Any<?, ?, ?> any) throws JobExecutionException {
         AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
 
         ProvisioningResult result = new ProvisioningResult();
@@ -329,14 +329,16 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
     }
 
     protected Any<?, ?, ?> update(final Any<?, ?, ?> sbj, final Boolean enabled) {
-        Set<String> vattrToBeRemoved = new HashSet<>();
-        Set<AttrMod> vattrToBeUpdated = new HashSet<>();
+        Set<AttrPatch> vattrs = new HashSet<>();
 
         // Search for all mapped vattrs
         Mapping mapping = profile.getTask().getResource().getProvision(sbj.getType()).getMapping();
         for (MappingItem mappingItem : mapping.getItems()) {
             if (mappingItem.getIntMappingType() == IntMappingType.UserVirtualSchema) {
-                vattrToBeRemoved.add(mappingItem.getIntAttrName());
+                vattrs.add(new AttrPatch.Builder().
+                        operation(PatchOperation.DELETE).
+                        attrTO(new AttrTO.Builder().schema(mappingItem.getIntAttrName()).build()).
+                        build());
             }
         }
 
@@ -344,11 +346,13 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         // 1. add mapped vattrs not owned by the user to the set of vattrs to be removed
         // 2. add all vattrs owned by the user to the set of vattrs to be update
         for (VirAttr<?> vattr : sbj.getVirAttrs()) {
-            vattrToBeRemoved.remove(vattr.getSchema().getKey());
-            AttrMod mod = new AttrMod();
-            mod.setSchema(vattr.getSchema().getKey());
-            mod.getValuesToBeAdded().addAll(vattr.getValues());
-            vattrToBeUpdated.add(mod);
+            vattrs.add(new AttrPatch.Builder().
+                    operation(PatchOperation.ADD_REPLACE).
+                    attrTO(new AttrTO.Builder().
+                            schema(vattr.getSchema().getKey()).
+                            values(vattr.getValues()).
+                            build()).
+                    build());
         }
 
         boolean changepwd;
@@ -367,11 +371,11 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         List<String> noPropResources = new ArrayList<>(resourceNames);
         noPropResources.remove(profile.getTask().getResource().getKey());
 
-        final PropagationByResource propByRes = new PropagationByResource();
+        PropagationByResource propByRes = new PropagationByResource();
         propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
 
         taskExecutor.execute(propagationManager.getUpdateTasks(
-                sbj, null, changepwd, enabled, vattrToBeRemoved, vattrToBeUpdated, propByRes, noPropResources));
+                sbj, null, changepwd, enabled, vattrs, propByRes, noPropResources));
 
         return getAny(sbj.getKey());
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/9f73fa43/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java
index a97a3ca..34ead69 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java
@@ -22,7 +22,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import org.apache.commons.lang3.exception.ExceptionUtils;
-import org.apache.syncope.common.lib.mod.AnyMod;
+import org.apache.syncope.common.lib.patch.AnyPatch;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.Result;
@@ -58,7 +58,7 @@ public abstract class AbstractSyncResultHandler extends AbstractSyncopeResultHan
 
     protected abstract AnyTO doLink(AnyTO before, ProvisioningResult result, boolean unlink);
 
-    protected abstract AnyTO doUpdate(AnyTO before, AnyMod anyMod, SyncDelta delta, ProvisioningResult result);
+    protected abstract AnyTO doUpdate(AnyTO before, AnyPatch anyPatch, SyncDelta delta, ProvisioningResult result);
 
     protected abstract void doDeprovision(Long key, boolean unlink);
 
@@ -242,7 +242,7 @@ public abstract class AbstractSyncResultHandler extends AbstractSyncopeResultHan
                     output = null;
                 } else {
                     try {
-                        AnyMod anyMod = connObjectUtils.getAnyMod(
+                        AnyPatch anyPatch = connObjectUtils.getAnyPatch(
                                 before.getKey(),
                                 workingDelta.getObject(),
                                 before,
@@ -251,10 +251,10 @@ public abstract class AbstractSyncResultHandler extends AbstractSyncopeResultHan
                                 getAnyUtils());
 
                         for (SyncActions action : profile.getActions()) {
-                            workingDelta = action.beforeUpdate(this.getProfile(), workingDelta, before, anyMod);
+                            workingDelta = action.beforeUpdate(this.getProfile(), workingDelta, before, anyPatch);
                         }
 
-                        AnyTO updated = doUpdate(before, anyMod, workingDelta, result);
+                        AnyTO updated = doUpdate(before, anyPatch, workingDelta, result);
 
                         for (SyncActions action : profile.getActions()) {
                             action.after(this.getProfile(), workingDelta, updated, result);

http://git-wip-us.apache.org/repos/asf/syncope/blob/9f73fa43/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectPushResultHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectPushResultHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectPushResultHandlerImpl.java
index 1d00cc3..0a276ab 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectPushResultHandlerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectPushResultHandlerImpl.java
@@ -22,10 +22,12 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.mod.AnyObjectMod;
+import org.apache.syncope.common.lib.patch.AnyObjectPatch;
+import org.apache.syncope.common.lib.patch.StringPatchItem;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.PropagationByResource;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.entity.Any;
@@ -78,35 +80,40 @@ public class AnyObjectPushResultHandlerImpl extends AbstractPushResultHandler im
 
     @Override
     protected Any<?, ?, ?> link(final Any<?, ?, ?> sbj, final Boolean unlink) {
-        AnyObjectMod anyObjectMod = new AnyObjectMod();
-        anyObjectMod.setKey(sbj.getKey());
+        AnyObjectPatch anyObjectPatch = new AnyObjectPatch();
+        anyObjectPatch.setKey(sbj.getKey());
+        anyObjectPatch.getResources().add(new StringPatchItem.Builder().
+                operation(unlink ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
+                value(profile.getTask().getResource().getKey()).build());
 
-        if (unlink) {
-            anyObjectMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
-        } else {
-            anyObjectMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
-        }
-
-        awfAdapter.update(anyObjectMod);
+        awfAdapter.update(anyObjectPatch);
 
         return anyObjectDAO.authFind(sbj.getKey());
     }
 
     @Override
     protected Any<?, ?, ?> unassign(final Any<?, ?, ?> sbj) {
-        AnyObjectMod anyObjectMod = new AnyObjectMod();
-        anyObjectMod.setKey(sbj.getKey());
-        anyObjectMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
-        awfAdapter.update(anyObjectMod);
+        AnyObjectPatch anyObjectPatch = new AnyObjectPatch();
+        anyObjectPatch.setKey(sbj.getKey());
+        anyObjectPatch.getResources().add(new StringPatchItem.Builder().
+                operation(PatchOperation.DELETE).
+                value(profile.getTask().getResource().getKey()).build());
+
+        awfAdapter.update(anyObjectPatch);
+
         return deprovision(sbj);
     }
 
     @Override
     protected Any<?, ?, ?> assign(final Any<?, ?, ?> sbj, final Boolean enabled) {
-        AnyObjectMod anyObjectMod = new AnyObjectMod();
-        anyObjectMod.setKey(sbj.getKey());
-        anyObjectMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
-        awfAdapter.update(anyObjectMod);
+        AnyObjectPatch anyObjectPatch = new AnyObjectPatch();
+        anyObjectPatch.setKey(sbj.getKey());
+        anyObjectPatch.getResources().add(new StringPatchItem.Builder().
+                operation(PatchOperation.ADD_REPLACE).
+                value(profile.getTask().getResource().getKey()).build());
+
+        awfAdapter.update(anyObjectPatch);
+
         return provision(sbj, enabled);
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/9f73fa43/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectSyncResultHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectSyncResultHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectSyncResultHandlerImpl.java
index 2b4b667..0227c87 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectSyncResultHandlerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectSyncResultHandlerImpl.java
@@ -22,12 +22,14 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.mod.AnyMod;
-import org.apache.syncope.common.lib.mod.AnyObjectMod;
+import org.apache.syncope.common.lib.patch.AnyObjectPatch;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.patch.StringPatchItem;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult;
 import org.apache.syncope.core.provisioning.api.sync.AnyObjectSyncResultHandler;
@@ -76,28 +78,25 @@ public class AnyObjectSyncResultHandlerImpl extends AbstractSyncResultHandler im
             final ProvisioningResult result,
             final boolean unlink) {
 
-        AnyObjectMod anyObjectMod = new AnyObjectMod();
-        anyObjectMod.setKey(before.getKey());
+        AnyObjectPatch anyObjectPatch = new AnyObjectPatch();
+        anyObjectPatch.setKey(before.getKey());
+        anyObjectPatch.getResources().add(new StringPatchItem.Builder().
+                operation(unlink ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
+                value(profile.getTask().getResource().getKey()).build());
 
-        if (unlink) {
-            anyObjectMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
-        } else {
-            anyObjectMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
-        }
-
-        return anyObjectDataBinder.getAnyObjectTO(awfAdapter.update(anyObjectMod).getResult());
+        return anyObjectDataBinder.getAnyObjectTO(awfAdapter.update(anyObjectPatch).getResult());
     }
 
     @Override
     protected AnyTO doUpdate(
             final AnyTO before,
-            final AnyMod anyMod,
+            final AnyPatch anyPatch,
             final SyncDelta delta,
             final ProvisioningResult result) {
 
-        AnyObjectMod anyObjectMod = AnyObjectMod.class.cast(anyMod);
+        AnyObjectPatch anyObjectPatch = AnyObjectPatch.class.cast(anyPatch);
 
-        Map.Entry<Long, List<PropagationStatus>> updated = anyObjectProvisioningManager.update(anyObjectMod);
+        Map.Entry<Long, List<PropagationStatus>> updated = anyObjectProvisioningManager.update(anyObjectPatch);
 
         AnyObjectTO after = anyObjectDataBinder.getAnyObjectTO(updated.getKey());
         result.setName(getName(after));
@@ -110,9 +109,11 @@ public class AnyObjectSyncResultHandlerImpl extends AbstractSyncResultHandler im
                 key, profile.getTask().getResource().getKey()));
 
         if (unlink) {
-            AnyObjectMod anyObjectMod = new AnyObjectMod();
-            anyObjectMod.setKey(key);
-            anyObjectMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+            AnyObjectPatch anyObjectPatch = new AnyObjectPatch();
+            anyObjectPatch.setKey(key);
+            anyObjectPatch.getResources().add(new StringPatchItem.Builder().
+                    operation(PatchOperation.DELETE).
+                    value(profile.getTask().getResource().getKey()).build());
         }
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/9f73fa43/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DBPasswordSyncActions.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DBPasswordSyncActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DBPasswordSyncActions.java
index ad08a92..ec7fb2e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DBPasswordSyncActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DBPasswordSyncActions.java
@@ -20,8 +20,9 @@ package org.apache.syncope.core.provisioning.java.sync;
 
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.Predicate;
-import org.apache.syncope.common.lib.mod.AnyMod;
-import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.patch.PasswordPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
@@ -74,15 +75,15 @@ public class DBPasswordSyncActions extends DefaultSyncActions {
 
     @Transactional(readOnly = true)
     @Override
-    public <A extends AnyTO, M extends AnyMod> SyncDelta beforeUpdate(
+    public <A extends AnyTO, M extends AnyPatch> SyncDelta beforeUpdate(
             final ProvisioningProfile<?, ?> profile,
             final SyncDelta delta,
             final A any,
-            final M anyMod) throws JobExecutionException {
+            final M anyPatch) throws JobExecutionException {
 
-        if (anyMod instanceof UserMod) {
-            String modPassword = ((UserMod) anyMod).getPassword();
-            parseEncodedPassword(modPassword, profile.getConnector());
+        if (anyPatch instanceof UserPatch) {
+            PasswordPatch modPassword = ((UserPatch) anyPatch).getPassword();
+            parseEncodedPassword(modPassword == null ? null : modPassword.getValue(), profile.getConnector());
         }
 
         return delta;