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 2016/06/14 16:22:32 UTC

[09/13] syncope git commit: [SYNCOPE-862] Features complete

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
index a2c9271..4c44b7f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
@@ -24,7 +24,6 @@ import java.util.List;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.policy.PullPolicySpec;
-import org.apache.syncope.core.provisioning.java.MappingManagerImpl;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
@@ -47,8 +46,8 @@ import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.Connector;
-import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
-import org.apache.syncope.core.provisioning.api.IntAttrNameParser.IntAttrName;
+import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
+import org.apache.syncope.core.provisioning.api.IntAttrName;
 import org.apache.syncope.core.provisioning.api.data.MappingItemTransformer;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.AttributeUtil;
@@ -63,6 +62,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 import org.apache.syncope.core.provisioning.api.pushpull.PullCorrelationRule;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 
 @Transactional(readOnly = true)
 @Component
@@ -103,6 +103,9 @@ public class PullUtils {
     @Autowired
     private AnyUtilsFactory anyUtilsFactory;
 
+    @Autowired
+    private IntAttrNameParser intAttrNameParser;
+
     public String findMatchingAnyKey(
             final AnyType anyType,
             final String name,
@@ -127,7 +130,7 @@ public class PullUtils {
             public boolean handle(final ConnectorObject obj) {
                 return found.add(obj);
             }
-        }, MappingManagerImpl.buildOperationOptions(MappingManagerImpl.getPullMappingItems(provision).iterator()));
+        }, MappingUtils.buildOperationOptions(MappingUtils.getPullMappingItems(provision).iterator()));
 
         if (found.isEmpty()) {
             LOG.debug("No {} found on {} with __NAME__ {}", provision.getObjectClass(), resource, name);
@@ -170,10 +173,10 @@ public class PullUtils {
 
         List<String> result = new ArrayList<>();
 
-        MappingItem connObjectKeyItem = MappingManagerImpl.getConnObjectKeyItem(provision);
+        MappingItem connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
 
         String transfUid = uid;
-        for (MappingItemTransformer transformer : MappingManagerImpl.getMappingItemTransformers(connObjectKeyItem)) {
+        for (MappingItemTransformer transformer : MappingUtils.getMappingItemTransformers(connObjectKeyItem)) {
             List<Object> output = transformer.beforePull(
                     connObjectKeyItem,
                     null,
@@ -183,9 +186,8 @@ public class PullUtils {
             }
         }
 
-        IntAttrName intAttrName = IntAttrNameParser.parse(
+        IntAttrName intAttrName = intAttrNameParser.parse(
                 connObjectKeyItem.getIntAttrName(),
-                anyUtilsFactory,
                 provision.getAnyType().getKind());
 
         if (intAttrName.getField() != null) {
@@ -214,6 +216,8 @@ public class PullUtils {
                         result.add(anyObject.getKey());
                     }
                     break;
+
+                default:
             }
         } else if (intAttrName.getSchemaType() != null) {
             switch (intAttrName.getSchemaType()) {
@@ -246,6 +250,8 @@ public class PullUtils {
                         result.add(any.getKey());
                     }
                     break;
+
+                default:
             }
         }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
index af2631c..c0ed5fc 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
@@ -18,7 +18,6 @@
  */
 package org.apache.syncope.core.provisioning.java.utils;
 
-import org.apache.syncope.core.provisioning.java.MappingManagerImpl;
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.commons.lang3.StringUtils;
@@ -222,7 +221,7 @@ public class ConnObjectUtils {
 
         // 1. fill with data from connector object
         anyTO.setRealm(pullTask.getDestinatioRealm().getFullPath());
-        for (MappingItem item : MappingManagerImpl.getPullMappingItems(provision)) {
+        for (MappingItem item : MappingUtils.getPullMappingItems(provision)) {
             mappingManager.setIntValues(item, obj.getAttributeByName(item.getExtAttrName()), anyTO, anyUtils);
         }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
new file mode 100644
index 0000000..63ceb98
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.jexl3.JexlContext;
+import org.apache.commons.jexl3.MapContext;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.resource.Mapping;
+import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.data.MappingItemTransformer;
+import org.apache.syncope.core.provisioning.java.data.JEXLMappingItemTransformer;
+import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.identityconnectors.framework.common.objects.Name;
+import org.identityconnectors.framework.common.objects.OperationOptions;
+import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
+import org.identityconnectors.framework.common.objects.OperationalAttributes;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+
+public final class MappingUtils {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MappingUtils.class);
+
+    public static MappingItem getConnObjectKeyItem(final Provision provision) {
+        Mapping mapping = null;
+        if (provision != null) {
+            mapping = provision.getMapping();
+        }
+
+        return mapping == null
+                ? null
+                : mapping.getConnObjectKeyItem();
+    }
+
+    private static List<MappingItem> getMappingItems(final Provision provision, final MappingPurpose purpose) {
+        List<? extends MappingItem> items = Collections.<MappingItem>emptyList();
+        if (provision != null) {
+            items = provision.getMapping().getItems();
+        }
+
+        List<MappingItem> result = new ArrayList<>();
+
+        switch (purpose) {
+            case PULL:
+                for (MappingItem item : items) {
+                    if (MappingPurpose.PROPAGATION != item.getPurpose()
+                            && MappingPurpose.NONE != item.getPurpose()) {
+
+                        result.add(item);
+                    }
+                }
+                break;
+
+            case PROPAGATION:
+                for (MappingItem item : items) {
+                    if (MappingPurpose.PULL != item.getPurpose()
+                            && MappingPurpose.NONE != item.getPurpose()) {
+
+                        result.add(item);
+                    }
+                }
+                break;
+
+            case BOTH:
+                for (MappingItem item : items) {
+                    if (MappingPurpose.NONE != item.getPurpose()) {
+                        result.add(item);
+                    }
+                }
+                break;
+
+            case NONE:
+                for (MappingItem item : items) {
+                    if (MappingPurpose.NONE == item.getPurpose()) {
+                        result.add(item);
+                    }
+                }
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
+    public static List<MappingItem> getPropagationMappingItems(final Provision provision) {
+        return getMappingItems(provision, MappingPurpose.PROPAGATION);
+    }
+
+    public static List<MappingItem> getPullMappingItems(final Provision provision) {
+        return getMappingItems(provision, MappingPurpose.PULL);
+    }
+
+    /**
+     * Build __NAME__ for propagation. First look if there ia a defined connObjectLink for the given resource (and in
+     * this case evaluate as JEXL); otherwise, take given connObjectKey.
+     *
+     * @param any given any object
+     * @param provision external resource
+     * @param connObjectKey connector object key
+     * @return the value to be propagated as __NAME__
+     */
+    public static Name evaluateNAME(final Any<?> any, final Provision provision, final String connObjectKey) {
+        if (StringUtils.isBlank(connObjectKey)) {
+            // LOG error but avoid to throw exception: leave it to the external resource
+            LOG.error("Missing ConnObjectKey for '{}': ", provision.getResource());
+        }
+
+        // Evaluate connObjectKey expression
+        String connObjectLink = provision == null || provision.getMapping() == null
+                ? null
+                : provision.getMapping().getConnObjectLink();
+        String evalConnObjectLink = null;
+        if (StringUtils.isNotBlank(connObjectLink)) {
+            JexlContext jexlContext = new MapContext();
+            JexlUtils.addFieldsToContext(any, jexlContext);
+            JexlUtils.addPlainAttrsToContext(any.getPlainAttrs(), jexlContext);
+            JexlUtils.addDerAttrsToContext(any, jexlContext);
+            evalConnObjectLink = JexlUtils.evaluate(connObjectLink, jexlContext);
+        }
+
+        // If connObjectLink evaluates to an empty string, just use the provided connObjectKey as Name(),
+        // otherwise evaluated connObjectLink expression is taken as Name().
+        Name name;
+        if (StringUtils.isBlank(evalConnObjectLink)) {
+            // add connObjectKey as __NAME__ attribute ...
+            LOG.debug("Add connObjectKey [{}] as __NAME__", connObjectKey);
+            name = new Name(connObjectKey);
+        } else {
+            LOG.debug("Add connObjectLink [{}] as __NAME__", evalConnObjectLink);
+            name = new Name(evalConnObjectLink);
+
+            // connObjectKey not propagated: it will be used to set the value for __UID__ attribute
+            LOG.debug("connObjectKey will be used just as __UID__ attribute");
+        }
+
+        return name;
+    }
+
+    public static List<MappingItemTransformer> getMappingItemTransformers(final MappingItem mappingItem) {
+        List<MappingItemTransformer> result = new ArrayList<>();
+
+        // First consider the JEXL transformation expressions
+        JEXLMappingItemTransformer jexlTransformer = null;
+        if (StringUtils.isNotBlank(mappingItem.getPropagationJEXLTransformer())
+                || StringUtils.isNotBlank(mappingItem.getPullJEXLTransformer())) {
+
+            try {
+                jexlTransformer = (JEXLMappingItemTransformer) ApplicationContextProvider.getBeanFactory().
+                        createBean(JEXLMappingItemTransformer.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+
+                jexlTransformer.setPropagationJEXL(mappingItem.getPropagationJEXLTransformer());
+                jexlTransformer.setPullJEXL(mappingItem.getPullJEXLTransformer());
+            } catch (Exception e) {
+                LOG.error("Could not instantiate {}, ignoring...", JEXLMappingItemTransformer.class.getName(), e);
+            }
+        }
+        if (jexlTransformer != null) {
+            result.add(jexlTransformer);
+        }
+
+        // Then other custom tranaformers
+        for (String className : mappingItem.getMappingItemTransformerClassNames()) {
+            try {
+                Class<?> transformerClass = ClassUtils.getClass(className);
+
+                result.add((MappingItemTransformer) ApplicationContextProvider.getBeanFactory().
+                        createBean(transformerClass, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false));
+            } catch (Exception e) {
+                LOG.error("Could not instantiate {}, ignoring...", className, e);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Build options for requesting all mapped connector attributes.
+     *
+     * @param mapItems mapping items
+     * @return options for requesting all mapped connector attributes
+     * @see OperationOptions
+     */
+    public static OperationOptions buildOperationOptions(final Iterator<? extends MappingItem> mapItems) {
+        OperationOptionsBuilder builder = new OperationOptionsBuilder();
+
+        Set<String> attrsToGet = new HashSet<>();
+        attrsToGet.add(Name.NAME);
+        attrsToGet.add(Uid.NAME);
+        attrsToGet.add(OperationalAttributes.ENABLE_NAME);
+
+        while (mapItems.hasNext()) {
+            MappingItem mapItem = mapItems.next();
+            if (mapItem.getPurpose() != MappingPurpose.NONE) {
+                attrsToGet.add(mapItem.getExtAttrName());
+            }
+        }
+
+        builder.setAttributesToGet(attrsToGet);
+        // -------------------------------------
+
+        return builder.build();
+    }
+
+    /**
+     * Private default constructor, for static-only classes.
+     */
+    private MappingUtils() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/core/provisioning-java/src/main/resources/provisioningContext.xml
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/resources/provisioningContext.xml b/core/provisioning-java/src/main/resources/provisioningContext.xml
index 068d1e7..37c8181 100644
--- a/core/provisioning-java/src/main/resources/provisioningContext.xml
+++ b/core/provisioning-java/src/main/resources/provisioningContext.xml
@@ -122,4 +122,5 @@ under the License.
     <property name="stringLocations" value="${connid.locations}"/>
   </bean>
 
+  <bean class="org.apache.syncope.core.provisioning.java.IntAttrNameParser"/>
 </beans>

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/IntAttrNameParserTest.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/IntAttrNameParserTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/IntAttrNameParserTest.java
new file mode 100644
index 0000000..c603f8a
--- /dev/null
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/IntAttrNameParserTest.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.SchemaType;
+import org.apache.syncope.core.provisioning.api.IntAttrName;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional("Master")
+public class IntAttrNameParserTest extends AbstractTest {
+
+    @Autowired
+    private IntAttrNameParser intAttrNameParser;
+
+    @Test
+    public void ownFields() {
+        IntAttrName intAttrName = intAttrNameParser.parse("key", AnyTypeKind.USER);
+        assertNotNull(intAttrName);
+        assertEquals(AnyTypeKind.USER, intAttrName.getAnyTypeKind());
+        assertNotNull(intAttrName.getField());
+        assertEquals("key", intAttrName.getField());
+        assertNull(intAttrName.getSchemaName());
+        assertNull(intAttrName.getSchemaType());
+        assertNull(intAttrName.getEnclosingGroup());
+        assertNull(intAttrName.getMembershipOfGroup());
+        assertNull(intAttrName.getRelatedAnyObject());
+
+        intAttrName = intAttrNameParser.parse("name", AnyTypeKind.GROUP);
+        assertNotNull(intAttrName);
+        assertEquals(AnyTypeKind.GROUP, intAttrName.getAnyTypeKind());
+        assertNotNull(intAttrName.getField());
+        assertEquals("name", intAttrName.getField());
+        assertNull(intAttrName.getSchemaName());
+        assertNull(intAttrName.getSchemaType());
+        assertNull(intAttrName.getEnclosingGroup());
+        assertNull(intAttrName.getMembershipOfGroup());
+        assertNull(intAttrName.getRelatedAnyObject());
+
+        intAttrName = intAttrNameParser.parse("userOwner", AnyTypeKind.GROUP);
+        assertNotNull(intAttrName);
+        assertEquals(AnyTypeKind.GROUP, intAttrName.getAnyTypeKind());
+        assertNotNull(intAttrName.getField());
+        assertEquals("userOwner", intAttrName.getField());
+        assertNull(intAttrName.getSchemaName());
+        assertNull(intAttrName.getSchemaType());
+        assertNull(intAttrName.getEnclosingGroup());
+        assertNull(intAttrName.getMembershipOfGroup());
+        assertNull(intAttrName.getRelatedAnyObject());
+
+        intAttrName = intAttrNameParser.parse("name", AnyTypeKind.USER);
+        assertNotNull(intAttrName);
+        assertEquals(AnyTypeKind.USER, intAttrName.getAnyTypeKind());
+        assertNull(intAttrName.getField());
+    }
+
+    @Test
+    public void ownSchema() {
+        IntAttrName intAttrName = intAttrNameParser.parse("email", AnyTypeKind.USER);
+        assertNotNull(intAttrName);
+        assertEquals(AnyTypeKind.USER, intAttrName.getAnyTypeKind());
+        assertNull(intAttrName.getField());
+        assertEquals("email", intAttrName.getSchemaName());
+        assertEquals(SchemaType.PLAIN, intAttrName.getSchemaType());
+        assertNull(intAttrName.getEnclosingGroup());
+        assertNull(intAttrName.getMembershipOfGroup());
+        assertNull(intAttrName.getRelatedAnyObject());
+
+        intAttrName = intAttrNameParser.parse("cn", AnyTypeKind.ANY_OBJECT);
+        assertNotNull(intAttrName);
+        assertEquals(AnyTypeKind.ANY_OBJECT, intAttrName.getAnyTypeKind());
+        assertNull(intAttrName.getField());
+        assertEquals("cn", intAttrName.getSchemaName());
+        assertEquals(SchemaType.DERIVED, intAttrName.getSchemaType());
+        assertNull(intAttrName.getEnclosingGroup());
+        assertNull(intAttrName.getMembershipOfGroup());
+        assertNull(intAttrName.getRelatedAnyObject());
+
+        intAttrName = intAttrNameParser.parse("rvirtualdata", AnyTypeKind.ANY_OBJECT);
+        assertNotNull(intAttrName);
+        assertEquals(AnyTypeKind.ANY_OBJECT, intAttrName.getAnyTypeKind());
+        assertNull(intAttrName.getField());
+        assertEquals("rvirtualdata", intAttrName.getSchemaName());
+        assertEquals(SchemaType.VIRTUAL, intAttrName.getSchemaType());
+        assertNull(intAttrName.getEnclosingGroup());
+        assertNull(intAttrName.getMembershipOfGroup());
+        assertNull(intAttrName.getRelatedAnyObject());
+    }
+
+    @Test
+    public void enclosingGroup() {
+        IntAttrName intAttrName = intAttrNameParser.parse("groups[readers].cn", AnyTypeKind.USER);
+        assertNotNull(intAttrName);
+        assertEquals(AnyTypeKind.GROUP, intAttrName.getAnyTypeKind());
+        assertNull(intAttrName.getField());
+        assertEquals("cn", intAttrName.getSchemaName());
+        assertEquals(SchemaType.DERIVED, intAttrName.getSchemaType());
+        assertEquals("readers", intAttrName.getEnclosingGroup());
+        assertNull(intAttrName.getMembershipOfGroup());
+        assertNull(intAttrName.getRelatedAnyObject());
+    }
+
+    @Test
+    public void relatedAnyObject() {
+        IntAttrName intAttrName = intAttrNameParser.parse("anyObjects[hp].name", AnyTypeKind.USER);
+        assertNotNull(intAttrName);
+        assertEquals(AnyTypeKind.ANY_OBJECT, intAttrName.getAnyTypeKind());
+        assertEquals("name", intAttrName.getField());
+        assertNull(intAttrName.getSchemaName());
+        assertNull(intAttrName.getSchemaType());
+        assertNull(intAttrName.getEnclosingGroup());
+        assertEquals("hp", intAttrName.getRelatedAnyObject());
+        assertNull(intAttrName.getMembershipOfGroup());
+    }
+
+    @Test
+    public void membership() {
+        IntAttrName intAttrName = intAttrNameParser.parse("memberships[top].cn", AnyTypeKind.USER);
+        assertNotNull(intAttrName);
+        assertEquals(AnyTypeKind.USER, intAttrName.getAnyTypeKind());
+        assertNull(intAttrName.getField());
+        assertEquals("cn", intAttrName.getSchemaName());
+        assertEquals(SchemaType.DERIVED, intAttrName.getSchemaType());
+        assertNull(intAttrName.getEnclosingGroup());
+        assertEquals("top", intAttrName.getMembershipOfGroup());
+        assertNull(intAttrName.getRelatedAnyObject());
+    }
+
+    @Test
+    public void invalid() {
+        try {
+            intAttrNameParser.parse("memberships.cn", AnyTypeKind.USER);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertNotNull(e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingTest.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingTest.java
index dd6792b..0e84b68 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingTest.java
@@ -27,6 +27,7 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.identityconnectors.framework.common.objects.Name;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -57,12 +58,12 @@ public class MappingTest extends AbstractTest {
         User user = userDAO.findByUsername("rossini");
         assertNotNull(user);
 
-        Name name = MappingManagerImpl.evaluateNAME(user, provision, user.getUsername());
+        Name name = MappingUtils.evaluateNAME(user, provision, user.getUsername());
         assertEquals("uid=rossini,ou=people,o=isp", name.getNameValue());
 
         provision.getMapping().setConnObjectLink("'uid=' + username + ',o=' + realm + ',ou=people,o=isp'");
 
-        name = MappingManagerImpl.evaluateNAME(user, provision, user.getUsername());
+        name = MappingUtils.evaluateNAME(user, provision, user.getUsername());
         assertEquals("uid=rossini,o=even,ou=people,o=isp", name.getNameValue());
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/PrefixMappingItemTransformer.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/PrefixMappingItemTransformer.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/PrefixMappingItemTransformer.java
index 7fc60df..822860f 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/PrefixMappingItemTransformer.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/PrefixMappingItemTransformer.java
@@ -34,11 +34,11 @@ public class PrefixMappingItemTransformer extends DefaultMappingItemTransformer
     @Override
     public List<PlainAttrValue> beforePropagation(
             final MappingItem mappingItem,
-            final List<Any<?>> anys,
+            final Any<?> any,
             final List<PlainAttrValue> values) {
 
         if (values == null || values.isEmpty() || values.get(0).getStringValue() == null) {
-            return super.beforePropagation(mappingItem, anys, values);
+            return super.beforePropagation(mappingItem, any, values);
         } else {
             String value = values.get(0).getStringValue();
             values.get(0).setStringValue(PREFIX + value);

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/fit/core-reference/src/test/java/org/apache/syncope/fit/console/NotificationsITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/NotificationsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/NotificationsITCase.java
index 1036fdc..849db31 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/NotificationsITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/NotificationsITCase.java
@@ -43,17 +43,7 @@ public class NotificationsITCase extends AbstractConsoleITCase {
         FormTester formTester = wicketTester.newFormTester(
                 "body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:form");
 
-        // -------------------------------
-        // generate event to populate recipientAttrName
-        // -------------------------------
-        formTester.setValue("content:form:view:recipientAttrType:dropDownChoiceField", "3");
-        wicketTester.executeAjaxEvent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:form:content:"
-                + "form:view:recipientAttrType:dropDownChoiceField", Constants.ON_CHANGE);
-        // -------------------------------
-
-        formTester.select("content:form:view:recipientAttrType:dropDownChoiceField", 3);
-        formTester.setValue("content:form:view:recipientAttrType:dropDownChoiceField", "3");
-        formTester.setValue("content:form:view:recipientAttrName:dropDownChoiceField", "0");
+        formTester.setValue("content:form:view:recipientAttrName:textField", "email");
         formTester.select("content:form:view:template:dropDownChoiceField", 2);
         formTester.select("content:form:view:traceLevel:dropDownChoiceField", 0);
         formTester.setValue("content:form:view:sender:textField", sender);

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/fit/core-reference/src/test/java/org/apache/syncope/fit/console/TopologyITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/TopologyITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/TopologyITCase.java
index 6c3b10a..1ad33ea 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/TopologyITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/TopologyITCase.java
@@ -168,23 +168,12 @@ public class TopologyITCase extends AbstractConsoleITCase {
         formTester = wicketTester.newFormTester(
                 "body:toggle:outerObjectsRepeater:3:outer:form:content:provision:container:content:wizard:form");
 
-        formTester.setValue("view:mapping:mappingContainer:mappings:0:entities:dropDownChoiceField", "0");
-        wicketTester.executeAjaxEvent(
-                "body:toggle:outerObjectsRepeater:3:outer:form:content:provision:container:content:wizard:form"
-                + ":view:mapping:mappingContainer:mappings:0:entities:dropDownChoiceField", Constants.ON_CHANGE);
-
-        formTester.setValue("view:mapping:mappingContainer:mappings:0:intMappingTypes:dropDownChoiceField", "4");
-        wicketTester.executeAjaxEvent(
-                "body:toggle:outerObjectsRepeater:3:outer:form:content:provision:container:content:wizard:form"
-                + ":view:mapping:mappingContainer:mappings:0:intMappingTypes:dropDownChoiceField", Constants.ON_CHANGE);
-
         formTester.setValue("view:mapping:mappingContainer:mappings:0:connObjectKey:checkboxField", "true");
         wicketTester.executeAjaxEvent(
                 "body:toggle:outerObjectsRepeater:3:outer:form:content:provision:container:content:wizard:form"
                 + ":view:mapping:mappingContainer:mappings:0:connObjectKey:checkboxField", Constants.ON_CHANGE);
 
-        formTester.setValue("view:mapping:mappingContainer:mappings:0:entities:dropDownChoiceField", "0");
-        formTester.setValue("view:mapping:mappingContainer:mappings:0:intMappingTypes:dropDownChoiceField", "4");
+        formTester.setValue("view:mapping:mappingContainer:mappings:0:intAttrName:textField", "key");
         formTester.setValue("view:mapping:mappingContainer:mappings:0:extAttrName:textField", "ID");
         formTester.setValue("view:mapping:mappingContainer:mappings:0:connObjectKey:checkboxField", "true");
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
index a09b9bb..326cf96 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
@@ -867,8 +867,17 @@ public class GroupITCase extends AbstractITCase {
 
     @Test
     public void issueSYNCOPE632() {
-        GroupTO groupTO = null;
+        GroupTO groupTO = getSampleTO("lastGroup");
         try {
+            // 0. create group
+            groupTO.getPlainAttrs().add(attrTO("icon", "anIcon"));
+            groupTO.getPlainAttrs().add(attrTO("show", "true"));
+            groupTO.getDerAttrs().add(attrTO("displayProperty", null));
+            groupTO.getResources().clear();
+
+            groupTO = createGroup(groupTO).getAny();
+            assertNotNull(groupTO);
+
             // 1. create new LDAP resource having ConnObjectKey mapped to a derived attribute
             ResourceTO newLDAP = resourceService.read(RESOURCE_NAME_LDAP);
             newLDAP.setKey("new-ldap");
@@ -887,22 +896,22 @@ public class GroupITCase extends AbstractITCase {
             mapping.setConnObjectLink("'cn=' + displayProperty + ',ou=groups,o=isp'");
 
             MappingItemTO description = new MappingItemTO();
+            description.setIntAttrName("key");
             description.setExtAttrName("description");
-            description.setPurpose(MappingPurpose.BOTH);
+            description.setPurpose(MappingPurpose.PROPAGATION);
             mapping.add(description);
 
             newLDAP = createResource(newLDAP);
             assertNotNull(newLDAP);
 
-            // 2. create a group and give the resource created above
-            groupTO = getSampleTO("lastGroup" + getUUIDString());
-            groupTO.getPlainAttrs().add(attrTO("icon", "anIcon"));
-            groupTO.getPlainAttrs().add(attrTO("show", "true"));
-            groupTO.getDerAttrs().add(attrTO("displayProperty", null));
-            groupTO.getResources().clear();
-            groupTO.getResources().add("new-ldap");
+            // 2. update group and give the resource created above
+            GroupPatch patch = new GroupPatch();
+            patch.setKey(groupTO.getKey());
+            patch.getResources().add(new StringPatchItem.Builder().
+                    operation(PatchOperation.ADD_REPLACE).
+                    value("new-ldap").build());
 
-            groupTO = createGroup(groupTO).getAny();
+            groupTO = updateGroup(patch).getAny();
             assertNotNull(groupTO);
 
             // 3. update the group
@@ -943,7 +952,7 @@ public class GroupITCase extends AbstractITCase {
 
             assertEquals(1, entries);
         } finally {
-            if (groupTO != null) {
+            if (groupTO.getKey() != null) {
                 groupService.delete(groupTO.getKey());
             }
             resourceService.delete("new-ldap");

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
index 38b3631..3d98f28 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
@@ -18,30 +18,47 @@
  */
 package org.apache.syncope.fit.core;
 
+import static org.apache.syncope.fit.core.AbstractTaskITCase.execProvisioningTask;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import javax.ws.rs.core.Response;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.IterableUtils;
 import org.apache.commons.collections4.Predicate;
+import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.patch.AttrPatch;
+import org.apache.syncope.common.lib.patch.DeassociationPatch;
 import org.apache.syncope.common.lib.patch.MembershipPatch;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.MappingItemTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.lib.to.PullTaskTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
 import org.apache.syncope.common.lib.types.PatchOperation;
+import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
+import org.apache.syncope.common.rest.api.beans.AnySearchQuery;
+import org.apache.syncope.common.rest.api.service.TaskService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runners.MethodSorters;
+import org.springframework.jdbc.core.JdbcTemplate;
 
 @FixMethodOrder(MethodSorters.JVM)
 public class MembershipITCase extends AbstractITCase {
@@ -203,4 +220,99 @@ public class MembershipITCase extends AbstractITCase {
         user = userService.read(user.getKey());
         assertTrue(user.getMemberships().isEmpty());
     }
+
+    @Test
+    public void syncWithMembershipAttr() {
+        // 0. create ad-hoc resource, with adequate mapping
+        ResourceTO newResource = resourceService.read(RESOURCE_NAME_DBPULL);
+        newResource.setKey(getUUIDString());
+
+        MappingItemTO item = IterableUtils.find(newResource.getProvision("USER").getMapping().getItems(),
+                new Predicate<MappingItemTO>() {
+
+            @Override
+            public boolean evaluate(final MappingItemTO object) {
+                return "firstname".equals(object.getIntAttrName());
+            }
+        });
+        assertNotNull(item);
+        assertEquals("ID", item.getExtAttrName());
+        item.setIntAttrName("memberships[additional].aLong");
+        item.setPurpose(MappingPurpose.BOTH);
+
+        item = IterableUtils.find(newResource.getProvision("USER").getMapping().getItems(),
+                new Predicate<MappingItemTO>() {
+
+            @Override
+            public boolean evaluate(final MappingItemTO object) {
+                return "fullname".equals(object.getIntAttrName());
+            }
+        });
+        item.setPurpose(MappingPurpose.PULL);
+
+        PullTaskTO newTask = null;
+        try {
+            newResource = createResource(newResource);
+            assertNotNull(newResource);
+
+            // 1. create user with new resource assigned
+            UserTO user = UserITCase.getUniqueSampleTO("memb@apache.org");
+            user.setRealm("/even/two");
+            user.getPlainAttrs().remove(user.getPlainAttrMap().get("ctype"));
+            user.getResources().clear();
+            user.getResources().add(newResource.getKey());
+
+            MembershipTO membership = new MembershipTO.Builder().group("034740a9-fa10-453b-af37-dc7897e98fb1").build();
+            membership.getPlainAttrs().add(new AttrTO.Builder().schema("aLong").value("5432").build());
+            user.getMemberships().add(membership);
+
+            user = createUser(user).getAny();
+            assertNotNull(user);
+
+            // 2. verify that user was found on resource
+            JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+            String idOnResource = jdbcTemplate.queryForObject(
+                    "SELECT id FROM testpull WHERE id=?", String.class, "5432");
+            assertEquals("5432", idOnResource);
+
+            // 3. unlink user from resource, then remove it
+            DeassociationPatch patch = new DeassociationPatch();
+            patch.setKey(user.getKey());
+            patch.setAction(ResourceDeassociationAction.UNLINK);
+            patch.getResources().add(newResource.getKey());
+            assertNotNull(userService.deassociate(patch).readEntity(BulkActionResult.class));
+
+            userService.delete(user.getKey());
+
+            // 4. create pull task and execute
+            newTask = taskService.read("7c2242f4-14af-4ab5-af31-cdae23783655", true);
+            newTask.setResource(newResource.getKey());
+            newTask.setDestinationRealm("/even/two");
+
+            Response response = taskService.create(newTask);
+            newTask = getObject(response.getLocation(), TaskService.class, PullTaskTO.class);
+            assertNotNull(newTask);
+
+            ExecTO execution = execProvisioningTask(taskService, newTask.getKey(), 50, false);
+            assertEquals(PropagationTaskExecStatus.SUCCESS, PropagationTaskExecStatus.valueOf(execution.getStatus()));
+
+            // 5. verify that pulled user has
+            PagedResult<UserTO> users = userService.search(new AnySearchQuery.Builder().
+                    realm("/").
+                    fiql(SyncopeClient.getUserSearchConditionBuilder().
+                            is("username").equalTo(user.getUsername()).query()).build());
+            assertEquals(1, users.getTotalCount());
+            assertEquals(1, users.getResult().get(0).getMemberships().size());
+            assertEquals("5432", users.getResult().get(0).getMemberships().get(0).
+                    getPlainAttrMap().get("aLong").getValues().get(0));
+        } catch (Exception e) {
+            LOG.error("Unexpected error", e);
+            fail(e.getMessage());
+        } finally {
+            if (newTask != null && !"83f7e85d-9774-43fe-adba-ccd856312994".equals(newTask.getKey())) {
+                taskService.delete(newTask.getKey());
+            }
+            resourceService.delete(newResource.getKey());
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MigrationITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MigrationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MigrationITCase.java
index a89fae3..59de26b 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MigrationITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MigrationITCase.java
@@ -339,7 +339,7 @@ public class MigrationITCase extends AbstractTaskITCase {
         provisionTO.setMapping(mapping);
 
         item = new MappingItemTO();
-        item.setIntAttrName("groupName");
+        item.setIntAttrName("name");
         item.setExtAttrName("name");
         item.setMandatoryCondition("true");
         item.setPurpose(MappingPurpose.PULL);
@@ -417,7 +417,7 @@ public class MigrationITCase extends AbstractTaskITCase {
     public void migrateFromSyncope12() throws InterruptedException {
         // 1. cleanup
         try {
-            realmService.delete(MIGRATION_REALM);
+            realmService.delete("/" + MIGRATION_REALM);
         } catch (Exception e) {
             // ignore
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/d7517772/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java
index f509611..6d7dd0d 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java
@@ -255,10 +255,8 @@ public class ResourceITCase extends AbstractITCase {
             createResource(resourceTO);
             fail("Create should not have worked");
         } catch (SyncopeClientException e) {
-            assertEquals(ClientExceptionType.Composite, e.getType());
-            SyncopeClientException rvm = e.asComposite().getException(ClientExceptionType.RequiredValuesMissing);
-            assertNotNull(rvm);
-            assertEquals("intAttrName", rvm.getElements().iterator().next());
+            assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
+            assertEquals("intAttrName", e.getElements().iterator().next());
         }
     }
 
@@ -701,4 +699,34 @@ public class ResourceITCase extends AbstractITCase {
             }
         }
     }
+
+    public void issueSYNCOPE645() {
+        ResourceTO resource = new ResourceTO();
+        resource.setKey("ws-target-resource-basic-save-invalid");
+
+        String connector = resourceService.read("ws-target-resource-1").getConnector();
+        resource.setConnector(connector);
+
+        ProvisionTO provision = new ProvisionTO();
+        provision.setAnyType(AnyTypeKind.USER.name());
+        provision.setObjectClass("__ACCOUNT__");
+        resource.getProvisions().add(provision);
+
+        MappingTO mapping = new MappingTO();
+        provision.setMapping(mapping);
+
+        MappingItemTO item = new MappingItemTO();
+        item.setIntAttrName("icon");
+        item.setExtAttrName("icon");
+        item.setPurpose(MappingPurpose.BOTH);
+        mapping.setConnObjectKeyItem(item);
+
+        // save the resource
+        try {
+            resourceService.create(resource);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.InvalidMapping, e.getType());
+        }
+    }
 }