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/02/12 10:14:21 UTC

[16/54] [abbrv] [partial] syncope git commit: [SYNCOPE-620] Renaming 'server' after 'core', to provide continuity with older releases (especially for archetype)

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/SyncJobImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/SyncJobImpl.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/SyncJobImpl.java
new file mode 100644
index 0000000..a786b94
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/SyncJobImpl.java
@@ -0,0 +1,219 @@
+/*
+ * 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.sync;
+
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.mod.ReferenceMod;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.types.SyncPolicySpec;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.SyncPolicy;
+import org.apache.syncope.core.persistence.api.entity.role.RMapping;
+import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask;
+import org.apache.syncope.core.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.core.persistence.api.entity.user.UMapping;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.core.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.core.provisioning.api.sync.SyncActions;
+import org.apache.syncope.core.misc.security.UnauthorizedRoleException;
+import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
+import org.apache.syncope.core.provisioning.api.job.SyncJob;
+import org.apache.syncope.core.provisioning.api.sync.RoleSyncResultHandler;
+import org.apache.syncope.core.provisioning.api.sync.UserSyncResultHandler;
+import org.apache.syncope.core.workflow.api.RoleWorkflowAdapter;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.SyncToken;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+
+/**
+ * Job for executing synchronization (from external resource) tasks.
+ *
+ * @see AbstractProvisioningJob
+ * @see SyncTask
+ */
+public class SyncJobImpl extends AbstractProvisioningJob<SyncTask, SyncActions> implements SyncJob {
+
+    /**
+     * Role workflow adapter.
+     */
+    @Autowired
+    private RoleWorkflowAdapter rwfAdapter;
+
+    @Autowired
+    protected SyncUtilities syncUtilities;
+
+    protected void setRoleOwners(final RoleSyncResultHandler rhandler)
+            throws UnauthorizedRoleException, NotFoundException {
+
+        for (Map.Entry<Long, String> entry : rhandler.getRoleOwnerMap().entrySet()) {
+            RoleMod roleMod = new RoleMod();
+            roleMod.setKey(entry.getKey());
+
+            if (StringUtils.isBlank(entry.getValue())) {
+                roleMod.setRoleOwner(null);
+                roleMod.setUserOwner(null);
+            } else {
+                Long userId = syncUtilities.findMatchingAttributableKey(
+                        ObjectClass.ACCOUNT,
+                        entry.getValue(),
+                        rhandler.getProfile().getTask().getResource(),
+                        rhandler.getProfile().getConnector());
+
+                if (userId == null) {
+                    Long roleId = syncUtilities.findMatchingAttributableKey(
+                            ObjectClass.GROUP,
+                            entry.getValue(),
+                            rhandler.getProfile().getTask().getResource(),
+                            rhandler.getProfile().getConnector());
+
+                    if (roleId != null) {
+                        roleMod.setRoleOwner(new ReferenceMod(roleId));
+                    }
+                } else {
+                    roleMod.setUserOwner(new ReferenceMod(userId));
+                }
+            }
+
+            rwfAdapter.update(roleMod);
+        }
+    }
+
+    @Override
+    protected String executeWithSecurityContext(
+            final SyncTask syncTask,
+            final Connector connector,
+            final UMapping uMapping,
+            final RMapping rMapping,
+            final boolean dryRun) throws JobExecutionException {
+
+        LOG.debug("Execute synchronization with token {}", syncTask.getResource().getUsyncToken());
+
+        final ProvisioningProfile<SyncTask, SyncActions> profile = new ProvisioningProfile<>(connector, syncTask);
+        if (actions != null) {
+            profile.getActions().addAll(actions);
+        }
+        profile.setDryRun(dryRun);
+        profile.setResAct(getSyncPolicySpec(syncTask).getConflictResolutionAction());
+
+        // Prepare handler for SyncDelta objects (users)
+        final UserSyncResultHandler uhandler =
+                (UserSyncResultHandler) ApplicationContextProvider.getApplicationContext().getBeanFactory().
+                createBean(UserSyncResultHandlerImpl.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+        uhandler.setProfile(profile);
+
+        // Prepare handler for SyncDelta objects (roles/groups)
+        final RoleSyncResultHandler rhandler =
+                (RoleSyncResultHandler) ApplicationContextProvider.getApplicationContext().getBeanFactory().
+                createBean(RoleSyncResultHandlerImpl.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+        rhandler.setProfile(profile);
+
+        if (actions != null && !profile.isDryRun()) {
+            for (SyncActions action : actions) {
+                action.beforeAll(profile);
+            }
+        }
+
+        try {
+            SyncToken latestUSyncToken = null;
+            if (uMapping != null && !syncTask.isFullReconciliation()) {
+                latestUSyncToken = connector.getLatestSyncToken(ObjectClass.ACCOUNT);
+            }
+            SyncToken latestRSyncToken = null;
+            if (rMapping != null && !syncTask.isFullReconciliation()) {
+                latestRSyncToken = connector.getLatestSyncToken(ObjectClass.GROUP);
+            }
+
+            if (syncTask.isFullReconciliation()) {
+                if (uMapping != null) {
+                    connector.getAllObjects(ObjectClass.ACCOUNT, uhandler,
+                            connector.getOperationOptions(uMapping.getItems()));
+                }
+                if (rMapping != null) {
+                    connector.getAllObjects(ObjectClass.GROUP, rhandler,
+                            connector.getOperationOptions(rMapping.getItems()));
+                }
+            } else {
+                if (uMapping != null) {
+                    connector.sync(ObjectClass.ACCOUNT, syncTask.getResource().getUsyncToken(), uhandler,
+                            connector.getOperationOptions(uMapping.getItems()));
+                }
+                if (rMapping != null) {
+                    connector.sync(ObjectClass.GROUP, syncTask.getResource().getRsyncToken(), rhandler,
+                            connector.getOperationOptions(rMapping.getItems()));
+                }
+            }
+
+            if (!dryRun && !syncTask.isFullReconciliation()) {
+                try {
+                    ExternalResource resource = resourceDAO.find(syncTask.getResource().getKey());
+                    if (uMapping != null) {
+                        resource.setUsyncToken(latestUSyncToken);
+                    }
+                    if (rMapping != null) {
+                        resource.setRsyncToken(latestRSyncToken);
+                    }
+                    resourceDAO.save(resource);
+                } catch (Exception e) {
+                    throw new JobExecutionException("While updating SyncToken", e);
+                }
+            }
+        } catch (Throwable t) {
+            throw new JobExecutionException("While syncing on connector", t);
+        }
+
+        try {
+            setRoleOwners(rhandler);
+        } catch (Exception e) {
+            LOG.error("While setting role owners", e);
+        }
+
+        if (actions != null && !profile.isDryRun()) {
+            for (SyncActions action : actions) {
+                action.afterAll(profile);
+            }
+        }
+
+        final String result = createReport(profile.getResults(), syncTask.getResource().getSyncTraceLevel(), dryRun);
+
+        LOG.debug("Sync result: {}", result);
+
+        return result;
+    }
+
+    private SyncPolicySpec getSyncPolicySpec(final ProvisioningTask task) {
+        SyncPolicySpec syncPolicySpec;
+
+        if (task instanceof SyncTask) {
+            final SyncPolicy syncPolicy = task.getResource().getSyncPolicy() == null
+                    ? policyDAO.getGlobalSyncPolicy()
+                    : task.getResource().getSyncPolicy();
+
+            syncPolicySpec = syncPolicy == null ? null : syncPolicy.getSpecification(SyncPolicySpec.class);
+        } else {
+            syncPolicySpec = null;
+        }
+
+        // step required because the call <policy>.getSpecification() could return a null value
+        return syncPolicySpec == null ? new SyncPolicySpec() : syncPolicySpec;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/SyncUtilities.java
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/SyncUtilities.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/SyncUtilities.java
new file mode 100644
index 0000000..90f8a13
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/SyncUtilities.java
@@ -0,0 +1,414 @@
+/*
+ * 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.sync;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.common.lib.types.SyncPolicySpec;
+import org.apache.syncope.core.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
+import org.apache.syncope.core.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
+import org.apache.syncope.core.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.dao.search.SubjectCond;
+import org.apache.syncope.core.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.core.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.MappingItem;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.persistence.api.entity.Subject;
+import org.apache.syncope.core.persistence.api.entity.SyncPolicy;
+import org.apache.syncope.core.persistence.api.entity.role.Role;
+import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask;
+import org.apache.syncope.core.persistence.api.entity.user.UDerAttr;
+import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr;
+import org.apache.syncope.core.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.core.provisioning.api.sync.SyncCorrelationRule;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeUtil;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.Name;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.OperationalAttributes;
+import org.identityconnectors.framework.common.objects.filter.EqualsFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SyncUtilities {
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(SyncUtilities.class);
+
+    /**
+     * Policy DAO.
+     */
+    @Autowired
+    protected PolicyDAO policyDAO;
+
+    /**
+     * Entitlement DAO.
+     */
+    @Autowired
+    protected EntitlementDAO entitlementDAO;
+
+    /**
+     * Schema DAO.
+     */
+    @Autowired
+    protected PlainSchemaDAO plainSchemaDAO;
+
+    /**
+     * User DAO.
+     */
+    @Autowired
+    protected UserDAO userDAO;
+
+    /**
+     * Role DAO.
+     */
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    /**
+     * Search DAO.
+     */
+    @Autowired
+    protected SubjectSearchDAO searchDAO;
+
+    @Autowired
+    protected AttributableUtilFactory attrUtilFactory;
+
+    public Long findMatchingAttributableKey(
+            final ObjectClass oclass,
+            final String name,
+            final ExternalResource resource,
+            final Connector connector) {
+
+        Long result = null;
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(oclass);
+
+        final List<ConnectorObject> found = connector.search(oclass,
+                new EqualsFilter(new Name(name)), connector.getOperationOptions(
+                        attrUtil.getMappingItems(resource, MappingPurpose.SYNCHRONIZATION)));
+
+        if (found.isEmpty()) {
+            LOG.debug("No {} found on {} with __NAME__ {}", oclass, resource, name);
+        } else {
+            if (found.size() > 1) {
+                LOG.warn("More than one {} found on {} with __NAME__ {} - taking first only", oclass, resource, name);
+            }
+
+            ConnectorObject connObj = found.iterator().next();
+            try {
+                List<Long> subjectKeys = findExisting(connObj.getUid().getUidValue(), connObj, resource, attrUtil);
+                if (subjectKeys.isEmpty()) {
+                    LOG.debug("No matching {} found for {}, aborting", attrUtil.getType(), connObj);
+                } else {
+                    if (subjectKeys.size() > 1) {
+                        LOG.warn("More than one {} found {} - taking first only", attrUtil.getType(), subjectKeys);
+                    }
+
+                    result = subjectKeys.iterator().next();
+                }
+            } catch (IllegalArgumentException e) {
+                LOG.warn(e.getMessage());
+            }
+        }
+
+        return result;
+    }
+
+    private List<Long> findByAccountIdItem(
+            final String uid, final ExternalResource resource, final AttributableUtil attrUtil) {
+        final List<Long> result = new ArrayList<>();
+
+        final MappingItem accountIdItem = attrUtil.getAccountIdItem(resource);
+        switch (accountIdItem.getIntMappingType()) {
+            case UserPlainSchema:
+            case RolePlainSchema:
+                final PlainAttrValue value = attrUtil.newPlainAttrValue();
+
+                PlainSchema schema = plainSchemaDAO.find(accountIdItem.getIntAttrName(), attrUtil.plainSchemaClass());
+                if (schema == null) {
+                    value.setStringValue(uid);
+                } else {
+                    try {
+                        value.parseValue(schema, uid);
+                    } catch (ParsingValidationException e) {
+                        LOG.error("While parsing provided __UID__ {}", uid, e);
+                        value.setStringValue(uid);
+                    }
+                }
+
+                List<? extends Subject<UPlainAttr, UDerAttr, UVirAttr>> users =
+                        userDAO.findByAttrValue(accountIdItem.getIntAttrName(), value, attrUtil);
+                for (Subject<UPlainAttr, UDerAttr, UVirAttr> subject : users) {
+                    result.add(subject.getKey());
+                }
+                break;
+
+            case UserDerivedSchema:
+            case RoleDerivedSchema:
+                users = userDAO.findByDerAttrValue(accountIdItem.getIntAttrName(), uid, attrUtil);
+                for (Subject<UPlainAttr, UDerAttr, UVirAttr> subject : users) {
+                    result.add(subject.getKey());
+                }
+                break;
+
+            case Username:
+                User user = userDAO.find(uid);
+                if (user != null) {
+                    result.add(user.getKey());
+                }
+                break;
+
+            case UserId:
+                user = userDAO.find(Long.parseLong(uid));
+                if (user != null) {
+                    result.add(user.getKey());
+                }
+                break;
+
+            case RoleName:
+                List<Role> roles = roleDAO.find(uid);
+                for (Role role : roles) {
+                    result.add(role.getKey());
+                }
+                break;
+
+            case RoleId:
+                Role role = roleDAO.find(Long.parseLong(uid));
+                if (role != null) {
+                    result.add(role.getKey());
+                }
+                break;
+
+            default:
+                LOG.error("Invalid accountId type '{}'", accountIdItem.getIntMappingType());
+        }
+
+        return result;
+    }
+
+    private List<Long> search(final SearchCond searchCond, final SubjectType type) {
+        final List<Long> result = new ArrayList<>();
+
+        List<Subject<?, ?, ?>> subjects = searchDAO.search(
+                RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll()),
+                searchCond, Collections.<OrderByClause>emptyList(), type);
+        for (Subject<?, ?, ?> subject : subjects) {
+            result.add(subject.getKey());
+        }
+
+        return result;
+    }
+
+    private List<Long> findByCorrelationRule(
+            final ConnectorObject connObj, final SyncCorrelationRule rule, final SubjectType type) {
+
+        return search(rule.getSearchCond(connObj), type);
+    }
+
+    private List<Long> findByAttributableSearch(
+            final ConnectorObject connObj,
+            final List<String> altSearchSchemas,
+            final ExternalResource resource,
+            final AttributableUtil attrUtil) {
+
+        // search for external attribute's name/value of each specified name
+        final Map<String, Attribute> extValues = new HashMap<>();
+
+        for (MappingItem item : attrUtil.getMappingItems(resource, MappingPurpose.SYNCHRONIZATION)) {
+            extValues.put(item.getIntAttrName(), connObj.getAttributeByName(item.getExtAttrName()));
+        }
+
+        // search for user/role by attribute(s) specified in the policy
+        SearchCond searchCond = null;
+
+        for (String schema : altSearchSchemas) {
+            Attribute value = extValues.get(schema);
+
+            if (value == null) {
+                throw new IllegalArgumentException(
+                        "Connector object does not contains the attributes to perform the search: " + schema);
+            }
+
+            AttributeCond.Type type;
+            String expression = null;
+
+            if (value.getValue() == null || value.getValue().isEmpty()
+                    || (value.getValue().size() == 1 && value.getValue().get(0) == null)) {
+
+                type = AttributeCond.Type.ISNULL;
+            } else {
+                type = AttributeCond.Type.EQ;
+                expression = value.getValue().size() > 1
+                        ? value.getValue().toString()
+                        : value.getValue().get(0).toString();
+            }
+
+            SearchCond nodeCond;
+            // users: just id or username can be selected to be used
+            // roles: just id or name can be selected to be used
+            if ("key".equalsIgnoreCase(schema)
+                    || "username".equalsIgnoreCase(schema) || "name".equalsIgnoreCase(schema)) {
+
+                SubjectCond cond = new SubjectCond();
+                cond.setSchema(schema);
+                cond.setType(type);
+                cond.setExpression(expression);
+
+                nodeCond = SearchCond.getLeafCond(cond);
+            } else {
+                AttributeCond cond = new AttributeCond();
+                cond.setSchema(schema);
+                cond.setType(type);
+                cond.setExpression(expression);
+
+                nodeCond = SearchCond.getLeafCond(cond);
+            }
+
+            searchCond = searchCond == null
+                    ? nodeCond
+                    : SearchCond.getAndCond(searchCond, nodeCond);
+        }
+
+        return search(searchCond, SubjectType.valueOf(attrUtil.getType().name()));
+    }
+
+    private SyncCorrelationRule getCorrelationRule(final AttributableType type, final SyncPolicySpec policySpec) {
+        String clazz;
+
+        switch (type) {
+            case USER:
+                clazz = policySpec.getUserJavaRule();
+                break;
+            case ROLE:
+                clazz = policySpec.getRoleJavaRule();
+                break;
+            case MEMBERSHIP:
+            case CONFIGURATION:
+            default:
+                clazz = null;
+        }
+
+        SyncCorrelationRule res = null;
+
+        if (StringUtils.isNotBlank(clazz)) {
+            try {
+                res = (SyncCorrelationRule) Class.forName(clazz).newInstance();
+            } catch (Exception e) {
+                LOG.error("Failure instantiating correlation rule class '{}'", clazz, e);
+            }
+        }
+
+        return res;
+    }
+
+    private List<String> getAltSearchSchemas(final AttributableType type, final SyncPolicySpec policySpec) {
+        List<String> result = Collections.emptyList();
+
+        switch (type) {
+            case USER:
+                result = policySpec.getuAltSearchSchemas();
+                break;
+            case ROLE:
+                result = policySpec.getrAltSearchSchemas();
+                break;
+            case MEMBERSHIP:
+            case CONFIGURATION:
+            default:
+        }
+
+        return result;
+    }
+
+    /**
+     * Find users / roles based on mapped uid value (or previous uid value, if updated).
+     *
+     * @param uid for finding by account id
+     * @param connObj for finding by attribute value
+     * @param resource external resource
+     * @param attrUtil attributable util
+     * @return list of matching users / roles
+     */
+    public List<Long> findExisting(
+            final String uid,
+            final ConnectorObject connObj,
+            final ExternalResource resource,
+            final AttributableUtil attrUtil) {
+
+        SyncPolicySpec syncPolicySpec = null;
+        if (resource.getSyncPolicy() == null) {
+            SyncPolicy globalSP = policyDAO.getGlobalSyncPolicy();
+            if (globalSP != null) {
+                syncPolicySpec = globalSP.getSpecification(SyncPolicySpec.class);
+            }
+        } else {
+            syncPolicySpec = resource.getSyncPolicy().getSpecification(SyncPolicySpec.class);
+        }
+
+        SyncCorrelationRule syncRule = null;
+        List<String> altSearchSchemas = null;
+
+        if (syncPolicySpec != null) {
+            syncRule = getCorrelationRule(attrUtil.getType(), syncPolicySpec);
+            altSearchSchemas = getAltSearchSchemas(attrUtil.getType(), syncPolicySpec);
+        }
+
+        return syncRule == null ? altSearchSchemas == null || altSearchSchemas.isEmpty()
+                ? findByAccountIdItem(uid, resource, attrUtil)
+                : findByAttributableSearch(connObj, altSearchSchemas, resource, attrUtil)
+                : findByCorrelationRule(connObj, syncRule, SubjectType.valueOf(attrUtil.getType().name()));
+    }
+
+    public Boolean readEnabled(final ConnectorObject connectorObject, final ProvisioningTask task) {
+        Boolean enabled = null;
+        if (task.isSyncStatus()) {
+            Attribute status = AttributeUtil.find(OperationalAttributes.ENABLE_NAME, connectorObject.getAttributes());
+            if (status != null && status.getValue() != null && !status.getValue().isEmpty()) {
+                enabled = (Boolean) status.getValue().get(0);
+            }
+        }
+
+        return enabled;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserPushResultHandlerImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserPushResultHandlerImpl.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserPushResultHandlerImpl.java
new file mode 100644
index 0000000..0c1ad8b
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserPushResultHandlerImpl.java
@@ -0,0 +1,160 @@
+/*
+ * 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.sync;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.entity.Mapping;
+import org.apache.syncope.core.persistence.api.entity.MappingItem;
+import org.apache.syncope.core.persistence.api.entity.Subject;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.TimeoutException;
+import org.apache.syncope.core.provisioning.api.sync.UserPushResultHandler;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.Uid;
+
+public class UserPushResultHandlerImpl extends AbstractPushResultHandler implements UserPushResultHandler {
+
+    @Override
+    protected Subject<?, ?, ?> deprovision(final Subject<?, ?, ?> sbj) {
+        final UserTO before = userTransfer.getUserTO(sbj.getKey());
+
+        final List<String> noPropResources = new ArrayList<>(before.getResources());
+        noPropResources.remove(profile.getTask().getResource().getKey());
+
+        taskExecutor.execute(propagationManager.getUserDeleteTaskIds(before.getKey(),
+                Collections.singleton(profile.getTask().getResource().getKey()), noPropResources));
+
+        return userDAO.authFetch(before.getKey());
+    }
+
+    @Override
+    protected Subject<?, ?, ?> provision(final Subject<?, ?, ?> sbj, final Boolean enabled) {
+        final UserTO before = userTransfer.getUserTO(sbj.getKey());
+
+        final List<String> noPropResources = new ArrayList<>(before.getResources());
+        noPropResources.remove(profile.getTask().getResource().getKey());
+
+        final PropagationByResource propByRes = new PropagationByResource();
+        propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
+
+        taskExecutor.execute(propagationManager.getUserCreateTaskIds(
+                before.getKey(),
+                enabled,
+                propByRes,
+                null,
+                Collections.unmodifiableCollection(before.getVirAttrs()),
+                Collections.unmodifiableCollection(before.getMemberships()),
+                noPropResources));
+
+        return userDAO.authFetch(before.getKey());
+    }
+
+    @Override
+    protected Subject<?, ?, ?> link(final Subject<?, ?, ?> sbj, final Boolean unlink) {
+        final UserMod userMod = new UserMod();
+        userMod.setKey(sbj.getKey());
+
+        if (unlink) {
+            userMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+        } else {
+            userMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
+        }
+
+        uwfAdapter.update(userMod);
+
+        return userDAO.authFetch(userMod.getKey());
+    }
+
+    @Override
+    protected Subject<?, ?, ?> unassign(final Subject<?, ?, ?> sbj) {
+        final UserMod userMod = new UserMod();
+        userMod.setKey(sbj.getKey());
+        userMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+        uwfAdapter.update(userMod);
+        return deprovision(sbj);
+    }
+
+    @Override
+    protected Subject<?, ?, ?> assign(final Subject<?, ?, ?> sbj, final Boolean enabled) {
+        final UserMod userMod = new UserMod();
+        userMod.setKey(sbj.getKey());
+        userMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
+        uwfAdapter.update(userMod);
+        return provision(sbj, enabled);
+    }
+
+    @Override
+    protected String getName(final Subject<?, ?, ?> subject) {
+        return User.class.cast(subject).getUsername();
+    }
+
+    @Override
+    protected AbstractSubjectTO getSubjectTO(final long key) {
+        try {
+            return userTransfer.getUserTO(key);
+        } catch (Exception e) {
+            LOG.warn("Error retrieving user {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected Subject<?, ?, ?> getSubject(final long key) {
+        try {
+            return userDAO.authFetch(key);
+        } catch (Exception e) {
+            LOG.warn("Error retrieving user {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected ConnectorObject getRemoteObject(final String accountId) {
+        ConnectorObject obj = null;
+
+        try {
+            final Uid uid = new Uid(accountId);
+
+            obj = profile.getConnector().getObject(
+                    ObjectClass.ACCOUNT,
+                    uid,
+                    profile.getConnector().getOperationOptions(Collections.<MappingItem>emptySet()));
+
+        } catch (TimeoutException toe) {
+            LOG.debug("Request timeout", toe);
+            throw toe;
+        } catch (RuntimeException ignore) {
+            LOG.debug("While resolving {}", accountId, ignore);
+        }
+        return obj;
+    }
+
+    @Override
+    protected Mapping<?> getMapping() {
+        return profile.getTask().getResource().getUmapping();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserSyncResultHandlerImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserSyncResultHandlerImpl.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserSyncResultHandlerImpl.java
new file mode 100644
index 0000000..0f0d785
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserSyncResultHandlerImpl.java
@@ -0,0 +1,148 @@
+/*
+ * 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.sync;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.core.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult;
+import org.apache.syncope.core.provisioning.api.sync.UserSyncResultHandler;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+
+public class UserSyncResultHandlerImpl extends AbstractSyncResultHandler implements UserSyncResultHandler {
+
+    @Override
+    protected AttributableUtil getAttributableUtil() {
+        return attrUtilFactory.getInstance(AttributableType.USER);
+    }
+
+    @Override
+    protected String getName(final AbstractSubjectTO subjectTO) {
+        return UserTO.class.cast(subjectTO).getUsername();
+    }
+
+    @Override
+    protected AbstractSubjectTO getSubjectTO(final long key) {
+        try {
+            return userTransfer.getUserTO(key);
+        } catch (Exception e) {
+            LOG.warn("Error retrieving user {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected AbstractSubjectMod getSubjectMod(
+            final AbstractSubjectTO subjectTO, final SyncDelta delta) {
+
+        return connObjectUtil.getAttributableMod(
+                subjectTO.getKey(),
+                delta.getObject(),
+                subjectTO,
+                profile.getTask(),
+                getAttributableUtil());
+    }
+
+    @Override
+    protected AbstractSubjectTO create(
+            final AbstractSubjectTO subjectTO, final SyncDelta delta, final ProvisioningResult result) {
+
+        UserTO userTO = UserTO.class.cast(subjectTO);
+
+        Boolean enabled = syncUtilities.readEnabled(delta.getObject(), profile.getTask());
+        Map.Entry<Long, List<PropagationStatus>> created = userProvisioningManager.create(userTO, true, true, enabled,
+                Collections.singleton(profile.getTask().getResource().getKey()));
+
+        userTO = userTransfer.getUserTO(created.getKey());
+
+        result.setId(created.getKey());
+
+        return userTO;
+    }
+
+    @Override
+    protected AbstractSubjectTO link(
+            final AbstractSubjectTO before,
+            final ProvisioningResult result,
+            final boolean unlink) {
+
+        final UserMod userMod = new UserMod();
+        userMod.setKey(before.getKey());
+
+        if (unlink) {
+            userMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+        } else {
+            userMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
+        }
+
+        return userTransfer.getUserTO(uwfAdapter.update(userMod).getResult().getKey().getKey());
+    }
+
+    @Override
+    protected AbstractSubjectTO update(
+            final AbstractSubjectTO before,
+            final AbstractSubjectMod subjectMod,
+            final SyncDelta delta,
+            final ProvisioningResult result) {
+
+        final UserMod userMod = UserMod.class.cast(subjectMod);
+        final Boolean enabled = syncUtilities.readEnabled(delta.getObject(), profile.getTask());
+
+        Map.Entry<Long, List<PropagationStatus>> updated = userProvisioningManager.update(userMod, before.getKey(),
+                result, enabled, Collections.singleton(profile.getTask().getResource().getKey()));
+
+        return userTransfer.getUserTO(updated.getKey());
+    }
+
+    @Override
+    protected void deprovision(
+            final Long key,
+            final boolean unlink) {
+
+        taskExecutor.execute(
+                propagationManager.getUserDeleteTaskIds(key, profile.getTask().getResource().getKey()));
+
+        if (unlink) {
+            final UserMod userMod = new UserMod();
+            userMod.setKey(key);
+            userMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+        }
+    }
+
+    @Override
+    protected void delete(final Long key) {
+        try {
+            userProvisioningManager.
+                    delete(key, Collections.<String>singleton(profile.getTask().getResource().getKey()));
+        } catch (Exception e) {
+            // A propagation failure doesn't imply a synchronization failure.
+            // The propagation exception status will be reported into the propagation task execution.
+            LOG.error("Could not propagate user " + key, e);
+        }
+
+        uwfAdapter.delete(key);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/resources/connid.properties
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/resources/connid.properties b/syncope620/core/provisioning-java/src/main/resources/connid.properties
new file mode 100644
index 0000000..24d5c93
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/resources/connid.properties
@@ -0,0 +1,17 @@
+# 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.
+connid.locations=${connid.location}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/resources/mail.properties
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/resources/mail.properties b/syncope620/core/provisioning-java/src/main/resources/mail.properties
new file mode 100644
index 0000000..12f04e7
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/resources/mail.properties
@@ -0,0 +1,25 @@
+# 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.
+templates.directory=${conf.directory}
+smtpHost=none.syncope.apache.org
+smtpPort=25
+smtpUser=
+smtpPassword=
+smtpProtocol=smtp
+smtpEncoding=UTF-8
+smtpConnectionTimeout=3000
+mailDebug=false

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/resources/mailTemplates/confirmPasswordReset.html.vm
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/resources/mailTemplates/confirmPasswordReset.html.vm b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/confirmPasswordReset.html.vm
new file mode 100644
index 0000000..90630ac
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/confirmPasswordReset.html.vm
@@ -0,0 +1,26 @@
+<!--
+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.
+-->
+<html>
+<body>
+<p>Hi,</br>
+we are happy to inform you that the password request was execute successfully for your account.</p>
+
+<p>Best regards.</p>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/resources/mailTemplates/confirmPasswordReset.txt.vm
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/resources/mailTemplates/confirmPasswordReset.txt.vm b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/confirmPasswordReset.txt.vm
new file mode 100644
index 0000000..33f75dc
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/confirmPasswordReset.txt.vm
@@ -0,0 +1,20 @@
+# 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.
+Hi,
+we are happy to inform you that the password request was execute successfully for your account.
+
+Best regards.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/resources/mailTemplates/optin.html.vm
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/resources/mailTemplates/optin.html.vm b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/optin.html.vm
new file mode 100644
index 0000000..8240c7b
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/optin.html.vm
@@ -0,0 +1,72 @@
+<!--
+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.
+-->
+<html>
+<body>
+<h3>Hi $user.getPlainAttrMap().get("firstname").getValues().get(0) $user.getPlainAttrMap().get("surname").getValues().get(0), welcome to Syncope!</h3>
+
+<p>
+   Your username is $user.getUsername().<br/>
+   Your email address is $user.getPlainAttrMap().get("email").getValues().get(0).
+   Your email address inside a <a href="http://localhost/?email=$esc.url($user.getPlainAttrMap().get("email").getValues().get(0))">link</a>.
+</p>
+
+<p>
+    This message was sent to the following recipients:
+<ul>
+#foreach($recipient in $recipients)
+  <li>$recipient.getPlainAttrMap().get("email").getValues().get(0)</li>
+#end
+</ul>
+
+because one of the following events occurred:
+<ul>
+#foreach($event in $events)
+  <li>$event</i>
+#end
+</ul>
+</p>
+
+#if(!$user.getMemberships().isEmpty())
+You have been provided with the following roles:
+<ul>
+#foreach($membership in $user.getMemberships())
+  <li>$membership.roleName</i>
+#end
+</ul>
+#end
+
+#if(${output.class.simpleName} == "TaskExec")
+Below you can read execution details of task $output.getTask().getClass().getSimpleName(), id $output.getId().
+Task Details:
+<ul>
+<li>
+START DATE:&nbsp$output.getStartDate()
+</li>
+<li>
+MESSAGE:<br/>
+$output.getMessage()
+</li>
+<li>
+END DATE:&nbsp$output.getEndDate()
+</li>
+</ul>
+#end
+
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/resources/mailTemplates/optin.txt.vm
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/resources/mailTemplates/optin.txt.vm b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/optin.txt.vm
new file mode 100644
index 0000000..fc8e398
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/optin.txt.vm
@@ -0,0 +1,51 @@
+# 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.
+Hi $user.getPlainAttrMap().get("firstname").getValues().get(0) $user.getPlainAttrMap().get("surname").getValues().get(0), welcome to Syncope!
+
+Your username is $user.getUsername().
+Your email address is $user.getPlainAttrMap().get("email").getValues().get(0).
+Your email address inside a link: http://localhost/?email=$esc.url($user.getPlainAttrMap().get("email").getValues().get(0)) .
+
+This message was sent to the following recipients:
+#foreach($recipient in $recipients)
+   * $recipient.getPlainAttrMap().get("surname").getValues().get(0)
+#end
+
+because one of the following events occurred:
+#foreach($event in $events)
+  * $event
+#end
+
+#if(!$user.getMemberships().isEmpty())
+You have been provided with the following roles:
+#foreach($membership in $user.getMemberships())
+  * $membership.roleName
+#end
+#end
+
+#if(${output.class.simpleName} == "TaskExec")
+Below you can read execution details of task $output.getTask().getClass().getSimpleName(), id $output.getId()
+
+Task Details:
+
+  * START DATE: $output.getStartDate() 
+
+  * MESSAGE:
+$output.getMessage()
+
+  * END DATE: $output.getEndDate()
+#end

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/resources/mailTemplates/requestPasswordReset.html.vm
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/resources/mailTemplates/requestPasswordReset.html.vm b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/requestPasswordReset.html.vm
new file mode 100644
index 0000000..6594c3f
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/requestPasswordReset.html.vm
@@ -0,0 +1,31 @@
+<!--
+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.
+-->
+<html>
+<body>
+<p>Hi,
+a password reset was request for $user.getUsername().</p>
+
+<p>In order to complete this request, you need to visit this 
+<a href="http://localhost:9080/syncope-console/?pwdResetToken=$input.get(0)">link</a></p>.
+
+<p>If you did not request this reset, just ignore the present e-mail.</p>
+
+<p>Best regards.</p>
+</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/resources/mailTemplates/requestPasswordReset.txt.vm
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/resources/mailTemplates/requestPasswordReset.txt.vm b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/requestPasswordReset.txt.vm
new file mode 100644
index 0000000..5ac028a
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/resources/mailTemplates/requestPasswordReset.txt.vm
@@ -0,0 +1,26 @@
+# 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.
+Hi,
+a password reset was request for $user.getUsername().
+
+In order to complete this request, you need to visit this link:
+
+http://localhost:9080/syncope-console/?pwdResetToken=$input.get(0)
+
+If you did not request this reset, just ignore the present e-mail.
+
+Best regards.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/resources/provisioning.properties
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/resources/provisioning.properties b/syncope620/core/provisioning-java/src/main/resources/provisioning.properties
new file mode 100644
index 0000000..af5deee
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/resources/provisioning.properties
@@ -0,0 +1,18 @@
+# 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.
+userProvisioningManager=org.apache.syncope.core.provisioning.java.DefaultUserProvisioningManager
+roleProvisioningManager=org.apache.syncope.core.provisioning.java.DefaultRoleProvisioningManager

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/resources/provisioningContext.xml
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/main/resources/provisioningContext.xml b/syncope620/core/provisioning-java/src/main/resources/provisioningContext.xml
new file mode 100644
index 0000000..fa31a73
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/main/resources/provisioningContext.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:task="http://www.springframework.org/schema/task"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                           http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context
+                           http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/task
+                           http://www.springframework.org/schema/task/spring-task.xsd">
+  
+  <task:annotation-driven executor="connectorExecutor"/>
+  <task:executor id="connectorExecutor" pool-size="10"/>
+  
+  <bean class="${userProvisioningManager}"/>
+  <bean class="${roleProvisioningManager}"/>
+
+  <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
+        lazy-init="false" depends-on="nonJPAdbInitializer">
+    <property name="autoStartup" value="true"/>
+    <property name="applicationContextSchedulerContextKey" value="applicationContext"/>
+    <property name="waitForJobsToCompleteOnShutdown" value="true"/>
+    <property name="overwriteExistingJobs" value="true"/>
+    <property name="dataSource" ref="dataSource"/>
+    <property name="transactionManager" ref="transactionManager"/>
+    <property name="jobFactory">
+      <bean class="org.apache.syncope.core.provisioning.java.job.SpringBeanJobFactory"/>
+    </property>
+    <property name="quartzProperties">
+      <props>
+        <prop key="org.quartz.scheduler.idleWaitTime">${quartz.scheduler.idleWaitTime:30000}</prop>
+
+        <prop key="org.quartz.jobStore.misfireThreshold">6000000</prop>
+        <prop key="org.quartz.jobStore.driverDelegateClass">${quartz.jobstore}</prop>
+
+        <prop key="org.quartz.jobStore.isClustered">true</prop>
+        <prop key="org.quartz.jobStore.clusterCheckinInterval">20000</prop>
+
+        <prop key="org.quartz.scheduler.instanceName">ClusteredScheduler</prop>
+        <prop key="org.quartz.scheduler.instanceId">AUTO</prop>
+        <prop key="org.quartz.scheduler.jmx.export">true</prop>
+      </props>
+    </property>
+  </bean>
+  
+  <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
+    <property name="defaultEncoding" value="${smtpEncoding}"/>
+    <property name="host" value="${smtpHost}"/>
+    <property name="port" value="${smtpPort}"/>
+    <property name="username" value="${smtpUser}"/>
+    <property name="password" value="${smtpPassword}"/>
+    <property name="protocol" value="${smtpProtocol}"/>
+
+    <property name="javaMailProperties">
+      <props>
+        <prop key="mail.smtp.connectiontimeout">${smtpConnectionTimeout}</prop>
+        <prop key="mail.debug">${mailDebug}</prop>
+      </props>
+    </property>
+  </bean>
+
+  <bean class="org.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl"/>
+  <bean class="org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter" scope="prototype"/>
+
+  <context:component-scan base-package="org.apache.syncope.core.misc"/>  
+  <context:component-scan base-package="org.apache.syncope.core.provisioning.java"/>
+
+  <bean id="virAttrCache" class="org.apache.syncope.core.provisioning.java.cache.MemoryVirAttrCache" scope="singleton">
+    <constructor-arg value="60"/>
+    <constructor-arg value="5000"/>
+  </bean>
+
+  <bean id="velocityResourceLoader" class="org.apache.syncope.core.misc.spring.ResourceWithFallbackLoader">
+    <property name="primary" value="file:${templates.directory}/"/>
+    <property name="fallback" value="classpath:"/>
+  </bean>
+  <bean id="velocityEngine" class="org.apache.syncope.core.provisioning.java.notification.VelocityEngineFactoryBean">
+    <property name="resourceLoader" ref="velocityResourceLoader"/>
+  </bean>
+  <bean id="velocityToolManager" class="org.apache.velocity.tools.ToolManager">
+    <!-- autoConfigure -->
+    <constructor-arg index="0" value="true"/>
+    <!-- include default velocity tools -->
+    <constructor-arg index="1" value="true"/>
+  </bean>
+
+  <bean id="connIdBundleManager" class="org.apache.syncope.core.provisioning.java.ConnIdBundleManagerImpl" scope="singleton">
+    <property name="stringLocations" value="${connid.locations}"/>
+  </bean>
+
+</beans>

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java b/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java
new file mode 100644
index 0000000..e26f238
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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 org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {
+    "classpath:persistenceTest.xml",
+    "classpath:provisioningContext.xml",
+    "classpath:workflowContext.xml",
+    "classpath:provisioningTest.xml"
+})
+public abstract class AbstractTest {
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/ConnectorManagerTest.java
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/ConnectorManagerTest.java b/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/ConnectorManagerTest.java
new file mode 100644
index 0000000..9ddc91d
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/ConnectorManagerTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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 org.apache.syncope.core.provisioning.java.ConnectorManager;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+public class ConnectorManagerTest extends AbstractTest {
+
+    private ConnectorManager connManager;
+
+    @Autowired
+    private ConnIdBundleManager connIdBundleManager;
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Before
+    public void before() {
+        connManager = new ConnectorManager();
+        ReflectionTestUtils.setField(connManager, "connIdBundleManager", connIdBundleManager);
+        ReflectionTestUtils.setField(connManager, "resourceDAO", resourceDAO);
+
+        // Remove any other connector instance bean set up by standard ConnectorManager.load()
+        connManager.unload();
+    }
+
+    @Test
+    public void load() {
+        connManager.load();
+
+        // only consider local connector bundles
+        int expected = 0;
+        for (ExternalResource resource : resourceDAO.findAll()) {
+            if (resource.getConnector().getLocation().startsWith("file")) {
+                expected++;
+            }
+        }
+
+        assertEquals(expected,
+                ApplicationContextProvider.getApplicationContext().
+                getBeanNamesForType(Connector.class, false, true).length);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java b/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java
new file mode 100644
index 0000000..40764ab
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.data;
+
+import org.apache.syncope.core.provisioning.java.AbstractTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.common.lib.to.MappingItemTO;
+import org.apache.syncope.common.lib.to.MappingTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.PropagationMode;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.MappingItem;
+import org.apache.syncope.core.persistence.api.entity.user.UPlainSchema;
+import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+public class ResourceDataBinderTest extends AbstractTest {
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private ResourceDataBinder resourceDataBinder;
+
+    @Autowired
+    private PlainSchemaDAO plainSchemaDAO;
+
+    @Test
+    public void databinding() throws IOException {
+        ExternalResource resource = resourceDAO.find("ws-target-resource-2");
+        assertNotNull(resource);
+
+        ResourceTO resourceTO = resourceDataBinder.getResourceTO(resource);
+        assertNotNull(resourceTO);
+
+        ExternalResource fromto = resourceDataBinder.update(resource, resourceTO);
+        assertNotNull(fromto);
+        assertEquals(resource, fromto);
+
+        ObjectMapper mapper = new ObjectMapper();
+
+        StringWriter writer = new StringWriter();
+        mapper.writeValue(writer, resourceTO);
+
+        assertEquals(resourceTO, mapper.readValue(writer.toString(), ResourceTO.class));
+
+        List<ResourceTO> resourceTOs = resourceDataBinder.getResourceTOs(resourceDAO.findAll());
+        assertNotNull(resourceTOs);
+        assertFalse(resourceTOs.isEmpty());
+
+        writer = new StringWriter();
+        mapper.writeValue(writer, resourceTOs);
+
+        ResourceTO[] actual = mapper.readValue(writer.toString(), ResourceTO[].class);
+        assertEquals(resourceTOs, Arrays.asList(actual));
+    }
+
+    @Test
+    public void issue42() {
+        UPlainSchema userId = plainSchemaDAO.find("userId", UPlainSchema.class);
+
+        Set<MappingItem> beforeUserIdMappings = new HashSet<>();
+        for (ExternalResource res : resourceDAO.findAll()) {
+            if (res.getUmapping() != null) {
+                for (MappingItem mapItem : res.getUmapping().getItems()) {
+                    if (userId.getKey().equals(mapItem.getIntAttrName())) {
+                        beforeUserIdMappings.add(mapItem);
+                    }
+                }
+            }
+        }
+
+        ResourceTO resourceTO = new ResourceTO();
+        resourceTO.setKey("resource-issue42");
+        resourceTO.setConnectorId(100L);
+        resourceTO.setPropagationMode(PropagationMode.ONE_PHASE);
+        resourceTO.setEnforceMandatoryCondition(true);
+
+        MappingTO mapping = new MappingTO();
+        resourceTO.setUmapping(mapping);
+
+        MappingItemTO item = new MappingItemTO();
+        item.setIntAttrName("userId");
+        item.setIntMappingType(IntMappingType.UserPlainSchema);
+        item.setExtAttrName("campo1");
+        item.setAccountid(true);
+        item.setMandatoryCondition("false");
+        item.setPurpose(MappingPurpose.BOTH);
+        mapping.setAccountIdItem(item);
+
+        ExternalResource resource = resourceDataBinder.create(resourceTO);
+        resource = resourceDAO.save(resource);
+        assertNotNull(resource);
+        assertNotNull(resource.getUmapping());
+        assertEquals(1, resource.getUmapping().getItems().size());
+
+        resourceDAO.flush();
+
+        ExternalResource actual = resourceDAO.find("resource-issue42");
+        assertNotNull(actual);
+        assertEquals(resource, actual);
+
+        userId = plainSchemaDAO.find("userId", UPlainSchema.class);
+
+        Set<MappingItem> afterUserIdMappings = new HashSet<>();
+        for (ExternalResource res : resourceDAO.findAll()) {
+            if (res.getUmapping() != null) {
+                for (MappingItem mapItem : res.getUmapping().getItems()) {
+                    if (userId.getKey().equals(mapItem.getIntAttrName())) {
+                        afterUserIdMappings.add(mapItem);
+                    }
+                }
+            }
+        }
+
+        assertEquals(beforeUserIdMappings.size(), afterUserIdMappings.size() - 1);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/test/resources/provisioningTest.xml
----------------------------------------------------------------------
diff --git a/syncope620/core/provisioning-java/src/test/resources/provisioningTest.xml b/syncope620/core/provisioning-java/src/test/resources/provisioningTest.xml
new file mode 100644
index 0000000..b6621c3
--- /dev/null
+++ b/syncope620/core/provisioning-java/src/test/resources/provisioningTest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                           http://www.springframework.org/schema/beans/spring-beans.xsd">
+    
+  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+    <property name="locations">
+      <list>
+        <value>classpath:persistence.properties</value>
+        <value>classpath:security.properties</value>
+        <value>classpath:connid.properties</value>
+        <value>classpath:mail.properties</value>
+        <value>classpath:workflow.properties</value>
+        <value>classpath:provisioning.properties</value>
+      </list>
+    </property>
+    <property name="ignoreResourceNotFound" value="true"/>
+    <property name="ignoreUnresolvablePlaceholders" value="true"/>
+  </bean>
+  
+  <bean id="contentXML" class="org.apache.syncope.core.misc.spring.ResourceWithFallbackLoader">
+    <property name="primary" value="file:${conf.directory}/content.xml"/>
+    <property name="fallback" value="classpath:content.xml"/>
+  </bean>
+  <bean class="org.apache.syncope.core.persistence.jpa.content.XMLContentLoader" init-method="load"/>
+  
+</beans>

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/rest-cxf/pom.xml
----------------------------------------------------------------------
diff --git a/syncope620/core/rest-cxf/pom.xml b/syncope620/core/rest-cxf/pom.xml
new file mode 100644
index 0000000..d9d06e0
--- /dev/null
+++ b/syncope620/core/rest-cxf/pom.xml
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.syncope</groupId>
+    <artifactId>syncope-core</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Core REST CXF</name>
+  <description>Apache Syncope Core REST CXF</description>
+  <groupId>org.apache.syncope.core</groupId>
+  <artifactId>syncope-core-rest-cxf</artifactId>
+  <packaging>jar</packaging>
+  
+  <properties>
+    <rootpom.basedir>${basedir}/../..</rootpom.basedir>
+  </properties>
+
+  <dependencies>
+    <dependency> 
+      <groupId>javax.servlet</groupId> 
+      <artifactId>javax.servlet-api</artifactId> 
+      <scope>provided</scope>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jpa_2.0_spec</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-orm</artifactId>
+    </dependency>      
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-web</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.security</groupId>
+      <artifactId>spring-security-web</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.security</groupId>
+      <artifactId>spring-security-config</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>com.fasterxml.jackson.jaxrs</groupId>
+      <artifactId>jackson-jaxrs-json-provider</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.module</groupId>
+      <artifactId>jackson-module-afterburner</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-frontend-jaxrs</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-extension-providers</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-extension-search</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-frontend-jaxws</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-service-description</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-rs-client</artifactId>
+    </dependency>  
+    
+    <dependency>
+      <groupId>org.apache.syncope.common</groupId>
+      <artifactId>syncope-common-rest-api</artifactId>
+      <version>${project.version}</version>
+      <classifier>javadoc</classifier>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.core</groupId>
+      <artifactId>syncope-core-logic</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.common</groupId>
+      <artifactId>syncope-common-rest-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+      </plugin>
+    </plugins>
+    
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+    </resources>
+  </build>
+</project>

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/QueryResourceInfoComparator.java
----------------------------------------------------------------------
diff --git a/syncope620/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/QueryResourceInfoComparator.java b/syncope620/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/QueryResourceInfoComparator.java
new file mode 100644
index 0000000..a45e275
--- /dev/null
+++ b/syncope620/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/QueryResourceInfoComparator.java
@@ -0,0 +1,114 @@
+/*
+ * 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.rest.cxf;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cxf.jaxrs.ext.ResourceComparator;
+import org.apache.cxf.jaxrs.model.ClassResourceInfo;
+import org.apache.cxf.jaxrs.model.OperationResourceInfo;
+import org.apache.cxf.jaxrs.model.OperationResourceInfoComparator;
+import org.apache.cxf.jaxrs.model.Parameter;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Message;
+
+public class QueryResourceInfoComparator extends OperationResourceInfoComparator implements ResourceComparator {
+
+    public QueryResourceInfoComparator() {
+        super(null, null);
+    }
+
+    @Override
+    public int compare(final ClassResourceInfo cri1, final ClassResourceInfo cri2, final Message message) {
+        // Leave Class selection to CXF
+        return 0;
+    }
+
+    @Override
+    public int compare(final OperationResourceInfo oper1, final OperationResourceInfo oper2, final Message message) {
+        // Check if CXF can make a decision
+        int cxfResult = super.compare(oper1, oper2);
+        if (cxfResult != 0) {
+            return cxfResult;
+        }
+
+        int op1Counter = getMatchingRate(oper1, message);
+        int op2Counter = getMatchingRate(oper2, message);
+
+        return op1Counter == op2Counter
+                ? 0
+                : op1Counter < op2Counter
+                ? 1
+                : -1;
+    }
+
+    /**
+     * This method calculates a number indicating a good or bad match between values provided within the request and
+     * expected method parameters. A higher number means a better match.
+     *
+     * @param operation The operation to be rated, based on contained parameterInfo values.
+     * @param message A message containing query and header values from user request
+     * @return A positive or negative number, indicating a good match between query and method
+     */
+    protected int getMatchingRate(final OperationResourceInfo operation, final Message message) {
+        List<Parameter> params = operation.getParameters();
+        if (params == null || params.isEmpty()) {
+            return 0;
+        }
+
+        // Get Request QueryParams
+        String query = (String) message.get(Message.QUERY_STRING);
+        String path = (String) message.get(Message.REQUEST_URI);
+        Map<String, List<String>> qParams = JAXRSUtils.getStructuredParams(query, "&", true, false);
+        Map<String, List<String>> mParams = JAXRSUtils.getMatrixParams(path, true);
+        // Get Request Headers
+        Map<?, ?> qHeader = (java.util.Map<?, ?>) message.get(Message.PROTOCOL_HEADERS);
+
+        int rate = 0;
+        for (Parameter p : params) {
+            switch (p.getType()) {
+                case QUERY:
+                    if (qParams.containsKey(p.getName())) {
+                        rate += 2;
+                    } else if (p.getDefaultValue() == null) {
+                        rate -= 1;
+                    }
+                    break;
+                case MATRIX:
+                    if (mParams.containsKey(p.getName())) {
+                        rate += 2;
+                    } else if (p.getDefaultValue() == null) {
+                        rate -= 1;
+                    }
+                    break;
+                case HEADER:
+                    if (qHeader.containsKey(p.getName())) {
+                        rate += 2;
+                    } else if (p.getDefaultValue() == null) {
+                        rate -= 1;
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+        return rate;
+    }
+}